diff --git a/luprex/cpp/drv/driver.cpp b/luprex/cpp/drv/driver.cpp index 5710691c..98429e07 100644 --- a/luprex/cpp/drv/driver.cpp +++ b/luprex/cpp/drv/driver.cpp @@ -5,6 +5,9 @@ #include "driver-windows.cpp" #endif +#include "slash-parser.hpp" +#include "slash-parser.cpp" + #define POLLVEC_SIZE (DRV_MAX_CHAN + 1) #define MAX_BIO_BUFFER (128 * 1024) @@ -191,14 +194,12 @@ class Driver { } } - void handle_lua_source() { - if (engw.get_rescan_lua_source(&engw)) { - drvutil::ostringstream oss; - std::string err = drvutil::package_lua_source(".", &oss); - if_error_print_and_exit(err); - std::string_view ossv = oss.view(); - engw.play_access(&engw, AccessKind::INVOKE_LUA_SOURCE, 0, ossv.size(), ossv.data(), nullptr, nullptr); - } + void inject_lua_source() { + drvutil::ostringstream oss; + std::string err = drvutil::package_lua_source(".", &oss); + if_error_print_and_exit(err); + std::string_view ossv = oss.view(); + engw.play_access(&engw, AccessKind::INVOKE_LUA_SOURCE, 0, ossv.size(), ossv.data(), nullptr, nullptr); } void channel_printbuffer() { @@ -213,6 +214,20 @@ class Driver { } } + void handle_slash_command(const std::string &cmd) + { + SlashCommandParser parser(cmd); + if (parser.Parse("/quit", "")) { + exit(0); + } + else if (parser.Parse("/cpl", "")) { + inject_lua_source(); + } + else { + readline_device_.printline(parser.Error()); + } + } + void add_console_command(std::string_view addition) { std::string cmd = console_command_ + std::string(addition); @@ -229,7 +244,7 @@ class Driver { } else if (message == "white space") { // no need to do anything. } else if (message == "slash command") { - readline_device_.printline("slash command."); + handle_slash_command(cmd); } else if (message.empty()) { engw.play_access(&engw, AccessKind::INVOKE_LUA_EXPR, 0, cmd.size(), cmd.c_str(), nullptr, nullptr); @@ -689,7 +704,9 @@ class Driver { // Main loop. while (!engw.get_stop_driver(&engw)) { - handle_lua_source(); + if (engw.get_rescan_lua_source(&engw)) { + inject_lua_source(); + } handle_new_outgoing_sockets(); handle_socket_input_output(); handle_console_input(); diff --git a/luprex/ext/slash-parser.cpp b/luprex/ext/slash-parser.cpp new file mode 100644 index 00000000..c746fb2b --- /dev/null +++ b/luprex/ext/slash-parser.cpp @@ -0,0 +1,150 @@ +#pragma once + +#ifndef DONT_INCLUDE_SLASH_PARSER_HPP +#include "slash-parser.hpp" +#endif +#include + + + +void SlashCommandParser::skip_white() +{ + while ((cursor_ < linelen_) && (ascii_isspace(line_[cursor_]))) + { + cursor_++; + } +} + +std::string SlashCommandParser::read_word() +{ + int start = cursor_; + while ((cursor_ < linelen_) && (!ascii_isspace(line_[cursor_]))) + { + cursor_++; + } + std::string result = line_.substr(start, cursor_ - start); + skip_white(); + return result; +} + + +SlashCommandParser::SlashCommandParser(std::string_view Line) +{ + line_ = Line; + linelen_ = int(Line.size()); + cursor_ = 0; + command_ = read_word(); + if ((command_.size() < 2) || (command_[0] != '/')) + { + error_ = "Not a slash-command"; + return; + } + if (!ascii_isalpha(command_[1])) + { + error_ = "Slash command must start with ascii alpha"; + return; + } + for (int i = 1; i < int(command_.size()); i++) + { + if (!ascii_isalnum(command_[i])) + { + error_ = "Slash-command must be alphanumeric"; + return; + } + } +} + +bool SlashCommandParser::Parse(std::string_view Command, std::string_view ArgTypes) +{ + if (Command != std::string_view(command_)) return false; + + args_.clear(); + for (int i = 0; i < int(ArgTypes.size()); i++) + { + switch(ArgTypes[i]) + { + case 's': + { + std::string word = read_word(); + if (word.empty()) + { + error_ = "Expected string arg"; + return false; + } + args_.emplace_back(word); + break; + } + case 'i': + { + std::string word = read_word(); + const char *p = word.c_str(); + const char *last = p + word.size(); + if ((p < last) && (*p == '+')) p++; + int64_t result; + auto r = std::from_chars(p, last, result, 10); + if ((r.ec != std::errc()) || (r.ptr != last)) { + error_ = "Expected int arg"; + return false; + } + args_.emplace_back(result); + break; + } + case 'd': + { + error_ = "Double args not implemented yet."; + break; + } + default: + { + error_ = "Invalid argtypes "; + error_ += ArgTypes; + error_ += " parsing "; + error_ += Command; + return false; + } + } + } + + if (cursor_ != linelen_) + { + error_ = "Too many arguments for command: "; + error_ += Command; + return false; + } + + error_.clear(); + return true; +} + +std::string SlashCommandParser::Error() const +{ + if (!error_.empty()) + { + return error_; + } + else + { + return std::string("Unrecognized command: ") + command_; + } +} + +std::string SlashCommandParser::StringArg(int n) const +{ + if ((n < 0) || (n >= int(args_.size()))) return ""; + + return args_[n].s_; +} + +int64_t SlashCommandParser::IntArg(int n) const +{ + if ((n < 0) || (n >= int(args_.size()))) return 0; + + return args_[n].i_; +} + +double SlashCommandParser::DoubleArg(int n) const +{ + if ((n < 0) || (n >= int(args_.size()))) return 0.0; + + return args_[n].d_; +} diff --git a/luprex/ext/slash-parser.hpp b/luprex/ext/slash-parser.hpp new file mode 100644 index 00000000..19c7be17 --- /dev/null +++ b/luprex/ext/slash-parser.hpp @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////////////// +// +// A slash-command parser. The intended use is as follows: +// +// SlashCommandParser parser(Command); +// +// if parser.Parse("/command1", "syntax1") { ... handle command1 ... } +// else if parser.Parse("/command2", "syntax2") { ... handle command2 ... } +// else if parser.Parse("/command3", "syntax3") { ... handle command3 ... } +// else { ... print an error message ... } +// +//////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include + +class SlashCommandParser +{ + public: + struct Value + { + std::string s_; + int64_t i_; + double d_; + Value(const std::string &s) : s_(s), i_(0), d_(0) {} + Value(int64_t i) : s_(), i_(i), d_(0) {} + Value(double d) : s_(), i_(0), d_(d) {} + }; + std::vector args_; + + private: + int cursor_; + std::string command_; + std::string line_; + int linelen_; + std::string error_; + + static bool ascii_isupper(char c) { return (c >= 'A') && (c <= 'Z'); } + static bool ascii_islower(char c) { return (c >= 'a') && (c <= 'z'); } + static bool ascii_isdigit(char c) { return (c >= '0') && (c <= '9'); } + static bool ascii_isalpha(char c) { return ascii_isupper(c) || ascii_islower(c); } + static bool ascii_isalnum(char c) { return ascii_isalpha(c) || ascii_isdigit(c); } + static bool ascii_isspace(char c) { return (c==0)||(c==' ')||(c=='\t')||(c=='\r')||(c=='\n')||(c=='\f')||(c=='\v'); } + + void skip_white(); + std::string read_word(); + + public: + // Initialize the slash-command parser with the full command line. + // + // The command line is stored. The first keyword (the slash-command + // itself) is extracted and stored. If the command-line doesn't + // start with a slash-command, an error message is stored. + // + // After initializing the parser, you should call Parse once + // for every known command. + // + SlashCommandParser(std::string_view Line); + + // Parse: Try to parse a slash-command. + // + // The intent is that you should call 'Parse' once for every + // known command. Hopefully, one of these 'Parse' commands + // will return true. If not, then the command couldn't be parsed. + // + // You must pass in the name of a slash-command (eg, "/compile") + // followed by the argument types that the command accepts. + // + // The argument types are represented as a string containing the + // characters i (for int64), s (for string), and d (for double). + // For example, if ArgTypes is "sid", then the command expects a + // string, then an int64, then a double. + // + // If the command matches, and the arguments parse correctly, + // returns true, stores the parsed arguments, and clears the + // stored error message. + // + // If the command matches, but the syntax is wrong, returns false + // and stores the error message. + // + // If the command doesn't match, returns false. + // + bool Parse(std::string_view Command, std::string_view ArgTypes); + + // Error: Declare that parsing has failed, and get an error message. + // + // You call this if you tried calling Parse on every known command, + // and none of the Parse calls returned true. Returns an error + // message indicating what went wrong. + // + std::string Error() const; + + // Get the Nth argument. + // + // After Parse returns true, the parsed arguments are stored in the + // SlashCommandParser. You can use these functions to fetch the + // parsed arguments. + // + // For example, if you call Parse("/hello", "sid") and it returns + // true, you should then call StringArg(0), then IntArg(1), then + // DoubleArg(2) to get all the arguments. + // + std::string StringArg(int n) const; + int64_t IntArg(int n) const; + double DoubleArg(int n) const; +}; + + +