KiCad PCB EDA Suite
Loading...
Searching...
No Matches
text_eval_parser.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19#pragma once
20#include <fast_float/fast_float.h>
21#include <kicommon.h>
23#include <iostream>
24#include <string>
25#include <memory>
26#include <vector>
27#include <variant>
28#include <concepts>
29#include <ranges>
30#include <fmt/format.h>
31#include <optional>
32#include <cassert>
33#include <cmath>
34#include <chrono>
35#include <random>
36#include <numeric>
37#include <algorithm>
38#include <sstream>
39#include <iomanip>
40#include <unordered_map>
41#include <functional>
42#include <cstring>
43
44#ifndef M_PI
45#define M_PI 3.14159265358979323846
46#endif
47
48namespace calc_parser
49{
50 using Value = std::variant<double, std::string>;
51
52 // Simple token type for parser compatibility
54 {
55 char text[256]; // Fixed size buffer for strings
56 double dValue; // Numeric value
57 bool isString; // Flag to indicate if this is a string token
58 };
59
60 // Helper functions for TOKEN_TYPE
61 inline TOKEN_TYPE MakeStringToken(const std::string& str)
62 {
63 TOKEN_TYPE token;
64 token.dValue = 0.0;
65 token.isString = true;
66 strncpy(token.text, str.c_str(), sizeof(token.text) - 1);
67 token.text[sizeof(token.text) - 1] = '\0';
68 return token;
69 }
70
71 inline TOKEN_TYPE MakeNumberToken(double val)
72 {
73 TOKEN_TYPE token;
74 token.dValue = val;
75 token.isString = false;
76 token.text[0] = '\0';
77 return token;
78 }
79
80 inline std::string GetTokenString(const TOKEN_TYPE& token)
81 {
82 return std::string(token.text);
83 }
84
85 inline double GetTokenDouble(const TOKEN_TYPE& token)
86 {
87 return token.dValue;
88 }
89
90 // Value utilities for type handling
92 {
93 public:
94 // Convert Value to double (for arithmetic operations)
95 static auto ToDouble( const Value& aVal ) -> Result<double>
96 {
97 if( std::holds_alternative<double>( aVal ) )
98 return MakeValue( std::get<double>( aVal ) );
99
100 const auto& str = std::get<std::string>( aVal );
101 try
102 {
103 double value;
104 auto result = fast_float::from_chars( str.data(), str.data() + str.size(), value );
105
106 if( result.ec != std::errc() || result.ptr != str.data() + str.size() )
107 throw std::invalid_argument( "Invalid number format" );
108
109 return MakeValue( value );
110 }
111 catch( ... )
112 {
113 return MakeError<double>( fmt::format( "Cannot convert '{}' to number", str ) );
114 }
115 }
116
117 // Convert Value to string (for display/concatenation)
118 static auto ToString( const Value& aVal ) -> std::string
119 {
120 if( std::holds_alternative<std::string>( aVal ) )
121 return std::get<std::string>( aVal );
122
123 const auto num = std::get<double>( aVal );
124
125 // Smart number formatting with tolerance for floating-point precision
126 constexpr double tolerance = 1e-10;
127 double rounded = std::round( num );
128
129 // If the number is very close to a whole number, treat it as such
130 if( std::abs( num - rounded ) < tolerance && std::abs( rounded ) < 1e15 )
131 return fmt::format( "{:.0f}", rounded );
132
133 return fmt::format( "{}", num );
134 }
135
136 // Check if Value represents a "truthy" value for conditionals
137 static auto IsTruthy( const Value& aVal ) -> bool
138 {
139 if( std::holds_alternative<double>( aVal ) )
140 return std::get<double>( aVal ) != 0.0;
141
142 return !std::get<std::string>( aVal ).empty();
143 }
144
145 // arithmetic operation with type coercion
146 static auto ArithmeticOp( const Value& aLeft, const Value& aRight, char aOp ) -> Result<Value>
147 {
148 auto leftNum = ToDouble( aLeft );
149 auto rightNum = ToDouble( aRight );
150
151 if( !leftNum ) return MakeError<Value>( leftNum.GetError() );
152 if( !rightNum ) return MakeError<Value>( rightNum.GetError() );
153
154 const auto leftVal = leftNum.GetValue();
155 const auto rightVal = rightNum.GetValue();
156
157 switch( aOp )
158 {
159 case '+': return MakeValue<Value>( leftVal + rightVal );
160 case '-': return MakeValue<Value>( leftVal - rightVal );
161 case '*': return MakeValue<Value>( leftVal * rightVal );
162 case '/':
163 if( rightVal == 0.0 )
164 return MakeError<Value>( "Division by zero" );
165 return MakeValue<Value>( leftVal / rightVal );
166 case '%':
167 if( rightVal == 0.0 )
168 return MakeError<Value>( "Modulo by zero" );
169 return MakeValue<Value>( std::fmod( leftVal, rightVal ) );
170 case '^': return MakeValue<Value>( std::pow( leftVal, rightVal ) );
171 case '<': return MakeValue<Value>( leftVal < rightVal ? 1.0 : 0.0 );
172 case '>': return MakeValue<Value>( leftVal > rightVal ? 1.0 : 0.0 );
173 case 1: return MakeValue<Value>( leftVal <= rightVal ? 1.0 : 0.0 ); // <=
174 case 2: return MakeValue<Value>( leftVal >= rightVal ? 1.0 : 0.0 ); // >=
175 case 3: return MakeValue<Value>( leftVal == rightVal ? 1.0 : 0.0 ); // ==
176 case 4: return MakeValue<Value>( leftVal != rightVal ? 1.0 : 0.0 ); // !=
177 default:
178 return MakeError<Value>( "Unknown operator" );
179 }
180 }
181
182 // String concatenation (special case of '+' for strings)
183 static auto ConcatStrings( const Value& aLeft, const Value& aRight ) -> Value
184 {
185 return Value{ ToString( aLeft ) + ToString( aRight ) };
186 }
187 };
188
189 class NODE;
190 class DOC;
191 class PARSE_CONTEXT;
192
193 // AST Node types - supporting mixed values
195
197 {
198 std::unique_ptr<NODE> left;
199 std::unique_ptr<NODE> right;
200 char op;
201
202 BIN_OP_DATA( std::unique_ptr<NODE> aLeft, char aOperation, std::unique_ptr<NODE> aRight ) :
203 left( std::move( aLeft ) ),
204 right( std::move( aRight ) ),
205 op( aOperation )
206 {}
207 };
208
210 {
211 std::string name;
212 std::vector<std::unique_ptr<NODE>> args;
213
214 FUNC_DATA( std::string aName, std::vector<std::unique_ptr<NODE>> aArguments ) :
215 name( std::move( aName ) ),
216 args( std::move( aArguments ) )
217 {}
218 };
219
220 class NODE
221 {
222 public:
224 std::variant<std::string, double, BIN_OP_DATA, FUNC_DATA> data;
225
226 // Factory methods for type safety
227 static auto CreateText( std::string aText ) -> std::unique_ptr<NODE>
228 {
229 auto node = std::make_unique<NODE>();
230 node->type = NodeType::Text;
231 node->data = std::move( aText );
232 return node;
233 }
234
235 static auto CreateCalc( std::unique_ptr<NODE> aExpr ) -> std::unique_ptr<NODE>
236 {
237 auto node = std::make_unique<NODE>();
238 node->type = NodeType::Calc;
239 node->data = BIN_OP_DATA( std::move( aExpr ), '=', nullptr );
240 return node;
241 }
242
243 static auto CreateVar( std::string aName ) -> std::unique_ptr<NODE>
244 {
245 auto node = std::make_unique<NODE>();
246 node->type = NodeType::Var;
247 node->data = std::move( aName );
248 return node;
249 }
250
251 static auto CreateNumber( double aValue ) -> std::unique_ptr<NODE>
252 {
253 auto node = std::make_unique<NODE>();
254 node->type = NodeType::Number;
255 node->data = aValue;
256 return node;
257 }
258
259 static auto CreateString( std::string aValue ) -> std::unique_ptr<NODE>
260 {
261 auto node = std::make_unique<NODE>();
262 node->type = NodeType::String;
263 node->data = std::move( aValue );
264 return node;
265 }
266
267 static auto CreateBinOp( std::unique_ptr<NODE> aLeft, char aOp, std::unique_ptr<NODE> aRight ) -> std::unique_ptr<NODE>
268 {
269 auto node = std::make_unique<NODE>();
270 node->type = NodeType::BinOp;
271 node->data = BIN_OP_DATA( std::move( aLeft ), aOp, std::move( aRight ) );
272 return node;
273 }
274
275 static auto CreateFunction( std::string aName, std::vector<std::unique_ptr<NODE>> aArgs ) -> std::unique_ptr<NODE>
276 {
277 auto node = std::make_unique<NODE>();
278 node->type = NodeType::Function;
279 node->data = FUNC_DATA( std::move( aName ), std::move( aArgs ) );
280 return node;
281 }
282
283 // Raw pointer factory methods for parser use
284 static auto CreateTextRaw( std::string aText ) -> NODE*
285 {
286 auto node = new NODE();
287 node->type = NodeType::Text;
288 node->data = std::move( aText );
289 return node;
290 }
291
292 static auto CreateCalcRaw( NODE* aExpr ) -> NODE*
293 {
294 auto node = new NODE();
295 node->type = NodeType::Calc;
296 node->data = BIN_OP_DATA( std::unique_ptr<NODE>( aExpr ), '=', nullptr );
297 return node;
298 }
299
300 static auto CreateVarRaw( std::string aName ) -> NODE*
301 {
302 auto node = new NODE();
303 node->type = NodeType::Var;
304 node->data = std::move( aName );
305 return node;
306 }
307
308 static auto CreateNumberRaw( double aValue ) -> NODE*
309 {
310 auto node = new NODE();
311 node->type = NodeType::Number;
312 node->data = aValue;
313 return node;
314 }
315
316 static auto CreateStringRaw( std::string aValue ) -> NODE*
317 {
318 auto node = new NODE();
319 node->type = NodeType::String;
320 node->data = std::move( aValue );
321 return node;
322 }
323
324 static auto CreateBinOpRaw( NODE* aLeft, char aOp, NODE* aRight ) -> NODE*
325 {
326 auto node = new NODE();
327 node->type = NodeType::BinOp;
328 node->data = BIN_OP_DATA( std::unique_ptr<NODE>( aLeft ), aOp, std::unique_ptr<NODE>( aRight ) );
329 return node;
330 }
331
332 static auto CreateFunctionRaw( std::string aName, std::vector<std::unique_ptr<NODE>>* aArgs ) -> NODE*
333 {
334 auto node = new NODE();
335 node->type = NodeType::Function;
336 node->data = FUNC_DATA( std::move( aName ), std::move( *aArgs ) );
337 delete aArgs;
338 return node;
339 }
340
341 // Mixed-type evaluation
342 template<typename Visitor>
343 auto Accept( Visitor&& aVisitor ) const -> Result<Value>
344 {
345 return std::forward<Visitor>( aVisitor )( *this );
346 }
347 };
348
349 class DOC
350 {
351 public:
352 std::vector<std::unique_ptr<NODE>> nodes;
354
355 auto AddNode( std::unique_ptr<NODE> aNode ) -> void
356 {
357 nodes.emplace_back( std::move( aNode ) );
358 }
359
360 auto AddNodeRaw( NODE* aNode ) -> void
361 {
362 nodes.emplace_back( std::unique_ptr<NODE>( aNode ) );
363 }
364
365 auto HasErrors() const -> bool { return errors.HasErrors(); }
366 auto GetErrors() const -> const std::vector<std::string>& { return errors.GetErrors(); }
367 auto GetErrorSummary() const -> std::string { return errors.GetAllMessages(); }
368
369 auto GetNodes() const -> const auto& { return nodes; }
370 auto begin() const { return nodes.begin(); }
371 auto end() const { return nodes.end(); }
372 };
373
374 // Global error collector for parser callbacks
375 extern thread_local ERROR_COLLECTOR* g_errorCollector;
376
378 {
379 public:
381
382 explicit PARSE_CONTEXT( ERROR_COLLECTOR& aErrorCollector ) :
383 errors( aErrorCollector )
384 {
385 g_errorCollector = &aErrorCollector;
386 }
387
389 {
390 g_errorCollector = nullptr;
391 }
392
393 PARSE_CONTEXT( const PARSE_CONTEXT& ) = delete;
397 };
398
399 // Enhanced evaluation visitor supporting callback-based variable resolution
401 {
402 public:
403 // Callback function type for variable resolution
404 using VariableCallback = std::function<Result<Value>(const std::string& aVariableName)>;
405
406 private:
408 [[maybe_unused]] ERROR_COLLECTOR& m_errors;
409 mutable std::random_device m_rd;
410 mutable std::mt19937 m_gen;
411
412 public:
418 explicit EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector );
419
420 // Visitor methods for evaluating different node types
421 auto operator()( const NODE& aNode ) const -> Result<Value>;
422
423 private:
424 auto evaluateFunction( const FUNC_DATA& aFunc ) const -> Result<Value>;
425 };
426
427 // Enhanced document processor supporting callback-based variable resolution
429 {
430 public:
432
439 static auto Process( const DOC& aDoc, VariableCallback aVariableCallback )
440 -> std::pair<std::string, bool>;
441
448 static auto ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
449 -> std::tuple<std::string, std::vector<std::string>, bool>;
450 };
451
452
453} // namespace calc_parser
454
static auto ProcessWithDetails(const DOC &aDoc, VariableCallback aVariableCallback) -> std::tuple< std::string, std::vector< std::string >, bool >
Process document with detailed error reporting.
EVAL_VISITOR::VariableCallback VariableCallback
static auto Process(const DOC &aDoc, VariableCallback aVariableCallback) -> std::pair< std::string, bool >
Process document using callback for variable resolution.
auto GetErrors() const -> const std::vector< std::string > &
auto HasErrors() const -> bool
auto AddNodeRaw(NODE *aNode) -> void
auto GetNodes() const -> const auto &
auto AddNode(std::unique_ptr< NODE > aNode) -> void
auto GetErrorSummary() const -> std::string
ERROR_COLLECTOR errors
std::vector< std::unique_ptr< NODE > > nodes
auto operator()(const NODE &aNode) const -> Result< Value >
VariableCallback m_variableCallback
std::function< Result< Value >(const std::string &aVariableName)> VariableCallback
EVAL_VISITOR(VariableCallback aVariableCallback, ERROR_COLLECTOR &aErrorCollector)
Construct evaluator with variable callback function.
auto evaluateFunction(const FUNC_DATA &aFunc) const -> Result< Value >
static auto CreateVar(std::string aName) -> std::unique_ptr< NODE >
static auto CreateNumber(double aValue) -> std::unique_ptr< NODE >
static auto CreateFunction(std::string aName, std::vector< std::unique_ptr< NODE > > aArgs) -> std::unique_ptr< NODE >
static auto CreateText(std::string aText) -> std::unique_ptr< NODE >
static auto CreateBinOp(std::unique_ptr< NODE > aLeft, char aOp, std::unique_ptr< NODE > aRight) -> std::unique_ptr< NODE >
static auto CreateFunctionRaw(std::string aName, std::vector< std::unique_ptr< NODE > > *aArgs) -> NODE *
static auto CreateString(std::string aValue) -> std::unique_ptr< NODE >
static auto CreateTextRaw(std::string aText) -> NODE *
static auto CreateStringRaw(std::string aValue) -> NODE *
std::variant< std::string, double, BIN_OP_DATA, FUNC_DATA > data
static auto CreateNumberRaw(double aValue) -> NODE *
static auto CreateCalcRaw(NODE *aExpr) -> NODE *
auto Accept(Visitor &&aVisitor) const -> Result< Value >
static auto CreateCalc(std::unique_ptr< NODE > aExpr) -> std::unique_ptr< NODE >
static auto CreateVarRaw(std::string aName) -> NODE *
static auto CreateBinOpRaw(NODE *aLeft, char aOp, NODE *aRight) -> NODE *
PARSE_CONTEXT(const PARSE_CONTEXT &)=delete
PARSE_CONTEXT & operator=(const PARSE_CONTEXT &)=delete
PARSE_CONTEXT(PARSE_CONTEXT &&)=delete
PARSE_CONTEXT(ERROR_COLLECTOR &aErrorCollector)
PARSE_CONTEXT & operator=(PARSE_CONTEXT &&)=delete
static auto ArithmeticOp(const Value &aLeft, const Value &aRight, char aOp) -> Result< Value >
static auto ToString(const Value &aVal) -> std::string
static auto IsTruthy(const Value &aVal) -> bool
static auto ToDouble(const Value &aVal) -> Result< double >
static auto ConcatStrings(const Value &aLeft, const Value &aRight) -> Value
#define KICOMMON_API
Definition kicommon.h:28
TOKEN_TYPE MakeNumberToken(double val)
thread_local ERROR_COLLECTOR * g_errorCollector
auto MakeValue(T aVal) -> Result< T >
std::variant< double, std::string > Value
TOKEN_TYPE MakeStringToken(const std::string &str)
std::string GetTokenString(const TOKEN_TYPE &token)
double GetTokenDouble(const TOKEN_TYPE &token)
auto MakeError(std::string aMsg) -> Result< T >
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
std::unique_ptr< NODE > right
BIN_OP_DATA(std::unique_ptr< NODE > aLeft, char aOperation, std::unique_ptr< NODE > aRight)
std::unique_ptr< NODE > left
FUNC_DATA(std::string aName, std::vector< std::unique_ptr< NODE > > aArguments)
std::vector< std::unique_ptr< NODE > > args
wxString result
Test unit parsing edge cases and error handling.