/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * * This program 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. * * This program 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. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #pragma once #include #include #include #include #include #include #include #include // Include EDA units support #include // Include the parser types #include // Forward declaration for NUMERIC_EVALUATOR compatibility class NUMERIC_EVALUATOR_COMPAT; /** * @brief High-level wrapper for evaluating mathematical and string expressions in wxString format * * This class provides a simple interface for evaluating expressions containing @{} syntax * within wxString objects. It supports both map-based variable lookup and flexible * callback-based variable resolution for dynamic data access. * * The evaluator can work in two modes: * 1. Static variable mode: Variables are stored internally and looked up from memory * 2. Callback mode: Variables are resolved dynamically using a user-provided function * * Example usage: * @code * // Static variable mode * EXPRESSION_EVALUATOR evaluator; * evaluator.SetVariable("price", 99.99); * evaluator.SetVariable("product", "Widget"); * evaluator.SetVariable("qty", 3); * * wxString input = "Product: @{upper(${product})} - Total: @{currency(${price} * ${qty})}"; * wxString result = evaluator.Evaluate(input); * // Result: "Product: WIDGET - Total: $299.97" * * // Callback mode * auto callback = [](const std::string& varName) -> calc_parser::Result { * if (varName == "current_time") { * return calc_parser::MakeValue(getCurrentTimestamp()); * } * return calc_parser::MakeError("Variable not found: " + varName); * }; * EXPRESSION_EVALUATOR callbackEvaluator(callback); * wxString result2 = callbackEvaluator.Evaluate("Current time: @{${current_time}}"); * @endcode */ class KICOMMON_API EXPRESSION_EVALUATOR { public: // Callback function type for dynamic variable resolution using VariableCallback = std::function(const std::string& aVariableName)>; private: std::unordered_map m_variables; mutable std::unique_ptr m_lastErrors; bool m_clearVariablesOnEvaluate; VariableCallback m_customCallback; bool m_useCustomCallback; EDA_UNITS m_defaultUnits; // Default units for calculations public: /** * @brief Construct a new Expression Evaluator in static variable mode * @param aClearVariablesOnEvaluate If true, variables are cleared after each evaluation */ explicit EXPRESSION_EVALUATOR( bool aClearVariablesOnEvaluate = false ); /** * @brief Construct with default units support * @param aUnits Default units for parsing and evaluating expressions * @param aClearVariablesOnEvaluate If true, variables are cleared after each evaluation */ explicit EXPRESSION_EVALUATOR( EDA_UNITS aUnits, bool aClearVariablesOnEvaluate = false ); /** * @brief Construct with custom variable resolver callback * @param aVariableCallback Custom function for variable resolution * @param aClearVariablesOnEvaluate If true, local variables are cleared after evaluation */ explicit EXPRESSION_EVALUATOR( VariableCallback aVariableCallback, bool aClearVariablesOnEvaluate = false ); /** * @brief Construct with units and custom variable resolver callback * @param aUnits Default units for parsing and evaluating expressions * @param aVariableCallback Custom function for variable resolution * @param aClearVariablesOnEvaluate If true, local variables are cleared after evaluation */ explicit EXPRESSION_EVALUATOR( EDA_UNITS aUnits, VariableCallback aVariableCallback, bool aClearVariablesOnEvaluate = false ); /** * @brief Destructor */ ~EXPRESSION_EVALUATOR(); // Copy and move operations EXPRESSION_EVALUATOR( const EXPRESSION_EVALUATOR& aOther ); EXPRESSION_EVALUATOR& operator=( const EXPRESSION_EVALUATOR& aOther ); EXPRESSION_EVALUATOR( EXPRESSION_EVALUATOR&& aOther ) noexcept; EXPRESSION_EVALUATOR& operator=( EXPRESSION_EVALUATOR&& aOther ) noexcept; /** * @brief Set a custom variable resolver callback * @param aCallback Function to call for variable resolution * * When set, this callback takes precedence over stored variables. * The callback receives variable names and should return Result. * Set to nullptr or call ClearVariableCallback() to disable callback mode. */ void SetVariableCallback( VariableCallback aCallback ); /** * @brief Clear the custom variable resolver callback * * After calling this, the evaluator will use stored variables only. */ void ClearVariableCallback(); /** * @brief Check if a custom variable callback is set * @return true if custom callback is active */ bool HasVariableCallback() const; /** * @brief Set the default units for expressions * @param aUnits The units to use as default (mm, mil, inch, etc.) * * When expressions contain numeric values with unit suffixes (e.g., "1mm", "25mil"), * they will be converted to the default units for calculation. */ void SetDefaultUnits( EDA_UNITS aUnits ); /** * @brief Get the current default units * @return Current default units */ EDA_UNITS GetDefaultUnits() const; /** * @brief Set a numeric variable for use in expressions * @param aName Variable name (used as ${name} in expressions) * @param aValue Numeric value * * This has no effect when using callback mode, unless the callback * chooses to fall back to stored variables. */ void SetVariable( const wxString& aName, double aValue ); /** * @brief Set a string variable for use in expressions * @param aName Variable name (used as ${name} in expressions) * @param aValue String value * * This has no effect when using callback mode, unless the callback * chooses to fall back to stored variables. */ void SetVariable( const wxString& aName, const wxString& aValue ); /** * @brief Set a variable using std::string (convenience overload) * @param aName Variable name * @param aValue String value */ void SetVariable( const std::string& aName, const std::string& aValue ); /** * @brief Remove a variable from the evaluator * @param aName Variable name to remove * @return true if variable was found and removed, false otherwise */ bool RemoveVariable( const wxString& aName ); /** * @brief Clear all stored variables * * This does not affect callback-based variable resolution. */ void ClearVariables(); /** * @brief Check if a variable exists in stored variables * @param aName Variable name to check * @return true if variable exists in stored variables, false otherwise * * Note: This only checks stored variables, not callback-resolved variables. */ bool HasVariable( const wxString& aName ) const; /** * @brief Get the current value of a stored variable * @param aName Variable name * @return Variable value as wxString, or empty string if not found * * Note: This only returns stored variables, not callback-resolved variables. */ wxString GetVariable( const wxString& aName ) const; /** * @brief Get all stored variable names currently defined * @return Vector of variable names * * Note: This only returns stored variables, not callback-available variables. */ std::vector GetVariableNames() const; /** * @brief Set multiple variables at once from a map * @param aVariables Map of variable names to numeric values */ void SetVariables( const std::unordered_map& aVariables ); /** * @brief Set multiple string variables at once from a map * @param aVariables Map of variable names to string values */ void SetVariables( const std::unordered_map& aVariables ); /** * @brief Main evaluation function - processes input string and evaluates all @{} expressions * @param aInput Input string potentially containing @{} expressions * @return Fully evaluated string with all expressions replaced by their values * * Variables are resolved using the callback (if set) or stored variables. */ wxString Evaluate( const wxString& aInput ); /** * @brief Evaluate with additional temporary variables (doesn't modify stored variables) * @param aInput Input string to evaluate * @param aTempVariables Temporary numeric variables for this evaluation only * @return Evaluated string * * Temporary variables have lower priority than callback resolution but higher * priority than stored variables. */ wxString Evaluate( const wxString& aInput, const std::unordered_map& aTempVariables ); /** * @brief Evaluate with mixed temporary variables * @param aInput Input string to evaluate * @param aTempNumericVars Temporary numeric variables * @param aTempStringVars Temporary string variables * @return Evaluated string * * Priority order: callback > temp string vars > temp numeric vars > stored variables */ wxString Evaluate( const wxString& aInput, const std::unordered_map& aTempNumericVars, const std::unordered_map& aTempStringVars ); /** * @brief Check if the last evaluation had errors * @return true if errors occurred during last evaluation */ bool HasErrors() const; /** * @brief Get count of errors from the last evaluation * @return Number of errors that occurred */ size_t GetErrorCount() const; /** * @brief Get detailed error information from the last evaluation * @return Error summary as wxString, empty if no errors */ wxString GetErrorSummary() const; /** * @brief Get individual error messages from the last evaluation * @return Vector of error messages */ std::vector GetErrors() const; /** * @brief Clear any stored error information */ void ClearErrors(); /** * @brief Enable or disable automatic variable clearing after evaluation * @param aEnable If true, stored variables are cleared after each Evaluate() call * * Note: This only affects stored variables, not callback behavior. */ void SetClearVariablesOnEvaluate( bool aEnable ); /** * @brief Check if automatic variable clearing is enabled * @return true if variables are cleared after each evaluation */ bool GetClearVariablesOnEvaluate() const; /** * @brief Test if an expression can be parsed without evaluating it * @param aExpression Single expression to test (without @{} wrapper) * @return true if expression is syntactically valid * * This creates a temporary evaluator to test syntax only. */ bool TestExpression( const wxString& aExpression ); /** * @brief Count the number of @{} expressions in input string * @param aInput Input string to analyze * @return Number of @{} expression blocks found */ size_t CountExpressions( const wxString& aInput ) const; /** * @brief Extract all @{} expressions from input without evaluating * @param aInput Input string to analyze * @return Vector of expression strings (content between @{} markers) */ std::vector ExtractExpressions( const wxString& aInput ) const; private: /** * @brief Convert wxString to std::string using UTF-8 encoding * @param aWxStr wxString to convert * @return Converted std::string */ std::string wxStringToStdString( const wxString& aWxStr ) const; /** * @brief Convert std::string to wxString using UTF-8 encoding * @param aStdStr std::string to convert * @return Converted wxString */ wxString stdStringToWxString( const std::string& aStdStr ) const; /** * @brief Create a callback function that combines all variable sources * @param aTempNumericVars Temporary numeric variables (optional) * @param aTempStringVars Temporary string variables (optional) * @return Combined callback for parser */ VariableCallback createCombinedCallback( const std::unordered_map* aTempNumericVars = nullptr, const std::unordered_map* aTempStringVars = nullptr ) const; /** * @brief Parse and evaluate the input string using the expression parser * @param aInput Input string in std::string format * @param aVariableCallback Callback function to use for variable resolution * @return Pair of (result_string, had_errors) */ std::pair evaluateWithParser( const std::string& aInput, VariableCallback aVariableCallback ); /** * @brief Parse and evaluate with partial error recovery - malformed expressions left unchanged * @param aInput Input string in std::string format * @param aVariableCallback Callback function to use for variable resolution * @return Pair of (result_string, had_errors) */ std::pair evaluateWithPartialErrorRecovery( const std::string& aInput, VariableCallback aVariableCallback ); /** * @brief Full parser evaluation (original behavior) - fails completely on any error * @param aInput Input string in std::string format * @param aVariableCallback Callback function to use for variable resolution * @return Pair of (result_string, had_errors) */ std::pair evaluateWithFullParser( const std::string& aInput, VariableCallback aVariableCallback ); /** * @brief Expand ${variable} patterns that are outside @{} expressions * @param aInput Input string to process * @param aTempNumericVars Temporary numeric variables * @param aTempStringVars Temporary string variables * @return String with ${variable} patterns outside expressions expanded */ wxString expandVariablesOutsideExpressions( const wxString& aInput, const std::unordered_map& aTempNumericVars, const std::unordered_map& aTempStringVars ) const; }; /** * @brief NUMERIC_EVALUATOR compatible wrapper around EXPRESSION_EVALUATOR * * This class provides a drop-in replacement for NUMERIC_EVALUATOR that uses * the new EXPRESSION_EVALUATOR backend. It maintains the same API to allow * seamless migration of existing code. * * The key difference is that expressions are automatically wrapped in @{...} * syntax before evaluation. * * Example usage: * @code * // Old NUMERIC_EVALUATOR code: * NUMERIC_EVALUATOR eval(EDA_UNITS::MM); * eval.Process("1 + 2"); * wxString result = eval.Result(); // "3" * * // New compatible code: * NUMERIC_EVALUATOR_COMPAT eval(EDA_UNITS::MM); * eval.Process("1 + 2"); * wxString result = eval.Result(); // "3" * @endcode */ class KICOMMON_API NUMERIC_EVALUATOR_COMPAT { private: EXPRESSION_EVALUATOR m_evaluator; wxString m_lastInput; wxString m_lastResult; bool m_lastValid; public: /** * @brief Constructor with default units * @param aUnits Default units for the evaluator */ explicit NUMERIC_EVALUATOR_COMPAT( EDA_UNITS aUnits ); /** * @brief Destructor */ ~NUMERIC_EVALUATOR_COMPAT(); /** * @brief Clear parser state but retain variables * * Resets the parser state for processing a new expression. * User-defined variables are retained. */ void Clear(); /** * @brief Set default units for evaluation * @param aUnits The default units to use */ void SetDefaultUnits( EDA_UNITS aUnits ); /** * @brief Handle locale changes (for decimal separator) * * This is a no-op in the EXPRESSION_EVALUATOR implementation * since it handles locale properly internally. */ void LocaleChanged(); /** * @brief Check if the last evaluation was successful * @return True if last Process() call was successful */ bool IsValid() const; /** * @brief Get the result of the last evaluation * @return Result string, or empty if invalid */ wxString Result() const; /** * @brief Process and evaluate an expression * @param aString Expression to evaluate * @return True if evaluation was successful */ bool Process( const wxString& aString ); /** * @brief Get the original input text * @return The last input string passed to Process() */ wxString OriginalText() const; /** * @brief Set a variable value * @param aString Variable name * @param aValue Variable value */ void SetVar( const wxString& aString, double aValue ); /** * @brief Get a variable value * @param aString Variable name * @return Variable value, or 0.0 if not defined */ double GetVar( const wxString& aString ); /** * @brief Remove a single variable * @param aString Variable name to remove */ void RemoveVar( const wxString& aString ); /** * @brief Remove all variables */ void ClearVar(); };