#pragma once #include #include #include #include namespace ThemeEngine { // Token types for expression parsing struct ExpressionToken { enum Type { LITERAL, VARIABLE }; Type type; std::string value; // Literal text or variable name }; // Pre-parsed expression for efficient repeated evaluation struct Expression { std::vector tokens; std::string rawExpr; // Original expression string for complex evaluation bool empty() const { return tokens.empty() && rawExpr.empty(); } static Expression parse(const std::string& str) { Expression expr; expr.rawExpr = str; if (str.empty()) return expr; size_t start = 0; while (start < str.length()) { size_t open = str.find('{', start); if (open == std::string::npos) { // Remaining literal expr.tokens.push_back({ExpressionToken::LITERAL, str.substr(start)}); break; } if (open > start) { // Literal before variable expr.tokens.push_back({ExpressionToken::LITERAL, str.substr(start, open - start)}); } size_t close = str.find('}', open); if (close == std::string::npos) { // Broken brace, treat as literal expr.tokens.push_back({ExpressionToken::LITERAL, str.substr(open)}); break; } // Variable expr.tokens.push_back({ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)}); start = close + 1; } return expr; } }; class ThemeContext { private: std::map strings; std::map ints; std::map bools; const ThemeContext* parent = nullptr; // Helper to trim whitespace static std::string trim(const std::string& s) { size_t start = s.find_first_not_of(" \t\n\r"); if (start == std::string::npos) return ""; size_t end = s.find_last_not_of(" \t\n\r"); return s.substr(start, end - start + 1); } // Helper to check if string is a number static bool isNumber(const std::string& s) { if (s.empty()) return false; size_t start = (s[0] == '-') ? 1 : 0; for (size_t i = start; i < s.length(); i++) { if (!isdigit(s[i])) return false; } return start < s.length(); } public: ThemeContext(const ThemeContext* parent = nullptr) : parent(parent) {} void setString(const std::string& key, const std::string& value) { strings[key] = value; } void setInt(const std::string& key, int value) { ints[key] = value; } void setBool(const std::string& key, bool value) { bools[key] = value; } std::string getString(const std::string& key, const std::string& defaultValue = "") const { auto it = strings.find(key); if (it != strings.end()) return it->second; if (parent) return parent->getString(key, defaultValue); return defaultValue; } int getInt(const std::string& key, int defaultValue = 0) const { auto it = ints.find(key); if (it != ints.end()) return it->second; if (parent) return parent->getInt(key, defaultValue); return defaultValue; } bool getBool(const std::string& key, bool defaultValue = false) const { auto it = bools.find(key); if (it != bools.end()) return it->second; if (parent) return parent->getBool(key, defaultValue); return defaultValue; } bool hasKey(const std::string& key) const { if (strings.count(key) || ints.count(key) || bools.count(key)) return true; if (parent) return parent->hasKey(key); return false; } // Get any value as string std::string getAnyAsString(const std::string& key) const { // Check strings first auto sit = strings.find(key); if (sit != strings.end()) return sit->second; // Check ints auto iit = ints.find(key); if (iit != ints.end()) return std::to_string(iit->second); // Check bools auto bit = bools.find(key); if (bit != bools.end()) return bit->second ? "true" : "false"; // Check parent if (parent) return parent->getAnyAsString(key); return ""; } // Evaluate a complex boolean expression // Supports: !, &&, ||, ==, !=, <, >, <=, >=, parentheses bool evaluateBool(const std::string& expression) const { std::string expr = trim(expression); if (expr.empty()) return false; // Handle literal true/false if (expr == "true" || expr == "1") return true; if (expr == "false" || expr == "0") return false; // Handle {var} wrapper if (expr.size() > 2 && expr.front() == '{' && expr.back() == '}') { expr = expr.substr(1, expr.size() - 2); } // Handle negation if (!expr.empty() && expr[0] == '!') { return !evaluateBool(expr.substr(1)); } // Handle parentheses if (!expr.empty() && expr[0] == '(') { int depth = 1; size_t closePos = 1; while (closePos < expr.length() && depth > 0) { if (expr[closePos] == '(') depth++; if (expr[closePos] == ')') depth--; closePos++; } if (closePos <= expr.length()) { std::string inner = expr.substr(1, closePos - 2); std::string rest = trim(expr.substr(closePos)); bool innerResult = evaluateBool(inner); // Check for && or || if (rest.length() >= 2 && rest.substr(0, 2) == "&&") { return innerResult && evaluateBool(rest.substr(2)); } if (rest.length() >= 2 && rest.substr(0, 2) == "||") { return innerResult || evaluateBool(rest.substr(2)); } return innerResult; } } // Handle && and || (lowest precedence) size_t andPos = expr.find("&&"); size_t orPos = expr.find("||"); // Process || first (lower precedence than &&) if (orPos != std::string::npos && (andPos == std::string::npos || orPos < andPos)) { return evaluateBool(expr.substr(0, orPos)) || evaluateBool(expr.substr(orPos + 2)); } if (andPos != std::string::npos) { return evaluateBool(expr.substr(0, andPos)) && evaluateBool(expr.substr(andPos + 2)); } // Handle comparisons size_t eqPos = expr.find("=="); if (eqPos != std::string::npos) { std::string left = trim(expr.substr(0, eqPos)); std::string right = trim(expr.substr(eqPos + 2)); return compareValues(left, right) == 0; } size_t nePos = expr.find("!="); if (nePos != std::string::npos) { std::string left = trim(expr.substr(0, nePos)); std::string right = trim(expr.substr(nePos + 2)); return compareValues(left, right) != 0; } size_t gePos = expr.find(">="); if (gePos != std::string::npos) { std::string left = trim(expr.substr(0, gePos)); std::string right = trim(expr.substr(gePos + 2)); return compareValues(left, right) >= 0; } size_t lePos = expr.find("<="); if (lePos != std::string::npos) { std::string left = trim(expr.substr(0, lePos)); std::string right = trim(expr.substr(lePos + 2)); return compareValues(left, right) <= 0; } size_t gtPos = expr.find('>'); if (gtPos != std::string::npos) { std::string left = trim(expr.substr(0, gtPos)); std::string right = trim(expr.substr(gtPos + 1)); return compareValues(left, right) > 0; } size_t ltPos = expr.find('<'); if (ltPos != std::string::npos) { std::string left = trim(expr.substr(0, ltPos)); std::string right = trim(expr.substr(ltPos + 1)); return compareValues(left, right) < 0; } // Simple variable lookup return getBool(expr, false); } // Compare two values (handles variables, numbers, strings) int compareValues(const std::string& left, const std::string& right) const { std::string leftVal = resolveValue(left); std::string rightVal = resolveValue(right); // Try numeric comparison if (isNumber(leftVal) && isNumber(rightVal)) { int l = std::stoi(leftVal); int r = std::stoi(rightVal); return (l < r) ? -1 : (l > r) ? 1 : 0; } // String comparison return leftVal.compare(rightVal); } // Resolve a value (variable name -> value, or literal) std::string resolveValue(const std::string& val) const { std::string v = trim(val); // Remove quotes for string literals if (v.size() >= 2 && v.front() == '"' && v.back() == '"') { return v.substr(1, v.size() - 2); } if (v.size() >= 2 && v.front() == '\'' && v.back() == '\'') { return v.substr(1, v.size() - 2); } // If it's a number, return as-is if (isNumber(v)) return v; // Check for hex color literals (0x00, 0xFF, etc.) if (v.size() > 2 && v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) { return v; } // Check for known color names - return as-is if (v == "black" || v == "white" || v == "gray" || v == "grey") { return v; } // Check for boolean literals if (v == "true" || v == "false") { return v; } // Try to look up as variable if (hasKey(v)) { return getAnyAsString(v); } // Return as literal if not found as variable return v; } // Evaluate a string expression with variable substitution std::string evaluatestring(const Expression& expr) const { if (expr.empty()) return ""; std::string result; for (const auto& token : expr.tokens) { if (token.type == ExpressionToken::LITERAL) { result += token.value; } else { // Variable lookup - check for comparison expressions inside std::string varName = token.value; // If the variable contains comparison operators, evaluate as condition if (varName.find("==") != std::string::npos || varName.find("!=") != std::string::npos || varName.find("&&") != std::string::npos || varName.find("||") != std::string::npos) { result += evaluateBool(varName) ? "true" : "false"; continue; } // Handle ternary: condition ? trueVal : falseVal size_t qPos = varName.find('?'); if (qPos != std::string::npos) { size_t cPos = varName.find(':', qPos); if (cPos != std::string::npos) { std::string condition = trim(varName.substr(0, qPos)); std::string trueVal = trim(varName.substr(qPos + 1, cPos - qPos - 1)); std::string falseVal = trim(varName.substr(cPos + 1)); bool condResult = evaluateBool(condition); result += resolveValue(condResult ? trueVal : falseVal); continue; } } // Normal variable lookup std::string strVal = getAnyAsString(varName); result += strVal; } } return result; } // Legacy method for backward compatibility std::string evaluateString(const std::string& expression) const { if (expression.empty()) return ""; Expression expr = Expression::parse(expression); return evaluatestring(expr); } }; } // namespace ThemeEngine