diff --git a/luprex/core/cpp/gui.cpp b/luprex/core/cpp/gui.cpp index 4e037023..79e713ec 100644 --- a/luprex/core/cpp/gui.cpp +++ b/luprex/core/cpp/gui.cpp @@ -35,6 +35,12 @@ bool Gui::has_action(const std::string &action) const { return false; } +std::string Gui::get_action(int index) { + if ((index < 0) || (index >= elts_.size())) { + return ""; + } + return elts_[index].action(); +} LuaDefine(gui_menu_item, "c") { Gui *gui = Gui::fetch_global_pointer(L); diff --git a/luprex/core/cpp/gui.hpp b/luprex/core/cpp/gui.hpp index 91881350..a45e8aef 100644 --- a/luprex/core/cpp/gui.hpp +++ b/luprex/core/cpp/gui.hpp @@ -36,6 +36,7 @@ public: void clear() { elts_.clear(); } bool has_action(const std::string &action) const; void menu_item(const std::string &action, const std::string &label); + std::string get_action(int index); // Put a pointer to a gui into the lua registry. // diff --git a/luprex/core/cpp/invocation.cpp b/luprex/core/cpp/invocation.cpp index 75388681..53b10fdd 100644 --- a/luprex/core/cpp/invocation.cpp +++ b/luprex/core/cpp/invocation.cpp @@ -36,6 +36,9 @@ Invocation::Invocation() : kind_(KIND_INVALID), actor_(0), place_(0) {} Invocation::Invocation(Kind kind, int64_t actor, int64_t place, const std::string &action, const InvocationData &data) : kind_(kind), actor_(actor), place_(place), action_(action), data_(data) {} +Invocation::Invocation(Kind kind, int64_t actor, int64_t place, const std::string &action) + : kind_(kind), actor_(actor), place_(place), action_(action) {} + void Invocation::serialize(StreamBuffer *sb) const { sb->write_uint8(kind_); sb->write_int64(actor_); diff --git a/luprex/core/cpp/invocation.hpp b/luprex/core/cpp/invocation.hpp index 0f6cc3f5..0f970694 100644 --- a/luprex/core/cpp/invocation.hpp +++ b/luprex/core/cpp/invocation.hpp @@ -33,7 +33,9 @@ private: public: Invocation(); Invocation(Kind kind, int64_t actor, int64_t place, const std::string &action, const InvocationData &data); - + Invocation(Kind kind, int64_t actor, int64_t place, const std::string &action); + + bool valid() const { return kind_ != KIND_INVALID; } Kind kind() const { return kind_; } int64_t actor() const { return actor_; } int64_t place() const { return place_; } diff --git a/luprex/core/cpp/lpxclient.cpp b/luprex/core/cpp/lpxclient.cpp index f420345a..63821a8a 100644 --- a/luprex/core/cpp/lpxclient.cpp +++ b/luprex/core/cpp/lpxclient.cpp @@ -96,29 +96,14 @@ public: } void do_lua_command(const StringVec &words) { - if (words.size() != 2) { - stdostream() << "lua command (lua) takes a single string" << std::endl; - return; - } - const std::string &exp = words[1]; - InvocationData dummyresult; - Invocation inv(Invocation::KIND_LUA, actor_id_, actor_id_, exp, dummyresult); - send_invocation(inv); + send_invocation(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1])); } void do_syntax_command(const StringVec &words) { - stdostream() << "Syntax Error: "; - for (int i = 1; i < int(words.size()); i++) { - stdostream() << words[i] << " "; - } - stdostream() << std::endl; + stdostream() << "Syntax Error: " << words[1] << std::endl; } void do_view_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "view command takes no arguments" << std::endl; - return; - } for (int64_t id : world_->get_near(actor_id_, 100, true)) { const Tangible *tan = world_->tangible_get(id); const AnimStep &aqback = tan->anim_queue_.back(); @@ -127,18 +112,9 @@ public: } void do_menu_command(const StringVec &cmd) { - int64_t id; - if (cmd.size() == 1) { - id = actor_id_; - } else if (cmd.size() == 2) { - id = util::strtoint(cmd[1], -1); - } else { - stdostream() << "menu command expects a tangible ID or defaults to actor_id" << std::endl; - return; - } world_to_asynchronous(); - gui_place_ = id; - world_->update_gui(actor_id_, id, &gui_); + gui_place_ = util::strtoint(cmd[1], actor_id_); + world_->update_gui(actor_id_, gui_place_, &gui_); int index = 0; for (const GuiElt &elt : gui_.elts()) { stdostream() << index << " " << elt.label() << std::endl; @@ -147,32 +123,17 @@ public: } void do_choose_command(const StringVec &cmd) { - int64_t index; - if (cmd.size() == 1) { - index = util::strtoint(cmd[0], -1); - } else { - stdostream() << "choose command consists of a single menu line number" << std::endl; + std::string action = gui_.get_action(util::strtoint(cmd[1], -1)); + if (action == "") { + stdostream() << "Invalid menu item #" << std::endl; return; } - const Gui::EltVec &elts = gui_.elts(); - if ((index < 0) || (index >= int(elts.size()))) { - stdostream() << "No menu item #" << index << std::endl; - return; - } - std::string action = elts[index].action(); stdostream() << "Invoking plan: " << action << std::endl; - InvocationData dummyresult; - dummyresult["flavor"] = "chocolate"; - dummyresult["color"] = "blue"; - Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_place_, action, dummyresult); + Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_place_, action); send_invocation(inv); } void do_quit_command(const util::StringVec &words) { - if (words.size() != 1) { - stdostream() << "quit command takes no arguments" << std::endl; - return; - } abandon_server(); stop_driver(); } @@ -184,9 +145,9 @@ public: 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 (util::validinteger(words[0])) do_choose_command(words); + else if (words[0] == "choose") do_choose_command(words); else { - stdostream() << "Unknown command: " << words[0] << std::endl; + stdostream() << "Unsupported command: " << words[0] << std::endl; } } diff --git a/luprex/core/cpp/lpxserver.cpp b/luprex/core/cpp/lpxserver.cpp index 7bc9314a..40ef2f96 100644 --- a/luprex/core/cpp/lpxserver.cpp +++ b/luprex/core/cpp/lpxserver.cpp @@ -43,29 +43,14 @@ public: } void do_lua_command(const util::StringVec &words) { - if (words.size() != 2) { - stdostream() << "lua command (lua) takes a single string" << std::endl; - return; - } - const std::string &exp = words[1]; - InvocationData dummyresult; - Invocation inv(Invocation::KIND_LUA, admin_id_, admin_id_, exp, dummyresult); - master_->invoke(inv); + master_->invoke(Invocation(Invocation::KIND_LUA, admin_id_, admin_id_, words[1])); } void do_syntax_command(const util::StringVec &words) { - stdostream() << "Syntax Error: "; - for (int i = 1; i < int(words.size()); i++) { - stdostream() << words[i] << " "; - } - stdostream() << std::endl; + stdostream() << "Syntax Error: " << words[1] << std::endl; } void do_quit_command(const util::StringVec &words) { - if (words.size() != 1) { - stdostream() << "quit command takes no arguments" << std::endl; - return; - } stop_driver(); } @@ -75,7 +60,7 @@ public: else if (words[0] == "syntax") do_syntax_command(words); else if (words[0] == "quit") do_quit_command(words); else { - stdostream() << "Unknown command: " << words[0] << std::endl; + stdostream() << "Unsupported command: " << words[0] << std::endl; } } diff --git a/luprex/core/cpp/luaconsole.cpp b/luprex/core/cpp/luaconsole.cpp index de8b6a83..4e0e315b 100644 --- a/luprex/core/cpp/luaconsole.cpp +++ b/luprex/core/cpp/luaconsole.cpp @@ -38,6 +38,65 @@ LuaConsole::StringVec LuaConsole::get_command() { return result; } +void LuaConsole::synerr(const std::string &msg) { + words_.clear(); + words_.push_back("syntax"); + words_.push_back(msg); +} + +void LuaConsole::simplify(const StringVec &words) { + words_ = words; + if (words.size() == 0) { + return; + } else if (util::validinteger(words[0])) { + if (words.size() == 1) { + words_.clear(); + words_.push_back("choose"); + words_.push_back(words[0]); + } else { + synerr("/choose command takes no arguments"); + } + } else if (words[0] == "choose") { + if ((words.size() == 2)&&(util::validinteger(words[1]))) { + // OK + } else { + synerr("/choose [menu-line-number]"); + } + } else if (words[0] == "view") { + if (words.size() != 1) { + synerr("/view takes no arguments"); + } + } else if (words[0] == "menu") { + if (words.size() == 1) { + words_.push_back("-"); + } else if ((words.size() == 2)&&(util::validinteger(words[1]))) { + // OK + } else { + synerr("/menu [optional-tangible-id]"); + } + } else if (words[0] == "quit") { + 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 + } else { + synerr("/tick [optional-timestamp]"); + } + } else { + synerr("unrecognized command"); + } +} + void LuaConsole::clear_raw_input() { raw_input_ = ""; lines_ = 0; @@ -52,10 +111,11 @@ void LuaConsole::add(std::string line) { raw_input_ += '\n'; lines_ += 1; prompt_ = ">> "; + words_.clear(); // Try to interpret it as a slash-command. if ((lines_ == 1)&&(raw_input_[0] == '/')) { - words_ = split_words(raw_input_.substr(1)); + simplify(split_words(raw_input_.substr(1))); clear_raw_input(); return; } diff --git a/luprex/core/cpp/luaconsole.hpp b/luprex/core/cpp/luaconsole.hpp index 73cac7e8..2b8ae23e 100644 --- a/luprex/core/cpp/luaconsole.hpp +++ b/luprex/core/cpp/luaconsole.hpp @@ -8,23 +8,28 @@ // 1. Print the prompt suggested by 'get_prompt'. // 2. Read a line of text from stdin. // 3. Add the line to the LuaConsole using 'add'. -// 4. Get the command words using 'get_command'. +// 4. Get the command word using get_command. // 5. If the command is empty, do nothing. -// 6. If the command is nonempty, execute it. +// 6. If the command is nonempty, get the args and execute it. // -// The LuaConsole expects you to type one of three things: +// The LuaConsole expects you to type a lua command, or one +// of the following slash-commands: // -// * Valid Lua Code. -// * Valid Lua Code preceded by '=' as shorthand for 'return' -// * A slash-command. +// /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 +// /1234 - choose menu item 1234 // -// The 'get_command' returned by the luaconsole can be one of -// the following: +// 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. // -// empty vector - still collecting input: do nothing. -// lua - found a lua command: execute it as lua. -// syntax - detected a syntax error: print the error. -// word word ... - read a slash-command: execute the command. +// 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() // ////////////////////////////////////////////////////// @@ -42,10 +47,12 @@ private: lua_State *lua_state_; std::string raw_input_; int lines_; - StringVec words_; std::string prompt_; + StringVec words_; void clear_raw_input(); + void simplify(const StringVec &words); + void synerr(const std::string &msg); public: LuaConsole(); @@ -57,15 +64,14 @@ public: // const std::string &get_prompt() { return prompt_; } - // Fetch the command to execute. + // Get the command words. + // + // Note that the command words may not be exactly what + // the user typed. Typically, the LuaConsole simplifies + // the command before returning it to the caller. // - // You should fetch the command after calling 'add'. - // Returns the empty vector if there is no command. - // If there is a command, the first word is the command word. - // See the file comment for certain built-in command words. - // StringVec get_command(); - + // Add a line of text that was just read from the console. // void add(std::string line); diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 72ca4b32..67649b75 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -29,32 +29,15 @@ private: int64_t gui_place_; int64_t actor_id_; - void do_lua_command(const StringVec &words) { - assert(world_->stack_is_clear()); - if (words.size() != 2) { - stdostream() << "lua command (lua) takes a single string" << std::endl; - return; - } - const std::string &exp = words[1]; - InvocationData dummyresult; - Invocation inv(Invocation::KIND_LUA, actor_id_, actor_id_, exp, dummyresult); - world_->invoke(inv); + world_->invoke(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1])); } void do_syntax_command(const StringVec &words) { - stdostream() << "Syntax Error: "; - for (int i = 1; i < int(words.size()); i++) { - stdostream() << words[i] << " "; - } - stdostream() << std::endl; + stdostream() << "Syntax Error: " << words[1] << std::endl; } void do_view_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "v command (view) takes no arguments" << std::endl; - return; - } for (int64_t id : world_->get_near(actor_id_, 100, true)) { const Tangible *tan = world_->tangible_get(id); const AnimStep &aqback = tan->anim_queue_.back(); @@ -63,17 +46,8 @@ private: } void do_menu_command(const StringVec &cmd) { - int64_t id; - if (cmd.size() == 1) { - id = actor_id_; - } else if (cmd.size() == 2) { - id = util::strtoint(cmd[1], -1); - } else { - stdostream() << "m command (menu) expects a tangible ID or defaults to actor_id" << std::endl; - return; - } - gui_place_ = id; - world_->update_gui(actor_id_, id, &gui_); + gui_place_ = util::strtoint(cmd[1], actor_id_); + world_->update_gui(actor_id_, gui_place_, &gui_); int index = 0; for (const GuiElt &elt : gui_.elts()) { stdostream() << index << " " << elt.label() << std::endl; @@ -82,60 +56,29 @@ private: } void do_choose_command(const StringVec &cmd) { - int64_t index; - if (cmd.size() == 1) { - index = util::strtoint(cmd[0], -1); - } else { - stdostream() << "c command (choose) expects a menu line number" << std::endl; + std::string action = gui_.get_action(util::strtoint(cmd[1], -1)); + if (action == "") { + stdostream() << "Invalid menu item #" << std::endl; return; } - const Gui::EltVec &elts = gui_.elts(); - if ((index < 0) || (index >= int(elts.size()))) { - stdostream() << "No menu item #" << index << std::endl; - return; - } - std::string action = elts[index].action(); - stdostream() << "Invoking plan: " << action << std::endl; - InvocationData dummyresult; - dummyresult["flavor"] = "chocolate"; - dummyresult["color"] = "blue"; - Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_place_, action, dummyresult); + Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_place_, action); + stdostream() << "Invoking: " << inv.debug_string() << std::endl; world_->invoke(inv); } - void do_snapshot_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "s command (snapshot) takes no arguments" << std::endl; - return; - } world_->snapshot(); } void do_rollback_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "r command (rollback) takes no arguments" << std::endl; - return; - } world_->rollback(); } void do_tick_command(const StringVec &cmd) { - int64_t clock; - if (cmd.size() == 2) { - clock = util::strtoint(cmd[1], -1); - } else { - stdostream() << "t command (tick) expects a time value" << std::endl; - return; - } - world_->run_scheduled_threads(clock); + world_->run_scheduled_threads(util::strtoint(cmd[1], -1)); } void do_quit_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "q command (quit) takes no arguments" << std::endl; - return; - } actor_id_ = 0; } @@ -149,9 +92,9 @@ private: 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 (util::validinteger(words[0])) do_choose_command(words); + else if (words[0] == "choose") do_choose_command(words); else { - stdostream() << "Unknown command: " << words[0] << std::endl; + stdostream() << "Unsupported command: " << words[0] << std::endl; } } diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index 3b565b16..be8d8807 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -142,6 +142,16 @@ StringVec split(const std::string &s, char sep) { return result; } +std::string join(const StringVec &strs, const std::string &sep) { + if (strs.empty()) return ""; + std::ostringstream oss; + oss << strs[0]; + for (int i = 1; i < strs.size(); i++) { + oss << sep << strs[i]; + } + return oss.str(); +} + std::string repeat_string(const std::string &a, int n) { int len = a.size(); std::string result(len * n, ' '); diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index b3b8cab1..9bc5b216 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -63,6 +63,9 @@ std::string hash_to_hex(const HashValue &hash); // Split a string into multiple strings StringVec split(const std::string &s, char sep); +// Join multiple strings into one string +std::string join(const StringVec &strs, std::string sep); + // Return N repetitions of string A std::string repeat_string(const std::string &a, int n);