Download src/Gui/StyleParameters/Parser.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 14.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/StyleParameters/Parser.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Gui/StyleParameters/Parser.cpp
-
curl -L -o Parser.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/StyleParameters/Parser.cpp
14.2 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /**************************************************************************** | |
| * * | |
| * Copyright (c) 2025 Kacper Donat <kacper@kadet.net> * | |
| * * | |
| * This file is part of FreeCAD. * | |
| * * | |
| * FreeCAD is free software: you can redistribute it and/or modify it * | |
| * under the terms of the GNU Lesser General Public License as * | |
| * published by the Free Software Foundation, either version 2.1 of the * | |
| * License, or (at your option) any later version. * | |
| * * | |
| * FreeCAD 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 * | |
| * Lesser General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Lesser General Public * | |
| * License along with FreeCAD. If not, see * | |
| * <https://www.gnu.org/licenses/>. * | |
| * * | |
| ***************************************************************************/ | |
| 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>(); | |
| // In Qt if you want to make color 20% darker or lighter, you need to pass 120 as the value | |
| // we, however, want users to pass only the relative difference, hence we need to add the | |
| // 100 required by Qt. | |
| // | |
| // NOLINTNEXTLINE(*-magic-numbers) | |
| 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(); | |
| // clang-format off | |
| return input[pos] == '#' | |
| || peekString(rgbFunction) | |
| || peekString(rgbaFunction); | |
| // clang-format on | |
| } | |
| std::unique_ptr<Expr> Parser::parseColor() | |
| { | |
| const auto parseHexadecimalColor = [&]() { | |
| constexpr int hexadecimalBase = 16; | |
| // Format is #RRGGBB | |
| 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; // NOLINT(*-magic-numbers) | |
| 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 { // NOLINT(*-avoid-do-while) | |
| 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; | |
| } | |
| } | |
| } // namespace Gui::StyleParameters | |