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 Author: Leonardo de Moura
*/ */
#include <iostream> #include <iostream>
#include <sstream>
#include <mutex>
#include <string>
#include "util/debug.h"
#include "util/exception.h"
#include "bindings/lua/leanlua_state.h" #include "bindings/lua/leanlua_state.h"
#ifdef LEAN_USE_LUA #ifdef LEAN_USE_LUA
#include <lua.hpp> #include <lua.hpp>
#include <mutex>
#include "util/exception.h"
#include "bindings/lua/name.h" #include "bindings/lua/name.h"
#include "bindings/lua/numerics.h" #include "bindings/lua/numerics.h"
#include "bindings/lua/options.h" #include "bindings/lua/options.h"
@ -36,14 +39,14 @@ struct leanlua_state::imp {
void exec() { void exec() {
int result = lua_pcall(m_state, 0, LUA_MULTRET, 0); int result = lua_pcall(m_state, 0, LUA_MULTRET, 0);
if (result) if (result)
throw exception(lua_tostring(m_state, -1)); throw lua_exception(lua_tostring(m_state, -1));
} }
void dofile(char const * fname) { void dofile(char const * fname) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
int result = luaL_loadfile(m_state, fname); int result = luaL_loadfile(m_state, fname);
if (result) if (result)
throw exception(lua_tostring(m_state, -1)); throw lua_exception(lua_tostring(m_state, -1));
exec(); exec();
} }
@ -51,7 +54,7 @@ struct leanlua_state::imp {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
int result = luaL_loadstring(m_state, str); int result = luaL_loadstring(m_state, str);
if (result) if (result)
throw exception(lua_tostring(m_state, -1)); throw lua_exception(lua_tostring(m_state, -1));
exec(); exec();
} }
}; };
@ -72,6 +75,7 @@ void leanlua_state::dostring(char const * str) {
} }
} }
#else #else
namespace lean {
leanlua_state::leanlua_state() { leanlua_state::leanlua_state() {
} }
@ -89,4 +93,74 @@ void leanlua_state::dofile(char const * fname) {
void leanlua_state::dostring(char const * str) { void leanlua_state::dostring(char const * str) {
throw_lua_not_supported(); throw_lua_not_supported();
} }
}
#endif #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 Author: Leonardo de Moura
*/ */
#include <memory> #include <memory>
#include <string>
#include "util/exception.h"
namespace lean { namespace lean {
/** /**
@ -28,4 +30,24 @@ public:
*/ */
void dostring(char const * str); 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; unsigned m_num_local_decls;
expr_pos_info m_expr_pos_info; expr_pos_info m_expr_pos_info;
pos_info m_last_cmd_pos; pos_info m_last_cmd_pos;
pos_info m_last_script_pos;
// Reference to temporary parser used to process import command. // Reference to temporary parser used to process import command.
// We need this reference to be able to interrupt it. // We need this reference to be able to interrupt it.
interruptable_ptr<parser> m_import_parser; 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(pos_info const & p) { display_error_pos(p.first, p.second); }
void display_error_pos(expr const & e) { void display_error_pos(expr const & e) {
if (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; 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() { void updt_options() {
m_verbose = get_parser_verbose(m_frontend.get_state().get_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()); m_show_errors = get_parser_show_errors(m_frontend.get_state().get_options());
@ -1566,6 +1590,7 @@ class parser::imp {
} }
void parse_script() { 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) if (!m_leanlua_state)
throw exception("failed to execute Lua script, parser does not have a Lua interpreter"); 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()); m_leanlua_state->dostring(m_scanner.get_str_val().c_str());
@ -1629,6 +1654,13 @@ public:
display_error(ex); display_error(ex);
if (m_use_exceptions) if (m_use_exceptions)
throw; 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) { } catch (interrupted & ex) {
if (m_verbose) if (m_verbose)
regular(m_frontend) << "\n!!!Interrupted!!!" << endl; regular(m_frontend) << "\n!!!Interrupted!!!" << endl;

View file

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

View file

@ -31,6 +31,9 @@ protected:
int m_pos; // start position of the token int m_pos; // start position of the token
std::istream & m_stream; 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; mpq m_num_val;
name m_name_val; name m_name_val;
std::string m_buffer; std::string m_buffer;
@ -68,6 +71,9 @@ public:
name const & get_name_val() const { return m_name_val; } name const & get_name_val() const { return m_name_val; }
mpq const & get_num_val() const { return m_num_val; } mpq const & get_num_val() const { return m_num_val; }
std::string const & get_str_val() const { return m_buffer; } 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); 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")