From 7fa3f39d72d1aded4169f835a490d22e6c14ed78 Mon Sep 17 00:00:00 2001 From: jyelon Date: Fri, 9 Jan 2026 14:28:58 -0500 Subject: [PATCH] On server, channel dprint through the readline-device. Also some refactors and quality improvements. --- luprex/Makefile | 2 +- luprex/cpp/drv/driver-linux.cpp | 16 +--- luprex/cpp/drv/driver-windows.cpp | 47 ------------ luprex/cpp/drv/driver.cpp | 33 ++++---- luprex/cpp/drv/drvutil-linux.cpp | 83 +++++++++++++++++++++ luprex/cpp/drv/drvutil-windows.cpp | 94 +++++++++++++++++++++++ luprex/cpp/drv/drvutil.cpp | 15 +++- luprex/cpp/drv/drvutil.hpp | 8 ++ luprex/cpp/drv/osdrvutil.cpp | 116 ----------------------------- luprex/cpp/drv/osdrvutil.hpp | 23 ------ luprex/cpp/drv/readline.cpp | 24 +++--- luprex/cpp/drv/readline.hpp | 6 +- luprex/cpp/drv/sslutil.cpp | 1 - luprex/ext/slash-parser.cpp | 41 +++++++--- luprex/ext/slash-parser.hpp | 2 + 15 files changed, 258 insertions(+), 253 deletions(-) create mode 100644 luprex/cpp/drv/drvutil-linux.cpp create mode 100644 luprex/cpp/drv/drvutil-windows.cpp delete mode 100644 luprex/cpp/drv/osdrvutil.cpp delete mode 100644 luprex/cpp/drv/osdrvutil.hpp diff --git a/luprex/Makefile b/luprex/Makefile index 0cc56a3b..edaa110a 100644 --- a/luprex/Makefile +++ b/luprex/Makefile @@ -86,7 +86,7 @@ BASE_CORE := \ world-difftab world-diffxmit world-pairtab world-testing lpxserver lpxclient \ eng-tests printbuffer serializelua -BASE_DRV := driver drvutil osdrvutil sslutil readline +BASE_DRV := driver drvutil sslutil readline ####################################################################### ## diff --git a/luprex/cpp/drv/driver-linux.cpp b/luprex/cpp/drv/driver-linux.cpp index cc087ca0..22e5bbf3 100644 --- a/luprex/cpp/drv/driver-linux.cpp +++ b/luprex/cpp/drv/driver-linux.cpp @@ -1,7 +1,6 @@ #include "drvutil.hpp" -#include "osdrvutil.hpp" #include "sslutil.hpp" #include "readline.hpp" #include "../core/enginewrapper.hpp" @@ -220,21 +219,8 @@ static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, std } // Write unicode onto the console. -static void console_write(const std::u32string &cps) { - std::string utf8 = drvutil::utf32_to_utf8(cps); - write(1, utf8.c_str(), utf8.size()); -} -static std::u32string console_read() { - std::u32string result; - char buffer[512]; - int nread = read(0, buffer, 512); - if (nread > 0) { - std::string_view s(buffer, nread); - result = drvutil::utf8_to_utf32(s, nullptr); - } - return result; -} + static void call_init_engine_wrapper(const std::filesystem::path &luprexroot, EngineWrapper *w) { using InitFn = void (*)(EngineWrapper *); diff --git a/luprex/cpp/drv/driver-windows.cpp b/luprex/cpp/drv/driver-windows.cpp index 31896443..5d04c6ce 100644 --- a/luprex/cpp/drv/driver-windows.cpp +++ b/luprex/cpp/drv/driver-windows.cpp @@ -2,7 +2,6 @@ #define _WIN32_WINNT 0x0600 #include "drvutil.hpp" -#include "osdrvutil.hpp" #include "sslutil.hpp" #include "readline.hpp" #include "../core/enginewrapper.hpp" @@ -232,52 +231,6 @@ static void init_winsock() { } -static void console_write(const std::u32string &cps) { - if (cps.size() == 0) return; - // Convert to wstring. Any character not representable as a single wchar_t - // is replaced with a box. It's not ideal, but it's pretty good. - std::wstring ws(cps.size(), 0); - for (int i = 0; i < int(cps.size()); i++) { - char32_t c = cps[i]; - if (drvutil::is_single_wchar_t(c)) ws[i] = (wchar_t)c; - else ws[i] = 0x2610; - } - HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE); - assert(hstdout != INVALID_HANDLE_VALUE); - DWORD nwrote; - std::wstring_view v(ws); - while (v.size() > 0) { - int nwrite = v.size(); - if (nwrite > 10000) nwrite = 10000; - assert(WriteConsoleW(hstdout, v.data(), nwrite, &nwrote, nullptr)); - assert(nwrote > 0); - v.remove_prefix(nwrote); - } -} - -static std::u32string console_read() { - HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE); - assert(hstdin != INVALID_HANDLE_VALUE); - INPUT_RECORD inrecords[512]; - DWORD nread, nevents; - if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) { - if (int(nevents) > 0) { - if (int(nevents) > 512) nevents = 512; - ReadConsoleInputW(hstdin, inrecords, nevents, &nread); - std::u32string result(nread, 0); - int len = 0; - for (int i = 0; i < int(nread); i++) { - const INPUT_RECORD &inr = inrecords[i]; - if (inr.EventType != KEY_EVENT) continue; - const KEY_EVENT_RECORD &key = inr.Event.KeyEvent; - if (!key.bKeyDown) continue; - result[len++] = key.uChar.UnicodeChar; - } - return result.substr(0, len); - } - } - return std::u32string(); -} static void ssl_load_certificate_authorities(SSL_CTX *ctx) { HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); diff --git a/luprex/cpp/drv/driver.cpp b/luprex/cpp/drv/driver.cpp index 98429e07..8185335e 100644 --- a/luprex/cpp/drv/driver.cpp +++ b/luprex/cpp/drv/driver.cpp @@ -11,6 +11,11 @@ #define POLLVEC_SIZE (DRV_MAX_CHAN + 1) #define MAX_BIO_BUFFER (128 * 1024) +static ReadlineDevice readline_device; + +static void dprint_callback(const char *oneline, size_t size) { + readline_device.printline(std::string_view(oneline, size)); +} static void if_error_print_and_exit(const std::string_view str) { if (!str.empty()) { @@ -19,16 +24,6 @@ static void if_error_print_and_exit(const std::string_view str) { } } -// DPrints are currently not going through the readline device. -// doing so would not currently be thread-safe. Do I care about -// that? I'm not sure. -static void dprint_callback(const char *oneline, size_t size) { - fwrite("**", 1, 2, stderr); - fwrite(oneline, 1, size, stderr); - fwrite("\n", 1, 1, stderr); - fflush(stderr); -} - inline bool file_exists(const std::filesystem::path &name) { std::ifstream f(name); return f.good(); @@ -96,7 +91,6 @@ class Driver { bool read_console_recently_; std::unique_ptr pollvec_; std::unique_ptr chbuf_; - ReadlineDevice readline_device_; std::string console_command_; sslutil::UniqueCTX ssl_server_ctx_; @@ -209,7 +203,7 @@ class Driver { engw.play_access(&engw, AccessKind::CHANNEL_PRINTS, 0, 0, "", &ndata, &data); if (ndata > 0) { if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; - readline_device_.printline(std::string_view(data, ndata)); + readline_device.printline(std::string_view(data, ndata)); } } } @@ -224,7 +218,7 @@ class Driver { inject_lua_source(); } else { - readline_device_.printline(parser.Error()); + readline_device.printline(parser.Error()); } } @@ -249,24 +243,24 @@ class Driver { engw.play_access(&engw, AccessKind::INVOKE_LUA_EXPR, 0, cmd.size(), cmd.c_str(), nullptr, nullptr); } else { - readline_device_.printline(message); + readline_device.printline(message); } if (console_command_.empty()) { - readline_device_.set_prompt(">"); + readline_device.set_prompt(">"); } else { - readline_device_.set_prompt(">>"); + readline_device.set_prompt(">>"); } } void handle_console_input() { read_console_recently_ = false; while (true) { - std::u32string cps = console_read(); + std::u32string cps = drvutil::console_read(); if (cps.size() == 0) break; read_console_recently_ = true; for (char32_t c : cps) { - std::string line = readline_device_.putcode(c); + std::string line = readline_device.putcode(c); if (!line.empty()) { add_console_command(line); } @@ -631,8 +625,7 @@ class Driver { int drive(int argc, char *argv[]) { // Set up the console readline device. - readline_device_.set_print_callback(console_write); - readline_device_.set_prompt(">"); + readline_device.set_prompt(">"); console_command_.clear(); // Remove the program name from argv. diff --git a/luprex/cpp/drv/drvutil-linux.cpp b/luprex/cpp/drv/drvutil-linux.cpp new file mode 100644 index 00000000..72246a8a --- /dev/null +++ b/luprex/cpp/drv/drvutil-linux.cpp @@ -0,0 +1,83 @@ + +#include +#include +#include +#include +#include +#include + +namespace drvutil { + +// strerror has to be the most overcomplicated function imaginable. The simple +// version, 'strerror', is not thread-safe, and the improved versions are all +// incompatible from OS to OS. Even different versions of linux aren't +// compatible. A lot of conditional compilation is needed. + +inline static void strerror_helper(int status, int errnum, char errbuf[256]) { + if (status != 0) { + snprintf(errbuf, 256, "unknown errno %d", errnum); + } +} + +inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) { + if (result != errbuf) { + snprintf(errbuf, 256, "%s", result); + } +} + +void strerror_safe(int errnum, char errbuf[256]) { + auto rval = strerror_r(errnum, errbuf, 256); + strerror_helper(rval, errnum, errbuf); +} + +std::string strerror_str(int errnum) { + char buf[256]; + strerror_safe(errnum, buf); + return buf; +} + + + + +class MonoClock { +private: + struct timespec base_; +public: + MonoClock() { + int status = clock_gettime(CLOCK_MONOTONIC, &base_); + assert(status == 0); + } + double get() { + struct timespec t; + int status = clock_gettime(CLOCK_MONOTONIC, &t); + assert(status == 0); + double tv_sec = t.tv_sec - base_.tv_sec; + double tv_nsec = t.tv_nsec - base_.tv_nsec; + return tv_sec + (tv_nsec * 1.0E-9); + } +}; + +static MonoClock monoclock; +double get_monotonic_clock() { + return monoclock.get(); +} + + +void console_write(const std::u32string &cps) { + std::string utf8 = drvutil::utf32_to_utf8(cps); + write(1, utf8.c_str(), utf8.size()); +} + +std::u32string console_read() { + std::u32string result; + char buffer[512]; + int nread = read(0, buffer, 512); + if (nread > 0) { + std::string_view s(buffer, nread); + result = drvutil::utf8_to_utf32(s, nullptr); + } + return result; +} + +} // namespace drvutil + diff --git a/luprex/cpp/drv/drvutil-windows.cpp b/luprex/cpp/drv/drvutil-windows.cpp new file mode 100644 index 00000000..5600419f --- /dev/null +++ b/luprex/cpp/drv/drvutil-windows.cpp @@ -0,0 +1,94 @@ + +#include +#include + +namespace drvutil { + +static void strerror_safe(int errnum, char errbuf[256]) { + int status = strerror_s(errbuf, 256, errnum); + if (status != 0) { + snprintf(errbuf, 256, "unknown errno %d", errnum); + } +} + +std::string strerror_str(int errnum) { + char buf[256]; + strerror_safe(errnum, buf); + return buf; +} + +class MonoClock { +public: + double freq_; + LONGLONG base_; + inline LONGLONG qpc() { + LARGE_INTEGER x; + BOOL status = QueryPerformanceCounter(&x); + assert(status != 0); + return x.QuadPart; + } + MonoClock() { + LARGE_INTEGER x; + BOOL status = QueryPerformanceFrequency(&x); + assert(status != 0); + freq_ = 1.0 / double(x.QuadPart); + base_ = qpc(); + } + double get() { + return (qpc() - base_) * freq_; + } +}; + +static MonoClock monoclock; +double get_monotonic_clock() { + return monoclock.get(); +} + +void console_write(const std::u32string &cps) { + if (cps.size() == 0) return; + // Convert to wstring. Any character not representable as a single wchar_t + // is replaced with a box. It's not ideal, but it's pretty good. + std::wstring ws(cps.size(), 0); + for (int i = 0; i < int(cps.size()); i++) { + char32_t c = cps[i]; + if (drvutil::is_single_wchar_t(c)) ws[i] = (wchar_t)c; + else ws[i] = 0x2610; + } + HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE); + assert(hstdout != INVALID_HANDLE_VALUE); + DWORD nwrote; + std::wstring_view v(ws); + while (v.size() > 0) { + int nwrite = v.size(); + if (nwrite > 10000) nwrite = 10000; + assert(WriteConsoleW(hstdout, v.data(), nwrite, &nwrote, nullptr)); + assert(nwrote > 0); + v.remove_prefix(nwrote); + } +} + +std::u32string console_read() { + HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE); + assert(hstdin != INVALID_HANDLE_VALUE); + INPUT_RECORD inrecords[512]; + DWORD nread, nevents; + if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) { + if (int(nevents) > 0) { + if (int(nevents) > 512) nevents = 512; + ReadConsoleInputW(hstdin, inrecords, nevents, &nread); + std::u32string result(nread, 0); + int len = 0; + for (int i = 0; i < int(nread); i++) { + const INPUT_RECORD &inr = inrecords[i]; + if (inr.EventType != KEY_EVENT) continue; + const KEY_EVENT_RECORD &key = inr.Event.KeyEvent; + if (!key.bKeyDown) continue; + result[len++] = key.uChar.UnicodeChar; + } + return result.substr(0, len); + } + } + return std::u32string(); +} + +} // namespace drvutil \ No newline at end of file diff --git a/luprex/cpp/drv/drvutil.cpp b/luprex/cpp/drv/drvutil.cpp index cd57c6f4..663cfadd 100644 --- a/luprex/cpp/drv/drvutil.cpp +++ b/luprex/cpp/drv/drvutil.cpp @@ -354,4 +354,17 @@ std::string package_lua_source(const std::filesystem::path &base, std::ostream * } -} // namespace drv \ No newline at end of file +} // namespace drvutil + + +// Include the system-dependent part of drvutil. + +#if defined(__linux__) + #include "drvutil-linux.cpp" +#elif defined(_WIN32) + #include "drvutil-windows.cpp" +#else + #error "Only support __linux__ or _WIN32" +#endif + + diff --git a/luprex/cpp/drv/drvutil.hpp b/luprex/cpp/drv/drvutil.hpp index c670c238..afc89c0f 100644 --- a/luprex/cpp/drv/drvutil.hpp +++ b/luprex/cpp/drv/drvutil.hpp @@ -19,6 +19,14 @@ namespace drvutil { +// Read/write the console. +// +// These only work when the program is being run from an actual +// text-based console. Otherwise, they may go to the bit-bucket. +// +std::u32string console_read(); +void console_write(const std::u32string &cps); + // Read the lua source from disk into an ostringstream. // // To pass the lua source into the DLL, here is what you do: Construct an diff --git a/luprex/cpp/drv/osdrvutil.cpp b/luprex/cpp/drv/osdrvutil.cpp deleted file mode 100644 index 4f7d5642..00000000 --- a/luprex/cpp/drv/osdrvutil.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "osdrvutil.hpp" - -#if defined(__linux__) - #include -#elif defined(_WIN32) - #include - #include -#else - #error "Only support __linux__ or _WIN32" -#endif - -#include -#include -#include -#include - -// strerror has to be the most overcomplicated function imaginable. The simple -// version, 'strerror', is not thread-safe, and the improved versions are all -// incompatible from OS to OS. Even different versions of linux aren't -// compatible. A lot of conditional compilation is needed. - -#if defined(__linux__) - -inline static void strerror_helper(int status, int errnum, char errbuf[256]) { - if (status != 0) { - snprintf(errbuf, 256, "unknown errno %d", errnum); - } -} - -inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) { - if (result != errbuf) { - snprintf(errbuf, 256, "%s", result); - } -} - -static void strerror_safe(int errnum, char errbuf[256]) { - auto rval = strerror_r(errnum, errbuf, 256); - strerror_helper(rval, errnum, errbuf); -} - -#elif defined(_WIN32) - -static void strerror_safe(int errnum, char errbuf[256]) { - int status = strerror_s(errbuf, 256, errnum); - if (status != 0) { - snprintf(errbuf, 256, "unknown errno %d", errnum); - } -} - -#endif - -// The monotonic clock is required to start at zero at initialization time, -// advance steadily, and never go backwards. It is okay, however, if it is a -// little inaccurate, or if it drifts a little over time. - -#if defined(__linux__) - - class MonoClock { - private: - struct timespec base_; - public: - MonoClock() { - int status = clock_gettime(CLOCK_MONOTONIC, &base_); - assert(status == 0); - } - double get() { - struct timespec t; - int status = clock_gettime(CLOCK_MONOTONIC, &t); - assert(status == 0); - double tv_sec = t.tv_sec - base_.tv_sec; - double tv_nsec = t.tv_nsec - base_.tv_nsec; - return tv_sec + (tv_nsec * 1.0E-9); - } - }; - -#elif defined(_WIN32) - - class MonoClock { - public: - double freq_; - LONGLONG base_; - inline LONGLONG qpc() { - LARGE_INTEGER x; - BOOL status = QueryPerformanceCounter(&x); - assert(status != 0); - return x.QuadPart; - } - MonoClock() { - LARGE_INTEGER x; - BOOL status = QueryPerformanceFrequency(&x); - assert(status != 0); - freq_ = 1.0 / double(x.QuadPart); - base_ = qpc(); - } - double get() { - return (qpc() - base_) * freq_; - } - }; - -#endif - - -namespace drvutil { - -static MonoClock monoclock; -double get_monotonic_clock() { - return monoclock.get(); -} - -std::string strerror_str(int errnum) { - char buf[256]; - strerror_safe(errnum, buf); - return buf; - -} -} // namespace drvutil \ No newline at end of file diff --git a/luprex/cpp/drv/osdrvutil.hpp b/luprex/cpp/drv/osdrvutil.hpp deleted file mode 100644 index 34af2bc8..00000000 --- a/luprex/cpp/drv/osdrvutil.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef OSDRVUTIL_HPP -#define OSDRVUTIL_HPP - -#include - -namespace drvutil { - -// Get a system error message, in an OS-independent manner. -// -std::string strerror_str(int errnum); - -// Get the amount of time elapsed since program start. -// -// This is guaranteed to be monotonically increasing. It is not -// guaranteed to be accurate. Error could gradually accumulate over -// time. -// -double get_monotonic_clock(); - -} // namespace drvutil - -#endif // OSDRVUTIL_HPP - diff --git a/luprex/cpp/drv/readline.cpp b/luprex/cpp/drv/readline.cpp index d9d487c6..3f3c0f27 100644 --- a/luprex/cpp/drv/readline.cpp +++ b/luprex/cpp/drv/readline.cpp @@ -24,11 +24,8 @@ static int common_prefix_length(const std::u32string &a, const std::u32string &b return minlen; } -void ReadlineDevice::set_print_callback(print_callback cb) { - print_cb_ = cb; -} - void ReadlineDevice::set_prompt(std::string_view prompt) { + std::scoped_lock lock(mutex_); desired_prompt_ = drvutil::utf8_to_utf32(prompt, nullptr); echo_command(); } @@ -36,7 +33,7 @@ void ReadlineDevice::set_prompt(std::string_view prompt) { void ReadlineDevice::erase_command() { int ccsize = current_prompt_.size() + current_command_.size(); if (ccsize > 0) { - print_cb_(n_backspaces(ccsize)); + drvutil::console_write(n_backspaces(ccsize)); current_prompt_.clear(); current_command_.clear(); } @@ -46,8 +43,8 @@ void ReadlineDevice::echo_command() { // If the prompt has changed, erase everything and start over. if (desired_prompt_ != current_prompt_) { int ccsize = current_prompt_.size() + current_command_.size(); - print_cb_(n_backspaces(ccsize)); - print_cb_(desired_prompt_); + drvutil::console_write(n_backspaces(ccsize)); + drvutil::console_write(desired_prompt_); current_command_.clear(); current_prompt_ = desired_prompt_; } @@ -58,31 +55,31 @@ void ReadlineDevice::echo_command() { // Echo backspaces to remove the non-matching part. int remove = current_command_.size() - match; if (remove > 0) { - print_cb_(n_backspaces(remove)); + drvutil::console_write(n_backspaces(remove)); current_command_ = current_command_.substr(0, match); } // Echo the new part. std::u32string newpart = desired_command_.substr(current_command_.size()); if (!newpart.empty()) { - print_cb_(newpart); + drvutil::console_write(newpart); current_command_ = desired_command_; } } std::string ReadlineDevice::putcode(char32_t c) { + std::scoped_lock lock(mutex_); if ((c == '\n') && (readline_lastc_ == '\r')) { // Ignore newline immediately after carriage return. // Otherwise, crlf produces two newlines. return ""; } else if ((c == '\r') || (c == '\n')) { echo_command(); - print_cb_(white_ + newline_); + drvutil::console_write(white_ + newline_); std::u32string result = desired_command_ + newline_; desired_command_.clear(); current_prompt_.clear(); current_command_.clear(); - echo_command(); return drvutil::utf32_to_utf8(result); } else if ((c == '\b') || (c == 127)) { int len = desired_command_.size(); @@ -105,11 +102,12 @@ std::string ReadlineDevice::putcode(char32_t c) { void ReadlineDevice::printline(std::string_view s) { + std::scoped_lock lock(mutex_); bool missing_newline = ((s.size() == 0) || (s[s.size() - 1] != '\n')); std::u32string utf32 = drvutil::utf8_to_utf32(s, nullptr); erase_command(); - print_cb_(utf32); - if (missing_newline) print_cb_(newline_); + drvutil::console_write(utf32); + if (missing_newline) drvutil::console_write(newline_); echo_command(); } diff --git a/luprex/cpp/drv/readline.hpp b/luprex/cpp/drv/readline.hpp index 098b24a3..90a16feb 100644 --- a/luprex/cpp/drv/readline.hpp +++ b/luprex/cpp/drv/readline.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "drvutil.hpp" @@ -12,7 +13,7 @@ public: using print_callback = void (*)(const std::u32string &text); private: - print_callback print_cb_; + std::mutex mutex_; std::u32string desired_command_; std::u32string current_command_; std::u32string desired_prompt_; @@ -24,9 +25,6 @@ private: public: - // The callback must be set before using the readline device. - void set_print_callback(print_callback cb); - // change the prompt. void set_prompt(std::string_view prompt); diff --git a/luprex/cpp/drv/sslutil.cpp b/luprex/cpp/drv/sslutil.cpp index dbae9be2..1cdd3072 100644 --- a/luprex/cpp/drv/sslutil.cpp +++ b/luprex/cpp/drv/sslutil.cpp @@ -1,5 +1,4 @@ #include "drvutil.hpp" -#include "osdrvutil.hpp" #include "sslutil.hpp" #include #include diff --git a/luprex/ext/slash-parser.cpp b/luprex/ext/slash-parser.cpp index c746fb2b..6a007d78 100644 --- a/luprex/ext/slash-parser.cpp +++ b/luprex/ext/slash-parser.cpp @@ -4,6 +4,7 @@ #include "slash-parser.hpp" #endif #include +#include @@ -61,16 +62,18 @@ bool SlashCommandParser::Parse(std::string_view Command, std::string_view ArgTyp args_.clear(); for (int i = 0; i < int(ArgTypes.size()); i++) { + if (cursor_ == linelen_) + { + std::ostringstream oss; + oss << "Not enough arguments for " << Command << " (need " << ArgTypes.size() << ")"; + error_ = oss.str(); + return false; + } switch(ArgTypes[i]) { case 's': { std::string word = read_word(); - if (word.empty()) - { - error_ = "Expected string arg"; - return false; - } args_.emplace_back(word); break; } @@ -82,8 +85,11 @@ bool SlashCommandParser::Parse(std::string_view Command, std::string_view ArgTyp 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"; + if ((r.ec != std::errc()) || (r.ptr != last)) + { + std::ostringstream oss; + oss << Command << " expects arg " << i << " to be an int, not '" << word << "'"; + error_ = oss.str(); return false; } args_.emplace_back(result); @@ -91,15 +97,26 @@ bool SlashCommandParser::Parse(std::string_view Command, std::string_view ArgTyp } case 'd': { - error_ = "Double args not implemented yet."; + std::string word = read_word(); + const char *p = word.c_str(); + const char *last = p + word.size(); + double result; + auto r = std::from_chars(p, last, result); + if ((r.ec != std::errc()) || (r.ptr != last)) + { + std::ostringstream oss; + oss << Command << " expects arg " << i << " to be a double, not '" << word << "'"; + error_ = oss.str(); + return false; + } + args_.emplace_back(result); break; } default: { - error_ = "Invalid argtypes "; - error_ += ArgTypes; - error_ += " parsing "; - error_ += Command; + std::ostringstream oss; + oss << "Invalid ArgTypes parameter (" << ArgTypes << ") to Parse routine for " << Command; + error_ = oss.str(); return false; } } diff --git a/luprex/ext/slash-parser.hpp b/luprex/ext/slash-parser.hpp index 19c7be17..62c456eb 100644 --- a/luprex/ext/slash-parser.hpp +++ b/luprex/ext/slash-parser.hpp @@ -49,6 +49,8 @@ class SlashCommandParser void skip_white(); std::string read_word(); + void not_enough_args(const std::string &cmd); + public: // Initialize the slash-command parser with the full command line.