From 1a0e17221b58abb7c7f2f258db2d87a64a6c2fb1 Mon Sep 17 00:00:00 2001 From: Twan van Laarhoven Date: Thu, 21 May 2020 19:29:42 +0200 Subject: [PATCH] Added StringView type (similar to std::string_view) --- src/data/action/value.cpp | 12 ++-- src/data/keyword.cpp | 2 +- src/script/parser.cpp | 6 +- src/util/io/reader.cpp | 2 +- src/util/io/reader.hpp | 2 +- src/util/locale.hpp | 2 - src/util/string.cpp | 96 ++++++++------------------ src/util/string.hpp | 133 +++++++++++++++++++++++++++++++------ src/util/tagged_string.hpp | 4 +- 9 files changed, 153 insertions(+), 106 deletions(-) diff --git a/src/data/action/value.cpp b/src/data/action/value.cpp index af85203a..f0bec4b6 100644 --- a/src/data/action/value.cpp +++ b/src/data/action/value.cpp @@ -153,18 +153,18 @@ unique_ptr toggle_format_action(const TextValueP& value, const // Are we inside the tag we are toggling? if (!is_in_tag(str, _("<") + tag, start_i, end_i)) { // we are not inside this tag, add it - new_value = str.substr(0, start_i); + new_value = substr(str, 0, start_i); new_value += _("<") + tag + _(">"); - new_value += str.substr(start_i, end_i - start_i); + new_value += substr(str, start_i, end_i - start_i); new_value += _(""); - new_value += str.substr(end_i); + new_value += substr(str, end_i); } else { // we are inside this tag, 'remove' it - new_value = str.substr(0, start_i); + new_value = substr(str, 0, start_i); new_value += _(""); - new_value += str.substr(start_i, end_i - start_i); + new_value += substr(str, start_i, end_i - start_i); new_value += _("<") + tag + _(">"); - new_value += str.substr(end_i); + new_value += substr(str, end_i); } // Build action if (start != end) { diff --git a/src/data/keyword.cpp b/src/data/keyword.cpp index a944172c..795193db 100644 --- a/src/data/keyword.cpp +++ b/src/data/keyword.cpp @@ -65,7 +65,7 @@ void read_compat(Reader& handler, Keyword* k) { size_t start = separator.find_first_of('['); size_t end = separator.find_first_of(']'); if (start != String::npos && end != String::npos) { - k->match += separator.substr(start + 1, end - start - 1); + k->match += substr(separator, start + 1, end - start - 1); } if (parameter == _("no parameter")) { parameter.clear(); // was used for magic to indicate absence of parameter diff --git a/src/script/parser.cpp b/src/script/parser.cpp index e761d209..bbda99ff 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -122,7 +122,7 @@ bool isOper (wxUniChar c) { return wxStrchr(_("+-*/!.@%^&:=<>;,"),c) != nullptr bool isLparen(wxUniChar c) { return c==_('(') || c==_('[') || c==_('{'); } bool isRparen(wxUniChar c) { return c==_(')') || c==_(']') || c==_('}'); } bool isDigitOrDot(wxUniChar c) { return isDigit(c) || c==_('.'); } -bool isLongOper(const String& s) { return s==_(":=") || s==_("==") || s==_("!=") || s==_("<=") || s==_(">="); } +bool isLongOper(StringView s) { return s==_(":=") || s==_("==") || s==_("!=") || s==_("<=") || s==_(">="); } // moveme // ----------------------------------------------------------------------------- : Tokenizing @@ -185,7 +185,7 @@ void TokenIterator::readToken() { pos += 13; // "include file:" const char* newlines = "\r\n"; auto eol = find_first_of(pos,end, newlines,newlines+2); - String include_file = trim(String(pos, eol)); + String include_file = trim(StringView(pos, eol)); // include_file("filename") addToken(TOK_NAME, "include_file", pos - 13); addToken(TOK_LPAREN, "(", pos); @@ -208,7 +208,7 @@ void TokenIterator::readToken() { addToken(type, String(start,pos), start); } else if (isOper(c)) { // operator - if (pos+1 != end && isLongOper(String(pos, pos+2))) { + if (pos+1 != end && isLongOper(StringView(pos, pos+2))) { // long operator addToken(TOK_OPER, String(pos, pos+2), pos); pos += 2; diff --git a/src/util/io/reader.cpp b/src/util/io/reader.cpp index 50e0ed79..4ca26407 100644 --- a/src/util/io/reader.cpp +++ b/src/util/io/reader.cpp @@ -225,7 +225,7 @@ void Reader::readLine(bool in_string) { } } key = canonical_name_form(trim(key)); - value = pos == String::npos ? _("") : trim_left(line.substr(pos+1)); + value = pos == String::npos ? String() : trim_left(substr(line, pos+1)); if (key.empty() && pos!=String::npos) key = _(" "); // we don't want an empty key if there was a colon } diff --git a/src/util/io/reader.hpp b/src/util/io/reader.hpp index f77c3378..79df57c5 100644 --- a/src/util/io/reader.hpp +++ b/src/util/io/reader.hpp @@ -131,7 +131,7 @@ private: OUTSIDE, ///< We have not entered the block of the current key ENTERED, ///< We just entered the block of the current key HANDLED, ///< We have handled a value, and moved to the next line, previous_value is the value we just handled - UNHANDLED, ///< Something has been 'unhandled()' + UNHANDLED, ///< Something has been 'unhandle()-ed' } state; /// Should all invalid keys be ignored? bool ignore_invalid; diff --git a/src/util/locale.hpp b/src/util/locale.hpp index d60c47fc..bf69db79 100644 --- a/src/util/locale.hpp +++ b/src/util/locale.hpp @@ -77,8 +77,6 @@ String tr(const Package&, const String& subcat, const String& key, DefaultLocale /// A localized string for menus, with 1 argument (printf style) #define _MENU_1_(s,a) format_string(_MENU_(s), a) -/// A localized string for context menus, contains no "\tshortcut" -#define _CONTEXT_MENU_(s) remove_shortcut(_MENU_(s)) /// A localized string for tooltip text, with 1 argument (printf style) #define _HELP_1_(s,a) format_string(_HELP_(s), a) diff --git a/src/util/string.cpp b/src/util/string.cpp index 8a7eeb88..2c3491c3 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -48,25 +48,19 @@ Char toUpper(Char c) { // ----------------------------------------------------------------------------- : String utilities -String trim(const String& s){ - size_t start = s.find_first_not_of(_(" \t")); - size_t end = s.find_last_not_of( _(" \t")); - if (start == String::npos) { - return String(); - } else if (start == 0 && end == s.size() - 1) { - return s; - } else { - return s.substr(start, end - start + 1); - } +StringView trim(StringView s) { + String::const_iterator begin = s.begin(); + String::const_iterator end = s.end(); + while (begin != end && isSpace(*begin)) ++begin; + while (begin != end && isSpace(*(end - 1))) --end; + return StringView(begin, end); } -String trim_left(const String& s) { - size_t start = s.find_first_not_of(_(" \t")); - if (start == String::npos) { - return String(); - } else { - return s.substr(start); - } +StringView trim_left(StringView s) { + String::const_iterator begin = s.begin(); + String::const_iterator end = s.end(); + while (begin != end && isSpace(*begin)) ++begin; + return StringView(begin, end); } String substr_replace(const String& input, size_t start, size_t end, const String& replacement) { @@ -98,60 +92,42 @@ bool is_substr(const String& s, String::const_iterator it, const Char* cmp) { return *cmp == 0; } -String capitalize(const String& s) { - String result = s; +void capitalize_in_place(String& s) { bool after_space = true; - FOR_EACH_IT(it, result) { + for (String::iterator it = s.begin(); it != s.end(); ++it) { if (*it == _(' ') || *it == _('/')) { after_space = true; } else if (after_space) { after_space = false; - if (it != result.begin() && - (is_substr(result,it,_("is ")) || is_substr(result,it,_("the ")) || - is_substr(result,it,_("in ")) || is_substr(result,it,_("of ")) || - is_substr(result,it,_("to ")) || is_substr(result,it,_("at ")) || - is_substr(result,it,_("a " )))) { + if (it != s.begin() && + (is_substr(it, s.end(), _("is ")) || is_substr(it, s.end(), _("the ")) || + is_substr(it, s.end(), _("in ")) || is_substr(it, s.end(), _("of ")) || + is_substr(it, s.end(), _("to ")) || is_substr(it, s.end(), _("at ")) || + is_substr(it, s.end(), _("a " )))) { // Short words are not capitalized, keep lower case } else { *it = toUpper(*it); } } } - return result; } -String capitalize_sentence(const String& s) { - String ret = s;//.Lower(); - if (!ret.empty()) { - ret[0] = toUpper(ret[0]); +void capitalize_sentence_in_place(String& s) { + if (!s.empty()) { + s[0] = toUpper(s[0]); } - return ret; } -wxUniChar canonical_name_form(wxUniChar c) { - if (c == _(' ')) return _('_'); - return c; -} -String canonical_name_form(const String& str) { - String ret; - ret.reserve(str.size()); - FOR_EACH_CONST(c, str) { - ret += canonical_name_form(c); +void canonical_name_form_in_place(String& str) { + for (String::iterator it = str.begin(); it != str.end(); ++it) { + if (*it == ' ') *it = '_'; } - return ret; } -wxUniChar uncanonical_name_form(wxUniChar c) { - if (c == _('_')) return _(' '); - return c; -} -String uncanonical_name_form(const String& str) { - String ret; - ret.reserve(str.size()); - FOR_EACH_CONST(c, str) { - ret += uncanonical_name_form(c); +void uncanonical_name_form_in_place(String& str) { + for (String::iterator it = str.begin(); it != str.end(); ++it) { + if (*it == '_') *it = ' '; } - return ret; } String name_to_caption(const String& str) { @@ -183,12 +159,6 @@ String singular_form(const String& str) { return str.substr(0, str.size() - 1); } -String remove_shortcut(const String& str) { - size_t tab = str.find_last_of(_('\t')); - if (tab == String::npos) return str; - else return str.substr(0, tab); -} - // ----------------------------------------------------------------------------- : Comparing / finding // Nice unicode normalization tables, probably not conform the standards @@ -355,16 +325,6 @@ bool starts_with(const String& str, const String& start) { return equal(start.begin(), start.end(), str.begin()); } -bool is_substr(const String& str, size_t pos, const Char* cmp) { - for (String::const_iterator it = str.begin() + pos ; *cmp && it < str.end() ; ++cmp, ++it) { - if (*cmp != *it) return false; - } - return *cmp == _('\0'); -} -bool is_substr(const String& str, size_t pos, const String& cmp) { - return str.size() >= cmp.size() + pos && str.compare(pos, cmp.size(), cmp) == 0; -} - bool is_substr_i(const String& str, size_t pos, const Char* cmp) { for (String::const_iterator it = str.begin() + pos ; *cmp && it < str.end() ; ++cmp, ++it) { @@ -376,7 +336,7 @@ bool is_substr_i(const String& str, size_t pos, const String& cmp) { return is_substr_i(str, pos, static_cast(cmp.c_str())); } -bool canonical_name_compare(const String& as, const Char* b) { +bool canonical_name_compare(StringView as, const Char* b) { assert(canonical_name_form(b) == b); for (String::const_iterator a_it = as.begin(); a_it != as.end(); ++a_it, ++b) { if (*a_it != *b && !(*a_it == '_' && *b == ' ')) { diff --git a/src/util/string.hpp b/src/util/string.hpp index ab835e39..48020bba 100644 --- a/src/util/string.hpp +++ b/src/util/string.hpp @@ -115,13 +115,77 @@ inline bool isPunct(Char c) { return IF_UNICODE( iswpunct(c) , ispunct((unsigned inline bool isSpace(Char c) { return IF_UNICODE( iswspace(c) , isspace((unsigned char)c) ) || c == CONNECTION_SPACE; } #endif +// ----------------------------------------------------------------------------- : String view + +// A view of (part of a string) +class StringView { +public: + StringView(String const& str) + : begin_(str.begin()), end_(str.end()) + {} + StringView(String const& str, size_t pos) + : begin_(str.begin() + pos), end_(str.end()) + {} + StringView(String const& str, size_t pos, size_t count) + : begin_(str.begin() + pos), end_(str.begin() + pos + min(count, str.size()-pos)) + {} + StringView(String::const_iterator begin, String::const_iterator end) + : begin_(begin), end_(end) + { + assert(begin <= end); + } + inline operator String () const { + return String(begin_, end_); + } + using iterator = String::const_iterator; + using const_iterator = String::const_iterator; + inline String::const_iterator begin() const { + return begin_; + } + inline String::const_iterator end() const { + return end_; + } + inline size_t size() const { + return end_ - begin_; + } + inline bool empty() const { + return begin() == end(); + } + inline bool operator == (StringView const& str) { + return str.size() == size() && std::equal(begin(), end(), str.begin()); + } + template + inline bool operator == (const AnyChar* str) { + String::const_iterator it = begin_; + while (true) { + if (it == end_) return *str == '\0'; + if (*str == '\0') return false; + if (*str != *it) return false; + ++it; ++str; + } + } +private: + String::const_iterator begin_, end_; +}; + +inline String& operator += (String& a, StringView b) { + return a.append(b.begin(), b.end()); +} + +inline StringView substr(String const& str, size_t pos, size_t len) { + return StringView(str, pos, len); +} +inline StringView substr(String const& str, size_t pos) { + return StringView(str, pos); +} + // ----------------------------------------------------------------------------- : String utilities /// Remove whitespace from both ends of a string -String trim(const String&); +StringView trim(StringView); /// Remove whitespace from the start of a string -String trim_left(const String&); +StringView trim_left(StringView); /// Replace the substring [start...end) of 'input' with 'replacement' String substr_replace(const String& input, size_t start, size_t end, const String& replacement); @@ -136,18 +200,37 @@ String reverse_string(String const& input); /// Make each word in a string start with an upper case character. /** for use in menus */ -String capitalize(const String&); +void capitalize_in_place(String&); +inline String capitalize(String const& s) { + String result = s; + capitalize_in_place(result); + return result; +} /// Make the first word in a string start with an upper case character. /** for use in dialogs */ -String capitalize_sentence(const String&); +void capitalize_sentence_in_place(String&); +inline String capitalize_sentence(String const& s) { + String result = s; + capitalize_sentence_in_place(result); + return result; +} /// Convert a field name to canonical form /** - converts ' ' to '_' */ -String canonical_name_form(const String&); +void canonical_name_form_in_place(String&); +inline String canonical_name_form(String s) { + canonical_name_form_in_place(s); + return s; +} + /// Undo canonical_name_form: replace '_' by ' ' -String uncanonical_name_form(const String&); +void uncanonical_name_form_in_place(String&); +inline String uncanonical_name_form(String s) { + uncanonical_name_form_in_place(s); + return s; +} /// Convert a field name to a string that can be shown to the user String name_to_caption(const String&); @@ -158,11 +241,6 @@ String name_to_caption(const String&); */ String singular_form(const String&); -/// Remove a shortcut from a menu string -/** e.g. "Cut\tCtrl+X" --> "Cut" - */ -String remove_shortcut(const String&); - // ----------------------------------------------------------------------------- : Comparing / finding /// Compare two strings @@ -180,19 +258,30 @@ bool smart_equal(const String&, const String&); /// Return whether str starts with start /** starts_with(a,b) == is_substr(a,0,b) */ -bool starts_with(const String& str, const String& start); +//bool starts_with(const String& str, const String& start); +inline bool starts_with(StringView str, StringView const& start) { + return str.size() >= start.size() && std::equal(start.begin(), start.end(), str.begin()); +} +template +inline bool starts_with(StringView str, const AnyChar* start) { + String::const_iterator it = str.begin(); + while (true) { + if (*start == '\0') return true; + if (it == str.end()) return false; + if (*start != *it) return false; + ++it; ++start; + } +} /// Return whether str contains the string cmp at position pos -bool is_substr(const String& str, size_t pos, const Char* cmp); -/// Return whether str contains the string cmp at position pos -bool is_substr(const String& str, size_t pos, const String& cmp); +template +inline bool is_substr(const String& str, size_t pos, const Cmp& cmp) { + return starts_with(StringView(str, pos), cmp); +} /// Return whether begin..end contains the string cmp at position begin -template -bool is_substr(It begin, It end, const char* cmp) { - for (; begin != end && *cmp; ++begin, ++cmp) { - if (*begin != *cmp) return false; - } - return true; +template +inline bool is_substr(It begin, It end, const Cmp& cmp) { + return starts_with(StringView(begin, end),cmp); } /// Return whether str contains the string cmp at position pos, case insensitive compare @@ -207,7 +296,7 @@ size_t find_i(const String& heystack, const String& needle); /** canoncial_name_compare(a,b) == (cannocial_name_form(a) == b) * b should already be in cannonical name form */ -bool canonical_name_compare(const String& a, const Char* b); +bool canonical_name_compare(StringView a, const Char* b); // ----------------------------------------------------------------------------- : Regular expressions diff --git a/src/util/tagged_string.hpp b/src/util/tagged_string.hpp index 9f152ee1..9705e8d8 100644 --- a/src/util/tagged_string.hpp +++ b/src/util/tagged_string.hpp @@ -23,8 +23,8 @@ const Char ESCAPED_LANGLE = _('\1'); wxUniChar untag_char(wxUniChar c); wxUniChar tag_char(wxUniChar c); -/// Remove all tags from a string and convert escaped '<' back to normal '<' -/** e.g. "R something (note)" +/// Remove all tags from a string and convert ESCAPED_LANGLE back to normal '<' +/** e.g. "R something (note)" * becomes "R something (note)" */ String untag(const String&);