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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
add_subdirectory(codegen/common)
add_subdirectory(codegen/emoji)
add_subdirectory(codegen/lang)
add_subdirectory(codegen/style)

View 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
)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
)

View 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 &current : 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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 &section : file[0]) {
const auto first = section.front().front();
const auto replacedSection = [&]() -> const Part* {
for (const auto &section : file[1]) {
if (section.front().front() == first) {
++replacementsUsed;
return &section;
}
}
return nullptr;
}();
const auto &sectionData = 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 &section : 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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();
}

View 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

View 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

View 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

View 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

View 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
)

View 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

View 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

View 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();
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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
)

File diff suppressed because it is too large Load Diff

View 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

View 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();
}

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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 &copyOf) const {
Value result(*this);
result.copyOf_ = copyOf;
return result;
}
const FullName &copyOf() 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