kicad-source/include/text_eval/text_eval_parser.h
Seth Hillbrand 44cc5b8e93 Move to fmt
2025-09-04 14:57:16 -07:00

455 lines
15 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <fast_float/fast_float.h>
#include <kicommon.h>
#include <text_eval/text_eval_types.h>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <variant>
#include <concepts>
#include <ranges>
#include <fmt/format.h>
#include <optional>
#include <cassert>
#include <cmath>
#include <chrono>
#include <random>
#include <numeric>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <unordered_map>
#include <functional>
#include <cstring>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace calc_parser
{
using Value = std::variant<double, std::string>;
// 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<double>
{
if( std::holds_alternative<double>( aVal ) )
return MakeValue( std::get<double>( aVal ) );
const auto& str = std::get<std::string>( 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<double>( 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<std::string>( aVal ) )
return std::get<std::string>( aVal );
const auto num = std::get<double>( 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<double>( aVal ) )
return std::get<double>( aVal ) != 0.0;
return !std::get<std::string>( aVal ).empty();
}
// arithmetic operation with type coercion
static auto ArithmeticOp( const Value& aLeft, const Value& aRight, char aOp ) -> Result<Value>
{
auto leftNum = ToDouble( aLeft );
auto rightNum = ToDouble( aRight );
if( !leftNum ) return MakeError<Value>( leftNum.GetError() );
if( !rightNum ) return MakeError<Value>( rightNum.GetError() );
const auto leftVal = leftNum.GetValue();
const auto rightVal = rightNum.GetValue();
switch( aOp )
{
case '+': return MakeValue<Value>( leftVal + rightVal );
case '-': return MakeValue<Value>( leftVal - rightVal );
case '*': return MakeValue<Value>( leftVal * rightVal );
case '/':
if( rightVal == 0.0 )
return MakeError<Value>( "Division by zero" );
return MakeValue<Value>( leftVal / rightVal );
case '%':
if( rightVal == 0.0 )
return MakeError<Value>( "Modulo by zero" );
return MakeValue<Value>( std::fmod( leftVal, rightVal ) );
case '^': return MakeValue<Value>( std::pow( leftVal, rightVal ) );
case '<': return MakeValue<Value>( leftVal < rightVal ? 1.0 : 0.0 );
case '>': return MakeValue<Value>( leftVal > rightVal ? 1.0 : 0.0 );
case 1: return MakeValue<Value>( leftVal <= rightVal ? 1.0 : 0.0 ); // <=
case 2: return MakeValue<Value>( leftVal >= rightVal ? 1.0 : 0.0 ); // >=
case 3: return MakeValue<Value>( leftVal == rightVal ? 1.0 : 0.0 ); // ==
case 4: return MakeValue<Value>( leftVal != rightVal ? 1.0 : 0.0 ); // !=
default:
return MakeError<Value>( "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<NODE> left;
std::unique_ptr<NODE> right;
char op;
BIN_OP_DATA( std::unique_ptr<NODE> aLeft, char aOperation, std::unique_ptr<NODE> aRight ) :
left( std::move( aLeft ) ),
right( std::move( aRight ) ),
op( aOperation )
{}
};
struct FUNC_DATA
{
std::string name;
std::vector<std::unique_ptr<NODE>> args;
FUNC_DATA( std::string aName, std::vector<std::unique_ptr<NODE>> aArguments ) :
name( std::move( aName ) ),
args( std::move( aArguments ) )
{}
};
class NODE
{
public:
NodeType type;
std::variant<std::string, double, BIN_OP_DATA, FUNC_DATA> data;
// Factory methods for type safety
static auto CreateText( std::string aText ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
node->type = NodeType::Text;
node->data = std::move( aText );
return node;
}
static auto CreateCalc( std::unique_ptr<NODE> aExpr ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
node->type = NodeType::Calc;
node->data = BIN_OP_DATA( std::move( aExpr ), '=', nullptr );
return node;
}
static auto CreateVar( std::string aName ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
node->type = NodeType::Var;
node->data = std::move( aName );
return node;
}
static auto CreateNumber( double aValue ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
node->type = NodeType::Number;
node->data = aValue;
return node;
}
static auto CreateString( std::string aValue ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
node->type = NodeType::String;
node->data = std::move( aValue );
return node;
}
static auto CreateBinOp( std::unique_ptr<NODE> aLeft, char aOp, std::unique_ptr<NODE> aRight ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
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<std::unique_ptr<NODE>> aArgs ) -> std::unique_ptr<NODE>
{
auto node = std::make_unique<NODE>();
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<NODE>( 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<NODE>( aLeft ), aOp, std::unique_ptr<NODE>( aRight ) );
return node;
}
static auto CreateFunctionRaw( std::string aName, std::vector<std::unique_ptr<NODE>>* 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<typename Visitor>
auto Accept( Visitor&& aVisitor ) const -> Result<Value>
{
return std::forward<Visitor>( aVisitor )( *this );
}
};
class DOC
{
public:
std::vector<std::unique_ptr<NODE>> nodes;
mutable ERROR_COLLECTOR errors;
auto AddNode( std::unique_ptr<NODE> aNode ) -> void
{
nodes.emplace_back( std::move( aNode ) );
}
auto AddNodeRaw( NODE* aNode ) -> void
{
nodes.emplace_back( std::unique_ptr<NODE>( aNode ) );
}
auto HasErrors() const -> bool { return errors.HasErrors(); }
auto GetErrors() const -> const std::vector<std::string>& { 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<Result<Value>(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<Value>;
private:
auto evaluateFunction( const FUNC_DATA& aFunc ) const -> Result<Value>;
};
// 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<std::string, bool>;
/**
* @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<std::string, std::vector<std::string>, bool>;
};
} // namespace calc_parser