diff --git a/src/bindings/lua/leanlua_state.cpp b/src/bindings/lua/leanlua_state.cpp index 3e3b175e2..49bb19b62 100644 --- a/src/bindings/lua/leanlua_state.cpp +++ b/src/bindings/lua/leanlua_state.cpp @@ -5,12 +5,15 @@ Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include +#include +#include +#include +#include "util/debug.h" +#include "util/exception.h" #include "bindings/lua/leanlua_state.h" #ifdef LEAN_USE_LUA #include -#include -#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 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 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(); +} +} diff --git a/src/bindings/lua/leanlua_state.h b/src/bindings/lua/leanlua_state.h index 16ee8c334..c55530d96 100644 --- a/src/bindings/lua/leanlua_state.h +++ b/src/bindings/lua/leanlua_state.h @@ -5,6 +5,8 @@ Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include +#include +#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; +}; } diff --git a/src/frontends/lean/parser.cpp b/src/frontends/lean/parser.cpp index 6c9abdd42..a320e2975 100644 --- a/src/frontends/lean/parser.cpp +++ b/src/frontends/lean/parser.cpp @@ -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 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(-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(-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; diff --git a/src/frontends/lean/scanner.cpp b/src/frontends/lean/scanner.cpp index 58c1c5f10..ac4c52bc2 100644 --- a/src/frontends/lean/scanner.cpp +++ b/src/frontends/lean/scanner.cpp @@ -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) { diff --git a/src/frontends/lean/scanner.h b/src/frontends/lean/scanner.h index 5b4a9a32f..b5b6a4bf4 100644 --- a/src/frontends/lean/scanner.h +++ b/src/frontends/lean/scanner.h @@ -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); } diff --git a/tests/lean/lua2.lean b/tests/lean/lua2.lean new file mode 100644 index 000000000..e26ea9f33 --- /dev/null +++ b/tests/lean/lua2.lean @@ -0,0 +1,15 @@ +Variable x : Bool + +{{ + a = {} + print("hello world") + print ("ok") + a = { + x = 10, + y = 20 + } + rint ("ok") +}} + +Variable y : Bool + diff --git a/tests/lean/lua2.lean.expected.out b/tests/lean/lua2.lean.expected.out new file mode 100644 index 000000000..3844e379d --- /dev/null +++ b/tests/lean/lua2.lean.expected.out @@ -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 diff --git a/tests/lean/lua3.lean b/tests/lean/lua3.lean new file mode 100644 index 000000000..606da73fa --- /dev/null +++ b/tests/lean/lua3.lean @@ -0,0 +1,6 @@ + +Variable x : Int + +{{ +dofile("script.lua") +}} \ No newline at end of file diff --git a/tests/lean/lua3.lean.expected.out b/tests/lean/lua3.lean.expected.out new file mode 100644 index 000000000..c81c26f33 --- /dev/null +++ b/tests/lean/lua3.lean.expected.out @@ -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) diff --git a/tests/lean/script.lua b/tests/lean/script.lua new file mode 100644 index 000000000..614fc0073 --- /dev/null +++ b/tests/lean/script.lua @@ -0,0 +1,4 @@ + + +print("hello") +prn("world") \ No newline at end of file