/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif namespace calc_parser { using Value = std::variant; // Simple token type for parser compatibility struct TOKEN_TYPE { char text[256]; // Fixed size buffer for strings double dValue; // Numeric value bool isString; // Flag to indicate if this is a string token }; // Helper functions for TOKEN_TYPE inline TOKEN_TYPE MakeStringToken(const std::string& str) { TOKEN_TYPE token; token.dValue = 0.0; token.isString = true; strncpy(token.text, str.c_str(), sizeof(token.text) - 1); token.text[sizeof(token.text) - 1] = '\0'; return token; } inline TOKEN_TYPE MakeNumberToken(double val) { TOKEN_TYPE token; token.dValue = val; token.isString = false; token.text[0] = '\0'; return token; } inline std::string GetTokenString(const TOKEN_TYPE& token) { return std::string(token.text); } inline double GetTokenDouble(const TOKEN_TYPE& token) { return token.dValue; } // Value utilities for type handling class VALUE_UTILS { public: // Convert Value to double (for arithmetic operations) static auto ToDouble( const Value& aVal ) -> Result { if( std::holds_alternative( aVal ) ) return MakeValue( std::get( aVal ) ); const auto& str = std::get( aVal ); try { double value; auto result = fast_float::from_chars( str.data(), str.data() + str.size(), value ); if( result.ec != std::errc() || result.ptr != str.data() + str.size() ) throw std::invalid_argument( "Invalid number format" ); return MakeValue( value ); } catch( ... ) { return MakeError( fmt::format( "Cannot convert '{}' to number", str ) ); } } // Convert Value to string (for display/concatenation) static auto ToString( const Value& aVal ) -> std::string { if( std::holds_alternative( aVal ) ) return std::get( aVal ); const auto num = std::get( aVal ); // Smart number formatting with tolerance for floating-point precision constexpr double tolerance = 1e-10; double rounded = std::round( num ); // If the number is very close to a whole number, treat it as such if( std::abs( num - rounded ) < tolerance && std::abs( rounded ) < 1e15 ) return fmt::format( "{:.0f}", rounded ); return fmt::format( "{}", num ); } // Check if Value represents a "truthy" value for conditionals static auto IsTruthy( const Value& aVal ) -> bool { if( std::holds_alternative( aVal ) ) return std::get( aVal ) != 0.0; return !std::get( aVal ).empty(); } // arithmetic operation with type coercion static auto ArithmeticOp( const Value& aLeft, const Value& aRight, char aOp ) -> Result { auto leftNum = ToDouble( aLeft ); auto rightNum = ToDouble( aRight ); if( !leftNum ) return MakeError( leftNum.GetError() ); if( !rightNum ) return MakeError( rightNum.GetError() ); const auto leftVal = leftNum.GetValue(); const auto rightVal = rightNum.GetValue(); switch( aOp ) { case '+': return MakeValue( leftVal + rightVal ); case '-': return MakeValue( leftVal - rightVal ); case '*': return MakeValue( leftVal * rightVal ); case '/': if( rightVal == 0.0 ) return MakeError( "Division by zero" ); return MakeValue( leftVal / rightVal ); case '%': if( rightVal == 0.0 ) return MakeError( "Modulo by zero" ); return MakeValue( std::fmod( leftVal, rightVal ) ); case '^': return MakeValue( std::pow( leftVal, rightVal ) ); case '<': return MakeValue( leftVal < rightVal ? 1.0 : 0.0 ); case '>': return MakeValue( leftVal > rightVal ? 1.0 : 0.0 ); case 1: return MakeValue( leftVal <= rightVal ? 1.0 : 0.0 ); // <= case 2: return MakeValue( leftVal >= rightVal ? 1.0 : 0.0 ); // >= case 3: return MakeValue( leftVal == rightVal ? 1.0 : 0.0 ); // == case 4: return MakeValue( leftVal != rightVal ? 1.0 : 0.0 ); // != default: return MakeError( "Unknown operator" ); } } // String concatenation (special case of '+' for strings) static auto ConcatStrings( const Value& aLeft, const Value& aRight ) -> Value { return Value{ ToString( aLeft ) + ToString( aRight ) }; } }; class NODE; class DOC; class PARSE_CONTEXT; // AST Node types - supporting mixed values enum class NodeType { Text, Calc, Var, Number, String, BinOp, Function }; struct BIN_OP_DATA { std::unique_ptr left; std::unique_ptr right; char op; BIN_OP_DATA( std::unique_ptr aLeft, char aOperation, std::unique_ptr aRight ) : left( std::move( aLeft ) ), right( std::move( aRight ) ), op( aOperation ) {} }; struct FUNC_DATA { std::string name; std::vector> args; FUNC_DATA( std::string aName, std::vector> aArguments ) : name( std::move( aName ) ), args( std::move( aArguments ) ) {} }; class NODE { public: NodeType type; std::variant data; // Factory methods for type safety static auto CreateText( std::string aText ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::Text; node->data = std::move( aText ); return node; } static auto CreateCalc( std::unique_ptr aExpr ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::Calc; node->data = BIN_OP_DATA( std::move( aExpr ), '=', nullptr ); return node; } static auto CreateVar( std::string aName ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::Var; node->data = std::move( aName ); return node; } static auto CreateNumber( double aValue ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::Number; node->data = aValue; return node; } static auto CreateString( std::string aValue ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::String; node->data = std::move( aValue ); return node; } static auto CreateBinOp( std::unique_ptr aLeft, char aOp, std::unique_ptr aRight ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::BinOp; node->data = BIN_OP_DATA( std::move( aLeft ), aOp, std::move( aRight ) ); return node; } static auto CreateFunction( std::string aName, std::vector> aArgs ) -> std::unique_ptr { auto node = std::make_unique(); node->type = NodeType::Function; node->data = FUNC_DATA( std::move( aName ), std::move( aArgs ) ); return node; } // Raw pointer factory methods for parser use static auto CreateTextRaw( std::string aText ) -> NODE* { auto node = new NODE(); node->type = NodeType::Text; node->data = std::move( aText ); return node; } static auto CreateCalcRaw( NODE* aExpr ) -> NODE* { auto node = new NODE(); node->type = NodeType::Calc; node->data = BIN_OP_DATA( std::unique_ptr( aExpr ), '=', nullptr ); return node; } static auto CreateVarRaw( std::string aName ) -> NODE* { auto node = new NODE(); node->type = NodeType::Var; node->data = std::move( aName ); return node; } static auto CreateNumberRaw( double aValue ) -> NODE* { auto node = new NODE(); node->type = NodeType::Number; node->data = aValue; return node; } static auto CreateStringRaw( std::string aValue ) -> NODE* { auto node = new NODE(); node->type = NodeType::String; node->data = std::move( aValue ); return node; } static auto CreateBinOpRaw( NODE* aLeft, char aOp, NODE* aRight ) -> NODE* { auto node = new NODE(); node->type = NodeType::BinOp; node->data = BIN_OP_DATA( std::unique_ptr( aLeft ), aOp, std::unique_ptr( aRight ) ); return node; } static auto CreateFunctionRaw( std::string aName, std::vector>* aArgs ) -> NODE* { auto node = new NODE(); node->type = NodeType::Function; node->data = FUNC_DATA( std::move( aName ), std::move( *aArgs ) ); delete aArgs; return node; } // Mixed-type evaluation template auto Accept( Visitor&& aVisitor ) const -> Result { return std::forward( aVisitor )( *this ); } }; class DOC { public: std::vector> nodes; mutable ERROR_COLLECTOR errors; auto AddNode( std::unique_ptr aNode ) -> void { nodes.emplace_back( std::move( aNode ) ); } auto AddNodeRaw( NODE* aNode ) -> void { nodes.emplace_back( std::unique_ptr( aNode ) ); } auto HasErrors() const -> bool { return errors.HasErrors(); } auto GetErrors() const -> const std::vector& { return errors.GetErrors(); } auto GetErrorSummary() const -> std::string { return errors.GetAllMessages(); } auto GetNodes() const -> const auto& { return nodes; } auto begin() const { return nodes.begin(); } auto end() const { return nodes.end(); } }; // Global error collector for parser callbacks extern thread_local ERROR_COLLECTOR* g_errorCollector; class PARSE_CONTEXT { public: ERROR_COLLECTOR& errors; explicit PARSE_CONTEXT( ERROR_COLLECTOR& aErrorCollector ) : errors( aErrorCollector ) { g_errorCollector = &aErrorCollector; } ~PARSE_CONTEXT() { g_errorCollector = nullptr; } PARSE_CONTEXT( const PARSE_CONTEXT& ) = delete; PARSE_CONTEXT& operator=( const PARSE_CONTEXT& ) = delete; PARSE_CONTEXT( PARSE_CONTEXT&& ) = delete; PARSE_CONTEXT& operator=( PARSE_CONTEXT&& ) = delete; }; // Enhanced evaluation visitor supporting callback-based variable resolution class KICOMMON_API EVAL_VISITOR { public: // Callback function type for variable resolution using VariableCallback = std::function(const std::string& aVariableName)>; private: VariableCallback m_variableCallback; [[maybe_unused]] ERROR_COLLECTOR& m_errors; mutable std::random_device m_rd; mutable std::mt19937 m_gen; public: /** * @brief Construct evaluator with variable callback function * @param aVariableCallback Function to call when resolving variables * @param aErrorCollector Error collector for storing errors */ explicit EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ); // Visitor methods for evaluating different node types auto operator()( const NODE& aNode ) const -> Result; private: auto evaluateFunction( const FUNC_DATA& aFunc ) const -> Result; }; // Enhanced document processor supporting callback-based variable resolution class KICOMMON_API DOC_PROCESSOR { public: using VariableCallback = EVAL_VISITOR::VariableCallback; /** * @brief Process document using callback for variable resolution * @param aDoc Document to process * @param aVariableCallback Function to resolve variables * @return Pair of (result_string, had_errors) */ static auto Process( const DOC& aDoc, VariableCallback aVariableCallback ) -> std::pair; /** * @brief Process document with detailed error reporting * @param aDoc Document to process * @param aVariableCallback Function to resolve variables * @return Tuple of (result_string, error_messages, had_errors) */ static auto ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback ) -> std::tuple, bool>; }; } // namespace calc_parser