From 1e93533246789e9f14702bea949be5a0253df60c Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Fri, 26 Nov 2021 13:56:24 -0500 Subject: [PATCH] Implement probe_lua and add it to lpxclient/lpxserver --- luprex/core/cpp/animqueue.cpp | 7 +--- luprex/core/cpp/lpxclient.cpp | 11 ++++-- luprex/core/cpp/lpxserver.cpp | 11 ++++-- luprex/core/cpp/luaconsole.cpp | 23 +++++++------ luprex/core/cpp/luaconsole.hpp | 10 ++++-- luprex/core/cpp/textgame.cpp | 21 +++++------- luprex/core/cpp/util.cpp | 12 +++++++ luprex/core/cpp/util.hpp | 4 +++ luprex/core/cpp/world-core.cpp | 61 ++++++++++++++++++++++++++++++++++ luprex/core/cpp/world.hpp | 9 +++++ 10 files changed, 134 insertions(+), 35 deletions(-) diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index b3f9067d..08c9bc64 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -671,12 +671,7 @@ LuaDefine(unittests_animqueue, "c") { // Change the queue size limit. aq.set_limit(13); LuaAssert(L, diff_works(aq, aqds)); - - // compare again, should be no differences. - LuaAssert(L, aqds.version_identical(aq)); - LuaAssert(L, aqds.size_and_steps_equal(aq)); - LuaAssert(L, diff_works(aq, aqds)); - + // Discard all but the last action. aq.set_limit(1); LuaAssert(L, diff_works(aq, aqds)); diff --git a/luprex/core/cpp/lpxclient.cpp b/luprex/core/cpp/lpxclient.cpp index 5cca5341..bf25589b 100644 --- a/luprex/core/cpp/lpxclient.cpp +++ b/luprex/core/cpp/lpxclient.cpp @@ -94,10 +94,16 @@ public: inv.serialize(sb); } - void do_lua_command(const StringVec &words) { + void do_luainvoke_command(const StringVec &words) { send_invocation(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1])); } + void do_luaprobe_command(const StringVec &words) { + world_to_asynchronous(); + stdostream() << world_->probe_lua(actor_id_, words[1]); + world_to_synchronous(); + } + void do_syntax_command(const StringVec &words) { stdostream() << "Syntax Error: " << words[1] << std::endl; } @@ -131,7 +137,8 @@ public: void do_command(const util::StringVec &words) { if (words.empty()) return; - else if (words[0] == "lua") do_lua_command(words); + else if (words[0] == "luainvoke") do_luainvoke_command(words); + else if (words[0] == "luaprobe") do_luaprobe_command(words); else if (words[0] == "syntax") do_syntax_command(words); else if (words[0] == "view") do_view_command(words); else if (words[0] == "menu") do_menu_command(words); diff --git a/luprex/core/cpp/lpxserver.cpp b/luprex/core/cpp/lpxserver.cpp index 8e9ca650..709a76d0 100644 --- a/luprex/core/cpp/lpxserver.cpp +++ b/luprex/core/cpp/lpxserver.cpp @@ -45,10 +45,16 @@ public: get_stdio_channel()->set_prompt(console_.get_prompt()); } - void do_lua_command(const util::StringVec &words) { + void do_luainvoke_command(const util::StringVec &words) { master_->invoke(Invocation(Invocation::KIND_LUA, admin_id_, admin_id_, words[1])); } + void do_luaprobe_command(const util::StringVec &words) { + master_->snapshot(); + stdostream() << master_->probe_lua(admin_id_, words[1]);; + master_->rollback(); + } + void do_syntax_command(const util::StringVec &words) { stdostream() << "Syntax Error: " << words[1] << std::endl; } @@ -59,7 +65,8 @@ public: void do_command(const util::StringVec &words) { if (words.empty()) return; - else if (words[0] == "lua") do_lua_command(words); + else if (words[0] == "luainvoke") do_luainvoke_command(words); + else if (words[0] == "luaprobe") do_luaprobe_command(words); else if (words[0] == "syntax") do_syntax_command(words); else if (words[0] == "quit") do_quit_command(words); else { diff --git a/luprex/core/cpp/luaconsole.cpp b/luprex/core/cpp/luaconsole.cpp index de71b6a4..a1c90b34 100644 --- a/luprex/core/cpp/luaconsole.cpp +++ b/luprex/core/cpp/luaconsole.cpp @@ -78,14 +78,6 @@ void LuaConsole::simplify(const StringVec &words) { if (words.size() != 1) { synerr("/quit takes no arguments"); } - } else if (words[0] == "snap") { - if (words.size() != 1) { - synerr("/snap takes no arguments"); - } - } else if (words[0] == "roll") { - if (words.size() != 1) { - synerr("/roll takes no arguments"); - } } else if (words[0] == "tick") { if ((words.size() == 2)&&(util::validinteger(words[1]))) { // OK @@ -120,11 +112,20 @@ void LuaConsole::add(std::string line) { return; } - // Translate lua expression with leading '=' to 'return' + // Translate lua expression, deal with initial prefix. std::string partial; - if (raw_input_[0] == '=') { + std::string lua_mode; + if (util::has_prefix(raw_input_, "?=")) { + lua_mode = "luaprobe"; + partial = std::string("return ") + raw_input_.substr(2); + } else if (util::has_prefix(raw_input_, "?")) { + lua_mode = "luaprobe"; + partial = raw_input_.substr(1); + } else if (util::has_prefix(raw_input_, "=")) { + lua_mode = "luainvoke"; partial = std::string("return ") + raw_input_.substr(1); } else { + lua_mode = "luainvoke"; partial = raw_input_; } @@ -144,7 +145,7 @@ void LuaConsole::add(std::string line) { clear_raw_input(); } } else { - words_.push_back("lua"); + words_.push_back(lua_mode); words_.push_back(util::rtrim(partial)); clear_raw_input(); } diff --git a/luprex/core/cpp/luaconsole.hpp b/luprex/core/cpp/luaconsole.hpp index 2b8ae23e..debdea8d 100644 --- a/luprex/core/cpp/luaconsole.hpp +++ b/luprex/core/cpp/luaconsole.hpp @@ -18,15 +18,21 @@ // /quit - exit the program // /view - display the nearby tangibles // /menu [tanid] - display the menu for tangible -// /snap - snapshot current state -// /roll - rollback to previous state // /tick [timevalue] - advance the simulation clock +// /choose [number] - choose menu item number // /1234 - choose menu item 1234 // // If you type anything else, the LuaConsole will generate a // syntax error. Note that not all of the commands above are // supported in all interpreters. // +// Lua commands can be one of the following: +// +// - invoke """ +// = - invoke "return " +// ? - probe "" +// ?= - probe "return " +// // Once a command has been typed (or a syntax error has been // typed), the LuaConsole will return the command. The command // can be fetched using command(), int_arg(), and str_arg() diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 515ce06b..618cdb6f 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -28,10 +28,16 @@ private: Gui gui_; int64_t actor_id_; - void do_lua_command(const StringVec &words) { + void do_luainvoke_command(const StringVec &words) { world_->invoke(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1])); } + void do_luaprobe_command(const StringVec &words) { + world_->snapshot(); + stdostream() << world_->probe_lua(actor_id_, words[1]); + world_->rollback(); + } + void do_syntax_command(const StringVec &words) { stdostream() << "Syntax Error: " << words[1] << std::endl; } @@ -57,14 +63,6 @@ private: world_->invoke(inv); } - void do_snapshot_command(const StringVec &cmd) { - world_->snapshot(); - } - - void do_rollback_command(const StringVec &cmd) { - world_->rollback(); - } - void do_tick_command(const StringVec &cmd) { world_->run_scheduled_threads(util::strtoint(cmd[1], -1)); } @@ -75,13 +73,12 @@ private: void do_command(const StringVec &words) { if (words.empty()) return; - else if (words[0] == "lua") do_lua_command(words); + else if (words[0] == "luainvoke") do_luainvoke_command(words); + else if (words[0] == "luaprobe") do_luaprobe_command(words); else if (words[0] == "syntax") do_syntax_command(words); else if (words[0] == "view") do_view_command(words); else if (words[0] == "menu") do_menu_command(words); else if (words[0] == "quit") do_quit_command(words); - else if (words[0] == "snap") do_snapshot_command(words); - else if (words[0] == "roll") do_rollback_command(words); else if (words[0] == "tick") do_tick_command(words); else if (words[0] == "choose") do_choose_command(words); else { diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index d63e9eb2..384dd7fa 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -185,6 +185,18 @@ std::string toupper(std::string input) { return input; } +bool has_prefix(const std::string &s, const std::string &prefix) { + return 0 == s.compare(0, prefix.size(), prefix); +} + +bool has_suffix(const std::string &s, const std::string &suffix) { + if (s.length() >= suffix.length()) { + return (0 == s.compare (s.length() - suffix.length(), suffix.length(), suffix)); + } else { + return false; + } +} + bool validinteger(const std::string &value) { char *endptr; if (value.size() == 0) return false; diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index 1b899d5b..5c6f78b3 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -77,6 +77,10 @@ int common_prefix_length(const std::string &a, const std::string &b); std::string tolower(std::string input); std::string toupper(std::string input); +// Return true if the string has the specified prefix or suffix. +bool has_prefix(const std::string &s, const std::string &prefix); +bool has_suffix(const std::string &s, const std::string &suffix); + // Return true if the string can be parsed as an integer. bool validinteger(const std::string &value); diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index ac69bfaf..712a0583 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -257,6 +257,65 @@ int64_t World::create_login_actor() { return tan->id(); } +std::string World::probe_lua(int64_t actor_id, const std::string &lua) { + assert(stack_is_clear()); + lua_State *L = state(); + + Tangible *actor = tangible_get(actor_id); + if (actor == nullptr) { + return ""; + } + + LuaVar closure; + LuaStack LS(L, closure); + + // create the compiled closure. + int status = luaL_loadbuffer(L, lua.c_str(), 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. + LS.result(); + return ""; + } + + // Call the closure. + int top = lua_gettop(L); + lua_pushvalue(L, closure.index()); + open_lthread_state(actor_id, actor_id, false, true); + status = traceback_pcall(L, 0, LUA_MULTRET); + + // If there's an error message, print it. + // Otherwise, pretty-print the results. + std::ostream *ostream = lthread_print_stream(); + if (status == LUA_OK) { + for (int i = top + 1; i <= lua_gettop(L); i++) { + LuaSpecial root(i); + pprint(LS, root, true, ostream); + (*ostream) << std::endl; + } + } else { + const char *msg = lua_tostring(L, -1); + if (msg == NULL) { + msg = "(error object is not a string)"; + } + (*ostream) << msg << std::endl; + } + + // Collect the lthread_prints (and also make sure they + // don't go into the printbuffer). + std::string result = lthread_prints_->str(); + lthread_prints_.reset(); + + close_lthread_state(); + + // And we're done. + LS.result(); + assert(stack_is_clear()); + return result; +} + void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { assert(stack_is_clear()); gui->clear(place_id); @@ -391,6 +450,8 @@ void World::invoke_lua(int64_t actor_id, int64_t place_id, const std::string &ac 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. LS.result(); return; } diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index ce31f4f7..765cfbb8 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -166,6 +166,15 @@ public: // Redirects fetch_redirects(); + // Probe an arbitrary lua expression. + // + // Any print-statements in the lua code are sent into + // a stringstream. The return value of probe_lua is the string + // from the stringstream. If the lua expression returns a + // value, that is also printed to the stringstream. + // + std::string probe_lua(int64_t actor_id, const std::string &lua); + // Probe the 'interface' function of the specified sprite. // void update_gui(int64_t actor_id, int64_t place_id, Gui *gui);