diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 6b0d349f6b..5b743ba20d 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -29,6 +29,7 @@ #include "../window_func.h" #include "../strings_func.h" #include "../core/endian_func.hpp" +#include "../core/string_builder.hpp" #include "../vehicle_base.h" #include "../company_func.h" #include "../timer/timer_game_economy.h" @@ -928,7 +929,7 @@ void FixSCCEncoded(std::string &str, bool fix_code) * `:""` becomes `` */ std::string result; - auto output = std::back_inserter(result); + StringBuilder builder(result); bool is_encoded = false; // Set if we determine by the presence of SCC_ENCODED that the string is an encoded string. bool in_string = false; // Set if we in a string, between double-quotes. @@ -940,11 +941,11 @@ void FixSCCEncoded(std::string &str, bool fix_code) char32_t c; Utf8Decode(&c, &*it); + it += len; if (c == SCC_ENCODED || (fix_code && (c == 0xE028 || c == 0xE02A))) { - Utf8Encode(output, SCC_ENCODED); + builder.PutUtf8(SCC_ENCODED); need_type = false; is_encoded = true; - it += len; continue; } @@ -955,27 +956,24 @@ void FixSCCEncoded(std::string &str, bool fix_code) in_string = !in_string; if (in_string && need_type) { /* Started a new string parameter. */ - Utf8Encode(output, SCC_ENCODED_STRING); + builder.PutUtf8(SCC_ENCODED_STRING); need_type = false; } - it += len; continue; } if (!in_string && c == ':') { - *output = SCC_RECORD_SEPARATOR; + builder.PutUtf8(SCC_RECORD_SEPARATOR); need_type = true; - it += len; continue; } if (need_type) { /* Started a new numeric parameter. */ - Utf8Encode(output, SCC_ENCODED_NUMERIC); + builder.PutUtf8(SCC_ENCODED_NUMERIC); need_type = false; } - Utf8Encode(output, c); - it += len; + builder.PutUtf8(c); } str = std::move(result); diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp index 5cc784a427..89333e6982 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -163,9 +163,9 @@ EncodedString ScriptText::GetEncodedText() ParamList params; int param_count = 0; std::string result; - auto output = std::back_inserter(result); + StringBuilder builder(result); this->_FillParamList(params, seen_texts); - this->_GetEncodedText(output, param_count, params, true); + this->_GetEncodedText(builder, param_count, params, true); if (param_count > SCRIPT_TEXT_MAX_PARAMETERS) throw Script_FatalError(fmt::format("{}: Too many parameters", GetGameStringName(this->string))); return ::EncodedString{std::move(result)}; } @@ -193,46 +193,46 @@ void ScriptText::_FillParamList(ParamList ¶ms, ScriptTextList &seen_texts) } } -void ScriptText::ParamCheck::Encode(std::back_insert_iterator &output, std::string_view cmd) +void ScriptText::ParamCheck::Encode(StringBuilder &builder, std::string_view cmd) { if (this->cmd.empty()) this->cmd = cmd; if (this->used) return; struct visitor { - std::back_insert_iterator &output; + StringBuilder &builder; void operator()(std::string value) { - Utf8Encode(this->output, SCC_ENCODED_STRING); + this->builder.PutUtf8(SCC_ENCODED_STRING); StrMakeValidInPlace(value, {StringValidationSetting::ReplaceWithQuestionMark, StringValidationSetting::AllowNewline, StringValidationSetting::ReplaceTabCrNlWithSpace}); - fmt::format_to(this->output, "{}", value); + this->builder.Put(value); } void operator()(const SQInteger &value) { - Utf8Encode(this->output, SCC_ENCODED_NUMERIC); - fmt::format_to(this->output, "{:X}", value); + this->builder.PutUtf8(SCC_ENCODED_NUMERIC); + this->builder.PutIntegerBase(value, 16); } void operator()(const ScriptTextRef &value) { - Utf8Encode(this->output, SCC_ENCODED); - fmt::format_to(this->output, "{:X}", value->string); + this->builder.PutUtf8(SCC_ENCODED); + this->builder.PutIntegerBase(value->string.base(), 16); } }; - *output = SCC_RECORD_SEPARATOR; - std::visit(visitor{output}, *this->param); + builder.PutUtf8(SCC_RECORD_SEPARATOR); + std::visit(visitor{builder}, *this->param); this->used = true; } -void ScriptText::_GetEncodedText(std::back_insert_iterator &output, int ¶m_count, ParamSpan args, bool first) +void ScriptText::_GetEncodedText(StringBuilder &builder, int ¶m_count, ParamSpan args, bool first) { const std::string &name = GetGameStringName(this->string); if (first) { - Utf8Encode(output, SCC_ENCODED); - fmt::format_to(output, "{:X}", this->string); + builder.PutUtf8(SCC_ENCODED); + builder.PutIntegerBase(this->string.base(), 16); } const StringParams ¶ms = GetGameStringParams(this->string); @@ -256,7 +256,7 @@ void ScriptText::_GetEncodedText(std::back_insert_iterator &output, case StringParam::RAW_STRING: { ParamCheck &p = *get_next_arg(); - p.Encode(output, cur_param.cmd); + p.Encode(builder, cur_param.cmd); if (p.cmd != cur_param.cmd) throw 1; if (!std::holds_alternative(*p.param)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects a raw string", name, param_count + 1, cur_param.cmd)); break; @@ -265,7 +265,7 @@ void ScriptText::_GetEncodedText(std::back_insert_iterator &output, case StringParam::STRING: { ParamCheck &p = *get_next_arg(); - p.Encode(output, cur_param.cmd); + p.Encode(builder, cur_param.cmd); if (p.cmd != cur_param.cmd) throw 1; if (!std::holds_alternative(*p.param)) { ScriptLog::Error(fmt::format("{}({}): {{{}}} expects a GSText", name, param_count + 1, cur_param.cmd)); @@ -274,12 +274,12 @@ void ScriptText::_GetEncodedText(std::back_insert_iterator &output, } int count = 0; ScriptTextRef &ref = std::get(*p.param); - ref->_GetEncodedText(output, count, args.subspan(idx), false); + ref->_GetEncodedText(builder, count, args.subspan(idx), false); if (++count != cur_param.consumes) { ScriptLog::Warning(fmt::format("{}({}): {{{}}} expects {} to be consumed, but {} consumes {}", name, param_count + 1, cur_param.cmd, cur_param.consumes - 1, GetGameStringName(ref->string), count - 1)); /* Fill missing params if needed. */ for (int i = count; i < cur_param.consumes; i++) { - Utf8Encode(output, SCC_RECORD_SEPARATOR); + builder.PutUtf8(SCC_RECORD_SEPARATOR); } } skip_args(cur_param.consumes - 1); @@ -289,7 +289,7 @@ void ScriptText::_GetEncodedText(std::back_insert_iterator &output, default: for (int i = 0; i < cur_param.consumes; i++) { ParamCheck &p = *get_next_arg(); - p.Encode(output, i == 0 ? cur_param.cmd : ""); + p.Encode(builder, i == 0 ? cur_param.cmd : ""); if (i == 0 && p.cmd != cur_param.cmd) throw 1; if (!std::holds_alternative(*p.param)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects an integer", name, param_count + i + 1, cur_param.cmd)); } diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp index c31da8a400..50f4864358 100644 --- a/src/script/api/script_text.hpp +++ b/src/script/api/script_text.hpp @@ -12,6 +12,7 @@ #include "script_object.hpp" #include "../../strings_func.h" +#include "../../core/string_builder.hpp" #include @@ -141,7 +142,7 @@ private: ParamCheck(StringIndexInTab owner, int idx, Param *param) : owner(owner), idx(idx), param(param) {} - void Encode(std::back_insert_iterator &output, std::string_view cmd); + void Encode(StringBuilder &output, std::string_view cmd); }; using ParamList = std::vector; @@ -168,7 +169,7 @@ private: * @param args The parameters to be consumed. * @param first Whether it's the first call in the recursion. */ - void _GetEncodedText(std::back_insert_iterator &output, int ¶m_count, ParamSpan args, bool first); + void _GetEncodedText(StringBuilder &output, int ¶m_count, ParamSpan args, bool first); /** * Set a parameter, where the value is the first item on the stack. diff --git a/src/strings.cpp b/src/strings.cpp index 9f550b374b..f97b13f46e 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -103,41 +103,38 @@ EncodedString GetEncodedString(StringID str) EncodedString GetEncodedStringWithArgs(StringID str, std::span params) { std::string result; - auto output = std::back_inserter(result); - Utf8Encode(output, SCC_ENCODED_INTERNAL); - fmt::format_to(output, "{:X}", str); + StringBuilder builder(result); + builder.PutUtf8(SCC_ENCODED_INTERNAL); + builder.PutIntegerBase(str, 16); struct visitor { - std::back_insert_iterator &output; + StringBuilder &builder; void operator()(const std::monostate &) {} void operator()(const uint64_t &arg) { - Utf8Encode(output, SCC_ENCODED_NUMERIC); - fmt::format_to(this->output, "{:X}", arg); + this->builder.PutUtf8(SCC_ENCODED_NUMERIC); + this->builder.PutIntegerBase(arg, 16); } void operator()(const std::string &value) { #ifdef WITH_ASSERT /* Don't allow an encoded string to contain another encoded string. */ - if (!value.empty()) { - char32_t c; - const char *p = value.data(); - if (Utf8Decode(&c, p)) { - assert(c != SCC_ENCODED && c != SCC_ENCODED_INTERNAL && c != SCC_RECORD_SEPARATOR); - } + { + auto [len, c] = DecodeUtf8(value); + assert(len == 0 || (c != SCC_ENCODED && c != SCC_ENCODED_INTERNAL && c != SCC_RECORD_SEPARATOR)); } #endif /* WITH_ASSERT */ - Utf8Encode(output, SCC_ENCODED_STRING); - fmt::format_to(this->output, "{}", value); + this->builder.PutUtf8(SCC_ENCODED_STRING); + this->builder += value; } }; - visitor v{output}; + visitor v{builder}; for (const auto ¶m : params) { - *output = SCC_RECORD_SEPARATOR; + builder.PutUtf8(SCC_RECORD_SEPARATOR); std::visit(v, param.data); }