diff --git a/luprex/core/cpp/invocation.cpp b/luprex/core/cpp/invocation.cpp index c79ffb4d..efba1c7e 100644 --- a/luprex/core/cpp/invocation.cpp +++ b/luprex/core/cpp/invocation.cpp @@ -1,6 +1,15 @@ #include "invocation.hpp" +const std::string &InvocationData::get(const std::string &key) const { + static std::string blank_; + auto iter = find(key); + if (iter == end()) { + return blank_; + } else { + return iter->second; + } +} void InvocationData::serialize(StreamBuffer *sb) const { assert(int(size()) < 65536); diff --git a/luprex/core/cpp/invocation.hpp b/luprex/core/cpp/invocation.hpp index c05908e8..3c728327 100644 --- a/luprex/core/cpp/invocation.hpp +++ b/luprex/core/cpp/invocation.hpp @@ -9,6 +9,8 @@ class InvocationData : public std::map { public: + const std::string &get(const std::string &key) const; + void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); }; @@ -18,6 +20,7 @@ public: enum Kind { KIND_INVALID, KIND_PLAN, + KIND_LUA, }; private: diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 4ffdd438..263ac1df 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -47,60 +47,6 @@ public: -static lua_State *globalL = NULL; - - -static void lstop(lua_State *L, lua_Debug *ar) -{ - (void)ar; /* unused arg. */ - lua_sethook(L, NULL, 0, 0); - /* Avoid luaL_error -- a C hook doesn't add an extra frame. */ - luaL_where(L, 0); - lua_pushfstring(L, "%sinterrupted!", lua_tostring(L, -1)); - lua_error(L); -} - -static void laction(int i) -{ - signal(i, SIG_DFL); /* if another SIGINT happens before lstop, - terminate process (default action) */ - lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); -} - -static void l_message(const char *msg) -{ - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -void TextGame::do_lua(const std::string &exp) { - assert(world_->stack_is_clear()); - lua_State *L = world_->state(); - // push the compiled function. - int status = luaL_loadbuffer(L, exp.c_str(), exp.size(), "=stdin"); - assert(status == LUA_OK); - globalL = L; - signal(SIGINT, laction); - status = traceback_pcall(L, 0, LUA_MULTRET); - signal(SIGINT, SIG_DFL); - if (status == LUA_OK) { - if (lua_gettop(L) > 0) { - lfn_pprint_pprint(L); - lua_settop(L, 0); - } - } else { - const char *msg = lua_tostring(L, -1); - if (msg == NULL) { - msg = "(error object is not a string)"; - } - l_message(msg); - lua_pop(L, 1); - lua_gc(L, LUA_GCCOLLECT, 0); - } - assert(world_->stack_is_clear()); -} - void TextGame::do_view_command(const StringVec &cmd) { if (cmd.size() != 1) { std::cerr << "v command (view) takes no arguments" << std::endl; @@ -154,6 +100,13 @@ void TextGame::do_choose_command(const StringVec &cmd) { world_->invoke(inv); } +void TextGame::do_lua(const std::string &exp) { + assert(world_->stack_is_clear()); + InvocationData dummyresult; + Invocation inv(Invocation::KIND_LUA, actor_id_, actor_id_, exp, dummyresult); + world_->invoke(inv); +} + void TextGame::do_snapshot_command(const StringVec &cmd) { if (cmd.size() != 1) { std::cerr << "s command (snapshot) takes no arguments" << std::endl; diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index 12c50c02..b2c5d266 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -4,6 +4,7 @@ #include "animqueue.hpp" #include "gui.hpp" #include "traceback.hpp" +#include "print.hpp" #include void World::store_global_pointer(lua_State *L, World *v) { @@ -320,11 +321,21 @@ void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { assert(stack_is_clear()); } +void World::update_source(util::LuaSourcePtr source) { + if (source != nullptr) { + source_db_.update(*source); + source_db_.rebuild(true); + } +} + void World::invoke(const Invocation &inv) { switch (inv.kind()) { case Invocation::KIND_PLAN: invoke_plan(inv.actor(), inv.place(), inv.action(), inv.data()); break; + case Invocation::KIND_LUA: + invoke_lua(inv.actor(), inv.place(), inv.action(), inv.data()); + break; default: // Do nothing. Standard behavior for any invalid command is to // simply do nothing at all. Perhaps eventually we may add a flag @@ -335,14 +346,56 @@ void World::invoke(const Invocation &inv) { } } -void World::update_source(util::LuaSourcePtr source) { - if (source != nullptr) { - source_db_.update(*source); - source_db_.rebuild(true); +void World::invoke_lua(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data) { + assert(stack_is_clear()); + + // Get the actor and place, which must be the same. + if (actor_id != place_id) return; + Tangible *tactor = tangible_get(actor_id); + if (tactor == nullptr) { + return; } + + lua_State *L = state(); + LuaVar closure; + LuaStack LS(L, closure); + + // create the compiled closure. + int status = luaL_loadbuffer(L, action.c_str(), action.size(), "=invoke"); + lua_replace(L, closure.index()); + if (status != LUA_OK) { + // The closure is actually an error message. Do nothing. + LS.result(); + return; + } + + // Call the closure. + int top = lua_gettop(L); + lua_pushvalue(L, closure.index()); + set_lthread_state(actor_id, place_id, false); + status = traceback_pcall(L, 0, LUA_MULTRET); + set_lthread_state(0, 0, false); + + // If there's an error message, print it. + // Otherwise, pretty-print the results. + if (status == LUA_OK) { + for (int i = top + 1; i <= lua_gettop(L); i++) { + LuaSpecial root(i); + pprint(LS, root, true, &std::cout); + std::cout << std::endl; + } + } else { + const char *msg = lua_tostring(L, top); + if (msg == NULL) { + msg = "(error object is not a string)"; + } + std::cerr << msg << std::endl; + } + LS.result(); + assert(stack_is_clear()); } -void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata) { +void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data) { assert(stack_is_clear()); // Validate that the action is legal. @@ -401,7 +454,7 @@ void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &a // Convert the InvocationData into a lua table. LS.newtable(invdata); - for (const auto &p : idata) { + for (const auto &p : data) { LS.rawset(invdata, p.first, p.second); } diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index 520136b2..9eb4d9f7 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -173,7 +173,7 @@ public: // To mutate a world model, create an invocation, then invoke it. // void invoke(const Invocation &inv); - + // Update the source database from disk. // // Special case: if the source pointer is nullptr, does not update. @@ -240,8 +240,12 @@ private: // Invoke a plan. // - void invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata); + void invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data); + // Invoke a lua string. + // + void invoke_lua(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data); + public: //////////////////////////////////////////////////////////////////////////// //