feat(lua): add lua_exception for wrapping lua errors, and improve Lua error messages in the Lean frontend

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
Leonardo de Moura 2013-11-07 15:19:26 -08:00
parent a9b2be0b9c
commit 57b9657bf0
10 changed files with 182 additions and 7 deletions

View file

@ -5,12 +5,15 @@ Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
*/
#include <iostream>
#include <sstream>
#include <mutex>
#include <string>
#include "util/debug.h"
#include "util/exception.h"
#include "bindings/lua/leanlua_state.h"
#ifdef LEAN_USE_LUA
#include <lua.hpp>
#include <mutex>
#include "util/exception.h"
#include "bindings/lua/name.h"
#include "bindings/lua/numerics.h"
#include "bindings/lua/options.h"
@ -36,14 +39,14 @@ struct leanlua_state::imp {
void exec() {
int result = lua_pcall(m_state, 0, LUA_MULTRET, 0);
if (result)
throw exception(lua_tostring(m_state, -1));
throw lua_exception(lua_tostring(m_state, -1));
}
void dofile(char const * fname) {
std::lock_guard<std::mutex> lock(m_mutex);
int result = luaL_loadfile(m_state, fname);
if (result)
throw exception(lua_tostring(m_state, -1));
throw lua_exception(lua_tostring(m_state, -1));
exec();
}
@ -51,7 +54,7 @@ struct leanlua_state::imp {
std::lock_guard<std::mutex> lock(m_mutex);
int result = luaL_loadstring(m_state, str);
if (result)
throw exception(lua_tostring(m_state, -1));
throw lua_exception(lua_tostring(m_state, -1));
exec();
}
};
@ -72,6 +75,7 @@ void leanlua_state::dostring(char const * str) {
}
}
#else
namespace lean {
leanlua_state::leanlua_state() {
}
@ -89,4 +93,74 @@ void leanlua_state::dofile(char const * fname) {
void leanlua_state::dostring(char const * str) {
throw_lua_not_supported();
}
}
#endif
namespace lean {
lua_exception::lua_exception(char const * lua_error):exception("") {
lean_assert(lua_error);
std::string fname;
std::string line;
std::string msg;
int state = 0;
char const * it = lua_error;
while (*it) {
if (state == 0) {
if (*it == ':') {
state = 1;
} else {
fname += *it;
}
} else if (state == 1) {
if (*it == ':') {
state = 2;
} else {
line += *it;
}
} else {
msg += *it;
}
it++;
}
if (state != 2) {
// failed to decode Lua error message
m_source = source::Unknown;
m_msg = lua_error;
} else {
if (fname == "[string \"...\"]") {
m_source = source::String;
} else {
m_source = source::File;
m_file = fname;
}
m_line = atoi(line.c_str());
m_msg = msg;
}
}
char const * lua_exception::get_filename() const {
lean_assert(get_source() == source::File);
return m_file.c_str();
}
unsigned lua_exception::get_line() const {
lean_assert(get_source() != source::Unknown);
return m_line;
}
char const * lua_exception::msg() const noexcept {
return exception::what();
}
char const * lua_exception::what() const noexcept {
static thread_local std::string buffer;
std::ostringstream strm;
switch (m_source) {
case source::String: strm << "[string]:" << m_line << ":" << msg() << "\n"; break;
case source::File: strm << m_file << ":" << m_file << ":" << msg() << "\n"; break;
case source::Unknown: return msg();
}
buffer = strm.str();
return buffer.c_str();
}
}

View file

@ -5,6 +5,8 @@ Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
*/
#include <memory>
#include <string>
#include "util/exception.h"
namespace lean {
/**
@ -28,4 +30,24 @@ public:
*/
void dostring(char const * str);
};
/**
\brief Exception for wrapping errors produced by the Lua engine.
*/
class lua_exception : public exception {
public:
enum class source { String, File, Unknown };
private:
source m_source;
std::string m_file; // if source == File, then this field contains the filename that triggered the error.
unsigned m_line; // line number
public:
lua_exception(char const * lua_error);
source get_source() const { return m_source; }
char const * get_filename() const;
unsigned get_line() const;
/** \brief Return error message without position information */
char const * msg() const noexcept;
virtual char const * what() const noexcept;
};
}

View file

