init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
This commit is contained in:
4
Telegram/codegen/CMakeLists.txt
Normal file
4
Telegram/codegen/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
add_subdirectory(codegen/common)
|
||||
add_subdirectory(codegen/emoji)
|
||||
add_subdirectory(codegen/lang)
|
||||
add_subdirectory(codegen/style)
|
||||
31
Telegram/codegen/codegen/common/CMakeLists.txt
Normal file
31
Telegram/codegen/codegen/common/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
add_library(codegen_common OBJECT)
|
||||
add_library(desktop-app::codegen_common ALIAS codegen_common)
|
||||
init_target(codegen_common "(codegen)")
|
||||
|
||||
get_filename_component(src_loc ../.. REALPATH)
|
||||
|
||||
nice_target_sources(codegen_common ${src_loc}
|
||||
PRIVATE
|
||||
codegen/common/basic_tokenized_file.cpp
|
||||
codegen/common/basic_tokenized_file.h
|
||||
codegen/common/checked_utf8_string.cpp
|
||||
codegen/common/checked_utf8_string.h
|
||||
codegen/common/clean_file.cpp
|
||||
codegen/common/clean_file.h
|
||||
codegen/common/clean_file_reader.h
|
||||
codegen/common/const_utf8_string.h
|
||||
codegen/common/cpp_file.cpp
|
||||
codegen/common/cpp_file.h
|
||||
codegen/common/logging.cpp
|
||||
codegen/common/logging.h
|
||||
)
|
||||
|
||||
target_include_directories(codegen_common
|
||||
PUBLIC
|
||||
${src_loc}
|
||||
)
|
||||
|
||||
target_link_libraries(codegen_common
|
||||
PUBLIC
|
||||
desktop-app::external_qt
|
||||
)
|
||||
299
Telegram/codegen/codegen/common/basic_tokenized_file.cpp
Normal file
299
Telegram/codegen/codegen/common/basic_tokenized_file.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/common/basic_tokenized_file.h"
|
||||
|
||||
#include "codegen/common/logging.h"
|
||||
#include "codegen/common/clean_file_reader.h"
|
||||
#include "codegen/common/checked_utf8_string.h"
|
||||
|
||||
using Token = codegen::common::BasicTokenizedFile::Token;
|
||||
using Type = Token::Type;
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
namespace {
|
||||
|
||||
constexpr int kErrorUnterminatedStringLiteral = 201;
|
||||
constexpr int kErrorIncorrectUtf8String = 202;
|
||||
constexpr int kErrorIncorrectToken = 203;
|
||||
constexpr int kErrorUnexpectedToken = 204;
|
||||
|
||||
bool isDigitChar(char ch) {
|
||||
return (ch >= '0') && (ch <= '9');
|
||||
}
|
||||
|
||||
bool isNameChar(char ch) {
|
||||
return isDigitChar(ch) || ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_');
|
||||
}
|
||||
|
||||
bool isWhitespaceChar(char ch) {
|
||||
return (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t');
|
||||
}
|
||||
|
||||
Token invalidToken() {
|
||||
return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) {
|
||||
}
|
||||
|
||||
BasicTokenizedFile::BasicTokenizedFile(const QByteArray &content, const QString &filepath) : reader_(content, filepath) {
|
||||
}
|
||||
|
||||
bool BasicTokenizedFile::putBack() {
|
||||
if (currentToken_ > 0) {
|
||||
--currentToken_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Token BasicTokenizedFile::getAnyToken() {
|
||||
if (currentToken_ >= tokens_.size()) {
|
||||
if (readToken() == Type::Invalid) {
|
||||
return invalidToken();
|
||||
}
|
||||
}
|
||||
return tokens_.at(currentToken_++);
|
||||
}
|
||||
|
||||
Token BasicTokenizedFile::getToken(Type typeCondition) {
|
||||
if (auto token = getAnyToken()) {
|
||||
if (token.type == typeCondition) {
|
||||
return token;
|
||||
}
|
||||
putBack();
|
||||
}
|
||||
return invalidToken();
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::readToken() {
|
||||
auto result = readOneToken(StartWithWhitespace::Allow);
|
||||
|
||||
// Try to read double token.
|
||||
if (result == Type::Int) {
|
||||
if (readOneToken(StartWithWhitespace::Deny) == Type::Dot) {
|
||||
// We got int and dot, so it is double already.
|
||||
result = uniteLastTokens(Type::Double);
|
||||
|
||||
// Try to read one more int (after dot).
|
||||
if (readOneToken(StartWithWhitespace::Deny) == Type::Int) {
|
||||
result = uniteLastTokens(Type::Double);
|
||||
}
|
||||
}
|
||||
} else if (result == Type::Dot) {
|
||||
if (readOneToken(StartWithWhitespace::Deny) == Type::Int) {
|
||||
//We got dot and int, so it is double.
|
||||
result = uniteLastTokens(Type::Double);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::readOneToken(StartWithWhitespace condition) {
|
||||
skipWhitespaces();
|
||||
if (tokenStartWhitespace_ && condition == StartWithWhitespace::Deny) {
|
||||
return Type::Invalid;
|
||||
}
|
||||
if (reader_.atEnd()) {
|
||||
return Type::Invalid;
|
||||
}
|
||||
|
||||
auto ch = reader_.currentChar();
|
||||
if (ch == '"') {
|
||||
return readString();
|
||||
} else if (isNameChar(ch)) {
|
||||
return readNameOrNumber();
|
||||
}
|
||||
return readSingleLetter();
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::saveToken(Type type, const QString &value) {
|
||||
ConstUtf8String original = { tokenStart_, reader_.currentPtr() };
|
||||
tokens_.push_back({ type, value, original, tokenStartWhitespace_ });
|
||||
return type;
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::uniteLastTokens(Type type) {
|
||||
auto size = tokens_.size();
|
||||
if (size < 2) {
|
||||
return Type::Invalid;
|
||||
}
|
||||
|
||||
auto &token(tokens_[size - 2]);
|
||||
auto originalFrom = token.original.data();
|
||||
auto originalTill = tokens_.back().original.end();
|
||||
token.type = type;
|
||||
token.original = { originalFrom, originalTill };
|
||||
token.value += tokens_.back().value;
|
||||
tokens_.pop_back();
|
||||
return type;
|
||||
}
|
||||
|
||||
QString BasicTokenizedFile::getCurrentLineComment() {
|
||||
if (lineNumber_ > singleLineComments_.size()) {
|
||||
reader_.logError(kErrorInternal, lineNumber_) << "internal tokenizer error (line number larger than comments list size).";
|
||||
failed_ = true;
|
||||
return QString();
|
||||
}
|
||||
auto commentBytes = singleLineComments_[lineNumber_ - 1].mid(2); // Skip "//"
|
||||
CheckedUtf8String comment(commentBytes);
|
||||
if (!comment.isValid()) {
|
||||
reader_.logError(kErrorIncorrectUtf8String, lineNumber_) << "incorrect UTF-8 string in the comment.";
|
||||
failed_ = true;
|
||||
return QString();
|
||||
}
|
||||
return comment.toString().trimmed();
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::readNameOrNumber() {
|
||||
while (!reader_.atEnd()) {
|
||||
if (!isDigitChar(reader_.currentChar())) {
|
||||
break;
|
||||
}
|
||||
reader_.skipChar();
|
||||
}
|
||||
bool onlyDigits = true;
|
||||
while (!reader_.atEnd()) {
|
||||
if (!isNameChar(reader_.currentChar())) {
|
||||
break;
|
||||
}
|
||||
onlyDigits = false;
|
||||
reader_.skipChar();
|
||||
}
|
||||
return saveToken(onlyDigits ? Type::Int : Type::Name);
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::readString() {
|
||||
reader_.skipChar();
|
||||
auto offset = reader_.currentPtr();
|
||||
|
||||
QByteArray value;
|
||||
while (!reader_.atEnd()) {
|
||||
auto ch = reader_.currentChar();
|
||||
if (ch == '"') {
|
||||
if (reader_.currentPtr() > offset) {
|
||||
value.append(offset, reader_.currentPtr() - offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ch == '\n') {
|
||||
reader_.logError(kErrorUnterminatedStringLiteral, lineNumber_) << "unterminated string literal.";
|
||||
failed_ = true;
|
||||
return Type::Invalid;
|
||||
}
|
||||
if (ch == '\\') {
|
||||
if (reader_.currentPtr() > offset) {
|
||||
value.append(offset, reader_.currentPtr() - offset);
|
||||
}
|
||||
reader_.skipChar();
|
||||
ch = reader_.currentChar();
|
||||
if (reader_.atEnd() || ch == '\n') {
|
||||
reader_.logError(kErrorUnterminatedStringLiteral, lineNumber_) << "unterminated string literal.";
|
||||
failed_ = true;
|
||||
return Type::Invalid;
|
||||
}
|
||||
offset = reader_.currentPtr() + 1;
|
||||
if (ch == 'n') {
|
||||
value.append('\n');
|
||||
} else if (ch == 't') {
|
||||
value.append('\t');
|
||||
} else if (ch == '"') {
|
||||
value.append('"');
|
||||
} else if (ch == '\\') {
|
||||
value.append('\\');
|
||||
}
|
||||
}
|
||||
reader_.skipChar();
|
||||
}
|
||||
if (reader_.atEnd()) {
|
||||
reader_.logError(kErrorUnterminatedStringLiteral, lineNumber_) << "unterminated string literal.";
|
||||
failed_ = true;
|
||||
return Type::Invalid;
|
||||
}
|
||||
CheckedUtf8String checked(value);
|
||||
if (!checked.isValid()) {
|
||||
reader_.logError(kErrorIncorrectUtf8String, lineNumber_) << "incorrect UTF-8 string literal.";
|
||||
failed_ = true;
|
||||
return Type::Invalid;
|
||||
}
|
||||
reader_.skipChar();
|
||||
return saveToken(Type::String, checked.toString());
|
||||
}
|
||||
|
||||
Type BasicTokenizedFile::readSingleLetter() {
|
||||
auto type = singleLetterTokens_.value(reader_.currentChar(), Type::Invalid);
|
||||
if (type == Type::Invalid) {
|
||||
reader_.logError(kErrorIncorrectToken, lineNumber_) << "incorrect token '" << reader_.currentChar() << "'";
|
||||
return Type::Invalid;
|
||||
}
|
||||
|
||||
reader_.skipChar();
|
||||
return saveToken(type);
|
||||
}
|
||||
|
||||
void BasicTokenizedFile::skipWhitespaces() {
|
||||
if (reader_.atEnd()) return;
|
||||
|
||||
auto ch = reader_.currentChar();
|
||||
tokenStartWhitespace_ = isWhitespaceChar(ch);
|
||||
if (tokenStartWhitespace_) {
|
||||
do {
|
||||
if (ch == '\n') {
|
||||
++lineNumber_;
|
||||
}
|
||||
reader_.skipChar();
|
||||
ch = reader_.currentChar();
|
||||
} while (!reader_.atEnd() && isWhitespaceChar(ch));
|
||||
}
|
||||
tokenStart_ = reader_.currentPtr();
|
||||
}
|
||||
|
||||
LogStream operator<<(LogStream &&stream, BasicTokenizedFile::Token::Type type) {
|
||||
const char *value = "'invalid'";
|
||||
switch (type) {
|
||||
case Type::Invalid: break;
|
||||
case Type::Int: value = "'int'"; break;
|
||||
case Type::Double: value = "'double'"; break;
|
||||
case Type::String: value = "'string'"; break;
|
||||
case Type::LeftParenthesis: value = "'('"; break;
|
||||
case Type::RightParenthesis: value = "')'"; break;
|
||||
case Type::LeftBrace: value = "'{'"; break;
|
||||
case Type::RightBrace: value = "'}'"; break;
|
||||
case Type::LeftBracket: value = "'['"; break;
|
||||
case Type::RightBracket: value = "']'"; break;
|
||||
case Type::Colon: value = "':'"; break;
|
||||
case Type::Semicolon: value = "';'"; break;
|
||||
case Type::Comma: value = "','"; break;
|
||||
case Type::Dot: value = "'.'"; break;
|
||||
case Type::Number: value = "'#'"; break;
|
||||
case Type::Plus: value = "'+'"; break;
|
||||
case Type::Minus: value = "'-'"; break;
|
||||
case Type::Equals: value = "'='"; break;
|
||||
case Type::Name: value = "'identifier'"; break;
|
||||
}
|
||||
return std::forward<LogStream>(stream) << value;
|
||||
}
|
||||
|
||||
LogStream BasicTokenizedFile::logError(int code) const {
|
||||
return reader_.logError(code, lineNumber_);
|
||||
}
|
||||
|
||||
LogStream BasicTokenizedFile::logErrorUnexpectedToken() const {
|
||||
if (currentToken_ < tokens_.size()) {
|
||||
auto token = tokens_.at(currentToken_).original.toStdString();
|
||||
return logError(kErrorUnexpectedToken) << "unexpected token '" << token << "', expected ";
|
||||
}
|
||||
return logError(kErrorUnexpectedToken) << "unexpected token, expected ";
|
||||
}
|
||||
|
||||
BasicTokenizedFile::~BasicTokenizedFile() = default;
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
155
Telegram/codegen/codegen/common/basic_tokenized_file.h
Normal file
155
Telegram/codegen/codegen/common/basic_tokenized_file.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QList>
|
||||
|
||||
#include "codegen/common/const_utf8_string.h"
|
||||
#include "codegen/common/clean_file_reader.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
class LogStream;
|
||||
|
||||
// Interface for reading a cleaned from comments file by basic tokens.
|
||||
class BasicTokenizedFile {
|
||||
public:
|
||||
explicit BasicTokenizedFile(const QString &filepath);
|
||||
explicit BasicTokenizedFile(const QByteArray &content, const QString &filepath = QString());
|
||||
BasicTokenizedFile(const BasicTokenizedFile &other) = delete;
|
||||
BasicTokenizedFile &operator=(const BasicTokenizedFile &other) = delete;
|
||||
|
||||
struct Token {
|
||||
// String - utf8 string converted to QString.
|
||||
enum class Type {
|
||||
Invalid = 0,
|
||||
Int,
|
||||
Double,
|
||||
String,
|
||||
LeftParenthesis,
|
||||
RightParenthesis,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
Colon,
|
||||
Semicolon,
|
||||
Comma,
|
||||
Dot,
|
||||
Number,
|
||||
Plus,
|
||||
Minus,
|
||||
Equals,
|
||||
And,
|
||||
Or,
|
||||
Name, // [0-9a-zA-Z_]+ with at least one letter.
|
||||
};
|
||||
Type type;
|
||||
QString value;
|
||||
ConstUtf8String original;
|
||||
bool hasLeftWhitespace;
|
||||
|
||||
explicit operator bool() const {
|
||||
return (type != Type::Invalid);
|
||||
}
|
||||
};
|
||||
|
||||
bool read() {
|
||||
if (reader_.read()) {
|
||||
singleLineComments_ = reader_.singleLineComments();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool atEnd() const {
|
||||
return reader_.atEnd();
|
||||
}
|
||||
|
||||
Token getAnyToken();
|
||||
Token getToken(Token::Type typeCondition);
|
||||
bool putBack();
|
||||
bool failed() const {
|
||||
return failed_;
|
||||
}
|
||||
|
||||
QString getCurrentLineComment();
|
||||
|
||||
// Log error to std::cerr with 'code' at the current position in file.
|
||||
LogStream logError(int code) const;
|
||||
LogStream logErrorUnexpectedToken() const;
|
||||
|
||||
~BasicTokenizedFile();
|
||||
|
||||
private:
|
||||
using Type = Token::Type;
|
||||
|
||||
void skipWhitespaces();
|
||||
|
||||
// Reads a token, including complex tokens, like double numbers.
|
||||
Type readToken();
|
||||
|
||||
// Read exactly one token, applying condition on the whitespaces.
|
||||
enum class StartWithWhitespace {
|
||||
Allow,
|
||||
Deny,
|
||||
};
|
||||
Type readOneToken(StartWithWhitespace condition);
|
||||
|
||||
// helpers
|
||||
Type readNameOrNumber();
|
||||
Type readString();
|
||||
Type readSingleLetter();
|
||||
|
||||
Type saveToken(Type type, const QString &value = QString());
|
||||
Type uniteLastTokens(Type type);
|
||||
|
||||
CleanFileReader reader_;
|
||||
QList<Token> tokens_;
|
||||
int currentToken_ = 0;
|
||||
int lineNumber_ = 1;
|
||||
bool failed_ = false;
|
||||
QVector<QByteArray> singleLineComments_;
|
||||
|
||||
// Where the last (currently read) token has started.
|
||||
const char *tokenStart_ = nullptr;
|
||||
|
||||
// Did the last (currently read) token start with a whitespace.
|
||||
bool tokenStartWhitespace_ = false;
|
||||
|
||||
const QMap<char, Type> singleLetterTokens_ = {
|
||||
{ '(', Type::LeftParenthesis },
|
||||
{ ')', Type::RightParenthesis },
|
||||
{ '{', Type::LeftBrace },
|
||||
{ '}', Type::RightBrace },
|
||||
{ '[', Type::LeftBracket },
|
||||
{ ']', Type::RightBracket },
|
||||
{ ':', Type::Colon },
|
||||
{ ';', Type::Semicolon },
|
||||
{ ',', Type::Comma },
|
||||
{ '.', Type::Dot },
|
||||
{ '#', Type::Number },
|
||||
{ '+', Type::Plus },
|
||||
{ '-', Type::Minus },
|
||||
{ '=', Type::Equals },
|
||||
{ '&', Type::And },
|
||||
{ '|', Type::Or },
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
LogStream operator<<(LogStream &&stream, BasicTokenizedFile::Token::Type type);
|
||||
template <>
|
||||
LogStream operator<< <BasicTokenizedFile::Token::Type>(LogStream &&stream, BasicTokenizedFile::Token::Type &&value) = delete;
|
||||
template <>
|
||||
LogStream operator<< <const BasicTokenizedFile::Token::Type&>(LogStream &&stream, const BasicTokenizedFile::Token::Type &value) = delete;
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
35
Telegram/codegen/codegen/common/checked_utf8_string.cpp
Normal file
35
Telegram/codegen/codegen/common/checked_utf8_string.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/common/checked_utf8_string.h"
|
||||
|
||||
#include "codegen/common/const_utf8_string.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
CheckedUtf8String::CheckedUtf8String(const char *string, int size) {
|
||||
if (size < 0) {
|
||||
size = strlen(string);
|
||||
}
|
||||
if (!size) { // Valid empty string
|
||||
return;
|
||||
}
|
||||
|
||||
string_ = QString::fromUtf8(string, size);
|
||||
if (string_.contains(QChar::ReplacementCharacter)) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
CheckedUtf8String::CheckedUtf8String(const QByteArray &string) : CheckedUtf8String(string.constData(), string.size()) {
|
||||
}
|
||||
|
||||
CheckedUtf8String::CheckedUtf8String(const ConstUtf8String &string) : CheckedUtf8String(string.data(), string.size()) {
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
43
Telegram/codegen/codegen/common/checked_utf8_string.h
Normal file
43
Telegram/codegen/codegen/common/checked_utf8_string.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
class QByteArray;
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
class ConstUtf8String;
|
||||
|
||||
// Parses a char sequence to a QString using UTF-8 codec.
|
||||
// You can check for invalid UTF-8 sequence by isValid() method.
|
||||
class CheckedUtf8String {
|
||||
public:
|
||||
CheckedUtf8String(const CheckedUtf8String &other) = default;
|
||||
CheckedUtf8String &operator=(const CheckedUtf8String &other) = default;
|
||||
|
||||
explicit CheckedUtf8String(const char *string, int size = -1);
|
||||
explicit CheckedUtf8String(const QByteArray &string);
|
||||
explicit CheckedUtf8String(const ConstUtf8String &string);
|
||||
|
||||
bool isValid() const {
|
||||
return valid_;
|
||||
}
|
||||
const QString &toString() const {
|
||||
return string_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString string_;
|
||||
bool valid_ = true;
|
||||
|
||||
};
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
172
Telegram/codegen/codegen/common/clean_file.cpp
Normal file
172
Telegram/codegen/codegen/common/clean_file.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/common/clean_file.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include "codegen/common/logging.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
namespace {
|
||||
|
||||
bool readFile(const QString &filepath, QByteArray *outResult) {
|
||||
QFile f(filepath);
|
||||
if (!f.exists()) {
|
||||
logError(kErrorFileNotFound, filepath) << ": error: file does not exist.";
|
||||
return false;
|
||||
}
|
||||
auto limit = CleanFile::MaxSize;
|
||||
if (f.size() > limit) {
|
||||
logError(kErrorFileTooLarge, filepath) << "' is too large, size=" << f.size() << " > maxsize=" << limit;
|
||||
return false;
|
||||
}
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
logError(kErrorFileNotOpened, filepath) << "' for read.";
|
||||
return false;
|
||||
}
|
||||
*outResult = f.readAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
CleanFile::CleanFile(const QString &filepath)
|
||||
: filepath_(filepath)
|
||||
, read_(true) {
|
||||
}
|
||||
|
||||
CleanFile::CleanFile(const QByteArray &content, const QString &filepath)
|
||||
: filepath_(filepath)
|
||||
, content_(content)
|
||||
, read_(false) {
|
||||
}
|
||||
|
||||
bool CleanFile::read() {
|
||||
if (read_) {
|
||||
if (!readFile(filepath_, &content_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
filepath_ = QFileInfo(filepath_).absoluteFilePath();
|
||||
|
||||
enum class InsideComment {
|
||||
None,
|
||||
SingleLine,
|
||||
MultiLine,
|
||||
};
|
||||
auto insideComment = InsideComment::None;
|
||||
bool insideString = false;
|
||||
|
||||
const char *begin = content_.cbegin(), *end = content_.cend(), *offset = begin;
|
||||
auto feedContent = [this, &offset, end](const char *ch) {
|
||||
if (ch > offset) {
|
||||
if (result_.isEmpty()) result_.reserve(end - offset - 2);
|
||||
result_.append(offset, ch - offset);
|
||||
offset = ch;
|
||||
}
|
||||
};
|
||||
|
||||
auto lineNumber = 0;
|
||||
auto feedComment = [this, &offset, end, &lineNumber](const char *ch, bool save = false) {
|
||||
if (ch > offset) {
|
||||
if (save) {
|
||||
singleLineComments_.resize(lineNumber + 1);
|
||||
singleLineComments_[lineNumber] = QByteArray(offset, ch - offset);
|
||||
}
|
||||
if (result_.isEmpty()) {
|
||||
result_.reserve(end - offset - 2);
|
||||
}
|
||||
result_.append(' ');
|
||||
offset = ch;
|
||||
}
|
||||
};
|
||||
for (const char *ch = offset; ch != end;) {
|
||||
char currentChar = *ch;
|
||||
char nextChar = (ch + 1 == end) ? 0 : *(ch + 1);
|
||||
|
||||
if (insideComment == InsideComment::None && currentChar == '"') {
|
||||
bool escaped = ((ch > begin) && *(ch - 1) == '\\') && ((ch - 1 < begin) || *(ch - 2) != '\\');
|
||||
if (!escaped) {
|
||||
insideString = !insideString;
|
||||
}
|
||||
}
|
||||
if (insideString) {
|
||||
if (currentChar == '\n') {
|
||||
++lineNumber;
|
||||
}
|
||||
++ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '/') {
|
||||
feedContent(ch);
|
||||
insideComment = InsideComment::SingleLine;
|
||||
ch += 2;
|
||||
} else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') {
|
||||
feedComment(ch, true);
|
||||
ch += 2;
|
||||
++lineNumber;
|
||||
insideComment = InsideComment::None;
|
||||
} else if (insideComment == InsideComment::SingleLine && currentChar == '\n') {
|
||||
feedComment(ch, true);
|
||||
++ch;
|
||||
++lineNumber;
|
||||
insideComment = InsideComment::None;
|
||||
} else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') {
|
||||
feedContent(ch);
|
||||
ch += 2;
|
||||
insideComment = InsideComment::MultiLine;
|
||||
} else if (insideComment == InsideComment::MultiLine && currentChar == '*' && nextChar == '/') {
|
||||
ch += 2;
|
||||
feedComment(ch);
|
||||
insideComment = InsideComment::None;
|
||||
} else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') {
|
||||
feedComment(ch);
|
||||
ch += 2;
|
||||
++lineNumber;
|
||||
feedContent(ch);
|
||||
} else if (insideComment == InsideComment::MultiLine && currentChar == '\n') {
|
||||
feedComment(ch);
|
||||
++ch;
|
||||
++lineNumber;
|
||||
feedContent(ch);
|
||||
} else {
|
||||
if (currentChar == '\n') {
|
||||
++lineNumber;
|
||||
}
|
||||
++ch;
|
||||
}
|
||||
}
|
||||
singleLineComments_.resize(lineNumber + 1);
|
||||
|
||||
if (insideComment == InsideComment::MultiLine) {
|
||||
common::logError(kErrorUnexpectedEndOfFile, filepath_);
|
||||
return false;
|
||||
}
|
||||
if (insideComment == InsideComment::None && end > offset) {
|
||||
if (result_.isEmpty()) {
|
||||
result_ = content_;
|
||||
} else {
|
||||
result_.append(offset, end - offset);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<QByteArray> CleanFile::singleLineComments() const {
|
||||
return singleLineComments_;
|
||||
}
|
||||
|
||||
LogStream CleanFile::logError(int code, int line) const {
|
||||
return common::logError(code, filepath_, line);
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
51
Telegram/codegen/codegen/common/clean_file.h
Normal file
51
Telegram/codegen/codegen/common/clean_file.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#include "codegen/common/logging.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
// Reads a file removing all C-style comments.
|
||||
class CleanFile {
|
||||
public:
|
||||
explicit CleanFile(const QString &filepath);
|
||||
explicit CleanFile(const QByteArray &content, const QString &filepath = QString());
|
||||
CleanFile(const CleanFile &other) = delete;
|
||||
CleanFile &operator=(const CleanFile &other) = delete;
|
||||
|
||||
bool read();
|
||||
QVector<QByteArray> singleLineComments() const;
|
||||
|
||||
const char *data() const {
|
||||
return result_.constData();
|
||||
}
|
||||
const char *end() const {
|
||||
return result_.constEnd();
|
||||
}
|
||||
|
||||
static constexpr int MaxSize = 10 * 1024 * 1024;
|
||||
|
||||
// Log error to std::cerr with 'code' at line number 'line' in data().
|
||||
LogStream logError(int code, int line) const;
|
||||
|
||||
private:
|
||||
QString filepath_;
|
||||
QByteArray content_, result_;
|
||||
bool read_;
|
||||
|
||||
QVector<QByteArray> singleLineComments_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
70
Telegram/codegen/codegen/common/clean_file_reader.h
Normal file
70
Telegram/codegen/codegen/common/clean_file_reader.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "codegen/common/clean_file.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
// Wrapper allows you to read forward the CleanFile without overflow checks.
|
||||
class CleanFileReader {
|
||||
public:
|
||||
explicit CleanFileReader(const QString &filepath) : file_(filepath) {
|
||||
}
|
||||
explicit CleanFileReader(const QByteArray &content, const QString &filepath = QString()) : file_(content, filepath) {
|
||||
}
|
||||
|
||||
bool read() {
|
||||
if (!file_.read()) {
|
||||
return false;
|
||||
}
|
||||
pos_ = file_.data();
|
||||
end_ = file_.end();
|
||||
return true;
|
||||
}
|
||||
bool atEnd() const {
|
||||
return (pos_ == end_);
|
||||
}
|
||||
char currentChar() const {
|
||||
return atEnd() ? 0 : *pos_;
|
||||
}
|
||||
bool skipChar() {
|
||||
if (atEnd()) {
|
||||
return false;
|
||||
}
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
const char *currentPtr() const {
|
||||
return pos_;
|
||||
}
|
||||
int charsLeft() const {
|
||||
return (end_ - pos_);
|
||||
}
|
||||
|
||||
QVector<QByteArray> singleLineComments() const {
|
||||
return file_.singleLineComments();
|
||||
}
|
||||
|
||||
// Log error to std::cerr with 'code' at line number 'line' in data().
|
||||
LogStream logError(int code, int line) const {
|
||||
return std::forward<LogStream>(file_.logError(code, line));
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
CleanFile file_;
|
||||
const char *pos_ = nullptr;
|
||||
const char *end_ = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
61
Telegram/codegen/codegen/common/const_utf8_string.h
Normal file
61
Telegram/codegen/codegen/common/const_utf8_string.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
// This is a simple wrapper around (const char*, size).
|
||||
// Not null-terminated! It does not hold any ownership.
|
||||
class ConstUtf8String {
|
||||
public:
|
||||
explicit ConstUtf8String(const char *string, int size = -1) : string_(string) {
|
||||
if (size < 0) {
|
||||
size = strlen(string);
|
||||
}
|
||||
size_ = size;
|
||||
}
|
||||
ConstUtf8String(const char *string, const char *end) : ConstUtf8String(string, end - string) {
|
||||
}
|
||||
|
||||
QByteArray toByteArray() const {
|
||||
return QByteArray(string_, size_);
|
||||
}
|
||||
std::string toStdString() const {
|
||||
return std::string(string_, size_);
|
||||
}
|
||||
QString toStringUnchecked() const {
|
||||
return QString::fromUtf8(string_, size_);
|
||||
}
|
||||
bool empty() const {
|
||||
return size_ == 0;
|
||||
}
|
||||
const char *data() const {
|
||||
return string_;
|
||||
}
|
||||
int size() const {
|
||||
return size_;
|
||||
}
|
||||
const char *end() const {
|
||||
return data() + size();
|
||||
}
|
||||
ConstUtf8String mid(int pos, int size = -1) {
|
||||
return ConstUtf8String(string_ + pos, std::max(std::min(size, size_ - pos), 0));
|
||||
}
|
||||
|
||||
private:
|
||||
const char *string_;
|
||||
int size_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
114
Telegram/codegen/codegen/common/cpp_file.cpp
Normal file
114
Telegram/codegen/codegen/common/cpp_file.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/common/cpp_file.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
namespace {
|
||||
|
||||
void writeLicense(QTextStream &stream, const ProjectInfo &project) {
|
||||
stream << "\
|
||||
// WARNING! All changes made in this file will be lost!\n\
|
||||
// Created from '" << project.source << "' by '" << project.name << "'\n\
|
||||
//\n\
|
||||
// This file is part of Desktop App Toolkit,\n\
|
||||
// a set of libraries for developing nice desktop applications.\n\
|
||||
//\n\
|
||||
// For license and copyright information please follow this link:\n\
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL\n\
|
||||
//\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CppFile::CppFile(const QString &path, const ProjectInfo &project)
|
||||
: stream_(&content_)
|
||||
, forceReGenerate_(project.forceReGenerate) {
|
||||
bool cpp = path.endsWith(".cpp", Qt::CaseInsensitive);
|
||||
|
||||
QFileInfo info(path);
|
||||
info.dir().mkpath(".");
|
||||
filepath_ = info.absoluteFilePath();
|
||||
|
||||
writeLicense(stream_, project);
|
||||
if (cpp) {
|
||||
include(info.baseName() + ".h").newline();
|
||||
} else {
|
||||
stream() << "#pragma once";
|
||||
newline().newline();
|
||||
}
|
||||
}
|
||||
|
||||
CppFile &CppFile::include(const QString &header) {
|
||||
stream() << "#include \"" << header << "\"";
|
||||
return newline();
|
||||
}
|
||||
|
||||
CppFile &CppFile::includeFromLibrary(const QString &header) {
|
||||
stream() << "#include <" << header << ">";
|
||||
return newline();
|
||||
}
|
||||
|
||||
CppFile &CppFile::pushNamespace(const QString &name) {
|
||||
namespaces_.push_back(name);
|
||||
|
||||
stream() << "namespace";
|
||||
if (!name.isEmpty()) {
|
||||
stream() << ' ' << name;
|
||||
}
|
||||
stream() << " {";
|
||||
return newline();
|
||||
}
|
||||
|
||||
CppFile &CppFile::popNamespace() {
|
||||
if (namespaces_.isEmpty()) {
|
||||
return *this;
|
||||
}
|
||||
auto name = namespaces_.back();
|
||||
namespaces_.pop_back();
|
||||
|
||||
stream() << "} // namespace";
|
||||
if (!name.isEmpty()) {
|
||||
stream() << ' ' << name;
|
||||
}
|
||||
return newline();
|
||||
}
|
||||
|
||||
bool CppFile::finalize() {
|
||||
while (!namespaces_.isEmpty()) {
|
||||
popNamespace();
|
||||
}
|
||||
stream_.flush();
|
||||
|
||||
QFile file(filepath_);
|
||||
if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) {
|
||||
if (file.readAll() == content_) {
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
if (file.write(content_) != content_.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TouchTimestamp(const QString &basepath) {
|
||||
auto file = QFile(basepath + ".timestamp");
|
||||
return file.open(QIODevice::WriteOnly) && (file.write("1", 1) == 1);
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
58
Telegram/codegen/codegen/common/cpp_file.h
Normal file
58
Telegram/codegen/codegen/common/cpp_file.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVector>
|
||||
#include <QtCore/QTextStream>
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
struct ProjectInfo {
|
||||
QString name;
|
||||
QString source;
|
||||
bool forceReGenerate;
|
||||
};
|
||||
|
||||
// Creates a file with license header and codegen warning.
|
||||
class CppFile {
|
||||
public:
|
||||
// If "basepath" is empty the folder containing "path" will be chosen.
|
||||
// File ending with .cpp will be treated as source, otherwise like header.
|
||||
CppFile(const QString &path, const ProjectInfo &project);
|
||||
|
||||
QTextStream &stream() {
|
||||
return stream_;
|
||||
}
|
||||
|
||||
CppFile &newline() {
|
||||
stream() << "\n";
|
||||
return *this;
|
||||
}
|
||||
CppFile &include(const QString &header);
|
||||
CppFile &includeFromLibrary(const QString &header);
|
||||
|
||||
// Empty name adds anonymous namespace.
|
||||
CppFile &pushNamespace(const QString &name = QString());
|
||||
CppFile &popNamespace();
|
||||
|
||||
bool finalize();
|
||||
|
||||
private:
|
||||
QString filepath_;
|
||||
QByteArray content_;
|
||||
QTextStream stream_;
|
||||
QVector<QString> namespaces_;
|
||||
bool forceReGenerate_;
|
||||
|
||||
};
|
||||
|
||||
bool TouchTimestamp(const QString &basepath);
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
34
Telegram/codegen/codegen/common/logging.cpp
Normal file
34
Telegram/codegen/codegen/common/logging.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/common/logging.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
namespace {
|
||||
|
||||
QString WorkingPath = ".";
|
||||
|
||||
} // namespace
|
||||
|
||||
LogStream logError(int code, const QString &filepath, int line) {
|
||||
std::cerr << filepath.toStdString();
|
||||
if (line > 0) {
|
||||
std::cerr << '(' << line << ')';
|
||||
}
|
||||
std::cerr << ": error " << code << ": ";
|
||||
return LogStream(std::cerr);
|
||||
}
|
||||
|
||||
void logSetWorkingPath(const QString &workingpath) {
|
||||
WorkingPath = workingpath;
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
66
Telegram/codegen/codegen/common/logging.h
Normal file
66
Telegram/codegen/codegen/common/logging.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <iostream>
|
||||
|
||||
namespace codegen {
|
||||
namespace common {
|
||||
|
||||
// Common error codes.
|
||||
constexpr int kErrorFileNotFound = 101;
|
||||
constexpr int kErrorFileTooLarge = 102;
|
||||
constexpr int kErrorFileNotOpened = 103;
|
||||
constexpr int kErrorUnexpectedEndOfFile = 104;
|
||||
|
||||
// Wrapper around std::ostream that adds '\n' to the end of the logging line.
|
||||
class LogStream {
|
||||
public:
|
||||
enum NullType {
|
||||
Null,
|
||||
};
|
||||
explicit LogStream(NullType) : final_(false) {
|
||||
}
|
||||
explicit LogStream(std::ostream &stream) : stream_(&stream) {
|
||||
}
|
||||
LogStream(LogStream &&other) : stream_(other.stream_), final_(other.final_) {
|
||||
other.final_ = false;
|
||||
}
|
||||
std::ostream *stream() const {
|
||||
return stream_;
|
||||
}
|
||||
~LogStream() {
|
||||
if (final_) {
|
||||
*stream_ << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream *stream_ = nullptr;
|
||||
bool final_ = true;
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
LogStream operator<<(LogStream &&stream, T &&value) {
|
||||
if (auto ostream = stream.stream()) {
|
||||
*ostream << std::forward<T>(value);
|
||||
}
|
||||
return std::forward<LogStream>(stream);
|
||||
}
|
||||
|
||||
// Outputs file name, line number and error code to std::err. Usage:
|
||||
// logError(kErrorFileTooLarge, filepath) << "file too large, size=" << size;
|
||||
LogStream logError(int code, const QString &filepath, int line = 0);
|
||||
|
||||
void logSetWorkingPath(const QString &workingpath);
|
||||
|
||||
static constexpr int kErrorInternal = 666;
|
||||
|
||||
} // namespace common
|
||||
} // namespace codegen
|
||||
34
Telegram/codegen/codegen/emoji/CMakeLists.txt
Normal file
34
Telegram/codegen/codegen/emoji/CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
add_executable(codegen_emoji)
|
||||
init_target(codegen_emoji "(codegen)")
|
||||
|
||||
get_filename_component(src_loc ../.. REALPATH)
|
||||
|
||||
nice_target_sources(codegen_emoji ${src_loc}
|
||||
PRIVATE
|
||||
codegen/emoji/data.cpp
|
||||
codegen/emoji/data.h
|
||||
codegen/emoji/data_old.cpp
|
||||
codegen/emoji/data_old.h
|
||||
codegen/emoji/data_read.cpp
|
||||
codegen/emoji/data_read.h
|
||||
codegen/emoji/generator.cpp
|
||||
codegen/emoji/generator.h
|
||||
codegen/emoji/main.cpp
|
||||
codegen/emoji/options.cpp
|
||||
codegen/emoji/options.h
|
||||
codegen/emoji/replaces.cpp
|
||||
codegen/emoji/replaces.h
|
||||
)
|
||||
|
||||
target_include_directories(codegen_emoji
|
||||
PUBLIC
|
||||
${src_loc}
|
||||
)
|
||||
|
||||
target_link_libraries(codegen_emoji
|
||||
PUBLIC
|
||||
desktop-app::lib_base
|
||||
desktop-app::codegen_common
|
||||
desktop-app::external_qt_static_plugins
|
||||
desktop-app::external_qt
|
||||
)
|
||||
759
Telegram/codegen/codegen/emoji/data.cpp
Normal file
759
Telegram/codegen/codegen/emoji/data.cpp
Normal file
@@ -0,0 +1,759 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/emoji/data.h"
|
||||
|
||||
#include "codegen/emoji/data_old.h"
|
||||
#include "codegen/emoji/data_read.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
namespace {
|
||||
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::set;
|
||||
using std::find;
|
||||
using std::make_pair;
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
// copied from emoji_box.cpp
|
||||
struct Replace {
|
||||
InputId code;
|
||||
const char *replace;
|
||||
};
|
||||
|
||||
Replace Replaces[] = {
|
||||
{ { 0xD83DDE0AU }, ":-)" },
|
||||
{ { 0xD83DDE0DU }, "8-)" },
|
||||
{ { 0x2764U }, "<3" },
|
||||
// { { 0xD83DDC8BU }, ":kiss:" },
|
||||
// { { 0xD83DDE01U }, ":grin:" },
|
||||
// { { 0xD83DDE02U }, ":joy:" },
|
||||
{ { 0xD83DDE1AU }, ":-*" },
|
||||
// { { 0xD83DDE06U }, "xD" }, // Conflicts with typing xDDD...
|
||||
// { { 0xD83DDC4DU }, ":like:" },
|
||||
// { { 0xD83DDC4EU }, ":dislike:" },
|
||||
// { { 0x261DU }, ":up:" },
|
||||
// { { 0x270CU }, ":v:" },
|
||||
// { { 0xD83DDC4CU }, ":ok:" },
|
||||
{ { 0xD83DDE0EU }, "B-)" },
|
||||
{ { 0xD83DDE03U }, ":-D" },
|
||||
{ { 0xD83DDE09U }, ";-)" },
|
||||
{ { 0xD83DDE1CU }, ";-P" },
|
||||
{ { 0xD83DDE0BU }, ":-p" },
|
||||
{ { 0xD83DDE14U }, "3(" },
|
||||
{ { 0xD83DDE1EU }, ":-(" },
|
||||
{ { 0xD83DDE0FU }, ":]" },
|
||||
{ { 0xD83DDE22U }, ":'(" },
|
||||
{ { 0xD83DDE2DU }, ":_(" },
|
||||
{ { 0xD83DDE29U }, ":((" },
|
||||
// { { 0xD83DDE28U }, ":o" }, // Conflicts with typing :ok...
|
||||
{ { 0xD83DDE10U }, ":|" },
|
||||
{ { 0xD83DDE0CU }, "3-)" },
|
||||
{ { 0xD83DDE20U }, ">(" },
|
||||
{ { 0xD83DDE21U }, ">((" },
|
||||
{ { 0xD83DDE07U }, "O:)" },
|
||||
{ { 0xD83DDE30U }, ";o" },
|
||||
{ { 0xD83DDE33U }, "8|" },
|
||||
{ { 0xD83DDE32U }, "8o" },
|
||||
{ { 0xD83DDE37U }, ":X" },
|
||||
{ { 0xD83DDE08U }, "}:)" },
|
||||
};
|
||||
|
||||
InputCategory PostfixRequired = {
|
||||
{ 0x2122U, 0xFE0FU, },
|
||||
{ 0xA9U, 0xFE0FU, },
|
||||
{ 0xAEU, 0xFE0FU, },
|
||||
};
|
||||
|
||||
using ColorId = uint32;
|
||||
ColorId Colors[] = {
|
||||
0xD83CDFFBU,
|
||||
0xD83CDFFCU,
|
||||
0xD83CDFFDU,
|
||||
0xD83CDFFEU,
|
||||
0xD83CDFFFU,
|
||||
};
|
||||
|
||||
constexpr auto ColorMask = 0xD83CDFFBU;
|
||||
|
||||
// Original data has those emoji only with gender symbols.
|
||||
// But they should be displayed as emoji even without gender symbols.
|
||||
// So we map which gender symbol to use for an emoji without one.
|
||||
std::map<InputId, uint32> WithoutGenderAliases = {
|
||||
// { { 0xD83EDD26U, }, 0x2642U },
|
||||
// { { 0xD83EDD37U, }, 0x2640U },
|
||||
// { { 0xD83EDD38U, }, 0x2642U },
|
||||
// { { 0xD83EDD3CU, }, 0x2640U },
|
||||
// { { 0xD83EDD3DU, }, 0x2642U },
|
||||
// { { 0xD83EDD3EU, }, 0x2640U },
|
||||
// { { 0xD83EDD39U, }, 0x2642U },
|
||||
// { { 0xD83EDDB8U, }, 0x2640U },
|
||||
// { { 0xD83EDDB9U, }, 0x2640U },
|
||||
// { { 0xD83EDDD6U, }, 0x2642U },
|
||||
// { { 0xD83EDDD7U, }, 0x2640U },
|
||||
// { { 0xD83EDDD8U, }, 0x2640U },
|
||||
// { { 0xD83EDDD9U, }, 0x2640U },
|
||||
// { { 0xD83EDDDAU, }, 0x2640U },
|
||||
// { { 0xD83EDDDBU, }, 0x2640U },
|
||||
// { { 0xD83EDDDCU, }, 0x2642U },
|
||||
// { { 0xD83EDDDDU, }, 0x2642U },
|
||||
// { { 0xD83EDDDEU, }, 0x2642U },
|
||||
// { { 0xD83EDDDFU, }, 0x2642U },
|
||||
};
|
||||
|
||||
// Some flags are sent as one string, but are rendered as a different too.
|
||||
std::map<InputId, InputId> FlagAliases = {
|
||||
// { { 0xD83CDDE8U, 0xD83CDDF5U, }, { 0xD83CDDEBU, 0xD83CDDF7U, } },
|
||||
// { { 0xD83CDDE7U, 0xD83CDDFBU, }, { 0xD83CDDF3U, 0xD83CDDF4U, } },
|
||||
// { { 0xD83CDDE6U, 0xD83CDDE8U, }, { 0xD83CDDF8U, 0xD83CDDEDU, } },
|
||||
//
|
||||
// // This is different flag, but macOS shows that glyph :(
|
||||
// { { 0xD83CDDE9U, 0xD83CDDECU, }, { 0xD83CDDEEU, 0xD83CDDF4U, } },
|
||||
//
|
||||
// { { 0xD83CDDF9U, 0xD83CDDE6U, }, { 0xD83CDDF8U, 0xD83CDDEDU, } },
|
||||
// { { 0xD83CDDF2U, 0xD83CDDEBU, }, { 0xD83CDDEBU, 0xD83CDDF7U, } },
|
||||
// { { 0xD83CDDEAU, 0xD83CDDE6U, }, { 0xD83CDDEAU, 0xD83CDDF8U, } },
|
||||
{ { 0xD83CDDE8U, 0xD83CDDF5U, }, { 0xD83CDDEBU, 0xD83CDDF7U, } },
|
||||
};
|
||||
|
||||
std::map<Id, std::vector<Id>> Aliases; // original -> list of aliased
|
||||
std::set<Id> AliasesAdded;
|
||||
|
||||
void AddAlias(const Id &original, const Id &aliased) {
|
||||
auto &aliases = Aliases[original];
|
||||
if (std::find(aliases.begin(), aliases.end(), aliased) == aliases.end()) {
|
||||
aliases.push_back(aliased);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr auto kErrorBadData = 401;
|
||||
|
||||
void append(Id &id, uint32 code) {
|
||||
if (auto first = static_cast<uint16>((code >> 16) & 0xFFFFU)) {
|
||||
id.append(QChar(first));
|
||||
}
|
||||
id.append(QChar(static_cast<uint16>(code & 0xFFFFU)));
|
||||
}
|
||||
|
||||
[[nodiscard]] Id BareIdFromInput(const InputId &id) {
|
||||
auto result = Id();
|
||||
for (const auto unicode : id) {
|
||||
if (unicode != kPostfix) {
|
||||
append(result, unicode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] set<Id> FillVariatedIds(const InputData &data) {
|
||||
auto result = set<Id>();
|
||||
for (const auto &row : data.colored) {
|
||||
auto variatedId = Id();
|
||||
if (row.size() < 2) {
|
||||
logDataError() << "colored string should have at least two characters.";
|
||||
return {};
|
||||
}
|
||||
for (auto i = size_t(0), size = row.size(); i != size; ++i) {
|
||||
auto code = row[i];
|
||||
if (i == 1) {
|
||||
if (code != ColorMask) {
|
||||
logDataError() << "color code should appear at index 1.";
|
||||
return {};
|
||||
}
|
||||
} else if (code == ColorMask) {
|
||||
logDataError() << "color code should appear only at index 1.";
|
||||
return {};
|
||||
} else if (code != kPostfix) {
|
||||
append(variatedId, code);
|
||||
}
|
||||
}
|
||||
result.emplace(variatedId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] map<Id, InputId> FillDoubleVariatedIds(const InputData &data) {
|
||||
auto result = map<Id, InputId>();
|
||||
for (const auto &[original, same, different] : data.doubleColored) {
|
||||
auto variatedId = Id();
|
||||
if (original.size() < 1) {
|
||||
logDataError() << "original string should have at least one character.";
|
||||
return {};
|
||||
} else if (same.size() < 2) {
|
||||
logDataError() << "colored string should have at least two characters.";
|
||||
return {};
|
||||
}
|
||||
if (same.size() == 2) {
|
||||
// original: 1
|
||||
// same: original + color
|
||||
if (same[1] != ColorMask) {
|
||||
logDataError() << "color code should appear at index 1.";
|
||||
return {};
|
||||
} else if (same[0] == ColorMask) {
|
||||
logDataError() << "color code should appear only at index 1.";
|
||||
return {};
|
||||
} else if (same[0] == kPostfix) {
|
||||
logDataError() << "postfix in double colored is not supported.";
|
||||
return {};
|
||||
} else {
|
||||
append(variatedId, same[0]);
|
||||
}
|
||||
if (different.size() == 5) {
|
||||
// different: some1 + color1 + sep + some2 + color2
|
||||
if (std::count(different.begin(), different.end(), kJoiner) != 1
|
||||
|| different[2] != kJoiner) {
|
||||
logDataError() << "complex short double colored bad different.";
|
||||
return {};
|
||||
}
|
||||
// add an alias to 'same' in the form ..
|
||||
// .. of 'some1 + color + sep + some2 + color'
|
||||
} else if (different.size() >= 7) {
|
||||
// different: some1 + color1 + sep + some2 + sep + some3 + color2
|
||||
if (std::count(different.begin(), different.end(), kJoiner) != 2) {
|
||||
logDataError() << "complex double colored bad different.";
|
||||
return {};
|
||||
}
|
||||
// add an alias to 'same' in the form ..
|
||||
// .. of 'some1 + color + sep + some2 + sep + some3 + color'
|
||||
} else {
|
||||
logDataError() << "complex double colored unknown different.";
|
||||
return {};
|
||||
}
|
||||
for (const auto color : Colors) {
|
||||
auto copy = same;
|
||||
copy[1] = color;
|
||||
auto sameWithColor = BareIdFromInput(copy);
|
||||
copy = different;
|
||||
for (auto &entry : copy) {
|
||||
if (entry == Colors[0] || entry == Colors[1]) {
|
||||
entry = color;
|
||||
}
|
||||
}
|
||||
auto differentWithColor = BareIdFromInput(copy);
|
||||
AddAlias(sameWithColor, differentWithColor);
|
||||
}
|
||||
} else {
|
||||
// same: some1 + color + sep + some2 + sep + some3 + color
|
||||
// different: some1 + color1 + sep + some2 + sep + some3 + color2
|
||||
auto copy = different;
|
||||
if (copy.size() < 7 || copy[1] != Colors[0] || copy[copy.size() - 1] != Colors[1]) {
|
||||
logDataError() << "complex double colored bad different.";
|
||||
return {};
|
||||
}
|
||||
copy[copy.size() - 1] = Colors[0];
|
||||
if (copy != same) {
|
||||
logDataError() << "complex double colored should colorize all the same.";
|
||||
return {};
|
||||
}
|
||||
if (original.size() == 1) {
|
||||
// original: 1
|
||||
// add an alias to 'same' in the form of 'original + color'
|
||||
for (const auto color : Colors) {
|
||||
auto copy = original;
|
||||
copy.push_back(color);
|
||||
auto originalWithColor = BareIdFromInput(copy);
|
||||
copy = same;
|
||||
for (auto &entry : copy) {
|
||||
if (entry == ColorMask) {
|
||||
entry = color;
|
||||
}
|
||||
}
|
||||
auto sameWithColor = BareIdFromInput(copy);
|
||||
AddAlias(originalWithColor, sameWithColor);
|
||||
}
|
||||
}
|
||||
variatedId = BareIdFromInput(original);
|
||||
}
|
||||
result.emplace(variatedId, different);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] set<Id> FillPostfixRequiredIds() {
|
||||
auto result = set<Id>();
|
||||
for (const auto &row : PostfixRequired) {
|
||||
result.emplace(BareIdFromInput(row));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void appendCategory(
|
||||
Data &result,
|
||||
const InputCategory &category,
|
||||
const set<Id> &variatedIds,
|
||||
const map<Id, InputId> &doubleVariatedIds,
|
||||
const set<Id> &postfixRequiredIds) {
|
||||
result.categories.emplace_back();
|
||||
for (auto &id : category) {
|
||||
auto emoji = Emoji();
|
||||
auto bareId = BareIdFromInput(id);
|
||||
auto from = id.cbegin(), to = id.cend();
|
||||
if (to - from == 2 && *(to - 1) == kPostfix) {
|
||||
emoji.postfixed = true;
|
||||
--to;
|
||||
}
|
||||
for (auto i = from; i != to; ++i) {
|
||||
auto code = *i;
|
||||
if (find(begin(Colors), end(Colors), code) != end(Colors)) {
|
||||
logDataError() << "color code found in a category emoji.";
|
||||
result = Data();
|
||||
return;
|
||||
}
|
||||
append(emoji.id, code);
|
||||
}
|
||||
if (bareId.isEmpty()) {
|
||||
logDataError() << "empty emoji id found.";
|
||||
result = Data();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto addOne = [&](const Id &bareId, Emoji &&emoji) {
|
||||
auto it = result.map.find(bareId);
|
||||
if (it == result.map.cend()) {
|
||||
const auto index = result.list.size();
|
||||
it = result.map.emplace(bareId, index).first;
|
||||
result.list.push_back(std::move(emoji));
|
||||
if (const auto a = Aliases.find(bareId); a != end(Aliases)) {
|
||||
AliasesAdded.emplace(bareId);
|
||||
for (const auto &alias : a->second) {
|
||||
const auto ok = result.map.emplace(alias, index).second;
|
||||
if (!ok) {
|
||||
logDataError() << "some emoji alias already in the map.";
|
||||
result = Data();
|
||||
return result.map.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (postfixRequiredIds.find(bareId) != end(postfixRequiredIds)) {
|
||||
result.postfixRequired.emplace(index);
|
||||
}
|
||||
} else if (result.list[it->second].postfixed != emoji.postfixed) {
|
||||
logDataError() << "same emoji found with different postfixed property.";
|
||||
result = Data();
|
||||
return result.map.end();
|
||||
} else if (result.list[it->second].id != emoji.id) {
|
||||
logDataError() << "same emoji found with different id.";
|
||||
result = Data();
|
||||
return result.map.end();
|
||||
}
|
||||
return it;
|
||||
};
|
||||
|
||||
auto it = addOne(bareId, std::move(emoji));
|
||||
if (it == result.map.end()) {
|
||||
return;
|
||||
}
|
||||
if (variatedIds.find(bareId) != end(variatedIds)) {
|
||||
result.list[it->second].variated = true;
|
||||
|
||||
auto baseId = Id();
|
||||
if (*from == kPostfix) {
|
||||
logDataError() << "bad first symbol in emoji.";
|
||||
result = Data();
|
||||
return;
|
||||
}
|
||||
append(baseId, *from++);
|
||||
// A few uncolored emoji contain two kPostfix.
|
||||
// (:(wo)man_lifting_weights:, :(wo)man_golfing:
|
||||
// :(wo)man_bouncing_ball:, :(wo)man_detective:)
|
||||
// But colored emoji should have only one kPostfix.
|
||||
if (std::count(from, to, kPostfix) == 2 && *from == kPostfix) {
|
||||
from++;
|
||||
}
|
||||
for (auto color : Colors) {
|
||||
auto colored = Emoji();
|
||||
colored.colored = true;
|
||||
colored.id = baseId;
|
||||
append(colored.id, color);
|
||||
auto bareColoredId = colored.id;
|
||||
for (auto i = from; i != to; ++i) {
|
||||
append(colored.id, *i);
|
||||
if (*i != kPostfix) {
|
||||
append(bareColoredId, *i);
|
||||
}
|
||||
}
|
||||
if (addOne(bareColoredId, std::move(colored)) == result.map.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (const auto d = doubleVariatedIds.find(bareId); d != end(doubleVariatedIds)) {
|
||||
//result.list[it->second].doubleVariated = true;
|
||||
|
||||
const auto baseId = bareId;
|
||||
const auto &different = d->second;
|
||||
if (different.size() < 4
|
||||
|| different[1] != Colors[0]
|
||||
|| different[different.size() - 1] != Colors[1]) {
|
||||
logDataError() << "bad data in double-colored emoji.";
|
||||
result = Data();
|
||||
return;
|
||||
}
|
||||
for (auto color1 : Colors) {
|
||||
for (auto color2 : Colors) {
|
||||
auto colored = Emoji();
|
||||
//colored.colored = true;
|
||||
if (color1 == color2 && baseId.size() == 2) {
|
||||
colored.id = baseId;
|
||||
append(colored.id, color1);
|
||||
} else {
|
||||
auto copy = different;
|
||||
copy[1] = color1;
|
||||
copy[copy.size() - 1] = color2;
|
||||
for (const auto code : copy) {
|
||||
append(colored.id, code);
|
||||
}
|
||||
}
|
||||
auto bareColoredId = colored.id.replace(QChar(kPostfix), QString());
|
||||
if (addOne(bareColoredId, std::move(colored)) == result.map.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
result.categories.back().push_back(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void fillReplaces(Data &result) {
|
||||
for (auto &replace : Replaces) {
|
||||
auto id = Id();
|
||||
for (auto code : replace.code) {
|
||||
append(id, code);
|
||||
}
|
||||
auto it = result.map.find(id);
|
||||
if (it == result.map.cend()) {
|
||||
logDataError() << "emoji from replaces not found in the map.";
|
||||
result = Data();
|
||||
return;
|
||||
}
|
||||
result.replaces.insert(make_pair(QString::fromUtf8(replace.replace), it->second));
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckOldInCurrent(
|
||||
const InputData &data,
|
||||
const InputData &old,
|
||||
const std::set<Id> &variatedIds) {
|
||||
const auto genders = { 0x2640U, 0x2642U };
|
||||
const auto addGender = [](const InputId &was, uint32 gender) {
|
||||
auto result = was;
|
||||
result.push_back(0x200DU);
|
||||
result.push_back(gender);
|
||||
result.push_back(kPostfix);
|
||||
return result;
|
||||
};
|
||||
const auto addGenderByIndex = [&](const InputId &was, int index) {
|
||||
return addGender(was, *(begin(genders) + index));
|
||||
};
|
||||
static const auto find = [](
|
||||
const InputCategory &list,
|
||||
const InputId &id) {
|
||||
return (std::find(begin(list), end(list), id) != end(list));
|
||||
};
|
||||
static const auto findInMany = [](
|
||||
const InputData &data,
|
||||
const InputId &id) {
|
||||
for (const auto ¤t : data.categories) {
|
||||
if (find(current, id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return find(data.other, id);
|
||||
};
|
||||
const auto emplaceColoredAlias = [](const InputId &real, const InputId &alias, uint32_t color) {
|
||||
if (real.size() < 2 || alias.size() < 2 || real[1] != Colors[0] || alias[1] != Colors[0]) {
|
||||
return false;
|
||||
}
|
||||
auto key = real;
|
||||
key[1] = color;
|
||||
auto value = alias;
|
||||
value[1] = color;
|
||||
AddAlias(BareIdFromInput(key), BareIdFromInput(value));
|
||||
return true;
|
||||
};
|
||||
auto result = true;
|
||||
for (auto c = begin(old.categories); c != end(old.categories); ++c) {
|
||||
const auto &category = *c;
|
||||
for (auto i = begin(category); i != end(category); ++i) {
|
||||
if (findInMany(data, *i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some emoji were ending with kPostfix and now are not.
|
||||
if (i->back() == kPostfix) {
|
||||
auto other = *i;
|
||||
other.pop_back();
|
||||
if (findInMany(data, other)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Some emoji were not ending with kPostfix and now are.
|
||||
if (i->back() != kPostfix) {
|
||||
auto other = *i;
|
||||
other.push_back(kPostfix);
|
||||
if (findInMany(data, other)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: old emoji '"
|
||||
<< InputIdToString(*i).toStdString()
|
||||
<< "' (category "
|
||||
<< (c - begin(old.categories))
|
||||
<< ", index "
|
||||
<< (i - begin(category))
|
||||
<< ") not found in current.";
|
||||
result = false;
|
||||
continue;
|
||||
|
||||
// Old way - auto add genders. New way - add gender neutral images,
|
||||
// but don't add them to any category in the picker.
|
||||
// // Some emoji were without gender symbol and now have gender symbol.
|
||||
// // Try adding 0x200DU, 0x2640U, 0xFE0FU or 0x200DU, 0x2642U, 0xFE0FU.
|
||||
// const auto otherGenderIndex = [&] {
|
||||
// for (auto g = begin(genders); g != end(genders); ++g) {
|
||||
// auto altered = *i;
|
||||
// altered.push_back(kJoiner);
|
||||
// altered.push_back(*g);
|
||||
// altered.push_back(kPostfix);
|
||||
// if (findInMany(old, altered)) {
|
||||
// return int(g - begin(genders));
|
||||
// }
|
||||
// }
|
||||
// return -1;
|
||||
// }();
|
||||
// if (otherGenderIndex < 0) {
|
||||
// common::logError(kErrorBadData, "input")
|
||||
// << "Bad data: old emoji '"
|
||||
// << InputIdToString(*i).toStdString()
|
||||
// << "' (category "
|
||||
// << (c - begin(old.categories))
|
||||
// << ", index "
|
||||
// << (i - begin(category))
|
||||
// << ") not found in current.";
|
||||
// result = false;
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// const auto genderIndex = (1 - otherGenderIndex);
|
||||
// const auto real = addGenderByIndex(*i, genderIndex);
|
||||
// const auto bare = BareIdFromInput(real);
|
||||
// if (!findInMany(data, real)) {
|
||||
// common::logError(kErrorBadData, "input")
|
||||
// << "Bad data: old emoji '"
|
||||
// << InputIdToString(*i).toStdString()
|
||||
// << "' (category "
|
||||
// << (c - begin(old.categories))
|
||||
// << ", index "
|
||||
// << (i - begin(category))
|
||||
// << ") not found in current with added gender: "
|
||||
// << genderIndex
|
||||
// << ".";
|
||||
// result = false;
|
||||
// } else {
|
||||
// AddAlias(bare, BareIdFromInput(*i));
|
||||
// }
|
||||
}
|
||||
}
|
||||
for (auto i = begin(old.doubleColored); i != end(old.doubleColored); ++i) {
|
||||
auto found = false;
|
||||
for (auto j = begin(data.doubleColored); j != end(data.doubleColored); ++j) {
|
||||
if (j->original == i->original) {
|
||||
found = true;
|
||||
if (j->same != i->same || j->different != i->different) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: old double colored emoji (index "
|
||||
<< (i - begin(old.doubleColored))
|
||||
<< ") not equal to current.";
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: old double colored emoji (index "
|
||||
<< (i - begin(old.doubleColored))
|
||||
<< ") not found in current.";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
for (auto i = begin(old.colored); i != end(old.colored); ++i) {
|
||||
if (find(data.colored, *i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto otherGenderIndex = [&] {
|
||||
for (auto g = begin(genders); g != end(genders); ++g) {
|
||||
auto altered = *i;
|
||||
altered.push_back(kJoiner);
|
||||
altered.push_back(*g);
|
||||
altered.push_back(kPostfix);
|
||||
if (find(old.colored, altered)) {
|
||||
return int(g - begin(genders));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}();
|
||||
if (otherGenderIndex < 0) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: old colored emoji (index "
|
||||
<< (i - begin(old.colored))
|
||||
<< ") not found in current.";
|
||||
result = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto genderIndex = (1 - otherGenderIndex);
|
||||
const auto real = addGenderByIndex(*i, genderIndex);
|
||||
if (!find(data.colored, real)) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: old colored emoji (index "
|
||||
<< (i - begin(old.colored))
|
||||
<< ") not found in current with added gender: "
|
||||
<< genderIndex
|
||||
<< ".";
|
||||
result = false;
|
||||
continue;
|
||||
} else {
|
||||
for (const auto color : Colors) {
|
||||
if (!emplaceColoredAlias(real, *i, color)) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: bad colored emoji.";
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &entry : WithoutGenderAliases) {
|
||||
const auto &inputId = entry.first;
|
||||
const auto &gender = entry.second;
|
||||
if (findInMany(data, inputId)) {
|
||||
continue;
|
||||
}
|
||||
const auto real = [&] {
|
||||
auto result = addGender(inputId, gender);
|
||||
if (findInMany(data, result)) {
|
||||
return result;
|
||||
}
|
||||
result.push_back(kPostfix);
|
||||
return result;
|
||||
}();
|
||||
const auto bare = BareIdFromInput(real);
|
||||
if (!findInMany(data, real)) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: without gender alias not found with gender.";
|
||||
result = false;
|
||||
} else {
|
||||
AddAlias(bare, BareIdFromInput(inputId));
|
||||
}
|
||||
if (variatedIds.find(bare) != variatedIds.end()) {
|
||||
auto colorReal = real;
|
||||
colorReal.insert(colorReal.begin() + 1, Colors[0]);
|
||||
auto colorInput = inputId;
|
||||
colorInput.insert(colorInput.begin() + 1, Colors[0]);
|
||||
for (const auto color : Colors) {
|
||||
if (!emplaceColoredAlias(colorReal, colorInput, color)) {
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: bad colored emoji.";
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[inputId, real] : FlagAliases) {
|
||||
AddAlias(BareIdFromInput(real), BareIdFromInput(inputId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CheckOldInCurrent(
|
||||
const InputData &data,
|
||||
const std::set<Id> &variatedIds,
|
||||
const std::vector<QString> &oldDataPaths) {
|
||||
if (!CheckOldInCurrent(data, GetDataOld1(), variatedIds)
|
||||
|| !CheckOldInCurrent(data, GetDataOld2(), variatedIds)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &path : oldDataPaths) {
|
||||
const auto old = ReadData(path);
|
||||
if (old.colored.empty() || old.doubleColored.empty()) {
|
||||
return false;
|
||||
} else if (!CheckOldInCurrent(data, old, variatedIds)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
common::LogStream logDataError() {
|
||||
return common::logError(kErrorBadData, "input") << "Bad data: ";
|
||||
}
|
||||
|
||||
Data PrepareData(const QString &dataPath, const std::vector<QString> &oldDataPaths) {
|
||||
Data result;
|
||||
|
||||
auto input = ReadData(dataPath);
|
||||
const auto variatedIds = FillVariatedIds(input);
|
||||
const auto doubleVariatedIds = FillDoubleVariatedIds(input);
|
||||
const auto postfixRequiredIds = FillPostfixRequiredIds();
|
||||
if (variatedIds.empty() || doubleVariatedIds.empty() || postfixRequiredIds.empty()) {
|
||||
return Data();
|
||||
}
|
||||
|
||||
if (!CheckOldInCurrent(input, variatedIds, oldDataPaths)) {
|
||||
return Data();
|
||||
}
|
||||
|
||||
for (const auto &category : input.categories) {
|
||||
appendCategory(result, category, variatedIds, doubleVariatedIds, postfixRequiredIds);
|
||||
if (result.list.empty()) {
|
||||
return Data();
|
||||
}
|
||||
}
|
||||
appendCategory(result, input.other, variatedIds, doubleVariatedIds, postfixRequiredIds);
|
||||
if (result.list.empty()) {
|
||||
return Data();
|
||||
}
|
||||
if (AliasesAdded.size() != Aliases.size()) {
|
||||
for (const auto &[key, list] : Aliases) {
|
||||
if (AliasesAdded.find(key) == AliasesAdded.end()) {
|
||||
QStringList expanded;
|
||||
for (const auto &ch : key) {
|
||||
expanded.push_back(QString::number(ch.unicode()));
|
||||
}
|
||||
common::logError(kErrorBadData, "input")
|
||||
<< "Bad data: Not added aliases list for: "
|
||||
<< expanded.join(QChar(',')).toStdString();
|
||||
}
|
||||
}
|
||||
return Data();
|
||||
}
|
||||
|
||||
fillReplaces(result);
|
||||
if (result.list.empty()) {
|
||||
return Data();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
48
Telegram/codegen/codegen/emoji/data.h
Normal file
48
Telegram/codegen/codegen/emoji/data.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "codegen/common/logging.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QMap>
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
|
||||
using Id = QString;
|
||||
struct Emoji {
|
||||
Id id;
|
||||
bool postfixed = false;
|
||||
bool variated = false;
|
||||
//bool doubleVariated = false;
|
||||
bool colored = false;
|
||||
};
|
||||
|
||||
struct Data {
|
||||
std::vector<Emoji> list;
|
||||
std::map<Id, int, std::greater<Id>> map;
|
||||
std::set<int> postfixRequired;
|
||||
std::vector<std::vector<int>> categories;
|
||||
std::map<QString, int, std::greater<QString>> replaces;
|
||||
};
|
||||
[[nodiscard]] Data PrepareData(
|
||||
const QString &dataPath,
|
||||
const std::vector<QString> &oldDataPaths);
|
||||
|
||||
constexpr auto kPostfix = 0xFE0FU;
|
||||
constexpr auto kJoiner = 0x200DU;
|
||||
|
||||
[[nodiscard]] common::LogStream logDataError();
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
3391
Telegram/codegen/codegen/emoji/data_old.cpp
Normal file
3391
Telegram/codegen/codegen/emoji/data_old.cpp
Normal file
File diff suppressed because it is too large
Load Diff
37
Telegram/codegen/codegen/emoji/data_old.h
Normal file
37
Telegram/codegen/codegen/emoji/data_old.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "codegen/common/logging.h"
|
||||
#include <vector>
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
|
||||
using uint16 = quint16;
|
||||
using uint32 = quint32;
|
||||
using uint64 = quint64;
|
||||
|
||||
using InputId = std::vector<uint32>;
|
||||
using InputCategory = std::vector<InputId>;
|
||||
struct DoubleColored {
|
||||
InputId original;
|
||||
InputId same;
|
||||
InputId different;
|
||||
};
|
||||
struct InputData {
|
||||
InputCategory colored;
|
||||
std::vector<DoubleColored> doubleColored;
|
||||
InputCategory categories[7];
|
||||
InputCategory other;
|
||||
};
|
||||
|
||||
[[nodiscard]] InputData GetDataOld1();
|
||||
[[nodiscard]] InputData GetDataOld2();
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
287
Telegram/codegen/codegen/emoji/data_read.cpp
Normal file
287
Telegram/codegen/codegen/emoji/data_read.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/emoji/data_read.h"
|
||||
|
||||
#include "codegen/emoji/data.h"
|
||||
#include "codegen/emoji/data_old.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
namespace {
|
||||
|
||||
using Line = std::vector<QString>;
|
||||
using Part = std::vector<Line>;
|
||||
using Section = std::vector<Part>;
|
||||
using File = std::vector<Section>;
|
||||
|
||||
[[nodiscard]] QStringView Skip(QStringView data, int endIndex) {
|
||||
return (endIndex >= 0) ? base::StringViewMid(data, endIndex + 1) : QStringView();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<QString, QStringView> ReadString(QStringView data) {
|
||||
const auto endIndex = data.indexOf(',');
|
||||
auto parse = base::StringViewMid(data, 0, endIndex);
|
||||
const auto start = parse.indexOf('"');
|
||||
const auto end = parse.indexOf('"', start + 1);
|
||||
auto result = (start >= 0 && end > start)
|
||||
? base::StringViewMid(parse, start + 1, end - start - 1).toString()
|
||||
: QString();
|
||||
return { std::move(result), Skip(data, endIndex) };
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<Line, QStringView> ReadLine(QStringView data) {
|
||||
const auto endIndex = data.indexOf('\n');
|
||||
auto parse = base::StringViewMid(data, 0, endIndex);
|
||||
auto result = Line();
|
||||
while (true) {
|
||||
auto [string, updated] = ReadString(parse);
|
||||
if (!string.isEmpty()) {
|
||||
result.push_back(std::move(string));
|
||||
}
|
||||
if (updated.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
parse = updated;
|
||||
}
|
||||
return { std::move(result), Skip(data, endIndex) };
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<Part, QStringView> ReadPart(QStringView data) {
|
||||
const auto endIndex1 = data.indexOf(u"\n\n");
|
||||
const auto endIndex2 = data.indexOf(u"\r\n\r\n");
|
||||
const auto endIndex = (endIndex1 >= 0) ? endIndex1 : endIndex2;
|
||||
auto parse = base::StringViewMid(data, 0, endIndex);
|
||||
auto result = Part();
|
||||
while (true) {
|
||||
auto [line, updated] = ReadLine(parse);
|
||||
if (!line.empty()) {
|
||||
result.push_back(std::move(line));
|
||||
}
|
||||
if (updated.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
parse = updated;
|
||||
}
|
||||
return { std::move(result), Skip(data, endIndex) };
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<Section, QStringView> ReadSection(QStringView data) {
|
||||
const auto endIndex1 = data.indexOf(u"--------");
|
||||
const auto endIndex2 = data.indexOf(u"========");
|
||||
const auto endIndex = (endIndex1 >= 0 && endIndex2 >= 0)
|
||||
? std::min(endIndex1, endIndex2)
|
||||
: std::max(endIndex1, endIndex2);
|
||||
auto parse = base::StringViewMid(data, 0, endIndex);
|
||||
auto result = Section();
|
||||
while (true) {
|
||||
auto [part, updated] = ReadPart(parse);
|
||||
if (!part.empty()) {
|
||||
result.push_back(std::move(part));
|
||||
}
|
||||
if (updated.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
parse = updated;
|
||||
}
|
||||
return { std::move(result), Skip(data, endIndex) };
|
||||
}
|
||||
|
||||
[[nodiscard]] File ReadFile(const QString &path) {
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return File();
|
||||
}
|
||||
const auto bytes = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
|
||||
auto parse = QStringView(bytes);
|
||||
auto result = File();
|
||||
while (true) {
|
||||
auto [section, updated] = ReadSection(parse);
|
||||
if (!section.empty()) {
|
||||
result.push_back(std::move(section));
|
||||
}
|
||||
if (updated.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
parse = updated;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] const Line &FindColoredLine(const File &file, const QString &colored) {
|
||||
const auto &withColored = file[0];
|
||||
for (const auto &withColoredPart : withColored) {
|
||||
for (const auto &withColoredLine : withColoredPart) {
|
||||
if (withColoredLine[0] == colored) {
|
||||
return withColoredLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
logDataError() << "Simple colored emoji not found: " << colored.toStdString();
|
||||
static auto result = Line();
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FindFirstColored(const File &file, const QString &colored) {
|
||||
const auto &withColoredLine = FindColoredLine(file, colored);
|
||||
if (withColoredLine.empty()) {
|
||||
return {};
|
||||
} else if (withColoredLine.size() != 6) {
|
||||
logDataError() << "Wrong simple colored emoji: " << colored.toStdString();
|
||||
return {};
|
||||
}
|
||||
return withColoredLine[1];
|
||||
}
|
||||
|
||||
struct DoubleColoredSample {
|
||||
QString original;
|
||||
QString same;
|
||||
QString different;
|
||||
};
|
||||
[[nodiscard]] DoubleColoredSample FindDoubleColored(const File &file, const QString &colored) {
|
||||
const auto &withColoredLine = FindColoredLine(file, colored);
|
||||
if (withColoredLine.empty()) {
|
||||
return {};
|
||||
} else if (withColoredLine.size() != 26) {
|
||||
logDataError() << "Wrong double colored emoji: " << colored.toStdString();
|
||||
return {};
|
||||
}
|
||||
return { withColoredLine[0], withColoredLine[1], withColoredLine[2] };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
InputId InputIdFromString(const QString &emoji) {
|
||||
if (emoji.isEmpty()) {
|
||||
return InputId();
|
||||
}
|
||||
auto result = InputId();
|
||||
for (auto i = 0, size = int(emoji.size()); i != size; ++i) {
|
||||
result.push_back(uint32(emoji[i].unicode()));
|
||||
if (emoji[i].isHighSurrogate()) {
|
||||
if (++i == size || !emoji[i].isLowSurrogate()) {
|
||||
logDataError()
|
||||
<< "Bad surrogate pair in InputIdFromString: "
|
||||
<< emoji.toStdString();
|
||||
return InputId();
|
||||
}
|
||||
result.back() = (result.back() << 16) | uint32(emoji[i].unicode());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString InputIdToString(const InputId &id) {
|
||||
auto result = QString();
|
||||
for (auto i = 0, size = int(id.size()); i != size; ++i) {
|
||||
if (id[i] > 0xFFFFU) {
|
||||
result.push_back(QChar(quint16(id[i] >> 16)));
|
||||
}
|
||||
result.push_back(QChar(quint16(id[i] & 0xFFFFU)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
InputData ReadData(const QString &path) {
|
||||
const auto file = ReadFile(path);
|
||||
if (file.size() < 3
|
||||
|| file[0].size() != 8
|
||||
|| file[1].size() > 8) {
|
||||
logDataError() << "Wrong file parts.";
|
||||
return InputData();
|
||||
}
|
||||
auto result = InputData();
|
||||
const auto &colored = file[2][0];
|
||||
for (const auto &coloredLine : colored) {
|
||||
for (const auto &coloredString : coloredLine) {
|
||||
const auto withFirstColor = FindFirstColored(file, coloredString);
|
||||
const auto inputId = InputIdFromString(withFirstColor);
|
||||
if (inputId.empty()) {
|
||||
return InputData();
|
||||
} else if (inputId.size() < 2) {
|
||||
logDataError() << "Bad colored emoji: " << withFirstColor.toStdString();
|
||||
return InputData();
|
||||
}
|
||||
result.colored.push_back(inputId);
|
||||
}
|
||||
}
|
||||
if (file[2].size() > 1) {
|
||||
const auto &doubleColored = file[2][1];
|
||||
for (const auto &doubleColoredLine : doubleColored) {
|
||||
for (const auto &doubleColoredString : doubleColoredLine) {
|
||||
const auto [original, same, different] = FindDoubleColored(file, doubleColoredString);
|
||||
const auto originalId = InputIdFromString(original);
|
||||
const auto sameId = InputIdFromString(same);
|
||||
const auto differentId = InputIdFromString(different);
|
||||
if (originalId.empty() || sameId.empty() || differentId.empty()) {
|
||||
return InputData();
|
||||
} else if (originalId.size() < 1 || sameId.size() < 2 || differentId.size() < 5) {
|
||||
logDataError()
|
||||
<< "Bad double colored emoji: "
|
||||
<< original.toStdString()
|
||||
<< ", " << different.toStdString();
|
||||
return InputData();
|
||||
}
|
||||
result.doubleColored.push_back({ originalId, sameId, differentId });
|
||||
}
|
||||
}
|
||||
}
|
||||
auto index = 0;
|
||||
auto replacementsUsed = 0;
|
||||
for (const auto §ion : file[0]) {
|
||||
const auto first = section.front().front();
|
||||
const auto replacedSection = [&]() -> const Part* {
|
||||
for (const auto §ion : file[1]) {
|
||||
if (section.front().front() == first) {
|
||||
++replacementsUsed;
|
||||
return §ion;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
const auto §ionData = replacedSection
|
||||
? *replacedSection
|
||||
: section;
|
||||
for (const auto &line : sectionData) {
|
||||
for (const auto &string : line) {
|
||||
const auto inputId = InputIdFromString(string);
|
||||
if (inputId.empty()) {
|
||||
return InputData();
|
||||
}
|
||||
result.categories[index].push_back(inputId);
|
||||
}
|
||||
}
|
||||
if (index + 1 < std::size(result.categories)) {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
if (replacementsUsed != file[1].size()) {
|
||||
logDataError() << "Could not use some non-colored section replacements!";
|
||||
return InputData();
|
||||
}
|
||||
if (file.size() > 3) {
|
||||
for (const auto §ion : file[3]) {
|
||||
for (const auto &line : section) {
|
||||
for (const auto &string : line) {
|
||||
const auto inputId = InputIdFromString(string);
|
||||
if (inputId.empty()) {
|
||||
return InputData();
|
||||
}
|
||||
result.other.push_back(inputId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
19
Telegram/codegen/codegen/emoji/data_read.h
Normal file
19
Telegram/codegen/codegen/emoji/data_read.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "codegen/emoji/data_old.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
|
||||
[[nodiscard]] InputId InputIdFromString(const QString &emoji);
|
||||
[[nodiscard]] QString InputIdToString(const InputId &id);
|
||||
[[nodiscard]] InputData ReadData(const QString &path);
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
1204
Telegram/codegen/codegen/emoji/generator.cpp
Normal file
1204
Telegram/codegen/codegen/emoji/generator.cpp
Normal file
File diff suppressed because it is too large
Load Diff
77
Telegram/codegen/codegen/emoji/generator.h
Normal file
77
Telegram/codegen/codegen/emoji/generator.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtGui/QImage>
|
||||
#include "codegen/common/cpp_file.h"
|
||||
#include "codegen/emoji/options.h"
|
||||
#include "codegen/emoji/data.h"
|
||||
#include "codegen/emoji/replaces.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
|
||||
using uint32 = unsigned int;
|
||||
|
||||
class Generator {
|
||||
public:
|
||||
Generator(const Options &options);
|
||||
Generator(const Generator &other) = delete;
|
||||
Generator &operator=(const Generator &other) = delete;
|
||||
|
||||
int generate();
|
||||
|
||||
private:
|
||||
[[nodiscard]] QImage generateImage(int imageIndex);
|
||||
bool writeImages();
|
||||
|
||||
bool writeSource();
|
||||
bool writeHeader();
|
||||
bool writeSuggestionsSource();
|
||||
bool writeSuggestionsHeader();
|
||||
|
||||
template <typename Callback>
|
||||
bool enumerateWholeList(Callback callback);
|
||||
|
||||
bool writeInitCode();
|
||||
bool writeSections();
|
||||
bool writeReplacements();
|
||||
bool writeGetSections();
|
||||
bool writeFindReplace();
|
||||
bool writeFind();
|
||||
bool writeFindFromDictionary(
|
||||
const std::map<QString, int, std::greater<QString>> &dictionary,
|
||||
bool skipPostfixes = false,
|
||||
const std::set<int> &postfixRequired = {});
|
||||
bool writeGetReplacements();
|
||||
void startBinary();
|
||||
bool writeStringBinary(common::CppFile *source, const QString &string);
|
||||
void writeIntBinary(common::CppFile *source, int data);
|
||||
void writeUintBinary(common::CppFile *source, uint32 data);
|
||||
|
||||
const common::ProjectInfo &project_;
|
||||
int colorsCount_ = 0;
|
||||
QString writeImages_;
|
||||
QString outputPath_;
|
||||
QString spritePath_;
|
||||
std::unique_ptr<common::CppFile> source_;
|
||||
Data data_;
|
||||
|
||||
QString suggestionsPath_;
|
||||
std::unique_ptr<common::CppFile> suggestionsSource_;
|
||||
Replaces replaces_;
|
||||
|
||||
int _binaryFullLength = 0;
|
||||
int _binaryCount = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
27
Telegram/codegen/codegen/emoji/main.cpp
Normal file
27
Telegram/codegen/codegen/emoji/main.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
#include "codegen/emoji/options.h"
|
||||
#include "codegen/emoji/generator.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const auto generateImages = [&] {
|
||||
for (auto i = 0; i != argc; ++i) {
|
||||
if (argv[i] == std::string("--images")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
const auto app = generateImages
|
||||
? std::make_unique<QGuiApplication>(argc, argv)
|
||||
: std::make_unique<QCoreApplication>(argc, argv);
|
||||
const auto options = codegen::emoji::parseOptions();
|
||||
codegen::emoji::Generator generator(options);
|
||||
return generator.generate();
|
||||
}
|
||||
66
Telegram/codegen/codegen/emoji/options.cpp
Normal file
66
Telegram/codegen/codegen/emoji/options.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/emoji/options.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include "codegen/common/logging.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
namespace {
|
||||
|
||||
constexpr int kErrorOutputPathExpected = 902;
|
||||
constexpr int kErrorReplacesPathExpected = 903;
|
||||
|
||||
} // namespace
|
||||
|
||||
using common::logError;
|
||||
|
||||
Options parseOptions() {
|
||||
Options result;
|
||||
auto args = QCoreApplication::instance()->arguments();
|
||||
for (int i = 1, count = args.size(); i < count; ++i) { // skip first
|
||||
auto &arg = args.at(i);
|
||||
|
||||
// Output path
|
||||
if (arg == "-o") {
|
||||
if (++i == count) {
|
||||
logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o";
|
||||
return Options();
|
||||
} else {
|
||||
result.outputPath = args.at(i);
|
||||
}
|
||||
} else if (arg.startsWith("-o")) {
|
||||
result.outputPath = arg.mid(2);
|
||||
} else if (arg == "--images") {
|
||||
if (++i == count) {
|
||||
logError(kErrorOutputPathExpected, "Command Line") << "images argument expected after --images";
|
||||
return Options();
|
||||
} else {
|
||||
result.writeImages = args.at(i);
|
||||
}
|
||||
} else if (result.dataPath.isEmpty()) {
|
||||
result.dataPath = arg;
|
||||
} else if (result.replacesPath.isEmpty()) {
|
||||
result.replacesPath = arg;
|
||||
} else {
|
||||
result.oldDataPaths.push_back(arg);
|
||||
}
|
||||
}
|
||||
if (result.outputPath.isEmpty()) {
|
||||
logError(kErrorOutputPathExpected, "Command Line") << "output path expected";
|
||||
return Options();
|
||||
} else if (result.replacesPath.isEmpty()) {
|
||||
logError(kErrorReplacesPathExpected, "Command Line") << "replaces path expected";
|
||||
return Options();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
27
Telegram/codegen/codegen/emoji/options.h
Normal file
27
Telegram/codegen/codegen/emoji/options.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
|
||||
struct Options {
|
||||
QString outputPath = ".";
|
||||
QString dataPath;
|
||||
QString replacesPath;
|
||||
std::vector<QString> oldDataPaths;
|
||||
QString writeImages;
|
||||
};
|
||||
|
||||
// Parsing failed if inputPath is empty in the result.
|
||||
Options parseOptions();
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
398
Telegram/codegen/codegen/emoji/replaces.cpp
Normal file
398
Telegram/codegen/codegen/emoji/replaces.cpp
Normal file
@@ -0,0 +1,398 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/emoji/replaces.h"
|
||||
|
||||
#include "codegen/emoji/data.h"
|
||||
#include "codegen/emoji/data_old.h"
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
namespace {
|
||||
|
||||
constexpr auto kErrorBadReplaces = 402;
|
||||
|
||||
common::LogStream logReplacesError(const QString &filename) {
|
||||
return common::logError(kErrorBadReplaces, filename) << "Bad data: ";
|
||||
}
|
||||
|
||||
auto RegExpCode = QRegularExpression("^:[\\+\\-a-z0-9_]+:$");
|
||||
auto RegExpTone = QRegularExpression("_tone[0-9]");
|
||||
auto RegExpHex = QRegularExpression("^[0-9a-f]+$");
|
||||
|
||||
class ReplacementWords {
|
||||
public:
|
||||
ReplacementWords(const QString &string);
|
||||
QVector<QString> result() const;
|
||||
|
||||
private:
|
||||
QMap<QString, int> wordsWithCounts_;
|
||||
|
||||
};
|
||||
|
||||
ReplacementWords::ReplacementWords(const QString &string) {
|
||||
auto feedWord = [this](QString &word) {
|
||||
if (!word.isEmpty()) {
|
||||
++wordsWithCounts_[word];
|
||||
word.clear();
|
||||
}
|
||||
};
|
||||
// Split by all non-letters-or-numbers.
|
||||
// Leave '-' and '+' inside a word only if they're followed by a number.
|
||||
auto word = QString();
|
||||
for (auto i = string.cbegin(), e = string.cend(); i != e; ++i) {
|
||||
if (i->isLetterOrNumber()) {
|
||||
word.append(*i);
|
||||
continue;
|
||||
} else if (*i == '-' || *i == '+') {
|
||||
if (i + 1 != e && (i + 1)->isNumber()) {
|
||||
word.append(*i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
feedWord(word);
|
||||
}
|
||||
feedWord(word);
|
||||
}
|
||||
|
||||
QVector<QString> ReplacementWords::result() const {
|
||||
auto result = QVector<QString>();
|
||||
for (auto i = wordsWithCounts_.cbegin(), e = wordsWithCounts_.cend(); i != e; ++i) {
|
||||
for (auto j = 0, count = i.value(); j != count; ++j) {
|
||||
result.push_back(i.key());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AddReplacement(Replaces &result, const Id &id, const QString &replacement, const QString &name) {
|
||||
auto replace = Replace();
|
||||
replace.id = id;
|
||||
replace.replacement = replacement;
|
||||
replace.words = (ReplacementWords(replacement)).result();// + ReplacementWords(name)).result();
|
||||
if (replace.words.isEmpty()) {
|
||||
logReplacesError(result.filename) << "Child '" << replacement.toStdString() << "' has no words.";
|
||||
return false;
|
||||
}
|
||||
result.list.push_back(replace);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ComposeString(const std::initializer_list<int> &chars) {
|
||||
auto result = QString();
|
||||
result.reserve(chars.size());
|
||||
for (auto ch : chars) {
|
||||
result.append(QChar(ch));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto NotSupported = [] {
|
||||
auto result = QSet<QString>();
|
||||
auto insert = [&result](auto... args) {
|
||||
result.insert(ComposeString({ args... }));
|
||||
};
|
||||
insert(0x0023, 0xFE0F); // :pound_symbol:
|
||||
insert(0x002A, 0xFE0F); // :asterisk_symbol:
|
||||
for (auto i = 0; i != 10; ++i) {
|
||||
insert(0x0030 + i, 0xFE0F); // :digit_zero: ... :digit_nine:
|
||||
}
|
||||
for (auto i = 0; i != 5; ++i) {
|
||||
insert(0xD83C, 0xDFFB + i); // :tone1: ... :tone5:
|
||||
}
|
||||
for (auto i = 0; i != 26; ++i) {
|
||||
insert(0xD83C, 0xDDE6 + i); // :regional_indicator_a: ... :regional_indicator_z:
|
||||
}
|
||||
insert(0x2640, 0xFE0F); // :female_sign:
|
||||
insert(0x2642, 0xFE0F); // :male_sign:
|
||||
insert(0x2695, 0xFE0F); // :medical_symbol:
|
||||
|
||||
return result;
|
||||
}();
|
||||
|
||||
const auto ConvertMap = ([] {
|
||||
auto result = QMap<QString, QString>();
|
||||
auto insert = [&result](const std::initializer_list<int> &from, const std::initializer_list<int> &to) {
|
||||
result.insert(ComposeString(from), ComposeString(to));
|
||||
};
|
||||
auto insertWithAdd = [&result](const std::initializer_list<int> &from, const QString &added) {
|
||||
auto code = ComposeString(from);
|
||||
result.insert(code, code + added);
|
||||
};
|
||||
auto maleModifier = ComposeString({ 0x200D, 0x2642, 0xFE0F });
|
||||
auto femaleModifier = ComposeString({ 0x200D, 0x2640, 0xFE0F });
|
||||
insertWithAdd({ 0xD83E, 0xDD26 }, maleModifier);
|
||||
insertWithAdd({ 0xD83E, 0xDD37 }, femaleModifier);
|
||||
insertWithAdd({ 0xD83E, 0xDD38 }, maleModifier);
|
||||
insertWithAdd({ 0xD83E, 0xDD39 }, maleModifier);
|
||||
insertWithAdd({ 0xD83E, 0xDD3C }, maleModifier);
|
||||
insertWithAdd({ 0xD83E, 0xDD3D }, maleModifier);
|
||||
insertWithAdd({ 0xD83E, 0xDD3E }, femaleModifier);
|
||||
|
||||
// :kiss_woman_man:
|
||||
insert({ 0xD83D, 0xDC69, 0x200D, 0x2764, 0xFE0F, 0x200D, 0xD83D, 0xDC8B, 0x200D, 0xD83D, 0xDC68 }, { 0xD83D, 0xDC8F });
|
||||
|
||||
// :family_man_woman_boy:
|
||||
insert({ 0xD83D, 0xDC68, 0x200D, 0xD83D, 0xDC69, 0x200D, 0xD83D, 0xDC66 }, { 0xD83D, 0xDC6A });
|
||||
|
||||
// :couple_with_heart_woman_man:
|
||||
insert({ 0xD83D, 0xDC69, 0x200D, 0x2764, 0xFE0F, 0x200D, 0xD83D, 0xDC68 }, { 0xD83D, 0xDC91 });
|
||||
|
||||
auto insertFlag = [insert](char ch1, char ch2, char ch3, char ch4) {
|
||||
insert({ 0xD83C, 0xDDE6 + (ch1 - 'a'), 0xD83C, 0xDDe6 + (ch2 - 'a') }, { 0xD83C, 0xDDE6 + (ch3 - 'a'), 0xD83C, 0xDDe6 + (ch4 - 'a') });
|
||||
};
|
||||
insertFlag('a', 'c', 's', 'h');
|
||||
insertFlag('b', 'v', 'n', 'o');
|
||||
insertFlag('c', 'p', 'f', 'r');
|
||||
insertFlag('d', 'g', 'i', 'o');
|
||||
insertFlag('e', 'a', 'e', 's');
|
||||
insertFlag('h', 'm', 'a', 'u');
|
||||
insertFlag('m', 'f', 'f', 'r');
|
||||
insertFlag('s', 'j', 'n', 'o');
|
||||
insertFlag('t', 'a', 's', 'h');
|
||||
insertFlag('u', 'm', 'u', 's');
|
||||
|
||||
return result;
|
||||
})();
|
||||
|
||||
// Empty string result means we should skip this one.
|
||||
QString ConvertEmojiId(const Id &id, const QString &replacement) {
|
||||
if (RegExpTone.match(replacement).hasMatch()) {
|
||||
return QString();
|
||||
}
|
||||
if (NotSupported.contains(id)) {
|
||||
return QString();
|
||||
}
|
||||
return ConvertMap.value(id, id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Replaces PrepareReplaces(const QString &filename) {
|
||||
auto result = Replaces(filename);
|
||||
auto content = ([filename] {
|
||||
QFile f(filename);
|
||||
return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray();
|
||||
})();
|
||||
if (content.isEmpty()) {
|
||||
logReplacesError(filename) << "Could not read data.";
|
||||
return result;
|
||||
}
|
||||
auto error = QJsonParseError();
|
||||
auto document = QJsonDocument::fromJson(content, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
logReplacesError(filename) << "Could not parse data (" << int(error.error) << "): " << error.errorString().toStdString();
|
||||
return result;
|
||||
}
|
||||
if (!document.isObject()) {
|
||||
logReplacesError(filename) << "Root object not found.";
|
||||
return result;
|
||||
}
|
||||
auto list = document.object();
|
||||
for (auto i = list.constBegin(), e = list.constEnd(); i != e; ++i) {
|
||||
if (!(*i).isObject()) {
|
||||
logReplacesError(filename) << "Child object not found.";
|
||||
return Replaces(filename);
|
||||
}
|
||||
auto childKey = i.key();
|
||||
auto child = (*i).toObject();
|
||||
auto failed = false;
|
||||
auto getString = [filename, childKey, &child, &failed](const QString &key) {
|
||||
auto it = child.constFind(key);
|
||||
if (it == child.constEnd() || !(*it).isString()) {
|
||||
logReplacesError(filename) << "Child '" << childKey.toStdString() << "' field not found: " << key.toStdString();
|
||||
failed = true;
|
||||
return QString();
|
||||
}
|
||||
return (*it).toString();
|
||||
};
|
||||
auto idParts = getString("output").split('-');
|
||||
auto name = getString("name");
|
||||
auto replacement = getString("alpha_code");
|
||||
auto aliases = getString("aliases").split('|');
|
||||
const auto Exceptions = { ":shrug:" };
|
||||
for (const auto &exception : Exceptions) {
|
||||
const auto index = aliases.indexOf(exception);
|
||||
if (index >= 0) {
|
||||
aliases.removeAt(index);
|
||||
}
|
||||
}
|
||||
if (aliases.size() == 1 && aliases[0].isEmpty()) {
|
||||
aliases.clear();
|
||||
}
|
||||
if (failed) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
if (!RegExpCode.match(replacement).hasMatch()) {
|
||||
logReplacesError(filename) << "Child '" << childKey.toStdString() << "' alpha_code invalid: " << replacement.toStdString();
|
||||
return Replaces(filename);
|
||||
}
|
||||
for (auto &alias : aliases) {
|
||||
if (!RegExpCode.match(alias).hasMatch()) {
|
||||
logReplacesError(filename) << "Child '" << childKey.toStdString() << "' alias invalid: " << alias.toStdString();
|
||||
return Replaces(filename);
|
||||
}
|
||||
}
|
||||
auto id = Id();
|
||||
for (auto &idPart : idParts) {
|
||||
auto ok = true;
|
||||
auto utf32 = idPart.toInt(&ok, 0x10);
|
||||
if (!ok || !RegExpHex.match(idPart).hasMatch()) {
|
||||
logReplacesError(filename) << "Child '" << childKey.toStdString() << "' output part invalid: " << idPart.toStdString();
|
||||
return Replaces(filename);
|
||||
}
|
||||
if (utf32 >= 0 && utf32 < 0x10000) {
|
||||
auto ch = QChar(ushort(utf32));
|
||||
if (ch.isLowSurrogate() || ch.isHighSurrogate()) {
|
||||
logReplacesError(filename) << "Child '" << childKey.toStdString() << "' output part invalid: " << idPart.toStdString();
|
||||
return Replaces(filename);
|
||||
}
|
||||
id.append(ch);
|
||||
} else if (utf32 >= 0x10000 && utf32 <= 0x10FFFF) {
|
||||
auto hi = ((utf32 - 0x10000) / 0x400) + 0xD800;
|
||||
auto lo = ((utf32 - 0x10000) % 0x400) + 0xDC00;
|
||||
id.append(QChar(ushort(hi)));
|
||||
id.append(QChar(ushort(lo)));
|
||||
} else {
|
||||
logReplacesError(filename) << "Child '" << childKey.toStdString() << "' output part invalid: " << idPart.toStdString();
|
||||
return Replaces(filename);
|
||||
}
|
||||
}
|
||||
id = ConvertEmojiId(id, replacement);
|
||||
if (id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!AddReplacement(result, id, replacement, name)) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
for (auto &alias : aliases) {
|
||||
if (!AddReplacement(result, id, alias, name)) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!AddReplacement(result, ComposeString({ 0xD83D, 0xDC4D }), ":like:", "thumbs up")) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
if (!AddReplacement(result, ComposeString({ 0xD83D, 0xDC4E }), ":dislike:", "thumbs down")) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
if (!AddReplacement(result, ComposeString({ 0xD83E, 0xDD14 }), ":hmm:", "thinking")) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
if (!AddReplacement(result, ComposeString({ 0xD83E, 0xDD73 }), ":party:", "celebration")) {
|
||||
return Replaces(filename);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CheckAndConvertReplaces(Replaces &replaces, const Data &data) {
|
||||
if (data.map.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto result = Replaces(replaces.filename);
|
||||
auto sorted = QMultiMap<Id, Replace>();
|
||||
auto findId = [&](const Id &id) {
|
||||
return data.map.find(id) != data.map.cend();
|
||||
};
|
||||
auto findAndSort = [&](Id id, const Replace &replace) {
|
||||
if (!findId(id)) {
|
||||
id.replace(QChar(0xFE0F), QString());
|
||||
if (!findId(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto it = data.map.find(id);
|
||||
id = data.list[it->second].id;
|
||||
if (data.list[it->second].postfixed) {
|
||||
id += QChar(kPostfix);
|
||||
}
|
||||
auto inserted = sorted.insert(id, replace);
|
||||
inserted.value().id = id;
|
||||
return true;
|
||||
};
|
||||
|
||||
auto success = true;
|
||||
|
||||
// Find all replaces in data.map, adjust id if necessary.
|
||||
// Store all replaces in sorted map to find them fast afterwards.
|
||||
auto maleModifier = ComposeString({ 0x200D, 0x2642, 0xFE0F });
|
||||
auto femaleModifier = ComposeString({ 0x200D, 0x2640, 0xFE0F });
|
||||
for (auto &replace : replaces.list) {
|
||||
if (findAndSort(replace.id, replace)) {
|
||||
continue;
|
||||
}
|
||||
if (replace.id.endsWith(maleModifier)) {
|
||||
auto defaultId = replace.id.mid(0, replace.id.size() - maleModifier.size());
|
||||
if (findAndSort(defaultId, replace)) {
|
||||
continue;
|
||||
}
|
||||
} else if (replace.id.endsWith(femaleModifier)) {
|
||||
auto defaultId = replace.id.mid(0, replace.id.size() - femaleModifier.size());
|
||||
if (findAndSort(defaultId, replace)) {
|
||||
continue;
|
||||
}
|
||||
} else if (findId(replace.id + maleModifier)) {
|
||||
if (findId(replace.id + femaleModifier)) {
|
||||
logReplacesError(replaces.filename)
|
||||
<< "Replace '"
|
||||
<< replace.replacement.toStdString()
|
||||
<< "' ("
|
||||
<< replace.id.toStdString()
|
||||
<< ") ambiguous.";
|
||||
success = false;
|
||||
continue;
|
||||
} else {
|
||||
findAndSort(replace.id + maleModifier, replace);
|
||||
continue;
|
||||
}
|
||||
} else if (findAndSort(replace.id + femaleModifier, replace)) {
|
||||
continue;
|
||||
}
|
||||
logReplacesError(replaces.filename)
|
||||
<< "Replace '"
|
||||
<< replace.replacement.toStdString()
|
||||
<< "' ("
|
||||
<< replace.id.toStdString()
|
||||
<< ") not found.";
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Go through all categories and put all replaces in order of emoji in categories.
|
||||
result.list.reserve(replaces.list.size());
|
||||
for (auto &category : data.categories) {
|
||||
for (auto index : category) {
|
||||
auto id = data.list[index].id;
|
||||
if (data.list[index].postfixed) {
|
||||
id += QChar(kPostfix);
|
||||
}
|
||||
for (auto it = sorted.find(id); it != sorted.cend(); sorted.erase(it), it = sorted.find(id)) {
|
||||
result.list.push_back(it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.list.size() != replaces.list.size()) {
|
||||
logReplacesError(replaces.filename) << "Some were not found.";
|
||||
success = false;
|
||||
}
|
||||
if (!sorted.isEmpty()) {
|
||||
logReplacesError(replaces.filename) << "Weird.";
|
||||
success = false;
|
||||
}
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
replaces = std::move(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
33
Telegram/codegen/codegen/emoji/replaces.h
Normal file
33
Telegram/codegen/codegen/emoji/replaces.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "codegen/common/logging.h"
|
||||
#include "codegen/emoji/data.h"
|
||||
#include <QtCore/QVector>
|
||||
|
||||
namespace codegen {
|
||||
namespace emoji {
|
||||
|
||||
struct Replace {
|
||||
Id id;
|
||||
QString replacement;
|
||||
QVector<QString> words;
|
||||
};
|
||||
|
||||
struct Replaces {
|
||||
Replaces(const QString &filename) : filename(filename) {
|
||||
}
|
||||
QString filename;
|
||||
QVector<Replace> list;
|
||||
};
|
||||
|
||||
Replaces PrepareReplaces(const QString &filename);
|
||||
bool CheckAndConvertReplaces(Replaces &replaces, const Data &data);
|
||||
|
||||
} // namespace emoji
|
||||
} // namespace codegen
|
||||
29
Telegram/codegen/codegen/lang/CMakeLists.txt
Normal file
29
Telegram/codegen/codegen/lang/CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
add_executable(codegen_lang)
|
||||
init_target(codegen_lang "(codegen)")
|
||||
|
||||
get_filename_component(src_loc ../.. REALPATH)
|
||||
|
||||
nice_target_sources(codegen_lang ${src_loc}
|
||||
PRIVATE
|
||||
codegen/lang/generator.cpp
|
||||
codegen/lang/generator.h
|
||||
codegen/lang/main.cpp
|
||||
codegen/lang/options.cpp
|
||||
codegen/lang/options.h
|
||||
codegen/lang/parsed_file.cpp
|
||||
codegen/lang/parsed_file.h
|
||||
codegen/lang/processor.cpp
|
||||
codegen/lang/processor.h
|
||||
)
|
||||
|
||||
target_include_directories(codegen_lang
|
||||
PUBLIC
|
||||
${src_loc}
|
||||
)
|
||||
|
||||
target_link_libraries(codegen_lang
|
||||
PUBLIC
|
||||
desktop-app::lib_base
|
||||
desktop-app::codegen_common
|
||||
desktop-app::external_qt
|
||||
)
|
||||
514
Telegram/codegen/codegen/lang/generator.cpp
Normal file
514
Telegram/codegen/codegen/lang/generator.cpp
Normal file
@@ -0,0 +1,514 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/lang/generator.h"
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtGui/QImage>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
|
||||
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<common::CppFile>(basePath_ + ".h", project_);
|
||||
header_->include("lang/lang_tag.h").include("lang/lang_values.h").newline();
|
||||
|
||||
writeHeaderForwardDeclarations();
|
||||
writeHeaderTagTypes();
|
||||
writeHeaderInterface();
|
||||
writeHeaderReactiveInterface();
|
||||
|
||||
return header_->finalize();
|
||||
}
|
||||
|
||||
void Generator::writeHeaderForwardDeclarations() {
|
||||
header_->pushNamespace("Lang").stream() << "\
|
||||
\n\
|
||||
inline constexpr auto kTagsCount = ushort(" << langpack_.tags.size() << ");\n\
|
||||
inline constexpr auto kKeysCount = ushort(" << langpack_.entries.size() << ");\n\
|
||||
\n";
|
||||
header_->popNamespace().newline();
|
||||
}
|
||||
|
||||
void Generator::writeHeaderTagTypes() {
|
||||
auto index = 0;
|
||||
for (auto &tag : langpack_.tags) {
|
||||
if (tag.tag == kPluralTags[0]) {
|
||||
auto elements = QStringList();
|
||||
header_->stream()
|
||||
<< "enum lngtag_" << tag.tag << " : int { ";
|
||||
for (auto i = 0; i != kPluralTags.size(); ++i) {
|
||||
elements.push_back("lt_" + kPluralTags[i] + " = " + QString::number(index + i * 1000));
|
||||
}
|
||||
header_->stream() << elements.join(", ") << " };\n";
|
||||
++index;
|
||||
} else {
|
||||
header_->stream() << "enum lngtag_" << tag.tag << " : int { lt_" << tag.tag << " = " << index++ << " };\n";
|
||||
}
|
||||
}
|
||||
header_->newline();
|
||||
}
|
||||
|
||||
void Generator::writeHeaderInterface() {
|
||||
header_->pushNamespace("Lang").stream() << "\
|
||||
\n\
|
||||
ushort GetTagIndex(QLatin1String tag);\n\
|
||||
ushort GetKeyIndex(QLatin1String key);\n\
|
||||
bool IsTagReplaced(ushort key, ushort tag);\n\
|
||||
QString GetOriginalValue(ushort key);\n\
|
||||
\n";
|
||||
writeHeaderTagValueLookup();
|
||||
header_->popNamespace().newline();
|
||||
}
|
||||
|
||||
void Generator::writeHeaderTagValueLookup() {
|
||||
header_->pushNamespace("details").stream() << "\
|
||||
\n\
|
||||
template <typename Tag>\n\
|
||||
struct TagData;\n\
|
||||
\n\
|
||||
template <typename Tag>\n\
|
||||
inline constexpr ushort TagValue() {\n\
|
||||
return TagData<Tag>::value;\n\
|
||||
}\n\
|
||||
\n";
|
||||
|
||||
for (auto &tag : langpack_.tags) {
|
||||
header_->stream() << "template <> struct TagData<lngtag_" << tag.tag << "> : std::integral_constant<ushort, ushort(lt_" << tag.tag << ")> {};\n";
|
||||
}
|
||||
|
||||
header_->newline().popNamespace();
|
||||
}
|
||||
|
||||
void Generator::writeHeaderReactiveInterface() {
|
||||
header_->pushNamespace("tr");
|
||||
|
||||
writeHeaderProducersInterface();
|
||||
writeHeaderProducersInstances();
|
||||
|
||||
header_->popNamespace().newline();
|
||||
}
|
||||
|
||||
void Generator::writeHeaderProducersInterface() {
|
||||
header_->pushNamespace("details").stream() << "\
|
||||
\n\
|
||||
struct Identity {\n\
|
||||
QString operator()(const QString &value) const {\n\
|
||||
return value;\n\
|
||||
}\n\
|
||||
};\n\
|
||||
\n";
|
||||
|
||||
header_->popNamespace().newline();
|
||||
header_->stream() << "\
|
||||
struct now_t {\n\
|
||||
};\n\
|
||||
\n\
|
||||
inline constexpr now_t now{};\n\
|
||||
\n\
|
||||
inline auto to_count() {\n\
|
||||
return rpl::map([](auto value) {\n\
|
||||
return float64(value);\n\
|
||||
});\n\
|
||||
}\n\
|
||||
\n\
|
||||
template <typename P>\n\
|
||||
using S = std::decay_t<decltype(std::declval<P>()(QString()))>;\n\
|
||||
\n\
|
||||
template <typename ...Tags>\n\
|
||||
struct phrase;\n\
|
||||
\n";
|
||||
std::set<QString> producersDeclared;
|
||||
for (auto &entry : langpack_.entries) {
|
||||
const auto isPlural = !entry.keyBase.isEmpty();
|
||||
auto tags = QStringList();
|
||||
auto producerArgs = QStringList();
|
||||
auto currentArgs = QStringList();
|
||||
auto values = QStringList();
|
||||
values.push_back("base");
|
||||
values.push_back("std::move(p)");
|
||||
for (auto &tagData : entry.tags) {
|
||||
const auto &tag = tagData.tag;
|
||||
const auto isPluralTag = isPlural && (tag == kPluralTags[0]);
|
||||
tags.push_back("lngtag_" + tag);
|
||||
const auto type1 = "lngtag_" + tag;
|
||||
const auto arg1 = type1 + (isPluralTag ? " type" : "");
|
||||
const auto producerType2 = (isPluralTag ? "rpl::producer<float64> " : "rpl::producer<S<P>> ");
|
||||
const auto producerArg2 = producerType2 + tag + "__val";
|
||||
const auto currentType2 = (isPluralTag ? "float64 " : "const S<P> &");
|
||||
const auto currentArg2 = currentType2 + tag + "__val";
|
||||
producerArgs.push_back(arg1 + ", " + producerArg2);
|
||||
currentArgs.push_back(arg1 + ", " + currentArg2);
|
||||
if (isPluralTag) {
|
||||
values.push_back("type");
|
||||
}
|
||||
values.push_back(tag + "__val");
|
||||
}
|
||||
producerArgs.push_back("P p = P()");
|
||||
currentArgs.push_back("P p = P()");
|
||||
if (!producersDeclared.emplace(tags.join(',')).second) {
|
||||
continue;
|
||||
}
|
||||
header_->stream() << "\
|
||||
template <>\n\
|
||||
struct phrase<" << tags.join(", ") << "> {\n\
|
||||
template <typename P = details::Identity>\n\
|
||||
rpl::producer<S<P>> operator()(" << producerArgs.join(", ") << ") const {\n\
|
||||
return ::Lang::details::Producer<" << tags.join(", ") << ">::Combine(" << values.join(", ") << ");\n\
|
||||
}\n\
|
||||
\n\
|
||||
template <typename P = details::Identity>\n\
|
||||
S<P> operator()(now_t, " << currentArgs.join(", ") << ") const {\n\
|
||||
return ::Lang::details::Producer<" << tags.join(", ") << ">::Current(" << values.join(", ") << ");\n\
|
||||
}\n\
|
||||
\n\
|
||||
ushort base;\n\
|
||||
};\n\
|
||||
\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Generator::writeHeaderProducersInstances() {
|
||||
auto index = 0;
|
||||
for (auto &entry : langpack_.entries) {
|
||||
const auto isPlural = !entry.keyBase.isEmpty();
|
||||
const auto &key = entry.key;
|
||||
auto tags = QStringList();
|
||||
for (auto &tagData : entry.tags) {
|
||||
const auto &tag = tagData.tag;
|
||||
tags.push_back("lngtag_" + tag);
|
||||
}
|
||||
if (!isPlural || key == ComputePluralKey(entry.keyBase, 0)) {
|
||||
header_->stream() << "\
|
||||
inline constexpr phrase<" << tags.join(", ") << "> " << (isPlural ? entry.keyBase : key) << "{ ushort(" << index << ") };\n";
|
||||
}
|
||||
++index;
|
||||
}
|
||||
header_->newline();
|
||||
}
|
||||
|
||||
bool Generator::writeSource() {
|
||||
source_ = std::make_unique<common::CppFile>(basePath_ + ".cpp", project_);
|
||||
|
||||
source_->include("lang/lang_keys.h").pushNamespace("Lang").pushNamespace();
|
||||
|
||||
source_->stream() << "\
|
||||
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() << "QChar(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_->popNamespace().stream() << "\
|
||||
\n\
|
||||
ushort GetTagIndex(QLatin1String tag) {\n\
|
||||
auto size = tag.size();\n\
|
||||
auto data = tag.data();\n";
|
||||
|
||||
auto tagsSet = std::set<QString, std::greater<>>();
|
||||
for (auto &tag : langpack_.tags) {
|
||||
tagsSet.insert(tag.tag);
|
||||
}
|
||||
|
||||
writeSetSearch(tagsSet, [](const QString &tag) {
|
||||
return "ushort(lt_" + tag + ")";
|
||||
}, "kTagsCount");
|
||||
|
||||
source_->stream() << "\
|
||||
}\n\
|
||||
\n\
|
||||
ushort GetKeyIndex(QLatin1String key) {\n\
|
||||
auto size = key.size();\n\
|
||||
auto data = key.data();\n";
|
||||
|
||||
auto index = 0;
|
||||
auto indices = std::map<QString, QString>();
|
||||
for (auto &entry : langpack_.entries) {
|
||||
indices.emplace(getFullKey(entry), QString::number(index++));
|
||||
}
|
||||
const auto indexOfKey = [&](const QString &full) {
|
||||
const auto i = indices.find(full);
|
||||
if (i == indices.end()) {
|
||||
return QString();
|
||||
}
|
||||
return i->second;
|
||||
};
|
||||
|
||||
auto taggedKeys = std::map<QString, QString>();
|
||||
auto keysSet = std::set<QString, std::greater<>>();
|
||||
for (auto &entry : langpack_.entries) {
|
||||
if (!entry.keyBase.isEmpty()) {
|
||||
for (auto i = 0; i != kPluralPartCount; ++i) {
|
||||
auto keyName = entry.keyBase + '#' + kPluralParts[i];
|
||||
taggedKeys.emplace(keyName, ComputePluralKey(entry.keyBase, i));
|
||||
keysSet.insert(keyName);
|
||||
}
|
||||
} else {
|
||||
auto full = getFullKey(entry);
|
||||
if (full != entry.key) {
|
||||
taggedKeys.emplace(entry.key, full);
|
||||
}
|
||||
keysSet.insert(entry.key);
|
||||
}
|
||||
}
|
||||
|
||||
writeSetSearch(keysSet, [&](const QString &key) {
|
||||
auto it = taggedKeys.find(key);
|
||||
const auto name = (it != taggedKeys.end()) ? it->second : key;
|
||||
return indexOfKey(name);
|
||||
}, "kKeysCount");
|
||||
header_->popNamespace().newline();
|
||||
|
||||
source_->stream() << "\
|
||||
}\n\
|
||||
\n\
|
||||
bool IsTagReplaced(ushort 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 " << indexOfKey(ComputePluralKey(entry.keyBase, i)) << ":" << ((i + 1 == kPluralPartCount) ? " {" : "") << "\n";
|
||||
}
|
||||
} else {
|
||||
source_->stream() << "\
|
||||
case " << indexOfKey(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(ushort key) {\n\
|
||||
Expects(key < kKeysCount);\n\
|
||||
\n\
|
||||
const auto offset = Offsets[key];\n\
|
||||
return QString::fromRawData(DefaultData + offset, Offsets[key + 1] - offset);\n\
|
||||
}\n\
|
||||
\n";
|
||||
|
||||
return source_->finalize();
|
||||
}
|
||||
|
||||
template <typename ComputeResult>
|
||||
void Generator::writeSetSearch(const std::set<QString, std::greater<>> &set, ComputeResult computeResult, const QString &invalidResult) {
|
||||
auto tabs = [](int size) {
|
||||
return QString(size, '\t');
|
||||
};
|
||||
|
||||
enum class UsedCheckType {
|
||||
Switch,
|
||||
If,
|
||||
UpcomingIf,
|
||||
};
|
||||
auto checkTypes = QVector<UsedCheckType>();
|
||||
auto checkLengthHistory = QVector<int>();
|
||||
auto chars = QString();
|
||||
auto tabsUsed = 1;
|
||||
|
||||
// Returns true if at least one check was finished.
|
||||
auto finishChecksTillKey = [this, &chars, &checkTypes, &checkLengthHistory, &tabsUsed, tabs](const QString &key) {
|
||||
auto result = false;
|
||||
while (!chars.isEmpty() && !key.startsWith(chars)) {
|
||||
result = true;
|
||||
|
||||
auto wasType = checkTypes.back();
|
||||
chars.resize(chars.size() - 1);
|
||||
checkTypes.pop_back();
|
||||
if (wasType == UsedCheckType::Switch || wasType == UsedCheckType::If) {
|
||||
--tabsUsed;
|
||||
if (wasType == UsedCheckType::Switch) {
|
||||
source_->stream() << tabs(tabsUsed) << "break;\n";
|
||||
}
|
||||
if ((!chars.isEmpty() && !key.startsWith(chars)) || key == chars) {
|
||||
source_->stream() << tabs(tabsUsed) << "}\n";
|
||||
checkLengthHistory.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Check if we can use "if" for a check on "charIndex" in "it" (otherwise only "switch")
|
||||
auto canUseIfForCheck = [](auto it, auto end, int charIndex) {
|
||||
auto key = *it;
|
||||
auto i = it;
|
||||
auto keyStart = key.mid(0, charIndex);
|
||||
for (++i; i != end; ++i) {
|
||||
auto nextKey = *i;
|
||||
if (nextKey.mid(0, charIndex) != keyStart) {
|
||||
return true;
|
||||
} else if (nextKey.size() > charIndex && nextKey[charIndex] != key[charIndex]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto countMinimalLength = [](auto it, auto end, int charIndex) {
|
||||
auto key = *it;
|
||||
auto i = it;
|
||||
auto keyStart = key.mid(0, charIndex);
|
||||
auto result = key.size();
|
||||
for (++i; i != end; ++i) {
|
||||
auto nextKey = *i;
|
||||
if (nextKey.mid(0, charIndex) != keyStart) {
|
||||
break;
|
||||
} else if (nextKey.size() > charIndex && result > nextKey.size()) {
|
||||
result = nextKey.size();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
for (auto i = set.begin(), e = set.end(); i != e; ++i) {
|
||||
// If we use just "auto" here and "name" becomes mutable,
|
||||
// the operator[] will return QCharRef instead of QChar,
|
||||
// and "auto ch = name[index]" will behave like "auto &ch =",
|
||||
// if you assign something to "ch" after that you'll change "name" (!)
|
||||
const auto name = *i;
|
||||
|
||||
auto weContinueOldSwitch = finishChecksTillKey(name);
|
||||
while (chars.size() != name.size()) {
|
||||
auto checking = chars.size();
|
||||
|
||||
auto keyChar = name[checking];
|
||||
auto usedIfForCheckCount = 0;
|
||||
auto minimalLengthCheck = countMinimalLength(i, e, checking);
|
||||
for (; checking + usedIfForCheckCount != name.size(); ++usedIfForCheckCount) {
|
||||
if (!canUseIfForCheck(i, e, checking + usedIfForCheckCount)
|
||||
|| countMinimalLength(i, e, checking + usedIfForCheckCount) != minimalLengthCheck) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto usedIfForCheck = !weContinueOldSwitch && (usedIfForCheckCount > 0);
|
||||
const auto checkedLength = checkLengthHistory.isEmpty()
|
||||
? 0
|
||||
: checkLengthHistory.back();
|
||||
const auto requiredLength = qMax(
|
||||
minimalLengthCheck,
|
||||
checkedLength);
|
||||
auto checkLengthCondition = QString();
|
||||
if (weContinueOldSwitch) {
|
||||
weContinueOldSwitch = false;
|
||||
} else {
|
||||
checkLengthCondition = (requiredLength > checkedLength) ? ("size >= " + QString::number(requiredLength)) : QString();
|
||||
if (!usedIfForCheck) {
|
||||
source_->stream() << tabs(tabsUsed) << (checkLengthCondition.isEmpty() ? QString() : ("if (" + checkLengthCondition + ") ")) << "switch (data[" << checking << "]) {\n";
|
||||
checkLengthHistory.push_back(requiredLength);
|
||||
}
|
||||
}
|
||||
if (usedIfForCheck) {
|
||||
auto conditions = QStringList();
|
||||
if (usedIfForCheckCount > 1) {
|
||||
conditions.push_back("!memcmp(data + " + QString::number(checking) + ", \"" + name.mid(checking, usedIfForCheckCount) + "\", " + QString::number(usedIfForCheckCount) + ")");
|
||||
} else {
|
||||
conditions.push_back("data[" + QString::number(checking) + "] == '" + keyChar + "'");
|
||||
}
|
||||
if (!checkLengthCondition.isEmpty()) {
|
||||
conditions.push_front(checkLengthCondition);
|
||||
}
|
||||
source_->stream() << tabs(tabsUsed) << "if (" << conditions.join(" && ") << ") {\n";
|
||||
checkLengthHistory.push_back(requiredLength);
|
||||
checkTypes.push_back(UsedCheckType::If);
|
||||
for (auto i = 1; i != usedIfForCheckCount; ++i) {
|
||||
checkTypes.push_back(UsedCheckType::UpcomingIf);
|
||||
chars.push_back(keyChar);
|
||||
keyChar = name[checking + i];
|
||||
}
|
||||
} else {
|
||||
source_->stream() << tabs(tabsUsed) << "case '" << keyChar << "':\n";
|
||||
checkTypes.push_back(UsedCheckType::Switch);
|
||||
}
|
||||
++tabsUsed;
|
||||
chars.push_back(keyChar);
|
||||
}
|
||||
source_->stream() << tabs(tabsUsed) << "return (size == " << chars.size() << ") ? " << computeResult(name) << " : " << invalidResult << ";\n";
|
||||
}
|
||||
finishChecksTillKey(QString());
|
||||
|
||||
source_->stream() << "\
|
||||
\n\
|
||||
return " << invalidResult << ";\n";
|
||||
}
|
||||
|
||||
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
|
||||
52
Telegram/codegen/codegen/lang/generator.h
Normal file
52
Telegram/codegen/codegen/lang/generator.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QSet>
|
||||
#include "codegen/common/cpp_file.h"
|
||||
#include "codegen/lang/parsed_file.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
|
||||
class Generator {
|
||||
public:
|
||||
Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project);
|
||||
Generator(const Generator &other) = delete;
|
||||
Generator &operator=(const Generator &other) = delete;
|
||||
|
||||
bool writeHeader();
|
||||
bool writeSource();
|
||||
|
||||
private:
|
||||
void writeHeaderForwardDeclarations();
|
||||
void writeHeaderTagTypes();
|
||||
void writeHeaderInterface();
|
||||
void writeHeaderTagValueLookup();
|
||||
void writeHeaderReactiveInterface();
|
||||
void writeHeaderProducersInterface();
|
||||
void writeHeaderProducersInstances();
|
||||
|
||||
QString getFullKey(const LangPack::Entry &entry);
|
||||
|
||||
template <typename ComputeResult>
|
||||
void writeSetSearch(const std::set<QString, std::greater<>> &set, ComputeResult computeResult, const QString &invalidResult);
|
||||
|
||||
const LangPack &langpack_;
|
||||
QString basePath_, baseName_;
|
||||
const common::ProjectInfo &project_;
|
||||
std::unique_ptr<common::CppFile> source_, header_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
22
Telegram/codegen/codegen/lang/main.cpp
Normal file
22
Telegram/codegen/codegen/lang/main.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include "codegen/lang/options.h"
|
||||
#include "codegen/lang/processor.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
auto options = codegen::lang::parseOptions();
|
||||
if (options.inputPath.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
codegen::lang::Processor processor(options);
|
||||
return processor.launch();
|
||||
}
|
||||
73
Telegram/codegen/codegen/lang/options.cpp
Normal file
73
Telegram/codegen/codegen/lang/options.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/lang/options.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDir>
|
||||
#include "codegen/common/logging.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
namespace {
|
||||
|
||||
constexpr int kErrorOutputPathExpected = 902;
|
||||
constexpr int kErrorInputPathExpected = 903;
|
||||
constexpr int kErrorSingleInputPathExpected = 904;
|
||||
constexpr int kErrorWorkingPathExpected = 905;
|
||||
|
||||
} // namespace
|
||||
|
||||
using common::logError;
|
||||
|
||||
Options parseOptions() {
|
||||
Options result;
|
||||
auto args = QCoreApplication::instance()->arguments();
|
||||
for (int i = 1, count = args.size(); i < count; ++i) { // skip first
|
||||
auto &arg = args.at(i);
|
||||
|
||||
// Output path
|
||||
if (arg == "-o") {
|
||||
if (++i == count) {
|
||||
logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o";
|
||||
return Options();
|
||||
} else {
|
||||
result.outputPath = args.at(i);
|
||||
}
|
||||
} else if (arg.startsWith("-o")) {
|
||||
result.outputPath = arg.mid(2);
|
||||
|
||||
// Working path
|
||||
} else if (arg == "-w") {
|
||||
if (++i == count) {
|
||||
logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w";
|
||||
return Options();
|
||||
} else {
|
||||
common::logSetWorkingPath(args.at(i));
|
||||
}
|
||||
} else if (arg.startsWith("-w")) {
|
||||
common::logSetWorkingPath(arg.mid(2));
|
||||
|
||||
// Input path
|
||||
} else {
|
||||
if (result.inputPath.isEmpty()) {
|
||||
result.inputPath = arg;
|
||||
} else {
|
||||
logError(kErrorSingleInputPathExpected, "Command Line") << "only one input path expected";
|
||||
return Options();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.inputPath.isEmpty()) {
|
||||
logError(kErrorInputPathExpected, "Command Line") << "input path expected";
|
||||
return Options();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
24
Telegram/codegen/codegen/lang/options.h
Normal file
24
Telegram/codegen/codegen/lang/options.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
|
||||
struct Options {
|
||||
QString outputPath = ".";
|
||||
QString inputPath;
|
||||
};
|
||||
|
||||
// Parsing failed if inputPath is empty in the result.
|
||||
Options parseOptions();
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
332
Telegram/codegen/codegen/lang/parsed_file.cpp
Normal file
332
Telegram/codegen/codegen/lang/parsed_file.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/lang/parsed_file.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include "codegen/common/basic_tokenized_file.h"
|
||||
#include "codegen/common/logging.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
namespace {
|
||||
|
||||
using BasicToken = codegen::common::BasicTokenizedFile::Token;
|
||||
using BasicType = BasicToken::Type;
|
||||
|
||||
constexpr int kErrorBadString = 806;
|
||||
|
||||
bool ValidateKey(const QString &key) {
|
||||
static const auto validator = QRegularExpression("^[a-z0-9_.-]+(#(one|other))?$", QRegularExpression::CaseInsensitiveOption);
|
||||
if (!validator.match(key).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
if (key.indexOf("__") >= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateTag(const QString &tag) {
|
||||
static const auto validator = QRegularExpression("^[a-z0-9_]+$", QRegularExpression::CaseInsensitiveOption);
|
||||
if (!validator.match(tag).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
if (tag.indexOf("__") >= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString PrepareCommandString(int index) {
|
||||
static const QChar TextCommand(0x0010);
|
||||
static const QChar TextCommandLangTag(0x0020);
|
||||
auto result = QString(4, TextCommand);
|
||||
result[1] = TextCommandLangTag;
|
||||
result[2] = QChar(0x0020 + ushort(index));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::array<QString, kPluralPartCount> kPluralParts = { {
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other",
|
||||
} };
|
||||
|
||||
const std::array<QString, kPluralTagsCount> kPluralTags = { {
|
||||
"count",
|
||||
"count_short",
|
||||
"count_decimal",
|
||||
} };
|
||||
|
||||
QString ComputePluralKey(const QString &base, int index) {
|
||||
return base + "__plural" + QString::number(index);
|
||||
}
|
||||
|
||||
ParsedFile::ParsedFile(const Options &options)
|
||||
: filePath_(options.inputPath)
|
||||
, file_(filePath_)
|
||||
, options_(options) {
|
||||
}
|
||||
|
||||
bool ParsedFile::read() {
|
||||
if (!file_.read()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
if (auto keyToken = file_.getToken(BasicType::String)) {
|
||||
if (ValidateKey(keyToken.value)) {
|
||||
if (auto equals = file_.getToken(BasicType::Equals)) {
|
||||
if (auto valueToken = file_.getToken(BasicType::String)) {
|
||||
assertNextToken(BasicType::Semicolon);
|
||||
addEntity(keyToken.value, valueToken.value);
|
||||
continue;
|
||||
} else {
|
||||
logErrorUnexpectedToken() << "string value for '" << keyToken.value.toStdString() << "' key";
|
||||
}
|
||||
} else {
|
||||
logErrorUnexpectedToken() << "'=' for '" << keyToken.value.toStdString() << "' key";
|
||||
}
|
||||
} else {
|
||||
logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+(#(one|other))?$/i)";
|
||||
}
|
||||
}
|
||||
if (file_.atEnd()) {
|
||||
break;
|
||||
}
|
||||
logErrorUnexpectedToken() << "ansi string key name";
|
||||
} while (!failed());
|
||||
|
||||
fillPluralTags();
|
||||
|
||||
return !failed();
|
||||
}
|
||||
|
||||
void ParsedFile::fillPluralTags() {
|
||||
auto count = result_.entries.size();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto &baseEntry = result_.entries[i];
|
||||
if (baseEntry.keyBase.isEmpty()) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
logAssert(i + kPluralPartCount < count);
|
||||
|
||||
// Accumulate all tags from all plural variants.
|
||||
auto tags = std::vector<LangPack::Tag>();
|
||||
for (auto j = i; j != i + kPluralPartCount; ++j) {
|
||||
if (tags.empty()) {
|
||||
tags = result_.entries[j].tags;
|
||||
} else {
|
||||
for (auto &tag : result_.entries[j].tags) {
|
||||
if (std::find(tags.begin(), tags.end(), tag) == tags.end()) {
|
||||
tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logAssert(!tags.empty());
|
||||
logAssert(tags.front().tag == kPluralTags[0]);
|
||||
|
||||
// Set this tags list to all plural variants.
|
||||
for (auto j = i; j != i + kPluralPartCount; ++j) {
|
||||
result_.entries[j].tags = tags;
|
||||
}
|
||||
|
||||
i += kPluralPartCount;
|
||||
}
|
||||
}
|
||||
|
||||
BasicToken ParsedFile::assertNextToken(BasicToken::Type type) {
|
||||
auto result = file_.getToken(type);
|
||||
if (!result) {
|
||||
logErrorUnexpectedToken() << type;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
common::LogStream ParsedFile::logErrorBadString() {
|
||||
return logError(kErrorBadString);
|
||||
}
|
||||
|
||||
QString ParsedFile::extractTagsData(const QString &value, LangPack *to) {
|
||||
auto tagStart = value.indexOf('{');
|
||||
if (tagStart < 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
auto tagEnd = 0;
|
||||
auto finalValue = QString();
|
||||
finalValue.reserve(value.size() * 2);
|
||||
while (tagStart >= 0) {
|
||||
if (tagStart > tagEnd) {
|
||||
finalValue.append(base::StringViewMid(value, tagEnd, tagStart - tagEnd));
|
||||
}
|
||||
++tagStart;
|
||||
tagEnd = value.indexOf('}', tagStart);
|
||||
if (tagEnd < 0) {
|
||||
logErrorBadString() << "unexpected end of value, end of tag expected.";
|
||||
return value;
|
||||
}
|
||||
finalValue.append(extractTagData(value.mid(tagStart, tagEnd - tagStart), to));
|
||||
++tagEnd;
|
||||
tagStart = value.indexOf('{', tagEnd);
|
||||
}
|
||||
if (tagEnd < value.size()) {
|
||||
finalValue.append(base::StringViewMid(value, tagEnd));
|
||||
}
|
||||
return finalValue;
|
||||
}
|
||||
|
||||
QString ParsedFile::extractTagData(const QString &tagText, LangPack *to) {
|
||||
auto numericPart = tagText.indexOf(':');
|
||||
auto tag = (numericPart > 0) ? tagText.mid(0, numericPart) : tagText;
|
||||
if (!ValidateTag(tag)) {
|
||||
logErrorBadString() << "bad tag characters: '" << tagText.toStdString() << "'";
|
||||
return QString();
|
||||
}
|
||||
for (auto &previousTag : to->tags) {
|
||||
if (previousTag.tag == tag) {
|
||||
logErrorBadString() << "duplicate found for tag '" << tagText.toStdString() << "'";
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
auto index = 0;
|
||||
auto tagIndex = result_.tags.size();
|
||||
for (auto &alreadyTag : result_.tags) {
|
||||
if (alreadyTag.tag == tag) {
|
||||
tagIndex = index;
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
if (tagIndex == result_.tags.size()) {
|
||||
result_.tags.push_back({ tag });
|
||||
}
|
||||
if (numericPart > 0) {
|
||||
auto numericParts = tagText.mid(numericPart + 1).split('|');
|
||||
if (numericParts.size() != 3) {
|
||||
logErrorBadString() << "bad option count for plural key part in tag: '" << tagText.toStdString() << "'";
|
||||
return QString();
|
||||
}
|
||||
auto index = 0;
|
||||
for (auto &part : numericParts) {
|
||||
auto numericPartEntry = LangPack::Entry();
|
||||
numericPartEntry.key = tag + QString::number(index++);
|
||||
if (part.indexOf('#') != part.lastIndexOf('#')) {
|
||||
logErrorBadString() << "bad option for plural key part in tag: '" << tagText.toStdString() << "', too many '#'.";
|
||||
return QString();
|
||||
}
|
||||
numericPartEntry.value = part.replace('#', PrepareCommandString(tagIndex));
|
||||
to->entries.push_back(numericPartEntry);
|
||||
}
|
||||
}
|
||||
to->tags.push_back({ tag });
|
||||
return PrepareCommandString(tagIndex);
|
||||
}
|
||||
|
||||
void ParsedFile::addEntity(QString key, const QString &value) {
|
||||
auto pluralPartOffset = key.indexOf('#');
|
||||
auto pluralIndex = -1;
|
||||
if (pluralPartOffset >= 0) {
|
||||
auto pluralPart = key.mid(pluralPartOffset + 1);
|
||||
pluralIndex = std::find(kPluralParts.begin(), kPluralParts.end(), pluralPart) - kPluralParts.begin();
|
||||
if (pluralIndex < 0 || pluralIndex >= kPluralParts.size()) {
|
||||
logErrorBadString() << "bad plural part for key '" << key.toStdString() << "': '" << pluralPart.toStdString() << "'";
|
||||
return;
|
||||
}
|
||||
key = key.mid(0, pluralPartOffset);
|
||||
}
|
||||
auto checkKey = [this](const QString &key) {
|
||||
for (auto &entry : result_.entries) {
|
||||
if (entry.key == key) {
|
||||
if (entry.keyBase.isEmpty() || !entry.tags.empty()) {
|
||||
// Empty tags in plural entry means it was not encountered yet.
|
||||
logErrorBadString() << "duplicate found for key '" << key.toStdString() << "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!checkKey(key)) {
|
||||
return;
|
||||
}
|
||||
auto tagsData = LangPack();
|
||||
auto entry = LangPack::Entry();
|
||||
entry.key = key;
|
||||
entry.value = extractTagsData(value, &tagsData);
|
||||
entry.tags = tagsData.tags;
|
||||
if (pluralIndex >= 0) {
|
||||
logAssert(tagsData.entries.empty());
|
||||
|
||||
entry.keyBase = entry.key;
|
||||
entry.key = ComputePluralKey(entry.keyBase, pluralIndex);
|
||||
if (!checkKey(entry.key)) {
|
||||
return;
|
||||
}
|
||||
auto baseIndex = -1;
|
||||
auto alreadyCount = result_.entries.size();
|
||||
for (auto i = 0; i != alreadyCount; ++i) {
|
||||
if (result_.entries[i].keyBase == entry.keyBase) {
|
||||
// This is not the first appearance of this plural key.
|
||||
baseIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (baseIndex < 0) {
|
||||
baseIndex = result_.entries.size();
|
||||
for (auto i = 0; i != kPluralPartCount; ++i) {
|
||||
auto addingEntry = LangPack::Entry();
|
||||
addingEntry.keyBase = entry.keyBase;
|
||||
addingEntry.key = ComputePluralKey(entry.keyBase, i);
|
||||
result_.entries.push_back(addingEntry);
|
||||
}
|
||||
}
|
||||
auto entryIndex = baseIndex + pluralIndex;
|
||||
logAssert(entryIndex < result_.entries.size());
|
||||
auto &realEntry = result_.entries[entryIndex];
|
||||
logAssert(realEntry.key == entry.key);
|
||||
realEntry.value = entry.value;
|
||||
|
||||
// Add all new tags to the existing ones.
|
||||
realEntry.tags = std::vector<LangPack::Tag>(1, LangPack::Tag{ kPluralTags[0] });
|
||||
|
||||
for (auto &tag : entry.tags) {
|
||||
if (std::find(realEntry.tags.begin(), realEntry.tags.end(), tag) == realEntry.tags.end()) {
|
||||
realEntry.tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result_.entries.push_back(entry);
|
||||
for (auto &tag : entry.tags) {
|
||||
const auto plural = std::find(std::begin(kPluralTags), std::end(kPluralTags), tag.tag);
|
||||
if (plural != std::end(kPluralTags)) {
|
||||
logErrorBadString() << "plural tag '" << tag.tag.toStdString() << "' used in non-plural key '" << key.toStdString() << "'";
|
||||
}
|
||||
}
|
||||
for (auto &tagEntry : tagsData.entries) {
|
||||
auto taggedEntry = LangPack::Entry();
|
||||
taggedEntry.key = key + "__" + tagEntry.key;
|
||||
taggedEntry.value = tagEntry.value;
|
||||
result_.entries.push_back(taggedEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
106
Telegram/codegen/codegen/lang/parsed_file.h
Normal file
106
Telegram/codegen/codegen/lang/parsed_file.h
Normal file
@@ -0,0 +1,106 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <QImage>
|
||||
#include "codegen/common/basic_tokenized_file.h"
|
||||
#include "codegen/lang/options.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
|
||||
constexpr auto kPluralPartCount = 6;
|
||||
extern const std::array<QString, kPluralPartCount> kPluralParts;
|
||||
|
||||
constexpr auto kPluralTagsCount = 3;
|
||||
extern const std::array<QString, kPluralTagsCount> kPluralTags;
|
||||
|
||||
QString ComputePluralKey(const QString &base, int index);
|
||||
|
||||
struct LangPack {
|
||||
struct Tag {
|
||||
QString tag;
|
||||
};
|
||||
struct Entry {
|
||||
QString key;
|
||||
QString value;
|
||||
QString keyBase; // Empty for not plural entries.
|
||||
std::vector<Tag> tags;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
std::vector<Tag> tags;
|
||||
|
||||
};
|
||||
|
||||
inline bool operator==(const LangPack::Tag &a, const LangPack::Tag &b) {
|
||||
return a.tag == b.tag;
|
||||
}
|
||||
|
||||
inline bool operator!=(const LangPack::Tag &a, const LangPack::Tag &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
// Parses an input file to the internal struct.
|
||||
class ParsedFile {
|
||||
public:
|
||||
explicit ParsedFile(const Options &options);
|
||||
ParsedFile(const ParsedFile &other) = delete;
|
||||
ParsedFile &operator=(const ParsedFile &other) = delete;
|
||||
|
||||
bool read();
|
||||
|
||||
LangPack getResult() {
|
||||
return result_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool failed() const {
|
||||
return failed_ || file_.failed();
|
||||
}
|
||||
|
||||
// Log error to std::cerr with 'code' at the current position in file.
|
||||
common::LogStream logError(int code) {
|
||||
failed_ = true;
|
||||
return file_.logError(code);
|
||||
}
|
||||
common::LogStream logErrorUnexpectedToken() {
|
||||
failed_ = true;
|
||||
return file_.logErrorUnexpectedToken();
|
||||
}
|
||||
common::LogStream logErrorBadString();
|
||||
common::LogStream logAssert(bool assertion) {
|
||||
if (!assertion) {
|
||||
return logError(common::kErrorInternal) << "internal - ";
|
||||
}
|
||||
return common::LogStream(common::LogStream::Null);
|
||||
}
|
||||
|
||||
// Read next token and fire unexpected token error if it is not of "type".
|
||||
using BasicToken = common::BasicTokenizedFile::Token;
|
||||
BasicToken assertNextToken(BasicToken::Type type);
|
||||
|
||||
void addEntity(QString key, const QString &value);
|
||||
QString extractTagsData(const QString &value, LangPack *to);
|
||||
QString extractTagData(const QString &tag, LangPack *to);
|
||||
|
||||
void fillPluralTags();
|
||||
|
||||
QString filePath_;
|
||||
common::BasicTokenizedFile file_;
|
||||
Options options_;
|
||||
bool failed_ = false;
|
||||
LangPack result_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
69
Telegram/codegen/codegen/lang/processor.cpp
Normal file
69
Telegram/codegen/codegen/lang/processor.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/lang/processor.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include "codegen/common/cpp_file.h"
|
||||
#include "codegen/lang/parsed_file.h"
|
||||
#include "codegen/lang/generator.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
namespace {
|
||||
|
||||
constexpr int kErrorCantWritePath = 821;
|
||||
|
||||
} // namespace
|
||||
|
||||
Processor::Processor(const Options &options)
|
||||
: parser_(std::make_unique<ParsedFile>(options))
|
||||
, options_(options) {
|
||||
}
|
||||
|
||||
int Processor::launch() {
|
||||
if (!parser_->read()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!write(parser_->getResult())) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Processor::write(const LangPack &langpack) const {
|
||||
bool forceReGenerate = false;
|
||||
QDir dir(options_.outputPath);
|
||||
if (!dir.mkpath(".")) {
|
||||
common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo srcFile(options_.inputPath);
|
||||
QString dstFilePath = dir.absolutePath() + "/lang_auto";
|
||||
|
||||
common::ProjectInfo project = {
|
||||
"codegen_style",
|
||||
srcFile.fileName(),
|
||||
forceReGenerate
|
||||
};
|
||||
|
||||
Generator generator(langpack, dstFilePath, project);
|
||||
if (!generator.writeHeader()
|
||||
|| !generator.writeSource()
|
||||
|| !common::TouchTimestamp(dstFilePath)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Processor::~Processor() = default;
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
39
Telegram/codegen/codegen/lang/processor.h
Normal file
39
Telegram/codegen/codegen/lang/processor.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QtCore/QString>
|
||||
#include "codegen/lang/options.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace lang {
|
||||
class ParsedFile;
|
||||
struct LangPack;
|
||||
|
||||
// Walks through a file, parses it and generates the output.
|
||||
class Processor {
|
||||
public:
|
||||
explicit Processor(const Options &options);
|
||||
Processor(const Processor &other) = delete;
|
||||
Processor &operator=(const Processor &other) = delete;
|
||||
|
||||
// Returns 0 on success.
|
||||
int launch();
|
||||
|
||||
~Processor();
|
||||
|
||||
private:
|
||||
bool write(const LangPack &langpack) const;
|
||||
|
||||
std::unique_ptr<ParsedFile> parser_;
|
||||
const Options &options_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
33
Telegram/codegen/codegen/style/CMakeLists.txt
Normal file
33
Telegram/codegen/codegen/style/CMakeLists.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
add_executable(codegen_style)
|
||||
init_target(codegen_style "(codegen)")
|
||||
|
||||
get_filename_component(src_loc ../.. REALPATH)
|
||||
|
||||
nice_target_sources(codegen_style ${src_loc}
|
||||
PRIVATE
|
||||
codegen/style/generator.cpp
|
||||
codegen/style/generator.h
|
||||
codegen/style/main.cpp
|
||||
codegen/style/module.cpp
|
||||
codegen/style/module.h
|
||||
codegen/style/options.cpp
|
||||
codegen/style/options.h
|
||||
codegen/style/parsed_file.cpp
|
||||
codegen/style/parsed_file.h
|
||||
codegen/style/processor.cpp
|
||||
codegen/style/processor.h
|
||||
codegen/style/structure_types.cpp
|
||||
codegen/style/structure_types.h
|
||||
)
|
||||
|
||||
target_include_directories(codegen_style
|
||||
PUBLIC
|
||||
${src_loc}
|
||||
)
|
||||
|
||||
target_link_libraries(codegen_style
|
||||
PUBLIC
|
||||
desktop-app::codegen_common
|
||||
desktop-app::lib_base
|
||||
desktop-app::lib_crl
|
||||
)
|
||||
1290
Telegram/codegen/codegen/style/generator.cpp
Normal file
1290
Telegram/codegen/codegen/style/generator.cpp
Normal file
File diff suppressed because it is too large
Load Diff
73
Telegram/codegen/codegen/style/generator.h
Normal file
73
Telegram/codegen/codegen/style/generator.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QMap>
|
||||
#include "codegen/common/cpp_file.h"
|
||||
#include "codegen/style/structure_types.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace structure {
|
||||
class Module;
|
||||
} // namespace structure
|
||||
|
||||
class Generator {
|
||||
public:
|
||||
Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project, bool isPalette);
|
||||
Generator(const Generator &other) = delete;
|
||||
Generator &operator=(const Generator &other) = delete;
|
||||
|
||||
bool writeHeader();
|
||||
bool writeSource();
|
||||
|
||||
private:
|
||||
QString typeToString(structure::Type type) const;
|
||||
QString typeToDefaultValue(structure::Type type) const;
|
||||
QString valueAssignmentCode(
|
||||
structure::Value value,
|
||||
bool ignoreCopy = false) const;
|
||||
|
||||
bool writeHeaderRequiredIncludes();
|
||||
bool writeHeaderStyleNamespace();
|
||||
bool writeStructsForwardDeclarations();
|
||||
bool writeStructsDefinitions();
|
||||
bool writePaletteDefinition();
|
||||
bool writeRefsDeclarations();
|
||||
|
||||
bool writeIncludesInSource();
|
||||
bool writeVariableDefinitions();
|
||||
bool writeRefsDefinition();
|
||||
bool writeSetPaletteColor();
|
||||
bool writeVariableInit();
|
||||
bool writePxValuesInit();
|
||||
bool writeFontFamiliesInit();
|
||||
bool writeIconValues();
|
||||
bool writeIconsInit();
|
||||
|
||||
bool collectUniqueValues();
|
||||
|
||||
const structure::Module &module_;
|
||||
QString basePath_, baseName_;
|
||||
const common::ProjectInfo &project_;
|
||||
std::unique_ptr<common::CppFile> source_, header_;
|
||||
bool isPalette_ = false;
|
||||
|
||||
QMap<int, bool> pxValues_;
|
||||
QMap<std::string, int> fontFamilies_;
|
||||
QMap<QString, int> iconMasks_; // icon file -> index
|
||||
std::map<QString, int, std::greater<QString>> paletteIndices_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
22
Telegram/codegen/codegen/style/main.cpp
Normal file
22
Telegram/codegen/codegen/style/main.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include "codegen/style/options.h"
|
||||
#include "codegen/style/processor.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
auto options = codegen::style::parseOptions();
|
||||
if (options.inputPaths.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
codegen::style::Processor processor(options);
|
||||
return processor.launch();
|
||||
}
|
||||
89
Telegram/codegen/codegen/style/module.cpp
Normal file
89
Telegram/codegen/codegen/style/module.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/style/module.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace structure {
|
||||
namespace {
|
||||
|
||||
QString fullNameKey(const FullName &name) {
|
||||
return name.join('.');
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Module::Module(const QString &fullpath) : fullpath_(fullpath) {
|
||||
}
|
||||
|
||||
void Module::addIncluded(std::shared_ptr<const Module> value) {
|
||||
included_.push_back(std::move(value));
|
||||
}
|
||||
|
||||
bool Module::addStruct(const Struct &value) {
|
||||
if (findStruct(value.name)) {
|
||||
return false;
|
||||
}
|
||||
structsByName_.insert(fullNameKey(value.name), structs_.size());
|
||||
structs_.push_back(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Struct *Module::findStruct(const FullName &name) const {
|
||||
if (auto result = findStructInModule(name, *this)) {
|
||||
return result;
|
||||
}
|
||||
for (const auto &module : included_) {
|
||||
if (auto result = module->findStruct(name)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Module::addVariable(const Variable &value) {
|
||||
if (findVariable(value.name)) {
|
||||
return false;
|
||||
}
|
||||
variablesByName_.insert(fullNameKey(value.name), variables_.size());
|
||||
variables_.push_back(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Variable *Module::findVariable(const FullName &name, bool *outFromThisModule) const {
|
||||
if (auto result = findVariableInModule(name, *this)) {
|
||||
if (outFromThisModule) *outFromThisModule = true;
|
||||
return result;
|
||||
}
|
||||
for (const auto &module : included_) {
|
||||
if (auto result = module->findVariable(name)) {
|
||||
if (outFromThisModule) *outFromThisModule = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Struct *Module::findStructInModule(const FullName &name, const Module &module) {
|
||||
auto index = module.structsByName_.value(fullNameKey(name), -1);
|
||||
if (index < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return &module.structs_.at(index);
|
||||
}
|
||||
|
||||
const Variable *Module::findVariableInModule(const FullName &name, const Module &module) {
|
||||
auto index = module.variablesByName_.value(fullNameKey(name), -1);
|
||||
if (index < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return &module.variables_.at(index);
|
||||
}
|
||||
|
||||
} // namespace structure
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
101
Telegram/codegen/codegen/style/module.h
Normal file
101
Telegram/codegen/codegen/style/module.h
Normal file
@@ -0,0 +1,101 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMap>
|
||||
#include <vector>
|
||||
#include "codegen/style/structure_types.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace structure {
|
||||
|
||||
class Module {
|
||||
public:
|
||||
|
||||
explicit Module(const QString &fullpath);
|
||||
|
||||
QString filepath() const {
|
||||
return fullpath_;
|
||||
}
|
||||
|
||||
void addIncluded(std::shared_ptr<const Module> value);
|
||||
|
||||
bool hasIncludes() const {
|
||||
return !included_.empty();
|
||||
}
|
||||
template <typename F>
|
||||
bool enumIncludes(F functor) const {
|
||||
for (const auto &module : included_) {
|
||||
if (!functor(*module)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns false if there is a struct with such name already.
|
||||
bool addStruct(const Struct &value);
|
||||
// Returns nullptr if there is no such struct in result_ or any of included modules.
|
||||
const Struct *findStruct(const FullName &name) const;
|
||||
bool hasStructs() const {
|
||||
return !structs_.isEmpty();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool enumStructs(F functor) const {
|
||||
for (const auto &value : structs_) {
|
||||
if (!functor(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns false if there is a variable with such name already.
|
||||
bool addVariable(const Variable &value);
|
||||
// Returns nullptr if there is no such variable in result_ or any of included modules.
|
||||
const Variable *findVariable(const FullName &name, bool *outFromThisModule = nullptr) const;
|
||||
bool hasVariables() const {
|
||||
return !variables_.isEmpty();
|
||||
}
|
||||
int variablesCount() const {
|
||||
return variables_.size();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool enumVariables(F functor) const {
|
||||
for (const auto &value : variables_) {
|
||||
if (!functor(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !fullpath_.isEmpty();
|
||||
}
|
||||
|
||||
static const Struct *findStructInModule(const FullName &name, const Module &module);
|
||||
static const Variable *findVariableInModule(const FullName &name, const Module &module);
|
||||
|
||||
private:
|
||||
QString fullpath_;
|
||||
std::vector<std::shared_ptr<const Module>> included_;
|
||||
QList<Struct> structs_;
|
||||
QList<Variable> variables_;
|
||||
QMap<QString, int> structsByName_;
|
||||
QMap<QString, int> variablesByName_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace structure
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
96
Telegram/codegen/codegen/style/options.cpp
Normal file
96
Telegram/codegen/codegen/style/options.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/style/options.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDir>
|
||||
#include "codegen/common/logging.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace {
|
||||
|
||||
constexpr int kErrorIncludePathExpected = 901;
|
||||
constexpr int kErrorOutputPathExpected = 902;
|
||||
constexpr int kErrorInputPathExpected = 903;
|
||||
constexpr int kErrorWorkingPathExpected = 905;
|
||||
|
||||
} // namespace
|
||||
|
||||
using common::logError;
|
||||
|
||||
Options parseOptions() {
|
||||
Options result;
|
||||
auto args = QCoreApplication::instance()->arguments();
|
||||
for (int i = 1, count = args.size(); i < count; ++i) { // skip first
|
||||
auto &arg = args.at(i);
|
||||
|
||||
// Include paths
|
||||
if (arg == "-I") {
|
||||
if (++i == count) {
|
||||
logError(kErrorIncludePathExpected, "Command Line") << "include path expected after -I";
|
||||
return Options();
|
||||
} else {
|
||||
result.includePaths.push_back(args.at(i));
|
||||
}
|
||||
} else if (arg.startsWith("-I")) {
|
||||
result.includePaths.push_back(arg.mid(2));
|
||||
|
||||
// Output path
|
||||
} else if (arg == "-o") {
|
||||
if (++i == count) {
|
||||
logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o";
|
||||
return Options();
|
||||
} else {
|
||||
result.outputPath = args.at(i);
|
||||
}
|
||||
} else if (arg.startsWith("-o")) {
|
||||
result.outputPath = arg.mid(2);
|
||||
|
||||
// Timestamp path
|
||||
} else if (arg == "-t") {
|
||||
if (++i == count) {
|
||||
logError(kErrorOutputPathExpected, "Command Line") << "timestamp path expected after -t";
|
||||
return Options();
|
||||
} else {
|
||||
result.timestampPath = args.at(i);
|
||||
}
|
||||
} else if (arg.startsWith("-t")) {
|
||||
result.timestampPath = arg.mid(2);
|
||||
|
||||
// Working path
|
||||
} else if (arg == "-w") {
|
||||
if (++i == count) {
|
||||
logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w";
|
||||
return Options();
|
||||
} else {
|
||||
common::logSetWorkingPath(args.at(i));
|
||||
}
|
||||
} else if (arg.startsWith("-w")) {
|
||||
common::logSetWorkingPath(arg.mid(2));
|
||||
|
||||
// Input path
|
||||
} else {
|
||||
result.inputPaths.push_back(arg);
|
||||
}
|
||||
}
|
||||
if (result.timestampPath.isEmpty()) {
|
||||
logError(kErrorInputPathExpected, "Command Line") << "timestamp path expected";
|
||||
return Options();
|
||||
}
|
||||
if (result.inputPaths.isEmpty()) {
|
||||
logError(kErrorInputPathExpected, "Command Line") << "input path expected";
|
||||
return Options();
|
||||
}
|
||||
result.isPalette = (result.inputPaths.size() == 1)
|
||||
&& (QFileInfo(result.inputPaths.front()).suffix() == "palette");
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
27
Telegram/codegen/codegen/style/options.h
Normal file
27
Telegram/codegen/codegen/style/options.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
|
||||
struct Options {
|
||||
QStringList includePaths = { "." };
|
||||
QString outputPath = ".";
|
||||
QString timestampPath;
|
||||
QStringList inputPaths;
|
||||
bool isPalette = false;
|
||||
};
|
||||
|
||||
// Parsing failed if inputPath is empty in the result.
|
||||
Options parseOptions();
|
||||
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
1048
Telegram/codegen/codegen/style/parsed_file.cpp
Normal file
1048
Telegram/codegen/codegen/style/parsed_file.cpp
Normal file
File diff suppressed because it is too large
Load Diff
139
Telegram/codegen/codegen/style/parsed_file.h
Normal file
139
Telegram/codegen/codegen/style/parsed_file.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <QImage>
|
||||
#include "codegen/common/basic_tokenized_file.h"
|
||||
#include "codegen/style/options.h"
|
||||
#include "codegen/style/module.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
|
||||
using Modifier = std::function<void(QImage &image)>;
|
||||
Modifier GetModifier(const QString &name);
|
||||
|
||||
[[nodiscard]] std::optional<QSize> GetSizeModifier(const QString &value);
|
||||
|
||||
// Parses an input file to the internal struct.
|
||||
class ParsedFile {
|
||||
public:
|
||||
ParsedFile(
|
||||
std::map<QString, std::shared_ptr<const structure::Module>> &includeCache,
|
||||
const Options &options,
|
||||
int index = 0,
|
||||
std::vector<QString> includeStack = {});
|
||||
ParsedFile(const ParsedFile &other) = delete;
|
||||
ParsedFile &operator=(const ParsedFile &other) = delete;
|
||||
|
||||
bool read();
|
||||
|
||||
std::unique_ptr<const structure::Module> getResult() {
|
||||
return std::move(module_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool failed() const {
|
||||
return failed_ || file_.failed();
|
||||
}
|
||||
|
||||
// Log error to std::cerr with 'code' at the current position in file.
|
||||
common::LogStream logError(int code) {
|
||||
failed_ = true;
|
||||
return file_.logError(code);
|
||||
}
|
||||
common::LogStream logErrorUnexpectedToken() {
|
||||
failed_ = true;
|
||||
return file_.logErrorUnexpectedToken();
|
||||
}
|
||||
common::LogStream logErrorTypeMismatch();
|
||||
common::LogStream logAssert(bool assertion) {
|
||||
if (!assertion) {
|
||||
return logError(common::kErrorInternal) << "internal - ";
|
||||
}
|
||||
return common::LogStream(common::LogStream::Null);
|
||||
}
|
||||
|
||||
// Helper methods for context-dependent reading.
|
||||
std::shared_ptr<const structure::Module> readIncluded();
|
||||
structure::Struct readStruct(const QString &name);
|
||||
structure::Variable readVariable(const QString &name);
|
||||
|
||||
structure::StructField readStructField(const QString &name);
|
||||
structure::Type readType();
|
||||
structure::Value readValue();
|
||||
|
||||
structure::Value readStructValue();
|
||||
structure::Value defaultConstructedStruct(const structure::FullName &name);
|
||||
void applyStructParent(structure::Value &result, const structure::FullName &parentName);
|
||||
bool readStructValueInner(structure::Value &result);
|
||||
bool checkNoImplicitUnnamedIcons(const structure::Value &value);
|
||||
bool assignStructField(structure::Value &result, const structure::Variable &field);
|
||||
bool readStructParents(structure::Value &result);
|
||||
|
||||
// Simple methods for reading value types.
|
||||
structure::Value readPositiveValue();
|
||||
structure::Value readNumericValue();
|
||||
structure::Value readStringValue();
|
||||
structure::Value readColorValue();
|
||||
structure::Value readPointValue();
|
||||
structure::Value readSizeValue();
|
||||
structure::Value readBoolValue();
|
||||
structure::Value readAlignValue();
|
||||
structure::Value readMarginsValue();
|
||||
structure::Value readFontValue();
|
||||
structure::Value readIconValue();
|
||||
structure::Value readCopyValue();
|
||||
|
||||
structure::Value readNumericOrNumericCopyValue();
|
||||
structure::Value readStringOrStringCopyValue();
|
||||
|
||||
structure::data::monoicon readMonoIconFields();
|
||||
QString readMonoIconFilename();
|
||||
|
||||
// Read next token and fire unexpected token error if it is not of "type".
|
||||
using BasicToken = common::BasicTokenizedFile::Token;
|
||||
BasicToken assertNextToken(BasicToken::Type type);
|
||||
|
||||
// Look through include directories in options_ and find absolute include path.
|
||||
Options includedOptions(const QString &filepath);
|
||||
|
||||
// Compose context-dependent full name.
|
||||
structure::FullName composeFullName(const QString &name);
|
||||
|
||||
std::map<QString, std::shared_ptr<const structure::Module>> includeCache_;
|
||||
|
||||
QString filePath_;
|
||||
common::BasicTokenizedFile file_;
|
||||
Options options_;
|
||||
bool failed_ = false;
|
||||
std::unique_ptr<structure::Module> module_;
|
||||
|
||||
std::vector<QString> includeStack_;
|
||||
|
||||
QMap<std::string, structure::Type> typeNames_ = {
|
||||
{ "int" , { structure::TypeTag::Int } },
|
||||
{ "bool" , { structure::TypeTag::Bool } },
|
||||
{ "double" , { structure::TypeTag::Double } },
|
||||
{ "pixels" , { structure::TypeTag::Pixels } },
|
||||
{ "string" , { structure::TypeTag::String } },
|
||||
{ "color" , { structure::TypeTag::Color } },
|
||||
{ "point" , { structure::TypeTag::Point } },
|
||||
{ "size" , { structure::TypeTag::Size } },
|
||||
{ "align" , { structure::TypeTag::Align } },
|
||||
{ "margins" , { structure::TypeTag::Margins } },
|
||||
{ "font" , { structure::TypeTag::Font } },
|
||||
{ "icon" , { structure::TypeTag::Icon } },
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
77
Telegram/codegen/codegen/style/processor.cpp
Normal file
77
Telegram/codegen/codegen/style/processor.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/style/processor.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include "codegen/common/cpp_file.h"
|
||||
#include "codegen/style/parsed_file.h"
|
||||
#include "codegen/style/generator.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace {
|
||||
|
||||
constexpr int kErrorCantWritePath = 821;
|
||||
|
||||
QString destFileBaseName(const structure::Module &module) {
|
||||
return "style_" + QFileInfo(module.filepath()).baseName();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Processor::Processor(const Options &options)
|
||||
: options_(options) {
|
||||
}
|
||||
|
||||
int Processor::launch() {
|
||||
auto cache = std::map<QString, std::shared_ptr<const structure::Module>>();
|
||||
for (auto i = 0; i != options_.inputPaths.size(); ++i) {
|
||||
auto parser = ParsedFile(cache, options_, i);
|
||||
if (!parser.read()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto module = parser.getResult();
|
||||
if (!write(*module)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (!common::TouchTimestamp(options_.timestampPath)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Processor::write(const structure::Module &module) const {
|
||||
bool forceReGenerate = false;
|
||||
QDir dir(options_.outputPath);
|
||||
if (!dir.mkpath(".")) {
|
||||
common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo srcFile(module.filepath());
|
||||
QString dstFilePath = dir.absolutePath() + '/' + (options_.isPalette ? "palette" : destFileBaseName(module));
|
||||
|
||||
common::ProjectInfo project = {
|
||||
"codegen_style",
|
||||
srcFile.fileName(),
|
||||
forceReGenerate
|
||||
};
|
||||
|
||||
Generator generator(module, dstFilePath, project, options_.isPalette);
|
||||
if (!generator.writeHeader() || !generator.writeSource()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Processor::~Processor() = default;
|
||||
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
41
Telegram/codegen/codegen/style/processor.h
Normal file
41
Telegram/codegen/codegen/style/processor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QtCore/QString>
|
||||
#include "codegen/style/options.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace structure {
|
||||
class Module;
|
||||
} // namespace structure
|
||||
class ParsedFile;
|
||||
|
||||
// Walks through a file, parses it and parses dependency files if necessary.
|
||||
// Uses Generator class to produce the final output.
|
||||
class Processor {
|
||||
public:
|
||||
explicit Processor(const Options &options);
|
||||
Processor(const Processor &other) = delete;
|
||||
Processor &operator=(const Processor &other) = delete;
|
||||
|
||||
// Returns 0 on success.
|
||||
int launch();
|
||||
|
||||
~Processor();
|
||||
|
||||
private:
|
||||
bool write(const structure::Module &module) const;
|
||||
|
||||
const Options &options_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
219
Telegram/codegen/codegen/style/structure_types.cpp
Normal file
219
Telegram/codegen/codegen/style/structure_types.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "codegen/style/structure_types.h"
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace structure {
|
||||
|
||||
data::icon Value::Icon() const { return data_->Icon(); }
|
||||
data::icon Value::DataBase::Icon() const { return {}; }
|
||||
|
||||
struct Value::DataTypes {
|
||||
class TInt : public DataBase {
|
||||
public:
|
||||
TInt(int value) : value_(value) {
|
||||
}
|
||||
int Int() const override { return value_; }
|
||||
|
||||
private:
|
||||
int value_;
|
||||
|
||||
};
|
||||
|
||||
class TBool : public DataBase {
|
||||
public:
|
||||
TBool(bool value) : value_(value) {
|
||||
}
|
||||
bool Bool() const override { return value_; }
|
||||
|
||||
private:
|
||||
bool value_;
|
||||
|
||||
};
|
||||
|
||||
class TDouble : public DataBase {
|
||||
public:
|
||||
TDouble(double value) : value_(value) {
|
||||
}
|
||||
double Double() const override { return value_; }
|
||||
|
||||
private:
|
||||
double value_;
|
||||
|
||||
};
|
||||
|
||||
class TString : public DataBase {
|
||||
public:
|
||||
TString(std::string value) : value_(value) {
|
||||
}
|
||||
std::string String() const override { return value_; }
|
||||
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
};
|
||||
|
||||
class TPoint : public DataBase {
|
||||
public:
|
||||
TPoint(data::point value) : value_(value) {
|
||||
}
|
||||
data::point Point() const override { return value_; }
|
||||
|
||||
private:
|
||||
data::point value_;
|
||||
|
||||
};
|
||||
|
||||
class TSize : public DataBase {
|
||||
public:
|
||||
TSize(data::size value) : value_(value) {
|
||||
}
|
||||
data::size Size() const override { return value_; }
|
||||
|
||||
private:
|
||||
data::size value_;
|
||||
|
||||
};
|
||||
|
||||
class TColor : public DataBase {
|
||||
public:
|
||||
TColor(data::color value) : value_(value) {
|
||||
}
|
||||
data::color Color() const override { return value_; }
|
||||
|
||||
private:
|
||||
data::color value_;
|
||||
|
||||
};
|
||||
|
||||
class TMargins : public DataBase {
|
||||
public:
|
||||
TMargins(data::margins value) : value_(value) {
|
||||
}
|
||||
data::margins Margins() const override { return value_; }
|
||||
|
||||
private:
|
||||
data::margins value_;
|
||||
|
||||
};
|
||||
|
||||
class TFont : public DataBase {
|
||||
public:
|
||||
TFont(data::font value) : value_(value) {
|
||||
}
|
||||
data::font Font() const override { return value_; }
|
||||
|
||||
private:
|
||||
data::font value_;
|
||||
|
||||
};
|
||||
|
||||
class TIcon : public DataBase {
|
||||
public:
|
||||
TIcon(data::icon value) : value_(value) {
|
||||
}
|
||||
data::icon Icon() const override { return value_; }
|
||||
|
||||
private:
|
||||
data::icon value_;
|
||||
|
||||
};
|
||||
|
||||
class TFields : public DataBase {
|
||||
public:
|
||||
TFields(data::fields value) : value_(value) {
|
||||
}
|
||||
const data::fields *Fields() const override { return &value_; }
|
||||
data::fields *Fields() override { return &value_; }
|
||||
|
||||
private:
|
||||
data::fields value_;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
Value::Value() : Value(TypeTag::Invalid, std::make_shared<DataBase>()) {
|
||||
}
|
||||
|
||||
Value::Value(data::point value) : Value(TypeTag::Point, std::make_shared<DataTypes::TPoint>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(data::size value) : Value(TypeTag::Size, std::make_shared<DataTypes::TSize>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(data::color value) : Value(TypeTag::Color, std::make_shared<DataTypes::TColor>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(data::margins value) : Value(TypeTag::Margins, std::make_shared<DataTypes::TMargins>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(data::font value) : Value(TypeTag::Font, std::make_shared<DataTypes::TFont>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(data::icon value) : Value(TypeTag::Icon, std::make_shared<DataTypes::TIcon>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(const FullName &type, data::fields value)
|
||||
: type_ { TypeTag::Struct, type }
|
||||
, data_(std::make_shared<DataTypes::TFields>(value)) {
|
||||
}
|
||||
|
||||
Value::Value(TypeTag type, double value) : Value(type, std::make_shared<DataTypes::TDouble>(value)) {
|
||||
if (type_.tag != TypeTag::Double) {
|
||||
type_.tag = TypeTag::Invalid;
|
||||
data_ = std::make_shared<DataBase>();
|
||||
}
|
||||
}
|
||||
|
||||
Value::Value(TypeTag type, int value) : Value(type, std::make_shared<DataTypes::TInt>(value)) {
|
||||
if (type_.tag != TypeTag::Int && type_.tag != TypeTag::Pixels) {
|
||||
type_.tag = TypeTag::Invalid;
|
||||
data_ = std::make_shared<DataBase>();
|
||||
}
|
||||
}
|
||||
|
||||
Value::Value(TypeTag type, bool value) : Value(type, std::make_shared<DataTypes::TBool>(value)) {
|
||||
if (type_.tag != TypeTag::Bool) {
|
||||
type_.tag = TypeTag::Invalid;
|
||||
data_ = std::make_shared<DataBase>();
|
||||
}
|
||||
}
|
||||
|
||||
Value::Value(TypeTag type, std::string value) : Value(type, std::make_shared<DataTypes::TString>(value)) {
|
||||
if (type_.tag != TypeTag::String &&
|
||||
type_.tag != TypeTag::Align) {
|
||||
type_.tag = TypeTag::Invalid;
|
||||
data_ = std::make_shared<DataBase>();
|
||||
}
|
||||
}
|
||||
|
||||
Value::Value(Type type, Qt::Initialization) : type_(type) {
|
||||
switch (type_.tag) {
|
||||
case TypeTag::Invalid: data_ = std::make_shared<DataBase>(); break;
|
||||
case TypeTag::Int: data_ = std::make_shared<DataTypes::TInt>(0); break;
|
||||
case TypeTag::Bool: data_ = std::make_shared<DataTypes::TBool>(false); break;
|
||||
case TypeTag::Double: data_ = std::make_shared<DataTypes::TDouble>(0.); break;
|
||||
case TypeTag::Pixels: data_ = std::make_shared<DataTypes::TInt>(0); break;
|
||||
case TypeTag::String: data_ = std::make_shared<DataTypes::TString>(""); break;
|
||||
case TypeTag::Color: data_ = std::make_shared<DataTypes::TColor>(data::color { 0, 0, 0, 255 }); break;
|
||||
case TypeTag::Point: data_ = std::make_shared<DataTypes::TPoint>(data::point { 0, 0 }); break;
|
||||
case TypeTag::Size: data_ = std::make_shared<DataTypes::TSize>(data::size { 0, 0 }); break;
|
||||
case TypeTag::Align: data_ = std::make_shared<DataTypes::TString>("topleft"); break;
|
||||
case TypeTag::Margins: data_ = std::make_shared<DataTypes::TMargins>(data::margins { 0, 0, 0, 0 }); break;
|
||||
case TypeTag::Font: data_ = std::make_shared<DataTypes::TFont>(data::font { "", 13, 0 }); break;
|
||||
case TypeTag::Icon: data_ = std::make_shared<DataTypes::TIcon>(data::icon {}); break;
|
||||
case TypeTag::Struct: data_ = std::make_shared<DataTypes::TFields>(data::fields {}); break;
|
||||
}
|
||||
}
|
||||
|
||||
Value::Value(TypeTag type, std::shared_ptr<DataBase> &&data) : type_ { type }, data_(std::move(data)) {
|
||||
}
|
||||
|
||||
} // namespace structure
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
237
Telegram/codegen/codegen/style/structure_types.h
Normal file
237
Telegram/codegen/codegen/style/structure_types.h
Normal file
@@ -0,0 +1,237 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QtMath>
|
||||
|
||||
namespace codegen {
|
||||
namespace style {
|
||||
namespace structure {
|
||||
|
||||
// List of names, like overview.document.bg
|
||||
using FullName = QStringList;
|
||||
inline std::string logFullName(const FullName &name) {
|
||||
return name.join('.').toStdString();
|
||||
}
|
||||
|
||||
struct Variable;
|
||||
class Value;
|
||||
|
||||
enum class TypeTag {
|
||||
Invalid,
|
||||
Int,
|
||||
Bool,
|
||||
Double,
|
||||
Pixels,
|
||||
String,
|
||||
Color,
|
||||
Point,
|
||||
Size,
|
||||
Align,
|
||||
Margins,
|
||||
Font,
|
||||
Icon,
|
||||
Struct,
|
||||
};
|
||||
|
||||
struct Type {
|
||||
TypeTag tag;
|
||||
FullName name; // only for type == ClassType::Struct
|
||||
|
||||
explicit operator bool() const {
|
||||
return (tag != TypeTag::Invalid);
|
||||
}
|
||||
};
|
||||
inline bool operator==(const Type &a, const Type &b) {
|
||||
return (a.tag == b.tag) && (a.name == b.name);
|
||||
}
|
||||
inline bool operator!=(const Type &a, const Type &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
namespace data {
|
||||
|
||||
struct point {
|
||||
int x, y;
|
||||
};
|
||||
|
||||
struct size {
|
||||
int width, height;
|
||||
};
|
||||
|
||||
struct color {
|
||||
uchar red, green, blue, alpha;
|
||||
QString fallback;
|
||||
};
|
||||
|
||||
struct margins {
|
||||
int left, top, right, bottom;
|
||||
};
|
||||
|
||||
struct font {
|
||||
enum Flag {
|
||||
Bold = 0x01,
|
||||
Italic = 0x02,
|
||||
Underline = 0x04,
|
||||
Semibold = 0x10,
|
||||
};
|
||||
std::string family;
|
||||
int size;
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct monoicon;
|
||||
struct icon {
|
||||
std::vector<monoicon> parts;
|
||||
};
|
||||
|
||||
struct field; // defined after Variable is defined
|
||||
using fields = QList<field>;
|
||||
|
||||
} // namespace data
|
||||
|
||||
class Value {
|
||||
public:
|
||||
Value();
|
||||
Value(data::point value);
|
||||
Value(data::size value);
|
||||
Value(data::color value);
|
||||
Value(data::margins value);
|
||||
Value(data::font value);
|
||||
Value(data::icon value);
|
||||
Value(const FullName &type, data::fields value);
|
||||
|
||||
// Can be only double.
|
||||
Value(TypeTag type, double value);
|
||||
|
||||
// Can be int / pixels.
|
||||
Value(TypeTag type, int value);
|
||||
|
||||
// Can be only bool.
|
||||
Value(TypeTag type, bool value);
|
||||
|
||||
// Can be string / align.
|
||||
Value(TypeTag type, std::string value);
|
||||
|
||||
// Default constructed value (uninitialized).
|
||||
Value(Type type, Qt::Initialization);
|
||||
|
||||
Type type() const { return type_; }
|
||||
int Int() const { return data_->Int(); }
|
||||
bool Bool() const { return data_->Bool(); }
|
||||
double Double() const { return data_->Double(); }
|
||||
std::string String() const { return data_->String(); }
|
||||
data::point Point() const { return data_->Point(); }
|
||||
data::size Size() const { return data_->Size(); };
|
||||
data::color Color() const { return data_->Color(); };
|
||||
data::margins Margins() const { return data_->Margins(); };
|
||||
data::font Font() const { return data_->Font(); };
|
||||
data::icon Icon() const;
|
||||
const data::fields *Fields() const { return data_->Fields(); };
|
||||
data::fields *Fields() { return data_->Fields(); };
|
||||
|
||||
explicit operator bool() const {
|
||||
return type_.tag != TypeTag::Invalid;
|
||||
}
|
||||
|
||||
Value makeCopy(const FullName ©Of) const {
|
||||
Value result(*this);
|
||||
result.copyOf_ = copyOf;
|
||||
return result;
|
||||
}
|
||||
|
||||
const FullName ©Of() const {
|
||||
return copyOf_;
|
||||
}
|
||||
|
||||
private:
|
||||
class DataBase {
|
||||
public:
|
||||
virtual int Int() const { return 0; }
|
||||
virtual bool Bool() const { return false; }
|
||||
virtual double Double() const { return 0.; }
|
||||
virtual std::string String() const { return std::string(); }
|
||||
virtual data::point Point() const { return {}; };
|
||||
virtual data::size Size() const { return {}; };
|
||||
virtual data::color Color() const { return {}; };
|
||||
virtual data::margins Margins() const { return {}; };
|
||||
virtual data::font Font() const { return {}; };
|
||||
virtual data::icon Icon() const;
|
||||
virtual const data::fields *Fields() const { return nullptr; };
|
||||
virtual data::fields *Fields() { return nullptr; };
|
||||
virtual ~DataBase() {
|
||||
}
|
||||
};
|
||||
struct DataTypes;
|
||||
|
||||
Value(TypeTag type, std::shared_ptr<DataBase> &&data);
|
||||
|
||||
Type type_;
|
||||
std::shared_ptr<DataBase> data_;
|
||||
|
||||
FullName copyOf_; // for copies of existing named values
|
||||
|
||||
};
|
||||
|
||||
struct Variable {
|
||||
FullName name;
|
||||
Value value;
|
||||
QString description;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !name.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
namespace data {
|
||||
struct field {
|
||||
enum class Status {
|
||||
Uninitialized,
|
||||
Implicit,
|
||||
ImplicitOtherModule,
|
||||
Explicit
|
||||
};
|
||||
Variable variable;
|
||||
Status status;
|
||||
};
|
||||
|
||||
struct monoicon {
|
||||
QString filename;
|
||||
Value color;
|
||||
Value padding;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !filename.isEmpty();
|
||||
}
|
||||
};
|
||||
} // namespace data
|
||||
|
||||
struct StructField {
|
||||
FullName name;
|
||||
Type type;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !name.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct Struct {
|
||||
FullName name;
|
||||
QList<StructField> fields;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !name.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace structure
|
||||
} // namespace style
|
||||
} // namespace codegen
|
||||
Reference in New Issue
Block a user