diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index e5dd764d..5e4b7c2a 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -1,6 +1,6 @@ #include "wrap-sstream.hpp" #include "wrap-map.hpp" - +#include "util.hpp" #include "animqueue.hpp" #include "luastack.hpp" #include "streambuffer.hpp" @@ -297,25 +297,25 @@ bool AnimStep::from_string(const eng::string &config) { if (key == "action") { action_ = val; } else if (key == "id") { - int64_t id = util::str_to_int64(val, -1); + int64_t id = sv::to_int64(val, -1); if (id < 0) return false; id_ = id; } else if (key == "plane") { set_plane(val); } else if (key == "x") { - double v = util::str_to_double(val); + double v = sv::to_double(val); if (std::isnan(v)) return false; set_x(v); } else if (key == "y") { - double v = util::str_to_double(val); + double v = sv::to_double(val); if (std::isnan(v)) return false; set_y(v); } else if (key == "z") { - double v = util::str_to_double(val); + double v = sv::to_double(val); if (std::isnan(v)) return false; set_z(v); } else if (key == "facing") { - double v = util::str_to_double(val); + double v = sv::to_double(val); if (std::isnan(v)) return false; set_facing(v); } else if (key == "graphic") { diff --git a/luprex/core/cpp/drivenengine.cpp b/luprex/core/cpp/drivenengine.cpp index a3be997d..b7ce9569 100644 --- a/luprex/core/cpp/drivenengine.cpp +++ b/luprex/core/cpp/drivenengine.cpp @@ -1,12 +1,12 @@ #include "wrap-string.hpp" #include "wrap-vector.hpp" +#include "util.hpp" +#include "drivenengine.hpp" #include #include #include -#include "drivenengine.hpp" - DrivenEngineReg *DrivenEngineReg::All; DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) { @@ -66,7 +66,7 @@ void Channel::echo_command() { } // Find out how much of the command matches. - int match = util::common_prefix_length(current_command_, desired_command_); + int match = sv::common_prefix_length(current_command_, desired_command_); // Echo backspaces to remove the non-matching part. int remove = current_command_.size() - match; diff --git a/luprex/core/cpp/driver-util.cpp b/luprex/core/cpp/driver-util.cpp index ba905f27..1997544b 100644 --- a/luprex/core/cpp/driver-util.cpp +++ b/luprex/core/cpp/driver-util.cpp @@ -1,17 +1,16 @@ #include "wrap-string.hpp" #include "wrap-vector.hpp" +#include "util.hpp" +#include "spookyv2.hpp" +#include "driver-util.hpp" +#include "luastack.hpp" #include #include #include #include -#include "spookyv2.hpp" -#include "driver-util.hpp" -#include "luastack.hpp" -#include "util.hpp" - #define RLOG_BUFSIZE (1024 * 1024) #define MAX_ARGC 1024 @@ -47,8 +46,8 @@ void split_target(std::string_view target, std::string &cert, std::string &host, std::vector parse_control_lst(std::string_view ctrl) { std::vector result; while (!ctrl.empty()) { - std::string_view line = util::sv_read_line(ctrl); - std::string_view trimmed = util::sv_trim(line); + std::string_view line = sv::read_to_line(ctrl); + std::string_view trimmed = sv::trim(line); if ((trimmed.size() > 0) && (trimmed[0] != '#')) { result.emplace_back(trimmed); } diff --git a/luprex/core/cpp/gui.cpp b/luprex/core/cpp/gui.cpp index fc8f77dc..91b53e66 100644 --- a/luprex/core/cpp/gui.cpp +++ b/luprex/core/cpp/gui.cpp @@ -1,7 +1,7 @@ #include "wrap-string.hpp" #include "wrap-map.hpp" #include "wrap-vector.hpp" - +#include "util.hpp" #include "gui.hpp" #include "luastack.hpp" @@ -62,7 +62,7 @@ LuaDefine(gui_menu_item, "action,label", "add a menu item to the current gui") { LuaStack LS(L, laction, llabel); eng::string action = LS.ckstring(laction); eng::string label = LS.ckstring(llabel); - if (!util::has_prefix(action, "cb_")) { + if (!sv::has_prefix(action, "cb_")) { luaL_error(L, "menuitem callbacks must start with cb_"); } gui->menu_item(action, label); diff --git a/luprex/core/cpp/http.cpp b/luprex/core/cpp/http.cpp index 8f941524..11850670 100644 --- a/luprex/core/cpp/http.cpp +++ b/luprex/core/cpp/http.cpp @@ -2,7 +2,9 @@ #include "http.hpp" #include "wrap-sstream.hpp" #include "wrap-string.hpp" +#include "util.hpp" #include "luastack.hpp" + #include static void url_encode(const eng::string &value, StreamBuffer *sb) { @@ -10,7 +12,7 @@ static void url_encode(const eng::string &value, StreamBuffer *sb) { for (int i = 0; i < int(value.size()); i++) { char c = value[i]; - if (util::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || (c == '/')) { + if (sv::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || (c == '/')) { sb->write_char(c); } else if (c == ' ') { sb->write_char('+'); @@ -77,7 +79,7 @@ void HttpRequest::set_host(const eng::string &s) { // This is not quite strict, but it's close. I believe // the DNS lookup will fail for invalid hostnames anyway. for (char c : host) { - if ((c != '-') && (c != '.') && (!util::ascii_isalnum(c))) { + if ((c != '-') && (c != '.') && (!sv::ascii_isalnum(c))) { ErrorStringStream error(&error_); error << "HTTPS hostnames can only contain letters, digits, and hyphen: " << host; return; @@ -106,11 +108,11 @@ void HttpRequest::set_port(int port) { } void HttpRequest::set_url(const eng::string &url) { - if (util::has_prefix(url, "https://")) { + if (sv::has_prefix(url, "https://")) { ErrorStringStream error(&error_); error << "set_url(full_url) not implemented yet."; return; - } else if (util::has_prefix(url, "/")) { + } else if (sv::has_prefix(url, "/")) { if (!path_.empty()) { ErrorStringStream error(&error_); error << "HTTP path specified twice: " << path_ << " and " << url; @@ -338,11 +340,6 @@ void HttpResponse::fail(int response_code, const eng::string &error) { content_ = ""; } -static std::string_view readline(std::string_view &v) { - std::string_view result = util::sv_split_one(v, '\n'); - return util::sv_rtrim(result, '\r'); -} - void HttpResponse::parse(const StreamBuffer *sb) { // We're not going to modify the StreamBuffer at all. // Instead, we work entirely on a view. @@ -355,7 +352,7 @@ void HttpResponse::parse(const StreamBuffer *sb) { } // Parse the status line. - std::string_view status = readline(view); + std::string_view status = sv::read_to_line(view); if (status.empty()) { fail(500, "HTTP status-line not present in response"); return; diff --git a/luprex/core/cpp/lpxclient.cpp b/luprex/core/cpp/lpxclient.cpp index c2c39832..8e113806 100644 --- a/luprex/core/cpp/lpxclient.cpp +++ b/luprex/core/cpp/lpxclient.cpp @@ -125,13 +125,13 @@ public: void do_menu_command(const StringVec &cmd) { world_to_asynchronous(); - int64_t place = util::str_to_int64(cmd[1], actor_id_); + int64_t place = sv::to_int64(cmd[1], actor_id_); world_->update_gui(actor_id_, place, &gui_); stdostream() << gui_.menu_debug_string(); } void do_choose_command(const StringVec &cmd) { - eng::string action = gui_.get_action(util::str_to_int64(cmd[1])); + eng::string action = gui_.get_action(sv::to_int64(cmd[1])); if (action == "") { stdostream() << "Invalid menu item #" << std::endl; return; diff --git a/luprex/core/cpp/luaconsole.cpp b/luprex/core/cpp/luaconsole.cpp index bfb21d8c..5f6204bc 100644 --- a/luprex/core/cpp/luaconsole.cpp +++ b/luprex/core/cpp/luaconsole.cpp @@ -1,8 +1,7 @@ -#include "eng-malloc.hpp" -#include "luastack.hpp" #include "wrap-string.hpp" #include "wrap-vector.hpp" - +#include "eng-malloc.hpp" +#include "luastack.hpp" #include "luaconsole.hpp" #include "util.hpp" @@ -53,7 +52,7 @@ void LuaConsole::simplify(const StringVec &words) { words_ = words; if (words.size() == 0) { return; - } else if (util::valid_int64(words[0])) { + } else if (sv::valid_int64(words[0])) { if (words.size() == 1) { words_.clear(); words_.push_back("choose"); @@ -62,7 +61,7 @@ void LuaConsole::simplify(const StringVec &words) { synerr("/choose command takes no arguments"); } } else if (words[0] == "choose") { - if ((words.size() == 2)&&(util::valid_int64(words[1]))) { + if ((words.size() == 2)&&(sv::valid_int64(words[1]))) { // OK } else { synerr("/choose [menu-line-number]"); @@ -74,7 +73,7 @@ void LuaConsole::simplify(const StringVec &words) { } else if (words[0] == "menu") { if (words.size() == 1) { words_.push_back("-"); - } else if ((words.size() == 2)&&(util::valid_int64(words[1]))) { + } else if ((words.size() == 2)&&(sv::valid_int64(words[1]))) { // OK } else { synerr("/menu [optional-tangible-id]"); @@ -126,13 +125,13 @@ void LuaConsole::add(eng::string line) { // Translate lua expression, deal with initial prefix. eng::string partial; eng::string lua_mode; - if (util::has_prefix(raw_input_, "?=")) { + if (sv::has_prefix(raw_input_, "?=")) { lua_mode = "luaprobe"; partial = eng::string("return ") + raw_input_.substr(2); - } else if (util::has_prefix(raw_input_, "?")) { + } else if (sv::has_prefix(raw_input_, "?")) { lua_mode = "luaprobe"; partial = raw_input_.substr(1); - } else if (util::has_prefix(raw_input_, "=")) { + } else if (sv::has_prefix(raw_input_, "=")) { lua_mode = "luainvoke"; partial = eng::string("return ") + raw_input_.substr(1); } else { @@ -157,7 +156,7 @@ void LuaConsole::add(eng::string line) { } } else { words_.push_back(lua_mode); - words_.push_back(util::rtrim(partial)); + words_.emplace_back(sv::rtrim(partial)); clear_raw_input(); } lua_settop(lua_state_, top); diff --git a/luprex/core/cpp/pprint.cpp b/luprex/core/cpp/pprint.cpp index 8185a11f..2b4d22f4 100644 --- a/luprex/core/cpp/pprint.cpp +++ b/luprex/core/cpp/pprint.cpp @@ -197,7 +197,7 @@ static void pprint_r(Inspector &insp, int level, LuaSlot root) { } else { multiline = true; tabify(insp, level + 1); - if (LS.isstring(key) && util::is_identifier(LS.ckstring(key))) { + if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) { (*insp.stream) << LS.ckstring(key); } else { (*insp.stream) << "["; @@ -254,7 +254,7 @@ LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua LuaStack LS(L, str, result); if (LS.isstring(str)) { eng::string s = LS.ckstring(str); - LS.set(result, util::is_identifier(s)); + LS.set(result, sv::is_lua_id(s)); } else { LS.set(result, false); } diff --git a/luprex/core/cpp/source.cpp b/luprex/core/cpp/source.cpp index 1a7f32e2..fede292a 100644 --- a/luprex/core/cpp/source.cpp +++ b/luprex/core/cpp/source.cpp @@ -7,7 +7,6 @@ #include "wrap-map.hpp" #include "wrap-set.hpp" #include "wrap-sstream.hpp" - #include "util.hpp" #include "luastack.hpp" #include "traceback.hpp" @@ -569,7 +568,7 @@ eng::string SourceDB::function_docs(const LuaStack &LS0, LuaSlot fn) { // Incorporate the function comment. int linelo = linehi; - while ((linelo > 0) && (util::is_lua_comment(lines[linelo-1]))) linelo -= 1; + while ((linelo > 0) && (sv::is_lua_comment(lines[linelo-1]))) linelo -= 1; // Output the docs. eng::ostringstream result; diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 29a94784..cfac55c2 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -1,7 +1,6 @@ #include "wrap-vector.hpp" #include "wrap-string.hpp" - #include "luastack.hpp" #include "util.hpp" #include "gui.hpp" @@ -47,13 +46,13 @@ private: } void do_menu_command(const StringVec &cmd) { - int64_t place = util::str_to_int64(cmd[1], actor_id_); + int64_t place = sv::to_int64(cmd[1], actor_id_); world_->update_gui(actor_id_, place, &gui_); stdostream() << gui_.menu_debug_string(); } void do_choose_command(const StringVec &cmd) { - eng::string action = gui_.get_action(util::str_to_int64(cmd[1])); + eng::string action = gui_.get_action(sv::to_int64(cmd[1])); if (action == "") { stdostream() << "Invalid menu item #" << std::endl; return; diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index 659cab92..47343748 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -1,11 +1,10 @@ #include "wrap-string.hpp" #include "wrap-vector.hpp" +#include "util.hpp" #include "fast-float.hpp" + #include - -#include "util.hpp" - #include #include #include @@ -23,29 +22,113 @@ #include #endif -namespace util { +namespace sv { -eng::string ascii_tolower(const eng::string &s) { - eng::string mod = s; - for (int i = 0; i < int(mod.size()); i++) { - if (ascii_isupper(mod[i])) { - mod[i] += 'a' - 'A'; - } - } - return mod; +bool valid_int64(string_view value) { + int64_t result; + const char *last = value.data() + value.size(); + auto r = std::from_chars(value.data(), last, result, 10); + if (r.ec != std::errc()) return false; + if (r.ptr != last) return false; + return true; } -eng::string ascii_toupper(const eng::string &s) { - eng::string mod = s; - for (int i = 0; i < int(mod.size()); i++) { - if (ascii_islower(mod[i])) { - mod[i] += 'A' - 'a'; - } - } - return mod; +bool valid_double(string_view value) { + double result; + const char *last = value.data() + value.size(); + auto r = fast_float::from_chars(value.data(), last, result); + if (r.ec != std::errc()) return false; + if (r.ptr != last) return false; + return true; } -bool is_identifier(const eng::string &str) { +int64_t to_int64(string_view value, int64_t errval) { + int64_t result; + const char *last = value.data() + value.size(); + auto r = std::from_chars(value.data(), last, result, 10); + if (r.ec != std::errc()) return errval; + if (r.ptr != last) return errval; + return result; +} + +double to_double(string_view value, double errval) { + double result; + const char *last = value.data() + value.size(); + auto r = fast_float::from_chars(value.data(), last, result); + if (r.ec != std::errc()) return errval; + if (r.ptr != last) return errval; + return result; +} + +string_view ltrim(string_view v) { + while ((!v.empty()) && (ascii_isspace(v.front()))) { + v.remove_prefix(1); + } + return v; +} + +string_view rtrim(string_view v) { + while ((!v.empty()) && (ascii_isspace(v.back()))) { + v.remove_suffix(1); + } + return v; +} + +string_view trim(string_view v) { + while ((!v.empty()) && (ascii_isspace(v.front()))) { + v.remove_prefix(1); + } + while ((!v.empty()) && (ascii_isspace(v.back()))) { + v.remove_suffix(1); + } + return v; +} + +string_view ltrim(string_view v, char c) { + while ((!v.empty()) && (v.front() == c)) { + v.remove_prefix(1); + } + return v; +} + +string_view rtrim(string_view v, char c) { + while ((!v.empty()) && (v.back() == c)) { + v.remove_suffix(1); + } + return v; +} + +string_view trim(string_view v, char c) { + while ((!v.empty()) && (v.front() == c)) { + v.remove_prefix(1); + } + while ((!v.empty()) && (v.back() == c)) { + v.remove_suffix(1); + } + return v; +} + +bool has_prefix(string_view s, string_view prefix) { + return 0 == s.compare(0, prefix.size(), prefix); +} + +bool has_suffix(string_view s, string_view suffix) { + if (s.length() >= suffix.length()) { + return (0 == s.compare (s.length() - suffix.length(), suffix.length(), suffix)); + } else { + return false; + } +} + +int common_prefix_length(string_view a, string_view b) { + int minlen = std::min(a.size(), b.size()); + for (int i = 0; i < minlen; i++) { + if (a[i] != b[i]) return i; + } + return minlen; +} + +bool is_lua_id(string_view str) { if (str.size() == 0) return false; char c=str[0]; if ((!ascii_isalpha(c)) && (c!='_')) return false; @@ -56,6 +139,84 @@ bool is_identifier(const eng::string &str) { return true; } +bool is_lua_comment(string_view s) { + int start = 0; + while ((start < int(s.size())) && ((s[start]==' ') || (s[start]=='\t'))) start++; + return s.substr(start, 2) == "--"; +} + +string_view read_to_sep(string_view &source, char sep) { + size_t pos = source.find(sep); + string_view result; + if (pos == string_view::npos) { + result = source; + source = string_view(); + } else { + result = source.substr(0, pos); + source = source.substr(pos + 1); + } + return result; +} + +string_view read_to_line(string_view &source) { + size_t pos = source.find('\n'); + string_view result; + if (pos == string_view::npos) { + result = source; + source = string_view(); + } else { + result = source.substr(0, pos); + source = source.substr(pos + 1); + } + if ((!result.empty()) && (result.back() == '\r')) { + result.remove_suffix(1); + } + return result; +} + +string_view read_to_space(string_view &source) { + size_t pos1 = 0; + while ((pos1 < source.size()) && (!ascii_isspace(source[pos1]))) { + pos1 += 1; + } + string_view result = source.substr(0, pos1); + if (pos1 == source.size()) { + source = string_view(); + return result; + } + size_t pos2 = pos1 + 1; + while ((pos2 < source.size()) && (ascii_isspace(source[pos2]))) { + pos2 += 1; + } + source = source.substr(pos2); + return result; +} + +} // namespace sv + + +namespace util { + +eng::string ascii_tolower(std::string_view s) { + eng::string mod(s); + for (int i = 0; i < int(mod.size()); i++) { + if (sv::ascii_isupper(mod[i])) { + mod[i] += 'a' - 'A'; + } + } + return mod; +} + +eng::string ascii_toupper(std::string_view s) { + eng::string mod(s); + for (int i = 0; i < int(mod.size()); i++) { + if (sv::ascii_islower(mod[i])) { + mod[i] += 'A' - 'a'; + } + } + return mod; +} + void quote_string(const eng::string &s, std::ostream *os) { bool anysq = false; bool anydq = false; @@ -251,14 +412,6 @@ eng::string repeat_string(const eng::string &a, int n) { } return result; } - -int common_prefix_length(const eng::string &a, const eng::string &b) { - int minlen = std::min(a.size(), b.size()); - for (int i = 0; i < minlen; i++) { - if (a[i] != b[i]) return i; - } - return minlen; -} eng::string tolower(eng::string input) { for (int i = 0; i < int(input.size()); i++) { @@ -274,148 +427,6 @@ eng::string toupper(eng::string input) { return input; } -bool has_prefix(const eng::string &s, const eng::string &prefix) { - return 0 == s.compare(0, prefix.size(), prefix); -} - -bool has_suffix(const eng::string &s, const eng::string &suffix) { - if (s.length() >= suffix.length()) { - return (0 == s.compare (s.length() - suffix.length(), suffix.length(), suffix)); - } else { - return false; - } -} - -bool valid_int64(std::string_view value) { - int64_t result; - const char *last = value.data() + value.size(); - auto r = std::from_chars(value.data(), last, result, 10); - if (r.ec != std::errc()) return false; - if (r.ptr != last) return false; - return true; -} - -int64_t str_to_int64(std::string_view value, int64_t errval) { - int64_t result; - const char *last = value.data() + value.size(); - auto r = std::from_chars(value.data(), last, result, 10); - if (r.ec != std::errc()) return errval; - if (r.ptr != last) return errval; - return result; -} - -bool valid_double(std::string_view value) { - double result; - const char *last = value.data() + value.size(); - auto r = fast_float::from_chars(value.data(), last, result); - if (r.ec != std::errc()) return false; - if (r.ptr != last) return false; - return true; -} - -double str_to_double(std::string_view value, double errval) { - double result; - const char *last = value.data() + value.size(); - auto r = fast_float::from_chars(value.data(), last, result); - if (r.ec != std::errc()) return errval; - if (r.ptr != last) return errval; - return result; -} - -std::string_view sv_ltrim(std::string_view v) { - const char *b = v.data(); - const char *e = v.data() + v.size(); - while ((e > b) && (std::isspace(b[0]))) { - b++; - } - return std::string_view(b, e-b); -} - -std::string_view sv_rtrim(std::string_view v) { - const char *b = v.data(); - const char *e = v.data() + v.size(); - while ((e > b) && (std::isspace(e[-1]))) { - e--; - } - return std::string_view(b, e-b); -} - -std::string_view sv_trim(std::string_view v) { - const char *b = v.data(); - const char *e = v.data() + v.size(); - while ((e > b) && (std::isspace(b[0]))) { - b++; - } - while ((e > b) && (std::isspace(e[-1]))) { - e--; - } - return std::string_view(b, e-b); -} - -std::string_view sv_ltrim(std::string_view v, char c) { - while ((!v.empty()) && (v.front() == c)) { - v.remove_prefix(1); - } - return v; -} - -std::string_view sv_rtrim(std::string_view v, char c) { - while ((!v.empty()) && (v.back() == c)) { - v.remove_suffix(1); - } - return v; -} - -std::string_view sv_trim(std::string_view v, char c) { - while ((!v.empty()) && (v.front() == c)) { - v.remove_prefix(1); - } - while ((!v.empty()) && (v.back() == c)) { - v.remove_suffix(1); - } - return v; -} - -eng::string ltrim(std::string_view v) { - return eng::string(sv_ltrim(v)); -} - -eng::string rtrim(std::string_view v) { - return eng::string(sv_rtrim(v)); -} - -eng::string trim(std::string_view v) { - return eng::string(sv_trim(v)); -} - -std::string_view sv_split_one(std::string_view &v, char sep) { - size_t pos = v.find(sep); - - // If there's no separator in the buffer, return a null view. - if (pos == std::string_view::npos) { - return std::string_view(); - } - - // Split into stuff before the separator, and stuff after. - std::string_view result = v.substr(0, pos); - v = v.substr(pos + 1); - return result; -} - -std::string_view sv_read_line(std::string_view &source) { - size_t pos = source.find('\n'); - std::string_view result; - if (pos == std::string_view::npos) { - result = source; - source = std::string_view(); - } else { - result = source.substr(0, pos); - source = source.substr(pos + 1); - } - result = sv_rtrim(result, '\r'); - return result; -} - double distance_squared(double x1, double y1, double x2, double y2) { double dx = x1 - x2; double dy = y1 - y2; @@ -433,12 +444,6 @@ LuaSourcePtr make_lua_source(const eng::string &code) { return result; } -bool is_lua_comment(const eng::string &s) { - int start = 0; - while ((start < int(s.size())) && ((s[start]==' ') || (s[start]=='\t'))) start++; - return s.substr(start, 2) == "--"; -} - eng::string XYZ::debug_string() const { eng::ostringstream oss; oss << "(" << x << "," << y << "," << z << ")"; @@ -468,6 +473,57 @@ std::ostream &operator<<(std::ostream &oss, const util::hex8 &v) { } LuaDefine(unittests_util, "", "some unit tests") { + // test str_to_int64, str_to_double + LuaAssert(L, sv::to_int64("123") == 123); + LuaAssert(L, sv::to_int64("123.4") == INT64_MIN); + LuaAssert(L, sv::to_int64("12ab") == INT64_MIN); + LuaAssert(L, sv::to_int64("") == INT64_MIN); + LuaAssert(L, sv::to_double("123.5") == 123.5); + LuaAssert(L, std::isnan(sv::to_double("12ab"))); + LuaAssert(L, std::isnan(sv::to_double(""))); + + // Test trim, ltrim, rtrim + LuaAssert(L, sv::ltrim(" foo ") == "foo "); + LuaAssert(L, sv::rtrim(" foo ") == " foo"); + LuaAssert(L, sv::trim(" foo ") == "foo"); + LuaAssert(L, sv::trim("foo") == "foo"); + LuaAssert(L, sv::trim("") == ""); + LuaAssert(L, sv::ltrim("**foo**", '*') == "foo**"); + LuaAssert(L, sv::rtrim("**foo**", '*') == "**foo"); + LuaAssert(L, sv::trim("**foo**", '*') == "foo"); + LuaAssert(L, sv::trim("foo", '*') == "foo"); + LuaAssert(L, sv::trim("", '*') == ""); + + // Test read_to_line + std::string_view v = "foo\nbar\r\nbaz"; + + std::string_view v1 = sv::read_to_line(v); + LuaAssertStrEq(L, v1, "foo"); + LuaAssertStrEq(L, v, "bar\r\nbaz"); + + std::string_view v2 = sv::read_to_line(v); + LuaAssertStrEq(L, v2, "bar"); + LuaAssertStrEq(L, v, "baz"); + + std::string_view v3 = sv::read_to_line(v); + LuaAssertStrEq(L, v3, "baz"); + LuaAssert(L, sv::isnull(v)); + + // Test read_to_space + v = "foo bar baz"; + + std::string_view s1 = sv::read_to_space(v); + LuaAssertStrEq(L, s1, "foo"); + LuaAssertStrEq(L, v, "bar baz"); + + std::string_view s2 = sv::read_to_space(v); + LuaAssertStrEq(L, s2, "bar"); + LuaAssertStrEq(L, v, "baz"); + + std::string_view s3 = sv::read_to_space(v); + LuaAssertStrEq(L, s3, "baz"); + LuaAssert(L, sv::isnull(v)); + // Test the unioning of ID vectors. util::IdVector idv1,idv2; idv1.push_back(1); @@ -512,31 +568,6 @@ LuaDefine(unittests_util, "", "some unit tests") { LuaAssert(L, util::toupper("fooBar") == "FOOBAR"); LuaAssert(L, util::tolower("fooBar") == "foobar"); - // test str_to_int64, str_to_double - LuaAssert(L, util::str_to_int64("123") == 123); - LuaAssert(L, util::str_to_int64("123.4") == INT64_MIN); - LuaAssert(L, util::str_to_int64("12ab") == INT64_MIN); - LuaAssert(L, util::str_to_int64("") == INT64_MIN); - LuaAssert(L, util::str_to_double("123.5") == 123.5); - LuaAssert(L, std::isnan(util::str_to_double("12ab"))); - LuaAssert(L, std::isnan(util::str_to_double(""))); - - // Test trim, ltrim, rtrim - LuaAssert(L, util::ltrim(" foo ") == "foo "); - LuaAssert(L, util::rtrim(" foo ") == " foo"); - LuaAssert(L, util::trim(" foo ") == "foo"); - LuaAssert(L, util::trim("foo") == "foo"); - LuaAssert(L, util::trim("") == ""); - - // Test sv_read_line - std::string_view v = "foo\nbar\r\n"; - std::string_view v1 = util::sv_read_line(v); - std::string_view v2 = util::sv_read_line(v); - std::string_view v3 = util::sv_read_line(v); - LuaAssertStrEq(L, v1, "foo"); - LuaAssertStrEq(L, v2, "bar"); - LuaAssertStrEq(L, v3, ""); - // Test distance_squared LuaAssert(L, util::distance_squared(1, 1, 5, 4) == 25.0); LuaAssert(L, util::distance_squared(5, 4, 1, 1) == 25.0); diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index e30734d0..0ea0b96a 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -1,3 +1,18 @@ +/////////////////////////////////////////////////////////////////////// +// +// NAMESPACE SV +// +// * Operate on string_view or just characters. +// * Do not allocate memory. +// * Do not copy strings. +// +// NAMESPACE UTIL +// +// * General purpose utility functions. +// * Sort of a catch-all. +// +/////////////////////////////////////////////////////////////////////// + #ifndef UTIL_HPP #define UTIL_HPP @@ -15,6 +30,92 @@ #include "luastack.hpp" #include "spookyv2.hpp" +namespace sv { + +// Bring this into our namespace. +using string_view = std::string_view; + +// Test character class, ignoring current locale and unicode issues. +inline bool ascii_isupper(char c) { return (c >= 'A') && (c <= 'Z'); } +inline bool ascii_islower(char c) { return (c >= 'a') && (c <= 'z'); } +inline bool ascii_isdigit(char c) { return (c >= '0') && (c <= '9'); } +inline bool ascii_isalpha(char c) { return ascii_isupper(c) || ascii_islower(c); } +inline bool ascii_isalnum(char c) { return ascii_isalpha(c) || ascii_isdigit(c); } +inline bool ascii_isspace(char c) { return (c==' ')||(c=='\t')||(c=='\r')||(c=='\n')||(c=='\f')||(c=='\v'); } + +// Check for the null string_view +// +// Note that the null string view is an empty string, +// but not every empty string is the null string view. +// +inline bool isnull(string_view v) { return v.data() == nullptr; } + +// Check if numbers can be parsed as int64/double +bool valid_int64(string_view v); +bool valid_double(string_view v); + +// Parse numbers as int64/double. Returns errval on failure. +int64_t to_int64(string_view v, int64_t errval = std::numeric_limits::min()); +double to_double(string_view v, double errval = std::numeric_limits::quiet_NaN()); + +// Trim whitspace from a string_view. +string_view ltrim(string_view v); +string_view rtrim(string_view v); +string_view trim(string_view v); + +// Trim specific character (all occurrences) from a string_view. +string_view ltrim(string_view v, char c); +string_view rtrim(string_view v, char c); +string_view trim(string_view v, char c); + +// Return true if the string has the specified prefix or suffix. +bool has_prefix(string_view s, string_view prefix); +bool has_suffix(string_view s, string_view suffix); + +// Return the length of the common prefix of A and B. +int common_prefix_length(string_view a, string_view b); + +// Return true if the string is a lua identifier. +bool is_lua_id(string_view s); + +// Return true if the line of code is a lua comment. +bool is_lua_comment(string_view s); + +// Read from a string_view until separator is reached. +// +// If the separator appears in the source, returns everything +// before the separator, and updates the source to everything +// after the separator. +// +// If the separator doesn't appear in the source, returns +// the entire source, and replaces source with the null string_view. +// +string_view read_to_sep(string_view &source, char sep); + +// Read from a string_view until newline is reached. +// +// If there's a line-break in the source (newline or CRLF), +// returns the text before the line-break, and updates the +// source to the text after the line-break. +// +// If there's no line-break in the source, returns the entire source, +// and updates source to the null string_view. +// +string_view read_to_line(string_view &source); + +// Read from a string_view until whitespace is reached. +// +// If there's any whitespace in the source, returns the text +// before the whitespace, and update the source to the text +// after the whitespace. +// +// If there's no whitespace in the source, returns the entire +// source, and updates the source to the null string_view. +// +string_view read_to_space(string_view &source); + +} // namespace sv + namespace util { enum WorldType { @@ -38,26 +139,14 @@ using LuaSourceVec = eng::vector; using LuaSourcePtr = std::unique_ptr; using HashValue = std::pair; using IdVector = eng::vector; -using OptionalInt64 = std::optional; -// This value is sometimes used to represent 'bad integer' -const int64_t BADINT64 = std::numeric_limits::min(); - -// Test character class ignoring 'current locale'. -inline bool ascii_isupper(char c) { return (c >= 'A') && (c <= 'Z'); } -inline bool ascii_islower(char c) { return (c >= 'a') && (c <= 'z'); } -inline bool ascii_isdigit(char c) { return (c >= '0') && (c <= '9'); } -inline bool ascii_isalpha(char c) { return ascii_isupper(c) || ascii_islower(c); } -inline bool ascii_isalnum(char c) { return ascii_isalpha(c) || ascii_isdigit(c); } -eng::string ascii_tolower(const eng::string &c); -eng::string ascii_toupper(const eng::string &c); +// Ascii uppercase and lowercase. +eng::string ascii_tolower(std::string_view c); +eng::string ascii_toupper(std::string_view c); // Return seconds elapsed, for profiling purposes. double profiling_clock(); -// Return true if the string is a valid lua identifier. -bool is_identifier(const eng::string &str); - // Output a string to a stream using Lua string escaping and quoting. void quote_string(const eng::string &str, std::ostream *os); @@ -101,50 +190,10 @@ eng::string join(const StringVec &strs, eng::string sep); // Return N repetitions of string A eng::string repeat_string(const eng::string &a, int n); -// Return the length of the common prefix of A and B. -int common_prefix_length(const eng::string &a, const eng::string &b); - // String to lowercase/uppercase. Ascii only, no unicode. eng::string tolower(eng::string input); eng::string toupper(eng::string input); -// Return true if the string has the specified prefix or suffix. -bool has_prefix(const eng::string &s, const eng::string &prefix); -bool has_suffix(const eng::string &s, const eng::string &suffix); - -// Check if numbers can be parsed as int64/double -bool valid_int64(std::string_view v); -bool valid_double(std::string_view v); - -// Parse numbers as int64/double. Returns errval on failure. -int64_t str_to_int64(std::string_view v, int64_t errval = INT64_MIN); -double str_to_double(std::string_view v, double errval = std::numeric_limits::quiet_NaN()); - -// Trim a string_view -std::string_view sv_ltrim(std::string_view v); -std::string_view sv_rtrim(std::string_view v); -std::string_view sv_trim(std::string_view v); - -std::string_view sv_ltrim(std::string_view v, char c); -std::string_view sv_rtrim(std::string_view v, char c); -std::string_view sv_trim(std::string_view v, char c); - -// sv_is_null is different from checking for empty. -inline bool sv_is_null(const std::string_view &v) { return v.data() == nullptr; } - -// Split a string view into stuff before and after separator. -// If the separator doesn't occur, returns a null string view -// and doesn't modify the source. -std::string_view sv_split_one(std::string_view &source, char sep); - -// Read a line from a string_view. -std::string_view sv_read_line(std::string_view &source); - -// Trim strings: left end, right end, both ends. -eng::string ltrim(std::string_view s); -eng::string rtrim(std::string_view s); -eng::string trim(std::string_view s); - // Calculate distance between two points double distance_squared(double x1, double y1, double x2, double y2); @@ -154,9 +203,6 @@ bool world_type_authoritative(util::WorldType wt); // Make a LuaSourceVec with one element, for unit testing. LuaSourcePtr make_lua_source(const eng::string &code); -// Return true if the line of code is a lua comment. -bool is_lua_comment(const eng::string &line); - // Remove nullptrs from a vector of unique pointers. template void remove_nullptrs(eng::vector> &vec) { diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index abd6cf9a..149e25ca 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -5,6 +5,8 @@ #include "gui.hpp" #include "traceback.hpp" #include "pprint.hpp" +#include "util.hpp" + #include void World::store_global_pointer(lua_State *L, World *v) { @@ -434,7 +436,7 @@ void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, const eng::s if (actor_id != place_id) { return; } - int64_t line = util::str_to_int64(action, -1); + int64_t line = sv::to_int64(action, -1); if ((line < 0)||(line > INT_MAX)) { return; } @@ -530,7 +532,7 @@ void World::invoke_plan(int64_t actor_id, int64_t place_id, const eng::string &a } // Make sure the action starts with "cb_" - if (!util::has_prefix(action, "cb_")) { + if (!sv::has_prefix(action, "cb_")) { return; }