/*
* 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( std::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 std::format( "{:.0f}", rounded );
return std::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