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 static auto ToChar( const Value& aVal ) -> char
137 {
138 std::string str = ToString( aVal );
139
140 if( str.empty() )
141 return ' ';
142 else
143 return str[0];
144 }
145
146 // Check if Value represents a "truthy" value for conditionals
147 static auto IsTruthy( const Value& aVal ) -> bool
148 {
149 if( std::holds_alternative<double>( aVal ) )
150 return std::get<double>( aVal ) != 0.0;
151
152 return !std::get<std::string>( aVal ).empty();
153 }
154
155 // arithmetic operation with type coercion
156 static auto ArithmeticOp( const Value& aLeft, const Value& aRight, char aOp ) -> Result<Value>
157 {
158 auto leftNum = ToDouble( aLeft );
159 auto rightNum = ToDouble( aRight );
160
161 if( !leftNum ) return MakeError<Value>( leftNum.GetError() );
162 if( !rightNum ) return MakeError<Value>( rightNum.GetError() );
163
164 const auto leftVal = leftNum.GetValue();
165 const auto rightVal = rightNum.GetValue();
166
167 switch( aOp )
168 {
169 case '+': return MakeValue<Value>( leftVal + rightVal );
170 case '-': return MakeValue<Value>( leftVal - rightVal );
171 case '*': return MakeValue<Value>( leftVal * rightVal );
172 case '/':
173 if( rightVal == 0.0 )
174 return MakeError<Value>( "Division by zero" );
175 return MakeValue<Value>( leftVal / rightVal );
176 case '%':
177 if( rightVal == 0.0 )
178 return MakeError<Value>( "Modulo by zero" );
179 return MakeValue<Value>( std::fmod( leftVal, rightVal ) );
180 case '^': return MakeValue<Value>( std::pow( leftVal, rightVal ) );
181 case '<': return MakeValue<Value>( leftVal < rightVal ? 1.0 : 0.0 );
182 case '>': return MakeValue<Value>( leftVal > rightVal ? 1.0 : 0.0 );
183 case 1: return MakeValue<Value>( leftVal <= rightVal ? 1.0 : 0.0 ); // <=
184 case 2: return MakeValue<Value>( leftVal >= rightVal ? 1.0 : 0.0 ); // >=
185 case 3: return MakeValue<Value>( leftVal == rightVal ? 1.0 : 0.0 ); // ==
186 case 4: return MakeValue<Value>( leftVal != rightVal ? 1.0 : 0.0 ); // !=
187 default:
188 return MakeError<Value>( "Unknown operator" );
189 }
190 }
191
192 // String concatenation (special case of '+' for strings)
193 static auto ConcatStrings( const Value& aLeft, const Value& aRight ) -> Value
194 {
195 return Value{ ToString( aLeft ) + ToString( aRight ) };
196 }
197 };
198
199 class NODE;
200 class DOC;
201 class PARSE_CONTEXT;
202
203 // AST Node types - supporting mixed values
205
207 {
208 std::unique_ptr<NODE> left;
209 std::unique_ptr<NODE> right;
210 char op;
211
212 BIN_OP_DATA( std::unique_ptr<NODE> aLeft, char aOperation, std::unique_ptr<NODE> aRight ) :
213 left( std::move( aLeft ) ),
214 right( std::move( aRight ) ),
215 op( aOperation )
216 {}
217 };
218
220 {
221 std::string name;
222 std::vector<std::unique_ptr<NODE>> args;
223
224 FUNC_DATA( std::string aName, std::vector<std::unique_ptr<NODE>> aArguments ) :
225 name( std::move( aName ) ),
226 args( std::move( aArguments ) )
227 {}
228 };
229
230 class NODE
231 {
232 public:
234 std::variant<std::string, double, BIN_OP_DATA, FUNC_DATA> data;
235
236 // Factory methods for type safety
237 static auto CreateText( std::string aText ) -> std::unique_ptr<NODE>
238 {
239 auto node = std::make_unique<NODE>();
240 node->type = NodeType::Text;
241 node->data = std::move( aText );
242 return node;
243 }
244
245 static auto CreateCalc( std::unique_ptr<NODE> aExpr ) -> std::unique_ptr<NODE>
246 {
247 auto node = std::make_unique<NODE>();
248 node->type = NodeType::Calc;
249 node->data = BIN_OP_DATA( std::move( aExpr ), '=', nullptr );
250 return node;
251 }
252
253 static auto CreateVar( std::string aName ) -> std::unique_ptr<NODE>
254 {
255 auto node = std::make_unique<NODE>();
256 node->type = NodeType::Var;
257 node->data = std::move( aName );
258 return node;
259 }
260
261 static auto CreateNumber( double aValue ) -> std::unique_ptr<NODE>
262 {
263 auto node = std::make_unique<NODE>();
264 node->type = NodeType::Number;
265 node->data = aValue;
266 return node;
267 }
268
269 static auto CreateString( std::string aValue ) -> std::unique_ptr<NODE>
270 {
271 auto node = std::make_unique<NODE>();
272 node->type = NodeType::String;
273 node->data = std::move( aValue );
274 return node;
275 }
276
277 static auto CreateBinOp( std::unique_ptr<NODE> aLeft, char aOp, std::unique_ptr<NODE> aRight ) -> std::unique_ptr<NODE>
278 {
279 auto node = std::make_unique<NODE>();
280 node->type = NodeType::BinOp;
281 node->data = BIN_OP_DATA( std::move( aLeft ), aOp, std::move( aRight ) );
282 return node;
283 }
284
285 static auto CreateFunction( std::string aName, std::vector<std::unique_ptr<NODE>> aArgs ) -> std::unique_ptr<NODE>
286 {
287 auto node = std::make_unique<NODE>();
288 node->type = NodeType::Function;
289 node->data = FUNC_DATA( std::move( aName ), std::move( aArgs ) );
290 return node;
291 }
292
293 // Raw pointer factory methods for parser use
294 static auto CreateTextRaw( std::string aText ) -> NODE*
295 {
296 auto node = new NODE();
297 node->type = NodeType::Text;
298 node->data = std::move( aText );
299 return node;
300 }
301
302 static auto CreateCalcRaw( NODE* aExpr ) -> NODE*
303 {
304 auto node = new NODE();
305 node->type = NodeType::Calc;
306 node->data = BIN_OP_DATA( std::unique_ptr<NODE>( aExpr ), '=', nullptr );
307 return node;
308 }
309
310 static auto CreateVarRaw( std::string aName ) -> NODE*
311 {
312 auto node = new NODE();
313 node->type = NodeType::Var;
314 node->data = std::move( aName );
315 return node;
316 }
317
318 static auto CreateNumberRaw( double aValue ) -> NODE*
319 {
320 auto node = new NODE();
321 node->type = NodeType::Number;
322 node->data = aValue;
323 return node;
324 }
325
326 static auto CreateStringRaw( std::string aValue ) -> NODE*
327 {
328 auto node = new NODE();
329 node->type = NodeType::String;
330 node->data = std::move( aValue );
331 return node;
332 }
333
334 static auto CreateBinOpRaw( NODE* aLeft, char aOp, NODE* aRight ) -> NODE*
335 {
336 auto node = new NODE();
337 node->type = NodeType::BinOp;
338 node->data = BIN_OP_DATA( std::unique_ptr<NODE>( aLeft ), aOp, std::unique_ptr<NODE>( aRight ) );
339 return node;
340 }
341
342 static auto CreateFunctionRaw( std::string aName, std::vector<std::unique_ptr<NODE>>* aArgs ) -> NODE*
343 {
344 auto node = new NODE();
345 node->type = NodeType::Function;
346 node->data = FUNC_DATA( std::move( aName ), std::move( *aArgs ) );
347 delete aArgs;
348 return node;
349 }
350
351 // Mixed-type evaluation
352 template<typename Visitor>
353 auto Accept( Visitor&& aVisitor ) const -> Result<Value>
354 {
355 return std::forward<Visitor>( aVisitor )( *this );
356 }
357 };
358
359 class DOC
360 {
361 public:
362 std::vector<std::unique_ptr<NODE>> nodes;
364
365 auto AddNode( std::unique_ptr<NODE> aNode ) -> void
366 {
367 nodes.emplace_back( std::move( aNode ) );
368 }
369
370 auto AddNodeRaw( NODE* aNode ) -> void
371 {
372 nodes.emplace_back( std::unique_ptr<NODE>( aNode ) );
373 }
374
375 auto HasErrors() const -> bool { return errors.HasErrors(); }
376 auto GetErrors() const -> const std::vector<std::string>& { return errors.GetErrors(); }
377 auto GetErrorSummary() const -> std::string { return errors.GetAllMessages(); }
378
379 auto GetNodes() const -> const auto& { return nodes; }
380 auto begin() const { return nodes.begin(); }
381 auto end() const { return nodes.end(); }
382 };
383
384 // Global error collector for parser callbacks
385 extern thread_local ERROR_COLLECTOR* g_errorCollector;
386
388 {
389 public:
391
392 explicit PARSE_CONTEXT( ERROR_COLLECTOR& aErrorCollector ) :
393 errors( aErrorCollector )
394 {
395 g_errorCollector = &aErrorCollector;
396 }
397
399 {
400 g_errorCollector = nullptr;
401 }
402
403 PARSE_CONTEXT( const PARSE_CONTEXT& ) = delete;
407 };
408
409 // Enhanced evaluation visitor supporting callback-based variable resolution
411 {
412 public:
413 // Callback function type for variable resolution
414 using VariableCallback = std::function<Result<Value>(const std::string& aVariableName)>;
415
416 private:
418 [[maybe_unused]] ERROR_COLLECTOR& m_errors;
419 mutable std::random_device m_rd;
420 mutable std::mt19937 m_gen;
421
422 public:
428 explicit EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector );
429
430 // Visitor methods for evaluating different node types
431 auto operator()( const NODE& aNode ) const -> Result<Value>;
432
433 private:
434 auto evaluateFunction( const FUNC_DATA& aFunc ) const -> Result<Value>;
435 };
436
437 // Enhanced document processor supporting callback-based variable resolution
439 {
440 public:
442
449 static auto Process( const DOC& aDoc, VariableCallback aVariableCallback )
450 -> std::pair<std::string, bool>;
451
458 static auto ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
459 -> std::tuple<std::string, std::vector<std::string>, bool>;
460 };
461
462
463} // namespace calc_parser
464
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
static auto ToChar(const Value &aVal) -> char
#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.