900 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			900 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop version of Telegram messaging app, see https://telegram.org
 | |
| 
 | |
| Telegram Desktop is free software: you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation, either version 3 of the License, or
 | |
| (at your option) any later version.
 | |
| 
 | |
| It is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| GNU General Public License for more details.
 | |
| 
 | |
| In addition, as a special exception, the copyright holders give permission
 | |
| to link the code of portions of this program with the OpenSSL library.
 | |
| 
 | |
| Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | |
| Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 | |
| */
 | |
| #include "codegen/style/parsed_file.h"
 | |
| 
 | |
| #include <iostream>
 | |
| #include <QtCore/QMap>
 | |
| #include <QtCore/QDir>
 | |
| #include <QtCore/QRegularExpression>
 | |
| #include "codegen/common/basic_tokenized_file.h"
 | |
| #include "codegen/common/logging.h"
 | |
| 
 | |
| using BasicToken = codegen::common::BasicTokenizedFile::Token;
 | |
| using BasicType = BasicToken::Type;
 | |
| 
 | |
| namespace codegen {
 | |
| namespace style {
 | |
| 
 | |
| using structure::logFullName;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| constexpr int kErrorInIncluded         = 801;
 | |
| constexpr int kErrorTypeMismatch       = 802;
 | |
| constexpr int kErrorUnknownField       = 803;
 | |
| constexpr int kErrorIdentifierNotFound = 804;
 | |
| constexpr int kErrorAlreadyDefined     = 805;
 | |
| constexpr int kErrorBadString          = 806;
 | |
| constexpr int kErrorIconDuplicate      = 807;
 | |
| 
 | |
| QString findInputFile(const Options &options) {
 | |
| 	for (const auto &dir : options.includePaths) {
 | |
| 		QString tryPath = QDir(dir).absolutePath() + '/' + options.inputPath;
 | |
| 		if (QFileInfo(tryPath).exists()) {
 | |
| 			return tryPath;
 | |
| 		}
 | |
| 	}
 | |
| 	return options.inputPath;
 | |
| }
 | |
| 
 | |
| QString tokenValue(const BasicToken &token) {
 | |
| 	if (token.type == BasicType::String) {
 | |
| 		return token.value;
 | |
| 	}
 | |
| 	return token.original.toStringUnchecked();
 | |
| }
 | |
| 
 | |
| bool isValidColor(const QString &str) {
 | |
| 	auto len = str.size();
 | |
| 	if (len != 3 && len != 4 && len != 6 && len != 8) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	for (auto ch : str) {
 | |
| 		auto code = ch.unicode();
 | |
| 		if ((code < '0' || code > '9') && (code < 'a' || code > 'f')) {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| uchar readHexUchar(QChar ch) {
 | |
| 	auto code = ch.unicode();
 | |
| 	return (code >= '0' && code <= '9') ? ((code - '0') & 0xFF) : ((code + 10 - 'a') & 0xFF);
 | |
| }
 | |
| 
 | |
| uchar readHexUchar(QChar char1, QChar char2) {
 | |
| 	return ((readHexUchar(char1) & 0x0F) << 4) | (readHexUchar(char2) & 0x0F);
 | |
| }
 | |
| 
 | |
| structure::data::color convertWebColor(const QString &str) {
 | |
| 	uchar r = 0, g = 0, b = 0, a = 255;
 | |
| 	if (isValidColor(str)) {
 | |
| 		auto len = str.size();
 | |
| 		if (len == 3 || len == 4) {
 | |
| 			r = readHexUchar(str.at(0), str.at(0));
 | |
| 			g = readHexUchar(str.at(1), str.at(1));
 | |
| 			b = readHexUchar(str.at(2), str.at(2));
 | |
| 			if (len == 4) a = readHexUchar(str.at(3), str.at(3));
 | |
| 		} else {
 | |
| 			r = readHexUchar(str.at(0), str.at(1));
 | |
| 			g = readHexUchar(str.at(2), str.at(3));
 | |
| 			b = readHexUchar(str.at(4), str.at(5));
 | |
| 			if (len == 8) a = readHexUchar(str.at(6), str.at(7));
 | |
| 		}
 | |
| 	}
 | |
| 	return { r, g, b, a };
 | |
| }
 | |
| 
 | |
| structure::data::color convertIntColor(int r, int g, int b, int a) {
 | |
| 	return { uchar(r & 0xFF), uchar(g & 0xFF), uchar(b & 0xFF), uchar(a & 0xFF) };
 | |
| }
 | |
| 
 | |
| std::string logType(const structure::Type &type) {
 | |
| 	if (type.tag == structure::TypeTag::Struct) {
 | |
| 		return "struct " + logFullName(type.name);
 | |
| 	}
 | |
| 	static auto builtInTypes = new QMap<structure::TypeTag, std::string> {
 | |
| 		{ structure::TypeTag::Int       , "int" },
 | |
| 		{ structure::TypeTag::Double    , "double" },
 | |
| 		{ structure::TypeTag::Pixels    , "pixels" },
 | |
| 		{ structure::TypeTag::String    , "string" },
 | |
| 		{ structure::TypeTag::Color     , "color" },
 | |
| 		{ structure::TypeTag::Point     , "point" },
 | |
| 		{ structure::TypeTag::Sprite    , "sprite" },
 | |
| 		{ structure::TypeTag::Size      , "size" },
 | |
| 		{ structure::TypeTag::Transition, "transition" },
 | |
| 		{ structure::TypeTag::Cursor    , "cursor" },
 | |
| 		{ structure::TypeTag::Align     , "align" },
 | |
| 		{ structure::TypeTag::Margins   , "margins" },
 | |
| 		{ structure::TypeTag::Font      , "font" },
 | |
| 	};
 | |
| 	return builtInTypes->value(type.tag, "invalid");
 | |
| }
 | |
| 
 | |
| bool validateAnsiString(const QString &value) {
 | |
| 	for (auto ch : value) {
 | |
| 		if (ch.unicode() > 127) {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool validateTransitionString(const QString &value) {
 | |
| 	return QRegularExpression("^[a-zA-Z_]+$").match(value).hasMatch();
 | |
| }
 | |
| 
 | |
| bool validateCursorString(const QString &value) {
 | |
| 	return QRegularExpression("^[a-z_]+$").match(value).hasMatch();
 | |
| }
 | |
| 
 | |
| bool validateAlignString(const QString &value) {
 | |
| 	return QRegularExpression("^[a-z_]+$").match(value).hasMatch();
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| ParsedFile::ParsedFile(const Options &options)
 | |
| : filePath_(findInputFile(options))
 | |
| , file_(filePath_)
 | |
| , options_(options) {
 | |
| }
 | |
| 
 | |
| bool ParsedFile::read() {
 | |
| 	if (!file_.read()) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	auto absolutePath = QFileInfo(filePath_).absoluteFilePath();
 | |
| 	module_ = std::make_unique<structure::Module>(absolutePath);
 | |
| 	do {
 | |
| 		if (auto startToken = file_.getToken(BasicType::Name)) {
 | |
| 			if (tokenValue(startToken) == "using") {
 | |
| 				if (auto includedResult = readIncluded()) {
 | |
| 					module_->addIncluded(std::move(includedResult));
 | |
| 					continue;
 | |
| 				}
 | |
| 			} else if (auto braceOpen = file_.getToken(BasicType::LeftBrace)) {
 | |
| 				if (auto structResult = readStruct(tokenValue(startToken))) {
 | |
| 					if (module_->addStruct(structResult)) {
 | |
| 						continue;
 | |
| 					}
 | |
| 					logError(kErrorAlreadyDefined) << "struct '" << logFullName(structResult.name) << "' already defined";
 | |
| 					break;
 | |
| 				}
 | |
| 			} else if (auto colonToken = file_.getToken(BasicType::Colon)) {
 | |
| 				if (auto variableResult = readVariable(tokenValue(startToken))) {
 | |
| 					if (module_->addVariable(variableResult)) {
 | |
| 						continue;
 | |
| 					}
 | |
| 					logError(kErrorAlreadyDefined) << "variable '" << logFullName(variableResult.name) << "' already defined";
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if (file_.atEnd()) {
 | |
| 			break;
 | |
| 		}
 | |
| 		logErrorUnexpectedToken() << "using keyword, or struct definition, or variable definition";
 | |
| 	} while (!failed());
 | |
| 
 | |
| 	if (failed()) {
 | |
| 		module_ = nullptr;
 | |
| 	}
 | |
| 	return !failed();
 | |
| }
 | |
| 
 | |
| common::LogStream ParsedFile::logErrorTypeMismatch() {
 | |
| 	return logError(kErrorTypeMismatch) << "type mismatch: ";
 | |
| }
 | |
| 
 | |
| ParsedFile::ModulePtr ParsedFile::readIncluded() {
 | |
| 	if (auto usingFile = assertNextToken(BasicType::String)) {
 | |
| 		if (assertNextToken(BasicType::Semicolon)) {
 | |
| 			ParsedFile included(includedOptions(tokenValue(usingFile)));
 | |
| 			if (included.read()) {
 | |
| 				return included.getResult();
 | |
| 			} else {
 | |
| 				logError(kErrorInIncluded) << "error while parsing '" << tokenValue(usingFile).toStdString() << "'";
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nullptr;
 | |
| }
 | |
| 
 | |
| structure::Struct ParsedFile::readStruct(const QString &name) {
 | |
| 	structure::Struct result = { composeFullName(name) };
 | |
| 	do {
 | |
| 		if (auto fieldName = file_.getToken(BasicType::Name)) {
 | |
| 			if (auto field = readStructField(tokenValue(fieldName))) {
 | |
| 				result.fields.push_back(field);
 | |
| 			}
 | |
| 		} else if (assertNextToken(BasicType::RightBrace)) {
 | |
| 			if (result.fields.isEmpty()) {
 | |
| 				logErrorUnexpectedToken() << "at least one field in struct";
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	} while (!failed());
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| structure::Variable ParsedFile::readVariable(const QString &name) {
 | |
| 	structure::Variable result = { composeFullName(name) };
 | |
| 	if (auto value = readValue()) {
 | |
| 		result.value = value;
 | |
| 		if (value.type().tag != structure::TypeTag::Struct) {
 | |
| 			assertNextToken(BasicType::Semicolon);
 | |
| 		}
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| structure::StructField ParsedFile::readStructField(const QString &name) {
 | |
| 	structure::StructField result = { composeFullName(name) };
 | |
| 	if (auto colonToken = assertNextToken(BasicType::Colon)) {
 | |
| 		if (auto type = readType()) {
 | |
| 			result.type = type;
 | |
| 			assertNextToken(BasicType::Semicolon);
 | |
| 		}
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| structure::Type ParsedFile::readType() {
 | |
| 	structure::Type result;
 | |
| 	if (auto nameToken = assertNextToken(BasicType::Name)) {
 | |
| 		auto name = tokenValue(nameToken);
 | |
| 		if (auto builtInType = typeNames_.value(name.toStdString())) {
 | |
| 			result = builtInType;
 | |
| 		} else {
 | |
| 			auto fullName = composeFullName(name);
 | |
| 			if (module_->findStruct(fullName)) {
 | |
| 				result.tag = structure::TypeTag::Struct;
 | |
| 				result.name = fullName;
 | |
| 			} else {
 | |
| 				logError(kErrorIdentifierNotFound) << "type name '" << logFullName(fullName) << "' not found";
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readValue() {
 | |
| 	if (auto colorValue = readColorValue()) {
 | |
| 		return colorValue;
 | |
| 	} else if (auto pointValue = readPointValue()) {
 | |
| 		return pointValue;
 | |
| 	} else if (auto spriteValue = readSpriteValue()) {
 | |
| 		return spriteValue;
 | |
| 	} else if (auto sizeValue = readSizeValue()) {
 | |
| 		return sizeValue;
 | |
| 	} else if (auto transitionValue = readTransitionValue()) {
 | |
| 		return transitionValue;
 | |
| 	} else if (auto cursorValue = readCursorValue()) {
 | |
| 		return cursorValue;
 | |
| 	} else if (auto alignValue = readAlignValue()) {
 | |
| 		return alignValue;
 | |
| 	} else if (auto marginsValue = readMarginsValue()) {
 | |
| 		return marginsValue;
 | |
| 	} else if (auto fontValue = readFontValue()) {
 | |
| 		return fontValue;
 | |
| 	} else if (auto iconValue = readIconValue()) {
 | |
| 		return iconValue;
 | |
| 	} else if (auto numericValue = readNumericValue()) {
 | |
| 		return numericValue;
 | |
| 	} else if (auto stringValue = readStringValue()) {
 | |
| 		return stringValue;
 | |
| 	} else if (auto structValue = readStructValue()) {
 | |
| 		return structValue;
 | |
| 	} else if (auto copyValue = readCopyValue()) {
 | |
| 		return copyValue;
 | |
| 	} else {
 | |
| 		logErrorUnexpectedToken() << "variable value";
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readStructValue() {
 | |
| 	if (auto structName = file_.getToken(BasicType::Name)) {
 | |
| 		if (auto result = defaultConstructedStruct(composeFullName(tokenValue(structName)))) {
 | |
| 			if (file_.getToken(BasicType::LeftParenthesis)) {
 | |
| 				if (!readStructParents(result)) {
 | |
| 					return {};
 | |
| 				}
 | |
| 			}
 | |
| 			if (assertNextToken(BasicType::LeftBrace)) {
 | |
| 				readStructValueInner(result);
 | |
| 			}
 | |
| 			return result;
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::defaultConstructedStruct(const structure::FullName &structName) {
 | |
| 	if (auto pattern = module_->findStruct(structName)) {
 | |
| 		QList<structure::data::field> fields;
 | |
| 		fields.reserve(pattern->fields.size());
 | |
| 		for (const auto &fieldType : pattern->fields) {
 | |
| 			fields.push_back({
 | |
| 				{ // variable
 | |
| 					fieldType.name,
 | |
| 					{ fieldType.type, Qt::Uninitialized }, // value
 | |
| 				},
 | |
| 				structure::data::field::Status::Uninitialized, // status
 | |
| 			});
 | |
| 		}
 | |
| 		return { structName, fields };
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| void ParsedFile::applyStructParent(structure::Value &result, const structure::FullName &parentName) {
 | |
| 	bool fromTheSameModule = false;
 | |
| 	if (auto parent = module_->findVariable(parentName, &fromTheSameModule)) {
 | |
| 		if (parent->value.type() != result.type()) {
 | |
| 			logErrorTypeMismatch() << "parent '" << logFullName(parentName) << "' has type '" << logType(parent->value.type()) << "' while child value has type " << logType(result.type());
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		const auto *srcFields(parent->value.Fields());
 | |
| 		auto *dstFields(result.Fields());
 | |
| 		if (!srcFields || !dstFields) {
 | |
| 			logAssert(false) << "struct data check failed";
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		logAssert(srcFields->size() == dstFields->size()) << "struct size check failed";
 | |
| 		for (int i = 0, s = srcFields->size(); i != s; ++i) {
 | |
| 			const auto &srcField(srcFields->at(i));
 | |
| 			auto &dstField((*dstFields)[i]);
 | |
| 			using Status = structure::data::field::Status;
 | |
| 			if (srcField.status == Status::Explicit ||
 | |
| 				dstField.status == Status::Uninitialized) {
 | |
| 				const auto &srcValue(srcField.variable.value);
 | |
| 				auto &dstValue(dstField.variable.value);
 | |
| 				logAssert(srcValue.type() == dstValue.type()) << "struct field type check failed";
 | |
| 
 | |
| 				// Optimization: don't let the style files to contain unnamed inherited
 | |
| 				// icons from the other (included) style files, because they will
 | |
| 				// duplicate the binary data across different style c++ source files.
 | |
| 				//
 | |
| 				// Example:
 | |
| 				// a.style has "A: Struct { icon: icon { ..file.. } };" and
 | |
| 				// b.style has "B: Struct(A) { .. };" with non-overriden icon field.
 | |
| 				// Then both style_a.cpp and style_b.cpp will contain binary data of "file".
 | |
| 				if (!fromTheSameModule
 | |
| 					&& srcValue.type().tag == structure::TypeTag::Icon
 | |
| 					&& !srcValue.Icon().parts.empty()
 | |
| 					&& srcValue.copyOf().isEmpty()) {
 | |
| 					logError(kErrorIconDuplicate) << "an unnamed icon field '" << logFullName(srcField.variable.name) << "' is inherited from parent '" << logFullName(parentName) << "'";
 | |
| 					return;
 | |
| 				}
 | |
| 				dstValue = srcValue;
 | |
| 				dstField.status = Status::Implicit;
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		logError(kErrorIdentifierNotFound) << "parent '" << logFullName(parentName) << "' not found";
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool ParsedFile::readStructValueInner(structure::Value &result) {
 | |
| 	do {
 | |
| 		if (auto fieldName = file_.getToken(BasicType::Name)) {
 | |
| 			if (!assertNextToken(BasicType::Colon)) {
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			if (auto field = readVariable(tokenValue(fieldName))) {
 | |
| 				if (!assignStructField(result, field)) {
 | |
| 					return false;
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (assertNextToken(BasicType::RightBrace)) {
 | |
| 			return true;
 | |
| 		}
 | |
| 	} while (!failed());
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| bool ParsedFile::assignStructField(structure::Value &result, const structure::Variable &field) {
 | |
| 	auto *fields = result.Fields();
 | |
| 	if (!fields) {
 | |
| 		logAssert(false) << "struct data check failed";
 | |
| 		return false;
 | |
| 	}
 | |
| 	for (auto &already : *fields) {
 | |
| 		if (already.variable.name == field.name) {
 | |
| 			if (already.variable.value.type() == field.value.type()) {
 | |
| 				already.variable.value = field.value;
 | |
| 				already.status = structure::data::field::Status::Explicit;
 | |
| 				return true;
 | |
| 			} else {
 | |
| 				logErrorTypeMismatch() << "field '" << logFullName(already.variable.name) << "' has type '" << logType(already.variable.value.type()) << "' while value has type '" << logType(field.value.type()) << "'";
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	logError(kErrorUnknownField) << "field '" << logFullName(field.name) << "' was not found in struct of type '" << logType(result.type()) << "'";
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| bool ParsedFile::readStructParents(structure::Value &result) {
 | |
| 	do {
 | |
| 		if (auto parentName = assertNextToken(BasicType::Name)) {
 | |
| 			applyStructParent(result, composeFullName(tokenValue(parentName)));
 | |
| 			if (file_.getToken(BasicType::RightParenthesis)) {
 | |
| 				return true;
 | |
| 			} else {
 | |
| 				assertNextToken(BasicType::Comma);
 | |
| 			}
 | |
| 		} else {
 | |
| 			logErrorUnexpectedToken() << "struct variable parent";
 | |
| 		}
 | |
| 	} while (!failed());
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readPositiveValue() {
 | |
| 	auto numericToken = file_.getAnyToken();
 | |
| 	if (numericToken.type == BasicType::Int) {
 | |
| 		return { structure::TypeTag::Int, tokenValue(numericToken).toInt() };
 | |
| 	} else if (numericToken.type == BasicType::Double) {
 | |
| 		return { structure::TypeTag::Double, tokenValue(numericToken).toDouble() };
 | |
| 	} else if (numericToken.type == BasicType::Name) {
 | |
| 		auto value = tokenValue(numericToken);
 | |
| 		auto match = QRegularExpression("^\\d+px$").match(value);
 | |
| 		if (match.hasMatch()) {
 | |
| 			return { structure::TypeTag::Pixels, value.mid(0, value.size() - 2).toInt() };
 | |
| 		}
 | |
| 	}
 | |
| 	file_.putBack();
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readNumericValue() {
 | |
| 	if (auto value = readPositiveValue()) {
 | |
| 		return value;
 | |
| 	} else if (auto minusToken = file_.getToken(BasicType::Minus)) {
 | |
| 		if (auto positiveValue = readNumericValue()) {
 | |
| 			return { positiveValue.type().tag, -positiveValue.Int() };
 | |
| 		}
 | |
| 		logErrorUnexpectedToken() << "numeric value";
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readStringValue() {
 | |
| 	if (auto stringToken = file_.getToken(BasicType::String)) {
 | |
| 		auto value = tokenValue(stringToken);
 | |
| 		if (validateAnsiString(value)) {
 | |
| 			return { structure::TypeTag::String, stringToken.value.toStdString() };
 | |
| 		}
 | |
| 		logError(kErrorBadString) << "unicode symbols are not supported";
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readColorValue() {
 | |
| 	if (auto numberSign = file_.getToken(BasicType::Number)) {
 | |
| 		auto color = file_.getAnyToken();
 | |
| 		if (color.type == BasicType::Int || color.type == BasicType::Name) {
 | |
| 			auto chars = tokenValue(color).toLower();
 | |
| 			if (isValidColor(chars)) {
 | |
| 				return { convertWebColor(chars) };
 | |
| 			}
 | |
| 		} else {
 | |
| 			logErrorUnexpectedToken() << "color value in #ccc, #ccca, #cccccc or #ccccccaa format";
 | |
| 		}
 | |
| 	} else if (auto rgbaToken = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(rgbaToken) == "rgba") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto r = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto g = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto b = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto a = readNumericValue();
 | |
| 			if (r.type().tag != structure::TypeTag::Int || r.Int() < 0 || r.Int() > 255 ||
 | |
| 				g.type().tag != structure::TypeTag::Int || g.Int() < 0 || g.Int() > 255 ||
 | |
| 				b.type().tag != structure::TypeTag::Int || b.Int() < 0 || b.Int() > 255 ||
 | |
| 				a.type().tag != structure::TypeTag::Int || a.Int() < 0 || a.Int() > 255) {
 | |
| 				logErrorTypeMismatch() << "expected four 0-255 values for the rgba color";
 | |
| 			}
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			return { convertIntColor(r.Int(), g.Int(), b.Int(), a.Int()) };
 | |
| 		} else if (tokenValue(rgbaToken) == "rgb") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto r = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto g = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto b = readNumericValue();
 | |
| 			if (r.type().tag != structure::TypeTag::Int || r.Int() < 0 || r.Int() > 255 ||
 | |
| 				g.type().tag != structure::TypeTag::Int || g.Int() < 0 || g.Int() > 255 ||
 | |
| 				b.type().tag != structure::TypeTag::Int || b.Int() < 0 || b.Int() > 255) {
 | |
| 				logErrorTypeMismatch() << "expected three int values for the rgb color";
 | |
| 			}
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			return { convertIntColor(r.Int(), g.Int(), b.Int(), 255) };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readPointValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "point") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto x = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto y = readNumericOrNumericCopyValue();
 | |
| 			if (x.type().tag != structure::TypeTag::Pixels ||
 | |
| 				y.type().tag != structure::TypeTag::Pixels) {
 | |
| 				logErrorTypeMismatch() << "expected two px values for the point";
 | |
| 			}
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			return { structure::data::point { x.Int(), y.Int() } };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readSpriteValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "sprite") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto x = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto y = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto w = readNumericValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto h = readNumericValue();
 | |
| 			if (x.type().tag != structure::TypeTag::Pixels ||
 | |
| 				y.type().tag != structure::TypeTag::Pixels ||
 | |
| 				w.type().tag != structure::TypeTag::Pixels ||
 | |
| 				h.type().tag != structure::TypeTag::Pixels) {
 | |
| 				logErrorTypeMismatch() << "expected four px values for the sprite";
 | |
| 			}
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			return { structure::data::sprite { x.Int(), y.Int(), w.Int(), h.Int() } };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readSizeValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "size") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto w = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto h = readNumericOrNumericCopyValue();
 | |
| 			if (w.type().tag != structure::TypeTag::Pixels ||
 | |
| 				h.type().tag != structure::TypeTag::Pixels) {
 | |
| 				logErrorTypeMismatch() << "expected two px values for the size";
 | |
| 			}
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			return { structure::data::size { w.Int(), h.Int() } };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readTransitionValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "transition") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto transition = tokenValue(assertNextToken(BasicType::Name));
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			if (validateTransitionString(transition)) {
 | |
| 				return { structure::TypeTag::Transition, transition.toStdString() };
 | |
| 			} else {
 | |
| 				logError(kErrorBadString) << "bad transition value";
 | |
| 			}
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readCursorValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "cursor") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto cursor = tokenValue(assertNextToken(BasicType::Name));
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			if (validateCursorString(cursor)) {
 | |
| 				return { structure::TypeTag::Cursor, cursor.toStdString() };
 | |
| 			} else {
 | |
| 				logError(kErrorBadString) << "bad cursor string";
 | |
| 			}
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readAlignValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "align") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto align = tokenValue(assertNextToken(BasicType::Name));
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			if (validateAlignString(align)) {
 | |
| 				return { structure::TypeTag::Align, align.toStdString() };
 | |
| 			} else {
 | |
| 				logError(kErrorBadString) << "bad align string";
 | |
| 			}
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readMarginsValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "margins") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			auto l = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto t = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto r = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
 | |
| 			auto b = readNumericOrNumericCopyValue();
 | |
| 			if (l.type().tag != structure::TypeTag::Pixels ||
 | |
| 				t.type().tag != structure::TypeTag::Pixels ||
 | |
| 				r.type().tag != structure::TypeTag::Pixels ||
 | |
| 				b.type().tag != structure::TypeTag::Pixels) {
 | |
| 				logErrorTypeMismatch() << "expected four px values for the margins";
 | |
| 			}
 | |
| 
 | |
| 			assertNextToken(BasicType::RightParenthesis);
 | |
| 
 | |
| 			return { structure::data::margins { l.Int(), t.Int(), r.Int(), b.Int() } };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readFontValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "font") {
 | |
| 			assertNextToken(BasicType::LeftParenthesis);
 | |
| 
 | |
| 			int flags = 0;
 | |
| 			structure::Value family, size;
 | |
| 			do {
 | |
| 				if (auto formatToken = file_.getToken(BasicType::Name)) {
 | |
| 					if (tokenValue(formatToken) == "bold") {
 | |
| 						flags |= structure::data::font::Bold;
 | |
| 					} else if (tokenValue(formatToken) == "italic") {
 | |
| 						flags |= structure::data::font::Italic;
 | |
| 					} else if (tokenValue(formatToken) == "underline") {
 | |
| 						flags |= structure::data::font::Underline;
 | |
| 					} else {
 | |
| 						file_.putBack();
 | |
| 					}
 | |
| 				}
 | |
| 				if (auto familyValue = readStringOrStringCopyValue()) {
 | |
| 					family = familyValue;
 | |
| 				} else if (auto sizeValue = readNumericOrNumericCopyValue()) {
 | |
| 					size = sizeValue;
 | |
| 				} else if (file_.getToken(BasicType::RightParenthesis)) {
 | |
| 					break;
 | |
| 				} else {
 | |
| 					logErrorUnexpectedToken() << "font family, font size or ')'";
 | |
| 				}
 | |
| 			} while (!failed());
 | |
| 
 | |
| 			if (size.type().tag != structure::TypeTag::Pixels) {
 | |
| 				logErrorTypeMismatch() << "px value for the font size expected";
 | |
| 			}
 | |
| 			return { structure::data::font { family.String(), size.Int(), flags } };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readIconValue() {
 | |
| 	if (auto font = file_.getToken(BasicType::Name)) {
 | |
| 		if (tokenValue(font) == "icon") {
 | |
| 			std::vector<structure::data::monoicon> parts;
 | |
| 			if (file_.getToken(BasicType::LeftBrace)) { // complex icon
 | |
| 				do {
 | |
| 					if (file_.getToken(BasicType::RightBrace)) {
 | |
| 						break;
 | |
| 					} else if (file_.getToken(BasicType::LeftBrace)) {
 | |
| 						if (auto part = readMonoIconFields()) {
 | |
| 							assertNextToken(BasicType::RightBrace);
 | |
| 							parts.push_back(part);
 | |
| 							file_.getToken(BasicType::Comma);
 | |
| 							continue;
 | |
| 						}
 | |
| 						return {};
 | |
| 					} else {
 | |
| 						logErrorUnexpectedToken() << "icon part or '}'";
 | |
| 						return {};
 | |
| 					}
 | |
| 				} while (true);
 | |
| 
 | |
| 			} else if (file_.getToken(BasicType::LeftParenthesis)) { // short icon
 | |
| 				if (auto theOnlyPart = readMonoIconFields()) {
 | |
| 					assertNextToken(BasicType::RightParenthesis);
 | |
| 					parts.push_back(theOnlyPart);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (parts.empty()) {
 | |
| 				logErrorUnexpectedToken() << "at least one icon part";
 | |
| 				return {};
 | |
| 			}
 | |
| 			return { structure::data::icon { parts } };
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readCopyValue() {
 | |
| 	if (auto copyName = file_.getToken(BasicType::Name)) {
 | |
| 		structure::FullName name = { tokenValue(copyName) };
 | |
| 		if (auto variable = module_->findVariable(name)) {
 | |
| 			return variable->value.makeCopy(variable->name);
 | |
| 		}
 | |
| 		file_.putBack();
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readNumericOrNumericCopyValue() {
 | |
| 	if (auto result = readNumericValue()) {
 | |
| 		return result;
 | |
| 	} else if (auto copy = readCopyValue()) {
 | |
| 		auto type = copy.type().tag;
 | |
| 		if (type == structure::TypeTag::Int
 | |
| 			|| type == structure::TypeTag::Double
 | |
| 			|| type == structure::TypeTag::Pixels) {
 | |
| 			return copy;
 | |
| 		} else {
 | |
| 			file_.putBack();
 | |
| 		}
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::Value ParsedFile::readStringOrStringCopyValue() {
 | |
| 	if (auto result = readStringValue()) {
 | |
| 		return result;
 | |
| 	} else if (auto copy = readCopyValue()) {
 | |
| 		auto type = copy.type().tag;
 | |
| 		if (type == structure::TypeTag::String) {
 | |
| 			return copy;
 | |
| 		} else {
 | |
| 			file_.putBack();
 | |
| 		}
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| structure::data::monoicon ParsedFile::readMonoIconFields() {
 | |
| 	structure::data::monoicon result;
 | |
| 	result.filename = readMonoIconFilename();
 | |
| 	if (!result.filename.isEmpty() && file_.getToken(BasicType::Comma)) {
 | |
| 		if (auto color = readValue()) {
 | |
| 			if (color.type().tag == structure::TypeTag::Color) {
 | |
| 				result.color = color;
 | |
| 				if (file_.getToken(BasicType::Comma)) {
 | |
| 					if (auto offset = readValue()) {
 | |
| 						if (offset.type().tag == structure::TypeTag::Point) {
 | |
| 							result.offset = offset;
 | |
| 						} else {
 | |
| 							logErrorUnexpectedToken() << "icon offset";
 | |
| 						}
 | |
| 					} else {
 | |
| 						logErrorUnexpectedToken() << "icon offset";
 | |
| 					}
 | |
| 				} else {
 | |
| 					result.offset = { structure::data::point { 0, 0 } };
 | |
| 				}
 | |
| 			} else {
 | |
| 				logErrorUnexpectedToken() << "icon color";
 | |
| 			}
 | |
| 		} else {
 | |
| 			logErrorUnexpectedToken() << "icon color";
 | |
| 		}
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| QString ParsedFile::readMonoIconFilename() {
 | |
| 	if (auto filename = readValue()) {
 | |
| 		if (filename.type().tag == structure::TypeTag::String) {
 | |
| 			auto filepath = QString::fromStdString(filename.String());
 | |
| 			for (const auto &path : options_.includePaths) {
 | |
| 				QFileInfo fileinfo(path + '/' + filepath + ".png");
 | |
| 				if (fileinfo.exists()) {
 | |
| 					return path + '/' + filepath;
 | |
| 				}
 | |
| 			}
 | |
| 			for (const auto &path : options_.includePaths) {
 | |
| 				QFileInfo fileinfo(path + "/icons/" + filepath + ".png");
 | |
| 				if (fileinfo.exists()) {
 | |
| 					return path + "/icons/" + filepath;
 | |
| 				}
 | |
| 			}
 | |
| 			logError(common::kErrorFileNotFound) << "could not open icon file '" << filename.String() << "'";
 | |
| 		} else if (filename.type().tag == structure::TypeTag::Size) {
 | |
| 			return QString("size://%1,%2").arg(filename.Size().width).arg(filename.Size().height);
 | |
| 		}
 | |
| 	}
 | |
| 	logErrorUnexpectedToken() << "icon filename or rect size";
 | |
| 	return QString();
 | |
| }
 | |
| 
 | |
| BasicToken ParsedFile::assertNextToken(BasicToken::Type type) {
 | |
| 	auto result = file_.getToken(type);
 | |
| 	if (!result) {
 | |
| 		logErrorUnexpectedToken() << type;
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| Options ParsedFile::includedOptions(const QString &filepath) {
 | |
| 	auto result = options_;
 | |
| 	result.inputPath = filepath;
 | |
| 	result.includePaths[0] = QFileInfo(filePath_).dir().absolutePath();
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| // Compose context-dependent full name.
 | |
| structure::FullName ParsedFile::composeFullName(const QString &name) {
 | |
| 	return { name };
 | |
| }
 | |
| 
 | |
| } // namespace style
 | |
| } // namespace codegen
 | 
