diff --git a/src/game/game_text.hpp b/src/game/game_text.hpp index 60a10e2dcc..f85c8aecd3 100644 --- a/src/game/game_text.hpp +++ b/src/game/game_text.hpp @@ -22,9 +22,9 @@ struct StringParam { ParamType type; uint8_t consumes; - const char *cmd; + std::string_view cmd; - StringParam(ParamType type, uint8_t consumes, const char *cmd) : type(type), consumes(consumes), cmd(cmd) {} + StringParam(ParamType type, uint8_t consumes, std::string_view cmd) : type(type), consumes(consumes), cmd(cmd) {} }; using StringParams = std::vector; using StringParamsList = std::vector; diff --git a/src/language.h b/src/language.h index c67df2bda1..603ca8cfdd 100644 --- a/src/language.h +++ b/src/language.h @@ -65,10 +65,10 @@ struct LanguagePackHeader { * @param gender_str The string representation of the gender. * @return The index of the gender, or MAX_NUM_GENDERS when the gender is unknown. */ - uint8_t GetGenderIndex(const char *gender_str) const + uint8_t GetGenderIndex(std::string_view gender_str) const { for (uint8_t i = 0; i < MAX_NUM_GENDERS; i++) { - if (strcmp(gender_str, this->genders[i]) == 0) return i; + if (gender_str.compare(this->genders[i]) == 0) return i; } return MAX_NUM_GENDERS; } @@ -78,10 +78,10 @@ struct LanguagePackHeader { * @param case_str The string representation of the case. * @return The index of the case, or MAX_NUM_CASES when the case is unknown. */ - uint8_t GetCaseIndex(const char *case_str) const + uint8_t GetCaseIndex(std::string_view case_str) const { for (uint8_t i = 0; i < MAX_NUM_CASES; i++) { - if (strcmp(case_str, this->cases[i]) == 0) return i; + if (case_str.compare(this->cases[i]) == 0) return i; } return MAX_NUM_CASES; } diff --git a/src/newgrf/newgrf_act0_globalvar.cpp b/src/newgrf/newgrf_act0_globalvar.cpp index edce43e7a1..803995d937 100644 --- a/src/newgrf/newgrf_act0_globalvar.cpp +++ b/src/newgrf/newgrf_act0_globalvar.cpp @@ -302,14 +302,14 @@ static ChangeInfoResult GlobalVarChangeInfo(uint first, uint last, int prop, Byt LanguageMap::Mapping map; map.newgrf_id = newgrf_id; if (prop == 0x13) { - map.openttd_id = lang->GetGenderIndex(name.data()); + map.openttd_id = lang->GetGenderIndex(name); if (map.openttd_id >= MAX_NUM_GENDERS) { GrfMsg(1, "GlobalVarChangeInfo: Gender name {} is not known, ignoring", StrMakeValid(name)); } else { _cur.grffile->language_map[curidx].gender_map.push_back(map); } } else { - map.openttd_id = lang->GetCaseIndex(name.data()); + map.openttd_id = lang->GetCaseIndex(name); if (map.openttd_id >= MAX_NUM_CASES) { GrfMsg(1, "GlobalVarChangeInfo: Case name {} is not known, ignoring", StrMakeValid(name)); } else { diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp index 630c432b28..7f963187bc 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -193,9 +193,9 @@ void ScriptText::_FillParamList(ParamList ¶ms, ScriptTextList &seen_texts) } } -void ScriptText::ParamCheck::Encode(std::back_insert_iterator &output, const char *cmd) +void ScriptText::ParamCheck::Encode(std::back_insert_iterator &output, std::string_view cmd) { - if (this->cmd == nullptr) this->cmd = cmd; + if (this->cmd.empty()) this->cmd = cmd; if (this->used) return; struct visitor { @@ -286,7 +286,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 : nullptr); + p.Encode(output, 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 02c757a427..c31da8a400 100644 --- a/src/script/api/script_text.hpp +++ b/src/script/api/script_text.hpp @@ -137,11 +137,11 @@ private: int idx; Param *param; bool used = false; - const char *cmd = nullptr; + std::string_view cmd; ParamCheck(StringIndexInTab owner, int idx, Param *param) : owner(owner), idx(idx), param(param) {} - void Encode(std::back_insert_iterator &output, const char *cmd); + void Encode(std::back_insert_iterator &output, std::string_view cmd); }; using ParamList = std::vector; diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp index 1cdb9c8225..7b4287eec5 100644 --- a/src/strgen/strgen.cpp +++ b/src/strgen/strgen.cpp @@ -146,26 +146,26 @@ void FileStringReader::HandlePragma(char *str) _lang.newgrflangid = static_cast(langid); } else if (!memcmp(str, "gender ", 7)) { if (this->master) FatalError("Genders are not allowed in the base translation."); - char *buf = str + 7; + const char *buf = str + 7; for (;;) { - const char *s = ParseWord(&buf); + auto s = ParseWord(&buf); - if (s == nullptr) break; + if (!s.has_value()) break; if (_lang.num_genders >= MAX_NUM_GENDERS) FatalError("Too many genders, max {}", MAX_NUM_GENDERS); - strecpy(_lang.genders[_lang.num_genders], s); + s->copy(_lang.genders[_lang.num_genders], CASE_GENDER_LEN - 1); _lang.num_genders++; } } else if (!memcmp(str, "case ", 5)) { if (this->master) FatalError("Cases are not allowed in the base translation."); - char *buf = str + 5; + const char *buf = str + 5; for (;;) { - const char *s = ParseWord(&buf); + auto s = ParseWord(&buf); - if (s == nullptr) break; + if (!s.has_value()) break; if (_lang.num_cases >= MAX_NUM_CASES) FatalError("Too many cases, max {}", MAX_NUM_CASES); - strecpy(_lang.cases[_lang.num_cases], s); + s->copy(_lang.cases[_lang.num_cases], CASE_GENDER_LEN - 1); _lang.num_cases++; } } else { @@ -344,7 +344,7 @@ int CDECL main(int argc, char *argv[]) } else { flags = '0'; // Command needs no parameters } - fmt::print("{}\t{:c}\t\"{}\"\t\"{}\"\n", cs.consumes, flags, cs.cmd, strstr(cs.cmd, "STRING") ? "STRING" : cs.cmd); + fmt::print("{}\t{:c}\t\"{}\"\t\"{}\"\n", cs.consumes, flags, cs.cmd, cs.cmd.find("STRING") != std::string::npos ? "STRING" : cs.cmd); } return 0; diff --git a/src/strgen/strgen.h b/src/strgen/strgen.h index bb6511661a..7253138063 100644 --- a/src/strgen/strgen.h +++ b/src/strgen/strgen.h @@ -152,7 +152,7 @@ void StrgenErrorI(const std::string &msg); #define StrgenWarning(format_string, ...) StrgenWarningI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) #define StrgenError(format_string, ...) StrgenErrorI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) #define StrgenFatal(format_string, ...) StrgenFatalI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) -char *ParseWord(char **buf); +std::optional ParseWord(const char **buf); extern const char *_file; extern size_t _cur_line; diff --git a/src/strgen/strgen_base.cpp b/src/strgen/strgen_base.cpp index e124504917..b4964cb4a7 100644 --- a/src/strgen/strgen_base.cpp +++ b/src/strgen/strgen_base.cpp @@ -118,10 +118,10 @@ LangString *StringData::Find(const std::string &s) * @param s The string hash. * @return The new hash. */ -static uint32_t VersionHashStr(uint32_t hash, const char *s) +static uint32_t VersionHashStr(uint32_t hash, std::string_view s) { - for (; *s != '\0'; s++) { - hash = std::rotl(hash, 3) ^ *s; + for (auto c : s) { + hash = std::rotl(hash, 3) ^ c; hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1); } return hash; @@ -139,12 +139,11 @@ uint32_t StringData::Version() const const LangString *ls = this->strings[i].get(); if (ls != nullptr) { - const char *s = ls->name.c_str(); hash ^= i * 0x717239; hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1); - hash = VersionHashStr(hash, s + 1); + hash = VersionHashStr(hash, ls->name); - s = ls->english.c_str(); + const char *s = ls->english.c_str(); ParsedCommandString cs; while ((cs = ParseCommandString(&s)).cmd != nullptr) { if (cs.cmd->flags.Test(CmdFlag::DontCount)) continue; @@ -230,7 +229,7 @@ static size_t Utf8Validate(const char *s) return 0; } -void EmitSingleChar(Buffer *buffer, char *buf, char32_t value) +void EmitSingleChar(Buffer *buffer, const char *buf, char32_t value) { if (*buf != '\0') StrgenWarning("Ignoring trailing letters in command"); buffer->AppendUtf8(value); @@ -238,7 +237,7 @@ void EmitSingleChar(Buffer *buffer, char *buf, char32_t value) /* The plural specifier looks like * {NUM} {PLURAL passenger passengers} then it picks either passenger/passengers depending on the count in NUM */ -static std::pair, std::optional> ParseRelNum(char **buf) +static std::pair, std::optional> ParseRelNum(const char **buf) { const char *s = *buf; char *end; @@ -258,65 +257,58 @@ static std::pair, std::optional> ParseRelNum(char } /* Parse out the next word, or nullptr */ -char *ParseWord(char **buf) +std::optional ParseWord(const char **buf) { - char *s = *buf, *r; + const char *s = *buf; while (*s == ' ' || *s == '\t') s++; - if (*s == '\0') return nullptr; + if (*s == '\0') return {}; if (*s == '"') { - r = ++s; + const char *begin = ++s; /* parse until next " or NUL */ for (;;) { - if (*s == '\0') break; + if (*s == '\0') StrgenFatal("Unterminated quotes"); if (*s == '"') { - *s++ = '\0'; - break; + *buf = s + 1; + return std::string_view(begin, s - begin); } s++; } } else { /* proceed until whitespace or NUL */ - r = s; + const char *begin = s; for (;;) { - if (*s == '\0') break; - if (*s == ' ' || *s == '\t') { - *s++ = '\0'; - break; + if (*s == '\0' || *s == ' ' || *s == '\t') { + *buf = s; + return std::string_view(begin, s - begin); } s++; } } - *buf = s; - return r; } /* This is encoded like * CommandByte {Length of each string} {each string} */ -static void EmitWordList(Buffer *buffer, const std::vector &words, size_t nw) +static void EmitWordList(Buffer *buffer, const std::vector &words) { /* Maximum word length in bytes, excluding trailing NULL. */ constexpr size_t MAX_WORD_LENGTH = UINT8_MAX - 2; - buffer->AppendByte(static_cast(nw)); - for (size_t i = 0; i < nw; i++) { - size_t len = strlen(words[i]) + 1; - if (len >= UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, nw, words[i], MAX_WORD_LENGTH); + buffer->AppendByte(static_cast(words.size())); + for (size_t i = 0; i < words.size(); i++) { + size_t len = words[i].size() + 1; + if (len >= UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, words.size(), words[i], MAX_WORD_LENGTH); buffer->AppendByte(static_cast(len)); } - for (size_t i = 0; i < nw; i++) { + for (size_t i = 0; i < words.size(); i++) { buffer->append(words[i]); buffer->AppendByte(0); } } -void EmitPlural(Buffer *buffer, char *buf, char32_t) +void EmitPlural(Buffer *buffer, const char *buf, char32_t) { - size_t expected = _plural_forms[_lang.plural_form].plural_count; - std::vector words(std::max(expected, MAX_PLURALS), nullptr); - size_t nw = 0; - /* Parse out the number, if one exists. Otherwise default to prev arg. */ auto [argidx, offset] = ParseRelNum(&buf); if (!argidx.has_value()) { @@ -334,26 +326,29 @@ void EmitPlural(Buffer *buffer, char *buf, char32_t) } /* Parse each string */ - for (nw = 0; nw < MAX_PLURALS; nw++) { - words[nw] = ParseWord(&buf); - if (words[nw] == nullptr) break; + std::vector words; + for (;;) { + auto word = ParseWord(&buf); + if (!word.has_value()) break; + words.emplace_back(*word); } - if (nw == 0) { + if (words.empty()) { StrgenFatal("{}: No plural words", _cur_ident); } - if (expected != nw) { + size_t expected = _plural_forms[_lang.plural_form].plural_count; + if (expected != words.size()) { if (_translated) { StrgenFatal("{}: Invalid number of plural forms. Expecting {}, found {}.", _cur_ident, - expected, nw); + expected, words.size()); } else { if (_show_warnings) StrgenWarning("'{}' is untranslated. Tweaking english string to allow compilation for plural forms", _cur_ident); - if (nw > expected) { - nw = expected; + if (words.size() > expected) { + words.resize(expected); } else { - for (; nw < expected; nw++) { - words[nw] = words[nw - 1]; + while (words.size() < expected) { + words.push_back(words.back()); } } } @@ -362,24 +357,22 @@ void EmitPlural(Buffer *buffer, char *buf, char32_t) buffer->AppendUtf8(SCC_PLURAL_LIST); buffer->AppendByte(_lang.plural_form); buffer->AppendByte(static_cast(TranslateArgumentIdx(*argidx, *offset))); - EmitWordList(buffer, words, nw); + EmitWordList(buffer, words); } -void EmitGender(Buffer *buffer, char *buf, char32_t) +void EmitGender(Buffer *buffer, const char *buf, char32_t) { if (buf[0] == '=') { buf++; /* This is a {G=DER} command */ - uint nw = _lang.GetGenderIndex(buf); + auto nw = _lang.GetGenderIndex(buf); if (nw >= MAX_NUM_GENDERS) StrgenFatal("G argument '{}' invalid", buf); /* now nw contains the gender index */ buffer->AppendUtf8(SCC_GENDER_INDEX); buffer->AppendByte(nw); } else { - std::vector words(MAX_NUM_GENDERS, nullptr); - /* This is a {G 0 foo bar two} command. * If no relative number exists, default to +0 */ auto [argidx, offset] = ParseRelNum(&buf); @@ -391,38 +384,33 @@ void EmitGender(Buffer *buffer, char *buf, char32_t) StrgenFatal("Command '{}' can't have a gender", cmd == nullptr ? "" : cmd->cmd); } - size_t nw; - for (nw = 0; nw < MAX_NUM_GENDERS; nw++) { - words[nw] = ParseWord(&buf); - if (words[nw] == nullptr) break; + std::vector words; + for (;;) { + auto word = ParseWord(&buf); + if (!word.has_value()) break; + words.emplace_back(*word); } - if (nw != _lang.num_genders) StrgenFatal("Bad # of arguments for gender command"); + if (words.size() != _lang.num_genders) StrgenFatal("Bad # of arguments for gender command"); assert(IsInsideBS(cmd->value, SCC_CONTROL_START, UINT8_MAX)); buffer->AppendUtf8(SCC_GENDER_LIST); buffer->AppendByte(static_cast(TranslateArgumentIdx(*argidx, *offset))); - EmitWordList(buffer, words, nw); + EmitWordList(buffer, words); } } -static const CmdStruct *FindCmd(const char *s, size_t len) +static const CmdStruct *FindCmd(std::string_view s) { for (const auto &cs : _cmd_structs) { - if (strncmp(cs.cmd, s, len) == 0 && cs.cmd[len] == '\0') return &cs; + if (cs.cmd == s) return &cs; } return nullptr; } -static uint ResolveCaseName(const char *str, size_t len) +static uint ResolveCaseName(std::string_view str) { - /* First get a clean copy of only the case name, then resolve it. */ - char case_str[CASE_GENDER_LEN]; - len = std::min(lengthof(case_str) - 1, len); - memcpy(case_str, str, len); - case_str[len] = '\0'; - - uint case_idx = _lang.GetCaseIndex(case_str); - if (case_idx >= MAX_NUM_CASES) StrgenFatal("Invalid case-name '{}'", case_str); + uint case_idx = _lang.GetCaseIndex(str); + if (case_idx >= MAX_NUM_CASES) StrgenFatal("Invalid case-name '{}'", str); return case_idx + 1; } @@ -453,9 +441,9 @@ static ParsedCommandString ParseCommandString(const char **str) c = *s++; } while (c != '}' && c != ' ' && c != '=' && c != '.' && c != 0); - result.cmd = FindCmd(start, s - start - 1); + std::string_view command(start, s - start - 1); + result.cmd = FindCmd(command); if (result.cmd == nullptr) { - std::string command(start, s - start - 1); StrgenError("Undefined command '{}'", command); return {}; } @@ -470,7 +458,7 @@ static ParsedCommandString ParseCommandString(const char **str) do { c = *s++; } while (c != '}' && c != ' ' && c != '\0'); - result.casei = ResolveCaseName(casep, s - casep - 1); + result.casei = ResolveCaseName(std::string_view(casep, s - casep - 1)); } if (c == '\0') { @@ -542,15 +530,15 @@ const CmdStruct *TranslateCmdForCompare(const CmdStruct *a) { if (a == nullptr) return nullptr; - if (strcmp(a->cmd, "STRING1") == 0 || - strcmp(a->cmd, "STRING2") == 0 || - strcmp(a->cmd, "STRING3") == 0 || - strcmp(a->cmd, "STRING4") == 0 || - strcmp(a->cmd, "STRING5") == 0 || - strcmp(a->cmd, "STRING6") == 0 || - strcmp(a->cmd, "STRING7") == 0 || - strcmp(a->cmd, "RAW_STRING") == 0) { - return FindCmd("STRING", 6); + if (a->cmd == "STRING1" || + a->cmd == "STRING2" || + a->cmd == "STRING3" || + a->cmd == "STRING4" || + a->cmd == "STRING5" || + a->cmd == "STRING6" || + a->cmd == "STRING7" || + a->cmd == "RAW_STRING") { + return FindCmd("STRING"); } return a; @@ -689,7 +677,7 @@ void StringReader::HandleString(char *str) if (!CheckCommandsMatch(s, ent->english.c_str(), str)) return; if (casep != nullptr) { - ent->translated_cases.emplace_back(ResolveCaseName(casep, strlen(casep)), s); + ent->translated_cases.emplace_back(ResolveCaseName(casep), s); } else { ent->translated = s; /* If the string was translated, use the line from the @@ -827,7 +815,7 @@ static void PutCommandString(Buffer *buffer, const char *str) } } - cmd->proc(buffer, cs.param.data(), cmd->value); + cmd->proc(buffer, cs.param.c_str(), cmd->value); } } diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h index c17074d0ef..0c8355f05c 100644 --- a/src/table/strgen_tables.h +++ b/src/table/strgen_tables.h @@ -17,10 +17,10 @@ enum class CmdFlag : uint8_t { using CmdFlags = EnumBitSet; struct Buffer; -typedef void (*ParseCmdProc)(Buffer *buffer, char *buf, char32_t value); +typedef void (*ParseCmdProc)(Buffer *buffer, const char *buf, char32_t value); struct CmdStruct { - const char *cmd; + std::string_view cmd; ParseCmdProc proc; char32_t value; uint8_t consumes; @@ -28,9 +28,9 @@ struct CmdStruct { CmdFlags flags; }; -extern void EmitSingleChar(Buffer *buffer, char *buf, char32_t value); -extern void EmitPlural(Buffer *buffer, char *buf, char32_t value); -extern void EmitGender(Buffer *buffer, char *buf, char32_t value); +extern void EmitSingleChar(Buffer *buffer, const char *buf, char32_t value); +extern void EmitPlural(Buffer *buffer, const char *buf, char32_t value); +extern void EmitGender(Buffer *buffer, const char *buf, char32_t value); static const CmdStruct _cmd_structs[] = { /* Font size */