| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "Parser.h" |
| | #include "ParameterManager.h" |
| |
|
| | #include <Utilities.h> |
| | #include <Base/Tools.h> |
| |
|
| | #include <QColor> |
| | #include <QRegularExpression> |
| | #include <QString> |
| | #include <ranges> |
| | #include <variant> |
| |
|
| | namespace Gui::StyleParameters |
| | { |
| |
|
| | Value ParameterReference::evaluate(const EvaluationContext& context) const |
| | { |
| | return context.manager->resolve(name, context.context).value_or("@" + name); |
| | } |
| |
|
| | Value Number::evaluate([[maybe_unused]] const EvaluationContext& context) const |
| | { |
| | return value; |
| | } |
| |
|
| | Value Color::evaluate([[maybe_unused]] const EvaluationContext& context) const |
| | { |
| | return color; |
| | } |
| |
|
| | Value FunctionCall::evaluate(const EvaluationContext& context) const |
| | { |
| | const auto lightenOrDarken = [this](const EvaluationContext& context) -> Value { |
| | if (arguments.size() != 2) { |
| | THROWM( |
| | Base::ExpressionError, |
| | fmt::format("Function '{}' expects 2 arguments, got {}", functionName, arguments.size()) |
| | ); |
| | } |
| |
|
| | auto colorArg = arguments[0]->evaluate(context); |
| | auto amountArg = arguments[1]->evaluate(context); |
| |
|
| | if (!std::holds_alternative<Base::Color>(colorArg)) { |
| | THROWM(Base::ExpressionError, fmt::format("'{}' is not supported for colors", functionName)); |
| | } |
| |
|
| | auto color = std::get<Base::Color>(colorArg).asValue<QColor>(); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | auto amount = 100 + static_cast<int>(std::get<Numeric>(amountArg).value); |
| |
|
| | if (functionName == "lighten") { |
| | return Base::Color::fromValue(color.lighter(amount)); |
| | } |
| |
|
| | if (functionName == "darken") { |
| | return Base::Color::fromValue(color.darker(amount)); |
| | } |
| |
|
| | return {}; |
| | }; |
| |
|
| | const auto blend = [this](const EvaluationContext& context) -> Value { |
| | if (arguments.size() != 3) { |
| | THROWM( |
| | Base::ExpressionError, |
| | fmt::format("Function '{}' expects 3 arguments, got {}", functionName, arguments.size()) |
| | ); |
| | } |
| |
|
| | auto firstColorArg = arguments[0]->evaluate(context); |
| | auto secondColorArg = arguments[1]->evaluate(context); |
| | auto amountArg = arguments[2]->evaluate(context); |
| |
|
| | if (!std::holds_alternative<Base::Color>(firstColorArg)) { |
| | THROWM( |
| | Base::ExpressionError, |
| | fmt::format("first argument of '{}' must be color", functionName) |
| | ); |
| | } |
| |
|
| | if (!std::holds_alternative<Base::Color>(secondColorArg)) { |
| | THROWM( |
| | Base::ExpressionError, |
| | fmt::format("second argument of '{}' must be color", functionName) |
| | ); |
| | } |
| |
|
| | auto firstColor = std::get<Base::Color>(firstColorArg); |
| | auto secondColor = std::get<Base::Color>(secondColorArg); |
| |
|
| | auto amount = Base::fromPercent(static_cast<long>(std::get<Numeric>(amountArg).value)); |
| |
|
| | return Base::Color( |
| | (1 - amount) * firstColor.r + amount * secondColor.r, |
| | (1 - amount) * firstColor.g + amount * secondColor.g, |
| | (1 - amount) * firstColor.b + amount * secondColor.b |
| | ); |
| | }; |
| |
|
| | std::map<std::string, std::function<Value(const EvaluationContext&)>> functions = { |
| | {"lighten", lightenOrDarken}, |
| | {"darken", lightenOrDarken}, |
| | {"blend", blend}, |
| | }; |
| |
|
| | if (functions.contains(functionName)) { |
| | auto function = functions.at(functionName); |
| | return function(context); |
| | } |
| |
|
| | THROWM(Base::ExpressionError, fmt::format("Unknown function '{}'", functionName)); |
| | } |
| |
|
| | Value BinaryOp::evaluate(const EvaluationContext& context) const |
| | { |
| | Value lval = left->evaluate(context); |
| | Value rval = right->evaluate(context); |
| |
|
| | if (!std::holds_alternative<Numeric>(lval) || !std::holds_alternative<Numeric>(rval)) { |
| | THROWM(Base::ExpressionError, "Math operations are supported only on lengths"); |
| | } |
| |
|
| | auto lhs = std::get<Numeric>(lval); |
| | auto rhs = std::get<Numeric>(rval); |
| |
|
| | switch (op) { |
| | case Operator::Add: |
| | return lhs + rhs; |
| | case Operator::Subtract: |
| | return lhs - rhs; |
| | case Operator::Multiply: |
| | return lhs * rhs; |
| | case Operator::Divide: |
| | return lhs / rhs; |
| | default: |
| | THROWM(Base::ExpressionError, "Unknown operator"); |
| | } |
| | } |
| |
|
| | Value UnaryOp::evaluate(const EvaluationContext& context) const |
| | { |
| | Value val = operand->evaluate(context); |
| | if (std::holds_alternative<Base::Color>(val)) { |
| | THROWM(Base::ExpressionError, "Unary operations on colors are not supported"); |
| | } |
| |
|
| | auto v = std::get<Numeric>(val); |
| | switch (op) { |
| | case Operator::Add: |
| | return v; |
| | case Operator::Subtract: |
| | return -v; |
| | default: |
| | THROWM(Base::ExpressionError, "Unknown unary operator"); |
| | } |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parse() |
| | { |
| | auto expr = parseExpression(); |
| | skipWhitespace(); |
| | if (pos != input.size()) { |
| | THROWM( |
| | Base::ParserError, |
| | fmt::format("Unexpected characters at end of input: {}", input.substr(pos)) |
| | ); |
| | } |
| | return expr; |
| | } |
| |
|
| | bool Parser::peekString(const char* function) const |
| | { |
| | return input.compare(pos, strlen(function), function) == 0; |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseExpression() |
| | { |
| | auto expr = parseTerm(); |
| | while (true) { |
| | skipWhitespace(); |
| | if (match('+')) { |
| | expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Add, parseTerm()); |
| | } |
| | else if (match('-')) { |
| | expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Subtract, parseTerm()); |
| | } |
| | else { |
| | break; |
| | } |
| | } |
| | return expr; |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseTerm() |
| | { |
| | auto expr = parseFactor(); |
| | while (true) { |
| | skipWhitespace(); |
| | if (match('*')) { |
| | expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Multiply, parseFactor()); |
| | } |
| | else if (match('/')) { |
| | expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Divide, parseFactor()); |
| | } |
| | else { |
| | break; |
| | } |
| | } |
| | return expr; |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseFactor() |
| | { |
| | skipWhitespace(); |
| | if (match('+') || match('-')) { |
| | Operator op = (input[pos - 1] == '+') ? Operator::Add : Operator::Subtract; |
| | return std::make_unique<UnaryOp>(op, parseFactor()); |
| | } |
| | if (match('(')) { |
| | auto expr = parseExpression(); |
| | if (!match(')')) { |
| | THROWM(Base::ParserError, fmt::format("Expected ')', got '{}'", input[pos])); |
| | } |
| | return expr; |
| | } |
| | if (peekColor()) { |
| | return parseColor(); |
| | } |
| | if (peekParameter()) { |
| | return parseParameter(); |
| | } |
| | if (peekFunction()) { |
| | return parseFunctionCall(); |
| | } |
| | return parseNumber(); |
| | } |
| |
|
| | bool Parser::peekColor() |
| | { |
| | skipWhitespace(); |
| | |
| | return input[pos] == '#' |
| | || peekString(rgbFunction) |
| | || peekString(rgbaFunction); |
| | |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseColor() |
| | { |
| | const auto parseHexadecimalColor = [&]() { |
| | constexpr int hexadecimalBase = 16; |
| |
|
| | |
| | pos++; |
| | int r = std::stoi(input.substr(pos, 2), nullptr, hexadecimalBase); |
| | pos += 2; |
| | int g = std::stoi(input.substr(pos, 2), nullptr, hexadecimalBase); |
| | pos += 2; |
| | int b = std::stoi(input.substr(pos, 2), nullptr, hexadecimalBase); |
| | pos += 2; |
| |
|
| | return std::make_unique<Color>(Base::Color(r / 255.0, g / 255.0, b / 255.0)); |
| | }; |
| |
|
| | const auto parseFunctionStyleColor = [&]() { |
| | bool hasAlpha = peekString(rgbaFunction); |
| |
|
| | pos += hasAlpha ? strlen(rgbaFunction) : strlen(rgbFunction); |
| |
|
| | int r = parseInt(); |
| | if (!match(',')) { |
| | THROWM(Base::ParserError, fmt::format("Expected ',' after red, got '{}'", input[pos])); |
| | } |
| | int g = parseInt(); |
| | if (!match(',')) { |
| | THROWM(Base::ParserError, fmt::format("Expected ',' after green, got '{}'", input[pos])); |
| | } |
| | int b = parseInt(); |
| | int a = 255; |
| | if (hasAlpha) { |
| | if (!match(',')) { |
| | THROWM(Base::ParserError, fmt::format("Expected ',' after blue, got '{}'", input[pos])); |
| | } |
| | a = parseInt(); |
| | } |
| | if (!match(')')) { |
| | THROWM( |
| | Base::ParserError, |
| | fmt::format("Expected ')' after color arguments, got '{}'", input[pos]) |
| | ); |
| | } |
| | return std::make_unique<Color>(Base::Color(r / 255.0, g / 255.0, b / 255.0, a / 255.0)); |
| | }; |
| |
|
| | skipWhitespace(); |
| |
|
| | try { |
| | if (input[pos] == '#') { |
| | return parseHexadecimalColor(); |
| | } |
| |
|
| | if (peekString(rgbFunction) || peekString(rgbaFunction)) { |
| | return parseFunctionStyleColor(); |
| | } |
| | } |
| | catch (std::invalid_argument&) { |
| | THROWM( |
| | Base::ParserError, |
| | "Invalid color format, expected #RRGGBB or rgb(r,g,b) or rgba(r,g,b,a)" |
| | ); |
| | } |
| |
|
| | THROWM(Base::ParserError, "Unknown color format"); |
| | } |
| |
|
| | bool Parser::peekParameter() |
| | { |
| | skipWhitespace(); |
| | return pos < input.size() && input[pos] == '@'; |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseParameter() |
| | { |
| | skipWhitespace(); |
| | if (!match('@')) { |
| | THROWM(Base::ParserError, fmt::format("Expected '@' for parameter, got '{}'", input[pos])); |
| | } |
| | size_t start = pos; |
| | while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_')) { |
| | ++pos; |
| | } |
| | if (start == pos) { |
| | THROWM( |
| | Base::ParserError, |
| | fmt::format("Expected parameter name after '@', got '{}'", input[pos]) |
| | ); |
| | } |
| | return std::make_unique<ParameterReference>(input.substr(start, pos - start)); |
| | } |
| |
|
| | bool Parser::peekFunction() |
| | { |
| | skipWhitespace(); |
| | return pos < input.size() && isalpha(input[pos]); |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseFunctionCall() |
| | { |
| | skipWhitespace(); |
| | size_t start = pos; |
| | while (pos < input.size() && isalnum(input[pos])) { |
| | ++pos; |
| | } |
| | std::string functionName = input.substr(start, pos - start); |
| |
|
| | if (!match('(')) { |
| | THROWM(Base::ParserError, fmt::format("Expected '(' after function name, got '{}'", input[pos])); |
| | } |
| |
|
| | std::vector<std::unique_ptr<Expr>> arguments; |
| | if (!match(')')) { |
| | do { |
| | arguments.push_back(parseExpression()); |
| | } while (match(',')); |
| |
|
| | if (!match(')')) { |
| | THROWM( |
| | Base::ParserError, |
| | fmt::format("Expected ')' after function arguments, got '{}'", input[pos]) |
| | ); |
| | } |
| | } |
| |
|
| | return std::make_unique<FunctionCall>(functionName, std::move(arguments)); |
| | } |
| |
|
| | int Parser::parseInt() |
| | { |
| | skipWhitespace(); |
| | size_t start = pos; |
| | while (pos < input.size() && (isdigit(input[pos]) || input[pos] == '.')) { |
| | ++pos; |
| | } |
| | return std::stoi(input.substr(start, pos - start)); |
| | } |
| |
|
| | std::unique_ptr<Expr> Parser::parseNumber() |
| | { |
| | skipWhitespace(); |
| | size_t start = pos; |
| | while (pos < input.size() && (isdigit(input[pos]) || input[pos] == '.')) { |
| | ++pos; |
| | } |
| |
|
| | std::string number = input.substr(start, pos - start); |
| |
|
| | try { |
| | double value = std::stod(number); |
| | std::string unit = parseUnit(); |
| | return std::make_unique<Number>(value, unit); |
| | } |
| | catch (std::invalid_argument&) { |
| | THROWM(Base::ParserError, fmt::format("Invalid number: {}", number)); |
| | } |
| | } |
| |
|
| | std::string Parser::parseUnit() |
| | { |
| | skipWhitespace(); |
| | size_t start = pos; |
| | while (pos < input.size() && (isalpha(input[pos]) || input[pos] == '%')) { |
| | ++pos; |
| | } |
| | if (start == pos) { |
| | return ""; |
| | } |
| | return input.substr(start, pos - start); |
| | } |
| |
|
| | bool Parser::match(char expected) |
| | { |
| | skipWhitespace(); |
| | if (pos < input.size() && input[pos] == expected) { |
| | ++pos; |
| | return true; |
| | } |
| | return false; |
| | } |
| |
|
| | void Parser::skipWhitespace() |
| | { |
| | while (pos < input.size() && isspace(input[pos])) { |
| | ++pos; |
| | } |
| | } |
| |
|
| | } |
| |
|