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
17 * along with this program. If not, see <https://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[4096]; // 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 RESULT<double> ToDouble( const VALUE& aVal )
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
102 try
103 {
104 double value;
105 auto result = fast_float::from_chars( str.data(), str.data() + str.size(), value );
106
107 if( result.ec != std::errc() || result.ptr != str.data() + str.size() )
108 throw std::invalid_argument( "Invalid number format" );
109
110 return MakeValue( value );
111 }
112 catch( ... )
113 {
114 return MakeError<double>( fmt::format( "Cannot convert '{}' to number", str ) );
115 }
116 }
117
118 // Convert Value to string (for display/concatenation)
119 static std::string ToString( const VALUE& aVal )
120 {
121 if( std::holds_alternative<std::string>( aVal ) )
122 return std::get<std::string>( aVal );
123
124 const auto num = std::get<double>( aVal );
125
126 // Smart number formatting with tolerance for floating-point precision
127 constexpr double tolerance = 1e-10;
128 double rounded = std::round( num );
129
130 // If the number is very close to a whole number, treat it as such
131 if( std::abs( num - rounded ) < tolerance && std::abs( rounded ) < 1e15 )
132 return fmt::format( "{:.0f}", rounded );
133
134 return fmt::format( "{}", num );
135 }
136
137 static char ToChar( const VALUE& aVal )
138 {
139 std::string str = ToString( aVal );
140
141 if( str.empty() )
142 return ' ';
143 else
144 return str[0];
145 }
146
147 // Check if Value represents a "truthy" value for conditionals
148 static bool IsTruthy( const VALUE& aVal )
149 {
150 if( std::holds_alternative<double>( aVal ) )
151 return std::get<double>( aVal ) != 0.0;
152
153 return !std::get<std::string>( aVal ).empty();
154 }
155
156 // arithmetic operation with type coercion
157 static RESULT<VALUE> ArithmeticOp( const VALUE& aLeft, const VALUE& aRight, char aOp )
158 {
159 auto leftNum = ToDouble( aLeft );
160 auto rightNum = ToDouble( aRight );
161
162 if( !leftNum )
163 return MakeError<VALUE>( leftNum.GetError() );
164
165 if( !rightNum )
166 return MakeError<VALUE>( rightNum.GetError() );
167
168 const auto leftVal = leftNum.GetValue();
169 const auto rightVal = rightNum.GetValue();
170
171 switch( aOp )
172 {
173 case '+':
174 return MakeValue<VALUE>( leftVal + rightVal );
175 case '-':
176 return MakeValue<VALUE>( leftVal - rightVal );
177 case '*':
178 return MakeValue<VALUE>( leftVal * rightVal );
179 case '/':
180 if( rightVal == 0.0 )
181 return MakeError<VALUE>( "Division by zero" );
182 return MakeValue<VALUE>( leftVal / rightVal );
183 case '%':
184 if( rightVal == 0.0 )
185 return MakeError<VALUE>( "Modulo by zero" );
186
187 return MakeValue<VALUE>( std::fmod( leftVal, rightVal ) );
188 case '^':
189 return MakeValue<VALUE>( std::pow( leftVal, rightVal ) );
190 case '<':
191 return MakeValue<VALUE>( leftVal < rightVal ? 1.0 : 0.0 );
192 case '>':
193 return MakeValue<VALUE>( leftVal > rightVal ? 1.0 : 0.0 );
194 case 1:
195 return MakeValue<VALUE>( leftVal <= rightVal ? 1.0 : 0.0 ); // <=
196 case 2:
197 return MakeValue<VALUE>( leftVal >= rightVal ? 1.0 : 0.0 ); // >=
198 case 3:
199 return MakeValue<VALUE>( leftVal == rightVal ? 1.0 : 0.0 ); // ==
200 case 4:
201 return MakeValue<VALUE>( leftVal != rightVal ? 1.0 : 0.0 ); // !=
202 default:
203 return MakeError<VALUE>( "Unknown operator" );
204 }
205 }
206
207 // String concatenation (special case of '+' for strings)
208 static VALUE ConcatStrings( const VALUE& aLeft, const VALUE& aRight )
209 {
210 return VALUE{ ToString( aLeft ) + ToString( aRight ) };
211 }
212 };
213
214 class NODE;
215 class DOC;
216 class PARSE_CONTEXT;
217
218 // AST Node types - supporting mixed values
220
222 {
223 BIN_OP_DATA( std::unique_ptr<NODE> aLeft, char aOperation, std::unique_ptr<NODE> aRight ) :
224 left( std::move( aLeft ) ),
225 right( std::move( aRight ) ),
226 op( aOperation )
227 {}
228
229 std::unique_ptr<NODE> left;
230 std::unique_ptr<NODE> right;
231 char op;
232 };
233
235 {
236 FUNC_DATA( std::string aName, std::vector<std::unique_ptr<NODE>> aArguments ) :
237 name( std::move( aName ) ),
238 args( std::move( aArguments ) )
239 {}
240
241 std::string name;
242 std::vector<std::unique_ptr<NODE>> args;
243 };
244
245 class NODE
246 {
247 public:
249 std::variant<std::string, double, BIN_OP_DATA, FUNC_DATA> data;
250
251 // Factory methods for type safety
252 static std::unique_ptr<NODE> CreateText( std::string aText )
253 {
254 auto node = std::make_unique<NODE>();
255 node->type = NODE_TYPE::Text;
256 node->data = std::move( aText );
257 return node;
258 }
259
260 static std::unique_ptr<NODE> CreateCalc( std::unique_ptr<NODE> aExpr )
261 {
262 auto node = std::make_unique<NODE>();
263 node->type = NODE_TYPE::Calc;
264 node->data = BIN_OP_DATA( std::move( aExpr ), '=', nullptr );
265 return node;
266 }
267
268 static std::unique_ptr<NODE> CreateVar( std::string aName )
269 {
270 auto node = std::make_unique<NODE>();
271 node->type = NODE_TYPE::Var;
272 node->data = std::move( aName );
273 return node;
274 }
275
276 static std::unique_ptr<NODE> CreateNumber( double aValue )
277 {
278 auto node = std::make_unique<NODE>();
279 node->type = NODE_TYPE::Number;
280 node->data = aValue;
281 return node;
282 }
283
284 static std::unique_ptr<NODE> CreateString( std::string aValue )
285 {
286 auto node = std::make_unique<NODE>();
287 node->type = NODE_TYPE::String;
288 node->data = std::move( aValue );
289 return node;
290 }
291
292 static std::unique_ptr<NODE> CreateBinOp( std::unique_ptr<NODE> aLeft, char aOp, std::unique_ptr<NODE> aRight )
293 {
294 auto node = std::make_unique<NODE>();
295 node->type = NODE_TYPE::BinOp;
296 node->data = BIN_OP_DATA( std::move( aLeft ), aOp, std::move( aRight ) );
297 return node;
298 }
299
300 static std::unique_ptr<NODE> CreateFunction( std::string aName, std::vector<std::unique_ptr<NODE>> aArgs )
301 {
302 auto node = std::make_unique<NODE>();
303 node->type = NODE_TYPE::Function;
304 node->data = FUNC_DATA( std::move( aName ), std::move( aArgs ) );
305 return node;
306 }
307
308 // Raw pointer factory methods for parser use
309 static NODE* CreateTextRaw( std::string aText )
310 {
311 NODE* node = new NODE();
312 node->type = NODE_TYPE::Text;
313 node->data = std::move( aText );
314 return node;
315 }
316
317 static NODE* CreateCalcRaw( NODE* aExpr )
318 {
319 auto node = new NODE();
320 node->type = NODE_TYPE::Calc;
321 node->data = BIN_OP_DATA( std::unique_ptr<NODE>( aExpr ), '=', nullptr );
322 return node;
323 }
324
325 static NODE* CreateVarRaw( std::string aName )
326 {
327 NODE* node = new NODE();
328 node->type = NODE_TYPE::Var;
329 node->data = std::move( aName );
330 return node;
331 }
332
333 static NODE* CreateNumberRaw( double aValue )
334 {
335 NODE* node = new NODE();
336 node->type = NODE_TYPE::Number;
337 node->data = aValue;
338 return node;
339 }
340
341 static NODE* CreateStringRaw( std::string aValue )
342 {
343 NODE* node = new NODE();
344 node->type = NODE_TYPE::String;
345 node->data = std::move( aValue );
346 return node;
347 }
348
349 static NODE* CreateBinOpRaw( NODE* aLeft, char aOp, NODE* aRight )
350 {
351 NODE* node = new NODE();
352 node->type = NODE_TYPE::BinOp;
353 node->data = BIN_OP_DATA( std::unique_ptr<NODE>( aLeft ), aOp, std::unique_ptr<NODE>( aRight ) );
354 return node;
355 }
356
357 static NODE* CreateFunctionRaw( std::string aName, std::vector<std::unique_ptr<NODE>>* aArgs )
358 {
359 NODE* node = new NODE();
361 node->data = FUNC_DATA( std::move( aName ), std::move( *aArgs ) );
362 delete aArgs;
363 return node;
364 }
365
366 // Mixed-type evaluation
367 template<typename Visitor>
368 RESULT<VALUE> Accept( Visitor&& aVisitor ) const
369 {
370 return std::forward<Visitor>( aVisitor )( *this );
371 }
372 };
373
374 class DOC
375 {
376 public:
377 void AddNode( std::unique_ptr<NODE> aNode )
378 {
379 nodes.emplace_back( std::move( aNode ) );
380 }
381
382 void AddNodeRaw( NODE* aNode )
383 {
384 nodes.emplace_back( std::unique_ptr<NODE>( aNode ) );
385 }
386
387 bool HasErrors() const { return errors.HasErrors(); }
388 const std::vector<std::string>& GetErrors() const { return errors.GetErrors(); }
389 std::string GetErrorSummary() const { return errors.GetAllMessages(); }
390
391 const std::vector<std::unique_ptr<NODE>>& GetNodes() const { return nodes; }
392 auto begin() const { return nodes.begin(); }
393 auto end() const { return nodes.end(); }
394
395 private:
396 std::vector<std::unique_ptr<NODE>> nodes;
398 };
399
400 // Global error collector for parser callbacks
401 extern thread_local ERROR_COLLECTOR* g_errorCollector;
402
404 {
405 public:
407
408 explicit PARSE_CONTEXT( ERROR_COLLECTOR& aErrorCollector ) :
409 errors( aErrorCollector )
410 {
411 g_errorCollector = &aErrorCollector;
412 }
413
415 {
416 g_errorCollector = nullptr;
417 }
418
419 PARSE_CONTEXT( const PARSE_CONTEXT& ) = delete;
423 };
424
425 // Enhanced evaluation visitor supporting callback-based variable resolution
427 {
428 public:
429 // Callback function type for variable resolution
430 using VAR_CALLBACK = std::function<RESULT<VALUE>(const std::string& aVariableName)>;
431
432 public:
438 explicit EVAL_VISITOR( VAR_CALLBACK aVariableCallback, ERROR_COLLECTOR& aErrorCollector );
439
440 // Visitor methods for evaluating different node types
441 RESULT<VALUE> operator()( const NODE& aNode ) const;
442
443 private:
444 RESULT<VALUE> evaluateFunction( const FUNC_DATA& aFunc ) const;
445
446 private:
449 mutable std::random_device m_rd;
450 mutable std::mt19937 m_gen;
451 };
452
453 // Enhanced document processor supporting callback-based variable resolution
455 {
456 public:
458
465 static std::pair<std::string, bool> Process( const DOC& aDoc, VAR_CALLBACK aVariableCallback );
466
473 static std::tuple<std::string, std::vector<std::string>, bool> ProcessWithDetails( const DOC& aDoc,
474 VAR_CALLBACK aCallback );
475 };
476
477
478} // namespace calc_parser
479
static std::tuple< std::string, std::vector< std::string >, bool > ProcessWithDetails(const DOC &aDoc, VAR_CALLBACK aCallback)
Process document with detailed error reporting.
EVAL_VISITOR::VAR_CALLBACK VAR_CALLBACK
static std::pair< std::string, bool > Process(const DOC &aDoc, VAR_CALLBACK aVariableCallback)
Process document using callback for variable resolution.
bool HasErrors() const
const std::vector< std::unique_ptr< NODE > > & GetNodes() const
void AddNodeRaw(NODE *aNode)
std::string GetErrorSummary() const
ERROR_COLLECTOR errors
void AddNode(std::unique_ptr< NODE > aNode)
std::vector< std::unique_ptr< NODE > > nodes
const std::vector< std::string > & GetErrors() const
std::function< RESULT< VALUE >(const std::string &aVariableName)> VAR_CALLBACK
EVAL_VISITOR(VAR_CALLBACK aVariableCallback, ERROR_COLLECTOR &aErrorCollector)
Construct evaluator with variable callback function.
RESULT< VALUE > evaluateFunction(const FUNC_DATA &aFunc) const
RESULT< VALUE > operator()(const NODE &aNode) const
static std::unique_ptr< NODE > CreateFunction(std::string aName, std::vector< std::unique_ptr< NODE > > aArgs)
static NODE * CreateFunctionRaw(std::string aName, std::vector< std::unique_ptr< NODE > > *aArgs)
static std::unique_ptr< NODE > CreateNumber(double aValue)
static std::unique_ptr< NODE > CreateString(std::string aValue)
static std::unique_ptr< NODE > CreateBinOp(std::unique_ptr< NODE > aLeft, char aOp, std::unique_ptr< NODE > aRight)
static std::unique_ptr< NODE > CreateVar(std::string aName)
static NODE * CreateStringRaw(std::string aValue)
static std::unique_ptr< NODE > CreateCalc(std::unique_ptr< NODE > aExpr)
static NODE * CreateCalcRaw(NODE *aExpr)
std::variant< std::string, double, BIN_OP_DATA, FUNC_DATA > data
static NODE * CreateNumberRaw(double aValue)
RESULT< VALUE > Accept(Visitor &&aVisitor) const
static std::unique_ptr< NODE > CreateText(std::string aText)
static NODE * CreateVarRaw(std::string aName)
static NODE * CreateTextRaw(std::string aText)
static NODE * CreateBinOpRaw(NODE *aLeft, char aOp, NODE *aRight)
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 bool IsTruthy(const VALUE &aVal)
static VALUE ConcatStrings(const VALUE &aLeft, const VALUE &aRight)
static RESULT< double > ToDouble(const VALUE &aVal)
static char ToChar(const VALUE &aVal)
static RESULT< VALUE > ArithmeticOp(const VALUE &aLeft, const VALUE &aRight, char aOp)
static std::string ToString(const VALUE &aVal)
#define KICOMMON_API
Definition kicommon.h:27
TOKEN_TYPE MakeNumberToken(double val)
thread_local ERROR_COLLECTOR * g_errorCollector
RESULT< T > MakeError(std::string aMsg)
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)
RESULT< T > MakeValue(T aVal)
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
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.