@ -128,6 +128,7 @@ class parser::imp {
unsigned m_num_local_decls;
expr_pos_info m_expr_pos_info;
pos_info m_last_cmd_pos;
pos_info m_last_script_pos;
// Reference to temporary parser used to process import command.
// We need this reference to be able to interrupt it.
interruptable_ptr<parser> m_import_parser;
@ -1504,7 +1505,12 @@ class parser::imp {
}
/*@}*/
void display_error_pos(unsigned line, unsigned pos) { regular(m_frontend) << "Error (line: " << line << ", pos: " << pos << ")"; }
void display_error_pos(unsigned line, unsigned pos) {
regular(m_frontend) << "Error (line: " << line;
if (pos != static_cast<unsigned>(-1))
regular(m_frontend) << ", pos: " << pos;
regular(m_frontend) << ")";
}
void display_error_pos(pos_info const & p) { display_error_pos(p.first, p.second); }
void display_error_pos(expr const & e) {
if (e) {
@ -1553,6 +1559,24 @@ class parser::imp {
regular(m_frontend) << mk_pair(ex.get_justification().pp(fmt, opts, &pos_provider, true), opts) << endl;
}
void display_error(lua_exception const & ex) {
switch (ex.get_source()) {
case lua_exception::source::String:
display_error_pos(ex.get_line() + m_last_script_pos.first - 1, static_cast<unsigned>(-1));
regular(m_frontend) << " executing script," << ex.msg() << endl;
break;
case lua_exception::source::File:
display_error_pos(m_last_script_pos);
regular(m_frontend) << " executing external script (" << ex.get_filename() << ":" << ex.get_line() << ")," << ex.msg() << endl;
break;
case lua_exception::source::Unknown:
display_error_pos(m_last_script_pos);
regular(m_frontend) << " executing script, but could not decode position information, " << ex.what() << endl;
break;
}
next();
}
void updt_options() {
m_verbose = get_parser_verbose(m_frontend.get_state().get_options());
m_show_errors = get_parser_show_errors(m_frontend.get_state().get_options());
@ -1566,6 +1590,7 @@ class parser::imp {
}
void parse_script() {
m_last_script_pos = mk_pair(m_scanner.get_script_block_line(), m_scanner.get_script_block_pos());
if (!m_leanlua_state)
throw exception("failed to execute Lua script, parser does not have a Lua interpreter");
m_leanlua_state->dostring(m_scanner.get_str_val().c_str());
@ -1629,6 +1654,13 @@ public:
display_error(ex);
if (m_use_exceptions)
throw;
} catch (lua_exception & ex) {
m_found_errors = true;
reset_interrupt();
if (m_show_errors)
display_error(ex);
if (m_use_exceptions)
throw;
} catch (interrupted & ex) {
if (m_verbose)
regular(m_frontend) << "\n!!!Interrupted!!!" << endl;

View file

@ -93,7 +93,9 @@ scanner::scanner(std::istream& stream):
m_curr(0),
m_line(1),
m_pos(0),
m_stream(stream) {
m_stream(stream),
m_script_line(1),
m_script_pos(0) {
next();
}
@ -337,6 +339,8 @@ scanner::token scanner::read_string() {
scanner::token scanner::read_script_block() {
lean_assert(curr() == '{');
next();
m_script_line = m_line;
m_script_pos = m_pos;
m_buffer.clear();
int num_open_blocks = 1;
while (true) {

View file

@ -31,6 +31,9 @@ protected:
int m_pos; // start position of the token
std::istream & m_stream;
int m_script_line; // hack for saving beginning of script block line and pos
int m_script_pos;
mpq m_num_val;
name m_name_val;
std::string m_buffer;
@ -68,6 +71,9 @@ public:
name const & get_name_val() const { return m_name_val; }
mpq const & get_num_val() const { return m_num_val; }
std::string const & get_str_val() const { return m_buffer; }
int get_script_block_line() const { return m_script_line; }
int get_script_block_pos() const { return m_script_pos; }
};
std::ostream & operator<<(std::ostream & out, scanner::token const & t);
}

15
tests/lean/lua2.lean Normal file
View file

@ -0,0 +1,15 @@
Variable x : Bool
{{
a = {}
print("hello world")
print ("ok")
a = {
x = 10,
y = 20
}
rint ("ok")
}}
Variable y : Bool

View file

@ -0,0 +1,7 @@
Set: pp::colors
Set: pp::unicode
Assumed: x
hello world
ok
Error (line: 11) executing script, attempt to call global 'rint' (a nil value)
Assumed: y

6
tests/lean/lua3.lean Normal file
View file

@ -0,0 +1,6 @@
Variable x : Int
{{
dofile("script.lua")
}}

View file

@ -0,0 +1,5 @@
Set: pp::colors
Set: pp::unicode
Assumed: x
hello
Error (line: 4, pos: 0) executing external script (script.lua:4), attempt to call global 'prn' (a nil value)

4
tests/lean/script.lua Normal file
View file

@ -0,0 +1,4 @@
print("hello")
prn("world")