// // This file is part of Kepka, // an unofficial desktop version of Telegram messaging app, // see https://github.com/procxx/kepka // // Kepka is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // It is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // In addition, as a special exception, the copyright holders give permission // to link the code of portions of this program with the OpenSSL library. // // Full license: https://github.com/procxx/kepka/blob/master/LICENSE // Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org // Copyright (c) 2017- Kepka Contributors, https://github.com/procxx // #include "codegen/lang/generator.h" #include #include #include #include #include #include namespace codegen { namespace lang { namespace { char hexChar(uchar ch) { if (ch < 10) { return '0' + ch; } else if (ch < 16) { return 'a' + (ch - 10); } return '0'; } char hexSecondChar(char ch) { return hexChar((*reinterpret_cast(&ch)) & 0x0F); } char hexFirstChar(char ch) { return hexChar((*reinterpret_cast(&ch)) >> 4); } QString stringToEncodedString(const QString &str) { QString result, lineBreak = "\\\n"; result.reserve(str.size() * 8); bool writingHexEscapedCharacters = false, startOnNewLine = false; int lastCutSize = 0; auto utf = str.toUtf8(); for (auto ch : utf) { if (result.size() - lastCutSize > 80) { startOnNewLine = true; result.append(lineBreak); lastCutSize = result.size(); } if (ch == '\n') { writingHexEscapedCharacters = false; result.append("\\n"); } else if (ch == '\t') { writingHexEscapedCharacters = false; result.append("\\t"); } else if (ch == '"' || ch == '\\') { writingHexEscapedCharacters = false; result.append('\\').append(ch); } else if (ch < 32 || static_cast(ch) > 127) { writingHexEscapedCharacters = true; result.append("\\x").append(hexFirstChar(ch)).append(hexSecondChar(ch)); } else { if (writingHexEscapedCharacters) { writingHexEscapedCharacters = false; result.append("\"\""); } result.append(ch); } } return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"'; } QString stringToEncodedString(const std::string &str) { return stringToEncodedString(QString::fromStdString(str)); } QString stringToBinaryArray(const std::string &str) { QStringList rows, chars; chars.reserve(13); rows.reserve(1 + (str.size() / 13)); for (uchar ch : str) { if (chars.size() > 12) { rows.push_back(chars.join(", ")); chars.clear(); } chars.push_back(QString("0x") + hexFirstChar(ch) + hexSecondChar(ch)); } if (!chars.isEmpty()) { rows.push_back(chars.join(", ")); } return QString("{") + ((rows.size() > 1) ? '\n' : ' ') + rows.join(",\n") + " }"; } } // namespace Generator::Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project) : langpack_(langpack) , basePath_(destBasePath) , baseName_(QFileInfo(basePath_).baseName()) , project_(project) {} bool Generator::writeHeader() { header_ = std::make_unique(basePath_ + ".h", project_); header_->include("utility", true); header_->include("QString", true); header_->include("QLatin1String", true); header_->include("lang/lang_tag.h").newline().pushNamespace("Lang").stream() << "\ \n\ constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ \n"; header_->popNamespace().newline(); auto index = 0; for (auto &tag : langpack_.tags) { header_->stream() << "enum lngtag_" << tag.tag << " { lt_" << tag.tag << " = " << index++ << " };\n"; } header_->stream() << "\ \n\ enum LangKey {\n"; for (auto &entry : langpack_.entries) { header_->stream() << "\t" << getFullKey(entry) << ",\n"; } header_->stream() << "\ \n\ kLangKeysCount,\n\ };\n\ \n\ QString lang(LangKey key);\n\ \n"; for (auto &entry : langpack_.entries) { auto isPlural = !entry.keyBase.isEmpty(); auto &key = entry.key; auto genericParams = QStringList(); auto params = QStringList(); auto applyTags = QStringList(); auto plural = QString(); auto nonPluralTagFound = false; for (auto &tagData : entry.tags) { auto &tag = tagData.tag; auto isPluralTag = isPlural && (tag == kPluralTag); genericParams.push_back("lngtag_" + tag + ", " + (isPluralTag ? "double " : "const ResultString &") + tag + "__val"); params.push_back("lngtag_" + tag + ", " + (isPluralTag ? "double " : "const QString &") + tag + "__val"); if (isPluralTag) { plural = "\tauto plural = Lang::Plural(" + key + ", " + kPluralTag + "__val);\n"; applyTags.push_back("\tresult = Lang::ReplaceTag::Call(std::move(result), lt_" + tag + ", Lang::StartReplacements::Call(std::move(plural.replacement)));\n"); } else { nonPluralTagFound = true; applyTags.push_back("\tresult = Lang::ReplaceTag::Call(std::move(result), lt_" + tag + ", " + tag + "__val);\n"); } } if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) { auto initialString = isPlural ? ("std::move(plural.string)") : ("lang(" + getFullKey(entry) + ")"); header_->stream() << "\ template \n\ inline ResultString " << (isPlural ? entry.keyBase : key) << "__generic(" << genericParams.join(QString(", ")) << ") {\n\ " << plural << "\ auto result = Lang::StartReplacements::Call(" << initialString << ");\n\ " << applyTags.join(QString()) << "\ return result;\n\ }\n\ constexpr auto " << (isPlural ? entry.keyBase : key) << " = &" << (isPlural ? entry.keyBase : key) << "__generic;\n\ \n"; } } header_->pushNamespace("Lang").stream() << "\ \n\ const char *GetKeyName(LangKey key);\n\ ushort GetTagIndex(QLatin1String tag);\n\ LangKey GetKeyIndex(QLatin1String key);\n\ bool IsTagReplaced(LangKey key, ushort tag);\n\ QString GetOriginalValue(LangKey key);\n\ \n"; return header_->finalize(); } bool Generator::writeSource() { source_ = std::make_unique(basePath_ + ".cpp", project_); source_->include("map", true); source_->include("string", true); source_->include("lang/lang_keys.h").pushNamespace("Lang").pushNamespace().stream() << "\ const std::map KeyMap = {\n\ \n"; for (auto &entry : langpack_.entries) { source_->stream() << "{\"" << entry.key << "\"," << getFullKey(entry) << "},\n"; } source_->stream() << "\ \n\ };\n\ \n\ const std::array KeyNames = {\n\ \n"; for (auto &entry : langpack_.entries) { source_->stream() << "\"" << entry.key << "\",\n"; } source_->stream() << "\ \n\ };\n\ \n\ QChar DefaultData[] = {"; auto count = 0; auto fulllength = 0; for (auto &entry : langpack_.entries) { for (auto ch : entry.value) { if (fulllength > 0) source_->stream() << ","; if (!count++) { source_->stream() << "\n"; } else { if (count == 12) { count = 0; } source_->stream() << " "; } source_->stream() << "0x" << QString::number(ch.unicode(), 16); ++fulllength; } } source_->stream() << " };\n\ \n\ int Offsets[] = {"; count = 0; auto offset = 0; auto writeOffset = [this, &count, &offset] { if (offset > 0) source_->stream() << ","; if (!count++) { source_->stream() << "\n"; } else { if (count == 12) { count = 0; } source_->stream() << " "; } source_->stream() << offset; }; for (auto &entry : langpack_.entries) { writeOffset(); offset += entry.value.size(); } writeOffset(); source_->stream() << " };\n"; source_->stream() << "\ const std::map TagMap = {\n\ \n"; for (auto &tag : langpack_.tags) { source_->stream() << "{\"" << tag.tag << "\"," << "lt_" << tag.tag << "},\n"; } source_->stream() << "\ \n\ };\n"; source_->popNamespace().stream() << "\ \n\ const char *GetKeyName(LangKey key) {\n\ return (key < 0 || key >= kLangKeysCount) ? \"\" : KeyNames[key].c_str();\n\ }\n\ \n\ ushort GetTagIndex(QLatin1String tag) {\n\ auto data = tag.data();\n\ return TagMap.find(data) != TagMap.end() ? TagMap.at(data) : kTagsCount;\n"; source_->stream() << "\ }\n\ \n\ LangKey GetKeyIndex(QLatin1String key) {\n\ auto data = key.data();\n\ return KeyMap.find(data) != KeyMap.end() ? KeyMap.at(data) : kLangKeysCount;\n"; source_->stream() << "\ }\n\ \n\ bool IsTagReplaced(LangKey key, ushort tag) {\n\ switch (key) {\n"; auto lastWrittenPluralEntry = QString(); for (auto &entry : langpack_.entries) { if (entry.tags.empty()) { continue; } if (!entry.keyBase.isEmpty()) { if (entry.keyBase == lastWrittenPluralEntry) { continue; } lastWrittenPluralEntry = entry.keyBase; for (auto i = 0; i != kPluralPartCount; ++i) { source_->stream() << "\ case " << ComputePluralKey(entry.keyBase, i) << ":" << ((i + 1 == kPluralPartCount) ? " {" : "") << "\n"; } } else { source_->stream() << "\ case " << getFullKey(entry) << ": {\n"; } source_->stream() << "\ switch (tag) {\n"; for (auto &tag : entry.tags) { source_->stream() << "\ case lt_" << tag.tag << ":\n"; } source_->stream() << "\ return true;\n\ }\n\ } break;\n"; } source_->stream() << "\ }\ \n\ return false;\n\ }\n\ \n\ QString GetOriginalValue(LangKey key) {\n\ Expects(key >= 0 && key < kLangKeysCount);\n\ auto offset = Offsets[key];\n\ return QString::fromRawData(DefaultData + offset, Offsets[key + 1] - offset);\n\ }\n\ \n"; return source_->finalize(); } QString Generator::getFullKey(const LangPack::Entry &entry) { if (!entry.keyBase.isEmpty() || entry.tags.empty()) { return entry.key; } return entry.key + "__tagged"; } } // namespace lang } // namespace codegen