diff --git a/Integration.code-workspace.tpl.json b/Integration.code-workspace.tpl.json index 32a45b9f..2953c07e 100644 --- a/Integration.code-workspace.tpl.json +++ b/Integration.code-workspace.tpl.json @@ -46,7 +46,8 @@ "--compile-commands-dir=[INTEGRATION]/.vscode", "--header-insertion=never" ], - "C_Cpp.autocomplete": "disabled" + "C_Cpp.autocomplete": "disabled", + "search.useIgnoreFiles": false }, "extensions": { "recommendations": [ diff --git a/luprex/cpp/core/enginewrapper.hpp b/luprex/cpp/core/enginewrapper.hpp index 965d919e..97ef68ff 100644 --- a/luprex/cpp/core/enginewrapper.hpp +++ b/luprex/cpp/core/enginewrapper.hpp @@ -32,6 +32,7 @@ enum class AccessKind { INVOKE_LUA_SOURCE, PROBE_LUA_CALL, CONNECT_TO_SERVER, + VALIDATE_LUA, }; class DrivenEngine; diff --git a/luprex/cpp/core/lpxclient.cpp b/luprex/cpp/core/lpxclient.cpp index bd8c4ae5..cda3312b 100644 --- a/luprex/cpp/core/lpxclient.cpp +++ b/luprex/cpp/core/lpxclient.cpp @@ -20,14 +20,21 @@ public: LuaConsole console_; PrintChanneler print_channeler_; eng::vector delayed_invocations_; + lua_State *lua_syntax_checker_; public: LpxClient() { + lua_syntax_checker_ = LuaCoreStack::newstate(eng::l_alloc); + set_console_prompt(console_.get_prompt()); set_initial_state_standalone(); } + ~LpxClient() { + lua_close(lua_syntax_checker_); + } + void set_initial_state_connect(const eng::string &hostspec) { // Create the world model. world_.reset(new World(WORLD_TYPE_PREDICTIVE)); @@ -256,6 +263,13 @@ public: set_initial_state_connect(util::ss("nocert:", datapk, ":8085")); break; } + case AccessKind::VALIDATE_LUA: { + LuaVar closure; + LuaDefStack LS(lua_syntax_checker_, closure); + eng::string errmsg = LS.load(closure, datapk, "stdin"); + retpk->write_bytes(errmsg); + break; + } default: { util::dprint("Invalid event_access: ", int(kind)); } diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp index 26d5110c..2dd6e65a 100644 --- a/luprex/cpp/core/lpxserver.cpp +++ b/luprex/cpp/core/lpxserver.cpp @@ -6,6 +6,7 @@ #include "luaconsole.hpp" #include "util.hpp" #include "printbuffer.hpp" +#include "luastack.hpp" #include @@ -33,10 +34,14 @@ public: eng::vector delayed_invocations_; int64_t admin_id_ = 0; double next_tick_ = 0; + lua_State *lua_syntax_checker_; public: LpxServer() { + // Create a little lua interpreter for syntax checking only. + lua_syntax_checker_ = LuaCoreStack::newstate(eng::l_alloc); + // Create the master world model. master_.reset(new World(WORLD_TYPE_MASTER)); @@ -62,6 +67,10 @@ public: rescan_lua_source(true); } + ~LpxServer() { + lua_close(lua_syntax_checker_); + } + virtual void do_syntax_error(std::string_view error) override { stdostream() << "Syntax error: " << error << std::endl; } @@ -178,6 +187,13 @@ public: master_->rollback(); break; } + case AccessKind::VALIDATE_LUA: { + LuaVar closure; + LuaDefStack LS(lua_syntax_checker_, closure); + eng::string errmsg = LS.load(closure, datapk, "stdin"); + retpk->write_bytes(errmsg); + break; + } default: { stdostream() << "Invalid event_access: " << int(kind) << std::endl; } diff --git a/luprex/cpp/core/luaconsole.cpp b/luprex/cpp/core/luaconsole.cpp index f58fefb4..602f5758 100644 --- a/luprex/cpp/core/luaconsole.cpp +++ b/luprex/cpp/core/luaconsole.cpp @@ -89,26 +89,23 @@ void LuaConsole::add(eng::string line) { } // Try to parse the lua expression - int top = lua_gettop(lua_state_); - int status = luaL_loadbuffer(lua_state_, partial.c_str(), partial.size(), "=stdin"); - if (status == LUA_ERRSYNTAX) { - const char *eof = ""; - int leof = strlen(eof); - size_t lmsg; - const char *msg = lua_tolstring(lua_state_, -1, &lmsg); - const char *tp = msg + lmsg - leof; - if (strstr(msg, eof) != tp) { + LuaVar result; + LuaDefStack LS(lua_state_, result); + eng::string errmsg = LS.load(result, partial, "stdin"); + if (errmsg.empty()) { + words_.push_back(lua_mode); + words_.emplace_back(sv::rtrim(partial)); + clear_raw_input(); + } else if (errmsg.find("") != std::string::npos) { + // We have an incomplete expression. + // Do nothing, just let the user type more stuff. + } else { words_.push_back("syntax"); - words_.push_back(msg); + words_.push_back(errmsg); clear_raw_input(); } - } else { - words_.push_back(lua_mode); - words_.emplace_back(sv::rtrim(partial)); - clear_raw_input(); } - lua_settop(lua_state_, top); } void CommonCommands::do_command(const StringVec &words) { diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index c3ffc76a..75c4d9bf 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -323,6 +323,29 @@ bool LuaCoreStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const { return (ret != 0); } +eng::string LuaCoreStack::load(LuaSlot result, std::string_view code, std::string_view context) +{ + eng::string fullcontext = eng::string("=") + eng::string(context); + luaL_loadbuffer(L_, code.data(), code.size(), fullcontext.c_str()); + int type = lua_type(L_, -1); + if (type == LUA_TFUNCTION) { + // compiler returned a closure. + lua_replace(L_, result.index()); + return ""; + } else if (type == LUA_TSTRING) { + // compiler returned an error message. + size_t len; + const char *str = lua_tolstring(L_, -1, &len); + eng::string message(str, len); + lua_pop(L_, 1); + if (message.empty()) message = "unknown compiler error"; + set(result, message); + return message; + } else { + assert(false && "lua compiler didn't return a closure, but didn't return an error message either"); + } +} + void LuaCoreStack::getglobaltable(LuaSlot target) const { lua_pushglobaltable(L_); lua_replace(L_, target); diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index cc52a1e4..d1cca7ea 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -792,6 +792,19 @@ public: // bool next(LuaSlot tab, LuaSlot key, LuaSlot val) const; + // Compile lua code. + // + // If the code contains a syntax error, then the result variable + // is set to the error message, and the error message is returned. + // + // If the code is valid, then the result variable is set to a + // closure, and an empty string is returned. + // + // If a syntax error occurs, the error message may contain the + // token . If so, the problem is an incomplete expression. + // + eng::string load(LuaSlot result, std::string_view code, std::string_view context); + // Return true if the int64 can be stored losslessly in a lua_Number. // // Lua numbers are actually double-precision floating point. double diff --git a/luprex/cpp/core/source.cpp b/luprex/cpp/core/source.cpp index 5de13fe4..ffc8b734 100644 --- a/luprex/cpp/core/source.cpp +++ b/luprex/cpp/core/source.cpp @@ -78,9 +78,7 @@ static void calculate_loadresult(LuaCoreStack &LS0, LuaSlot info, const eng::str if (code == "") { LS.rawset(info, "loadresult", "missing or empty source file"); } else { - eng::string chunk = "=" + fn; - luaL_loadbuffer(LS.state(), code.c_str(), code.size(), chunk.c_str()); - lua_replace(LS.state(), loadresult.index()); + LS.load(loadresult, code, fn); LS.rawset(info, "loadresult", loadresult); } } diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 4d1a3d14..ac6e106e 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -377,14 +377,10 @@ eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) { LuaVar closure; LuaExtStack LS(L, closure); - - // create the compiled closure. - int status = luaL_loadbuffer(L, lua.data(), lua.size(), "=probe"); - lua_replace(L, closure.index()); - if (status != LUA_OK) { - // The closure is actually an error message. Do nothing. - // This should normally not happen: LuaConsole should filter - // out syntax errors. + eng::string errmsg = LS.load(closure, lua, "probe"); + if (!errmsg.empty()) { + // This should normally not happen: the front end should + // filter expressions for syntactic lua validity. return ""; }