diff --git a/src/game/game_text.cpp b/src/game/game_text.cpp index 15c4fd7bdf..dfb170e934 100644 --- a/src/game/game_text.cpp +++ b/src/game/game_text.cpp @@ -25,19 +25,19 @@ void CDECL StrgenWarningI(const std::string &msg) { - Debug(script, 0, "{}:{}: warning: {}", _file, _cur_line, msg); - _warnings++; + Debug(script, 0, "{}:{}: warning: {}", _strgen.file, _strgen.cur_line, msg); + _strgen.warnings++; } void CDECL StrgenErrorI(const std::string &msg) { - Debug(script, 0, "{}:{}: error: {}", _file, _cur_line, msg); - _errors++; + Debug(script, 0, "{}:{}: error: {}", _strgen.file, _strgen.cur_line, msg); + _strgen.errors++; } void CDECL StrgenFatalI(const std::string &msg) { - Debug(script, 0, "{}:{}: FATAL: {}", _file, _cur_line, msg); + Debug(script, 0, "{}:{}: FATAL: {}", _strgen.file, _strgen.cur_line, msg); throw std::exception(); } @@ -289,7 +289,7 @@ void GameStrings::Compile() StringData data(32); StringListReader master_reader(data, this->raw_strings[0], true, false); master_reader.ParseFile(); - if (_errors != 0) throw std::exception(); + if (_strgen.errors != 0) throw std::exception(); this->version = data.Version(); @@ -302,7 +302,7 @@ void GameStrings::Compile() data.FreeTranslation(); StringListReader translation_reader(data, p, false, p.language != "english"); translation_reader.ParseFile(); - if (_errors != 0) throw std::exception(); + if (_strgen.errors != 0) throw std::exception(); auto &strings = this->compiled_strings.emplace_back(p.language); TranslationWriter writer(strings.lines); diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp index 7b4287eec5..c86c0c1029 100644 --- a/src/strgen/strgen.cpp +++ b/src/strgen/strgen.cpp @@ -35,34 +35,34 @@ void StrgenWarningI(const std::string &msg) { - if (_translation) { - fmt::print(stderr, LINE_NUM_FMT("info"), _file, _cur_line, msg); + if (_strgen.translation) { + fmt::print(stderr, LINE_NUM_FMT("info"), _strgen.file, _strgen.cur_line, msg); } else { - fmt::print(stderr, LINE_NUM_FMT("warning"), _file, _cur_line, msg); + fmt::print(stderr, LINE_NUM_FMT("warning"), _strgen.file, _strgen.cur_line, msg); } - _warnings++; + _strgen.warnings++; } void StrgenErrorI(const std::string &msg) { - fmt::print(stderr, LINE_NUM_FMT("error"), _file, _cur_line, msg); - _errors++; + fmt::print(stderr, LINE_NUM_FMT("error"), _strgen.file, _strgen.cur_line, msg); + _strgen.errors++; } [[noreturn]] void StrgenFatalI(const std::string &msg) { - fmt::print(stderr, LINE_NUM_FMT("FATAL"), _file, _cur_line, msg); + fmt::print(stderr, LINE_NUM_FMT("FATAL"), _strgen.file, _strgen.cur_line, msg); #ifdef _MSC_VER - fmt::print(stderr, LINE_NUM_FMT("warning"), _file, _cur_line, "language is not compiled"); + fmt::print(stderr, LINE_NUM_FMT("warning"), _strgen.file, _strgen.cur_line, "language is not compiled"); #endif throw std::exception(); } [[noreturn]] void FatalErrorI(const std::string &msg) { - fmt::print(stderr, LINE_NUM_FMT("FATAL"), _file, _cur_line, msg); + fmt::print(stderr, LINE_NUM_FMT("FATAL"), _strgen.file, _strgen.cur_line, msg); #ifdef _MSC_VER - fmt::print(stderr, LINE_NUM_FMT("warning"), _file, _cur_line, "language is not compiled"); + fmt::print(stderr, LINE_NUM_FMT("warning"), _strgen.file, _strgen.cur_line, "language is not compiled"); #endif exit(2); } @@ -91,59 +91,59 @@ struct FileStringReader : StringReader { return result; } - void HandlePragma(char *str) override; + void HandlePragma(char *str, LanguagePackHeader &lang) override; void ParseFile() override { this->StringReader::ParseFile(); - if (StrEmpty(_lang.name) || StrEmpty(_lang.own_name) || StrEmpty(_lang.isocode)) { + if (StrEmpty(_strgen.lang.name) || StrEmpty(_strgen.lang.own_name) || StrEmpty(_strgen.lang.isocode)) { FatalError("Language must include ##name, ##ownname and ##isocode"); } } }; -void FileStringReader::HandlePragma(char *str) +void FileStringReader::HandlePragma(char *str, LanguagePackHeader &lang) { if (!memcmp(str, "id ", 3)) { this->data.next_string_id = std::strtoul(str + 3, nullptr, 0); } else if (!memcmp(str, "name ", 5)) { - strecpy(_lang.name, str + 5); + strecpy(lang.name, str + 5); } else if (!memcmp(str, "ownname ", 8)) { - strecpy(_lang.own_name, str + 8); + strecpy(lang.own_name, str + 8); } else if (!memcmp(str, "isocode ", 8)) { - strecpy(_lang.isocode, str + 8); + strecpy(lang.isocode, str + 8); } else if (!memcmp(str, "textdir ", 8)) { if (!memcmp(str + 8, "ltr", 3)) { - _lang.text_dir = TD_LTR; + lang.text_dir = TD_LTR; } else if (!memcmp(str + 8, "rtl", 3)) { - _lang.text_dir = TD_RTL; + lang.text_dir = TD_RTL; } else { FatalError("Invalid textdir {}", str + 8); } } else if (!memcmp(str, "digitsep ", 9)) { str += 9; - strecpy(_lang.digit_group_separator, strcmp(str, "{NBSP}") == 0 ? NBSP : str); + strecpy(lang.digit_group_separator, strcmp(str, "{NBSP}") == 0 ? NBSP : str); } else if (!memcmp(str, "digitsepcur ", 12)) { str += 12; - strecpy(_lang.digit_group_separator_currency, strcmp(str, "{NBSP}") == 0 ? NBSP : str); + strecpy(lang.digit_group_separator_currency, strcmp(str, "{NBSP}") == 0 ? NBSP : str); } else if (!memcmp(str, "decimalsep ", 11)) { str += 11; - strecpy(_lang.digit_decimal_separator, strcmp(str, "{NBSP}") == 0 ? NBSP : str); + strecpy(lang.digit_decimal_separator, strcmp(str, "{NBSP}") == 0 ? NBSP : str); } else if (!memcmp(str, "winlangid ", 10)) { const char *buf = str + 10; long langid = std::strtol(buf, nullptr, 16); if (langid > UINT16_MAX || langid < 0) { FatalError("Invalid winlangid {}", buf); } - _lang.winlangid = static_cast(langid); + lang.winlangid = static_cast(langid); } else if (!memcmp(str, "grflangid ", 10)) { const char *buf = str + 10; long langid = std::strtol(buf, nullptr, 16); if (langid >= 0x7F || langid < 0) { FatalError("Invalid grflangid {}", buf); } - _lang.newgrflangid = static_cast(langid); + lang.newgrflangid = static_cast(langid); } else if (!memcmp(str, "gender ", 7)) { if (this->master) FatalError("Genders are not allowed in the base translation."); const char *buf = str + 7; @@ -152,9 +152,9 @@ void FileStringReader::HandlePragma(char *str) auto s = ParseWord(&buf); if (!s.has_value()) break; - if (_lang.num_genders >= MAX_NUM_GENDERS) FatalError("Too many genders, max {}", MAX_NUM_GENDERS); - s->copy(_lang.genders[_lang.num_genders], CASE_GENDER_LEN - 1); - _lang.num_genders++; + if (lang.num_genders >= MAX_NUM_GENDERS) FatalError("Too many genders, max {}", MAX_NUM_GENDERS); + 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."); @@ -164,12 +164,12 @@ void FileStringReader::HandlePragma(char *str) auto s = ParseWord(&buf); if (!s.has_value()) break; - if (_lang.num_cases >= MAX_NUM_CASES) FatalError("Too many cases, max {}", MAX_NUM_CASES); - s->copy(_lang.cases[_lang.num_cases], CASE_GENDER_LEN - 1); - _lang.num_cases++; + if (lang.num_cases >= MAX_NUM_CASES) FatalError("Too many cases, max {}", MAX_NUM_CASES); + s->copy(lang.cases[lang.num_cases], CASE_GENDER_LEN - 1); + lang.num_cases++; } } else { - StringReader::HandlePragma(str); + StringReader::HandlePragma(str, lang); } } @@ -364,11 +364,11 @@ int CDECL main(int argc, char *argv[]) return 0; case 't': - _annotate_todos = true; + _strgen.annotate_todos = true; break; case 'w': - _show_warnings = true; + _strgen.show_warnings = true; break; case 'h': @@ -417,7 +417,7 @@ int CDECL main(int argc, char *argv[]) StringData data(TEXT_TAB_END); FileStringReader master_reader(data, input_path, true, false); master_reader.ParseFile(); - if (_errors != 0) return 1; + if (_strgen.errors != 0) return 1; /* write strings.h */ std::filesystem::path output_path = dest_dir; @@ -427,7 +427,7 @@ int CDECL main(int argc, char *argv[]) HeaderFileWriter writer(output_path); writer.WriteHeader(data); writer.Finalise(data); - if (_errors != 0) return 1; + if (_strgen.errors != 0) return 1; } else { std::filesystem::path input_path = std::move(src_dir); input_path /= "english.txt"; @@ -443,7 +443,7 @@ int CDECL main(int argc, char *argv[]) std::filesystem::path lang_file = argument; FileStringReader translation_reader(data, lang_file, false, lang_file.filename() != "english.txt"); translation_reader.ParseFile(); // target file - if (_errors != 0) return 1; + if (_strgen.errors != 0) return 1; /* get the targetfile, strip any directories and append to destination path */ std::filesystem::path output_file = dest_dir; @@ -455,8 +455,8 @@ int CDECL main(int argc, char *argv[]) writer.Finalise(); /* if showing warnings, print a summary of the language */ - if (_show_warnings) { - fmt::print("{} warnings and {} errors for {}\n", _warnings, _errors, output_file); + if (_strgen.show_warnings) { + fmt::print("{} warnings and {} errors for {}\n", _strgen.warnings, _strgen.errors, output_file); } } } diff --git a/src/strgen/strgen.h b/src/strgen/strgen.h index 7253138063..78a6c8def7 100644 --- a/src/strgen/strgen.h +++ b/src/strgen/strgen.h @@ -74,7 +74,7 @@ struct StringReader { * Handle the pragma of the file. * @param str The pragma string to parse. */ - virtual void HandlePragma(char *str); + virtual void HandlePragma(char *str, LanguagePackHeader &lang); /** * Start parsing the file. @@ -154,10 +154,17 @@ void StrgenErrorI(const std::string &msg); #define StrgenFatal(format_string, ...) StrgenFatalI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) std::optional ParseWord(const char **buf); -extern const char *_file; -extern size_t _cur_line; -extern size_t _errors, _warnings; -extern bool _show_warnings, _annotate_todos, _translation; -extern LanguagePackHeader _lang; +/** Global state shared between strgen.cpp, game_text.cpp and strgen_base.cpp */ +struct StrgenState { + std::string file = "(unknown file)"; ///< The filename of the input, so we can refer to it in errors/warnings + size_t cur_line = 0; ///< The current line we're parsing in the input file + size_t errors = 0; + size_t warnings = 0; + bool show_warnings = false; + bool annotate_todos = false; + bool translation = false; ///< Is the current file actually a translation or not + LanguagePackHeader lang; ///< Header information about a language. +}; +extern StrgenState _strgen; #endif /* STRGEN_H */ diff --git a/src/strgen/strgen_base.cpp b/src/strgen/strgen_base.cpp index c30ad4ae9b..534003ec16 100644 --- a/src/strgen/strgen_base.cpp +++ b/src/strgen/strgen_base.cpp @@ -21,13 +21,8 @@ #include "../safeguards.h" +StrgenState _strgen; static bool _translated; ///< Whether the current language is not the master language -bool _translation; ///< Is the current file actually a translation or not -const char *_file = "(unknown file)"; ///< The filename of the input, so we can refer to it in errors/warnings -size_t _cur_line; ///< The current line we're parsing in the input file -size_t _errors, _warnings; -bool _show_warnings = false, _annotate_todos = false; -LanguagePackHeader _lang; ///< Header information about a language. static const char *_cur_ident; static ParsedCommandStruct _cur_pcs; static size_t _cur_argidx; @@ -297,13 +292,13 @@ void EmitPlural(StringBuilder &builder, const char *buf, char32_t) StrgenFatal("{}: No plural words", _cur_ident); } - size_t expected = _plural_forms[_lang.plural_form].plural_count; + size_t expected = _plural_forms[_strgen.lang.plural_form].plural_count; if (expected != words.size()) { if (_translated) { StrgenFatal("{}: Invalid number of plural forms. Expecting {}, found {}.", _cur_ident, expected, words.size()); } else { - if (_show_warnings) StrgenWarning("'{}' is untranslated. Tweaking english string to allow compilation for plural forms", _cur_ident); + if (_strgen.show_warnings) StrgenWarning("'{}' is untranslated. Tweaking english string to allow compilation for plural forms", _cur_ident); if (words.size() > expected) { words.resize(expected); } else { @@ -315,7 +310,7 @@ void EmitPlural(StringBuilder &builder, const char *buf, char32_t) } builder.PutUtf8(SCC_PLURAL_LIST); - builder.PutUint8(_lang.plural_form); + builder.PutUint8(_strgen.lang.plural_form); builder.PutUint8(static_cast(TranslateArgumentIdx(*argidx, *offset))); EmitWordList(builder, words); } @@ -326,7 +321,7 @@ void EmitGender(StringBuilder &builder, const char *buf, char32_t) buf++; /* This is a {G=DER} command */ - auto nw = _lang.GetGenderIndex(buf); + auto nw = _strgen.lang.GetGenderIndex(buf); if (nw >= MAX_NUM_GENDERS) StrgenFatal("G argument '{}' invalid", buf); /* now nw contains the gender index */ @@ -350,7 +345,7 @@ void EmitGender(StringBuilder &builder, const char *buf, char32_t) if (!word.has_value()) break; words.emplace_back(*word); } - if (words.size() != _lang.num_genders) StrgenFatal("Bad # of arguments for gender command"); + if (words.size() != _strgen.lang.num_genders) StrgenFatal("Bad # of arguments for gender command"); assert(IsInsideBS(cmd->value, SCC_CONTROL_START, UINT8_MAX)); builder.PutUtf8(SCC_GENDER_LIST); @@ -369,7 +364,7 @@ static const CmdStruct *FindCmd(std::string_view s) static uint8_t ResolveCaseName(std::string_view str) { - uint8_t case_idx = _lang.GetCaseIndex(str); + uint8_t case_idx = _strgen.lang.GetCaseIndex(str); if (case_idx >= MAX_NUM_CASES) StrgenFatal("Invalid case-name '{}'", str); return case_idx + 1; } @@ -510,7 +505,7 @@ static bool CheckCommandsMatch(const char *a, const char *b, const char *name) * it is pointless to do all these checks as it'll always be correct. * After all, all checks are based on the base language. */ - if (!_translation) return true; + if (!_strgen.translation) return true; bool result = true; @@ -558,7 +553,7 @@ static bool CheckCommandsMatch(const char *a, const char *b, const char *name) void StringReader::HandleString(char *str) { if (*str == '#') { - if (str[1] == '#' && str[2] != '#') this->HandlePragma(str + 2); + if (str[1] == '#' && str[2] != '#') this->HandlePragma(str + 2, _strgen.lang); return; } @@ -621,7 +616,7 @@ void StringReader::HandleString(char *str) } /* Allocate a new LangString */ - this->data.Add(std::make_unique(str, s, this->data.next_string_id++, _cur_line)); + this->data.Add(std::make_unique(str, s, this->data.next_string_id++, _strgen.cur_line)); } else { if (ent == nullptr) { StrgenWarning("String name '{}' does not exist in master file", str); @@ -643,17 +638,17 @@ void StringReader::HandleString(char *str) /* If the string was translated, use the line from the * translated language so errors in the translated file * are properly referenced to. */ - ent->line = _cur_line; + ent->line = _strgen.cur_line; } } } -void StringReader::HandlePragma(char *str) +void StringReader::HandlePragma(char *str, LanguagePackHeader &lang) { if (!memcmp(str, "plural ", 7)) { - _lang.plural_form = atoi(str + 7); - if (_lang.plural_form >= lengthof(_plural_forms)) { - StrgenFatal("Invalid pluralform {}", _lang.plural_form); + lang.plural_form = atoi(str + 7); + if (lang.plural_form >= lengthof(_plural_forms)) { + StrgenFatal("Invalid pluralform {}", lang.plural_form); } } else { StrgenFatal("unknown pragma '{}'", str); @@ -667,25 +662,25 @@ static void StripTrailingWhitespace(std::string &str) void StringReader::ParseFile() { - _warnings = _errors = 0; + _strgen.warnings = _strgen.errors = 0; - _translation = this->translation; - _file = this->file.c_str(); + _strgen.translation = this->translation; + _strgen.file = this->file; /* For each new file we parse, reset the genders, and language codes. */ - MemSetT(&_lang, 0); - strecpy(_lang.digit_group_separator, ","); - strecpy(_lang.digit_group_separator_currency, ","); - strecpy(_lang.digit_decimal_separator, "."); + MemSetT(&_strgen.lang, 0); + strecpy(_strgen.lang.digit_group_separator, ","); + strecpy(_strgen.lang.digit_group_separator_currency, ","); + strecpy(_strgen.lang.digit_decimal_separator, "."); - _cur_line = 1; + _strgen.cur_line = 1; while (this->data.next_string_id < this->data.max_strings) { std::optional line = this->ReadLine(); if (!line.has_value()) return; StripTrailingWhitespace(line.value()); this->HandleString(line.value().data()); - _cur_line++; + _strgen.cur_line++; } if (this->data.next_string_id == this->data.max_strings) { @@ -812,20 +807,20 @@ void LanguageWriter::WriteLang(const StringData &data) size_t n = data.CountInUse(tab); in_use.push_back(n); - _lang.offsets[tab] = TO_LE16(static_cast(n)); + _strgen.lang.offsets[tab] = TO_LE16(static_cast(n)); for (size_t j = 0; j != in_use[tab]; j++) { const LangString *ls = data.strings[(tab * TAB_SIZE) + j].get(); - if (ls != nullptr && ls->translated.empty()) _lang.missing++; + if (ls != nullptr && ls->translated.empty()) _strgen.lang.missing++; } } - _lang.ident = TO_LE32(LanguagePackHeader::IDENT); - _lang.version = TO_LE32(data.Version()); - _lang.missing = TO_LE16(_lang.missing); - _lang.winlangid = TO_LE16(_lang.winlangid); + _strgen.lang.ident = TO_LE32(LanguagePackHeader::IDENT); + _strgen.lang.version = TO_LE32(data.Version()); + _strgen.lang.missing = TO_LE16(_strgen.lang.missing); + _strgen.lang.winlangid = TO_LE16(_strgen.lang.winlangid); - this->WriteHeader(&_lang); + this->WriteHeader(&_strgen.lang); for (size_t tab = 0; tab < data.tabs; tab++) { for (size_t j = 0; j != in_use[tab]; j++) { @@ -840,14 +835,14 @@ void LanguageWriter::WriteLang(const StringData &data) std::string output; StringBuilder builder(output); _cur_ident = ls->name.c_str(); - _cur_line = ls->line; + _strgen.cur_line = ls->line; /* Produce a message if a string doesn't have a translation. */ if (ls->translated.empty()) { - if (_show_warnings) { + if (_strgen.show_warnings) { StrgenWarning("'{}' is untranslated", ls->name); } - if (_annotate_todos) { + if (_strgen.annotate_todos) { builder.Put(" "); } }