KiCad PCB EDA Suite
Loading...
Searching...
No Matches
text_eval_parser.cpp
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
21#include <fmt/format.h>
22#include <array>
23#include <cctype>
24#include <wx/string.h>
25
26namespace calc_parser
27{
28thread_local ERROR_COLLECTOR* g_errorCollector = nullptr;
29
31{
32private:
33 static constexpr int epochYear = 1970;
34 static constexpr std::array<int, 12> daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
35 static constexpr std::array<const char*, 12> monthNames = {
36 "January", "February", "March", "April", "May", "June",
37 "July", "August", "September", "October", "November", "December"
38 };
39 static constexpr std::array<const char*, 12> monthAbbrev = {
40 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
41 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
42 };
43 static constexpr std::array<const char*, 7> weekdayNames = {
44 "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
45 };
46
47 static auto isLeapYear( int aYear ) -> bool
48 {
49 return ( aYear % 4 == 0 && aYear % 100 != 0 ) || ( aYear % 400 == 0 );
50 }
51
52 static auto daysInYear( int aYear ) -> int
53 {
54 return isLeapYear( aYear ) ? 366 : 365;
55 }
56
57 static auto daysInMonthForYear( int aMonth, int aYear ) -> int
58 {
59 if( aMonth == 2 && isLeapYear( aYear ) )
60 return 29;
61
62 return daysInMonth[aMonth - 1];
63 }
64
65public:
66 static auto DaysToYmd( int aDaysSinceEpoch ) -> std::tuple<int, int, int>
67 {
68 int year = epochYear;
69 int remainingDays = aDaysSinceEpoch;
70
71 if( remainingDays >= 0 )
72 {
73 while( remainingDays >= daysInYear( year ) )
74 {
75 remainingDays -= daysInYear( year );
76 year++;
77 }
78 }
79 else
80 {
81 while( remainingDays < 0 )
82 {
83 year--;
84 remainingDays += daysInYear( year );
85 }
86 }
87
88 int month = 1;
89 while( month <= 12 && remainingDays >= daysInMonthForYear( month, year ) )
90 {
91 remainingDays -= daysInMonthForYear( month, year );
92 month++;
93 }
94
95 int day = remainingDays + 1;
96 return {year, month, day};
97 }
98
99 static auto YmdToDays( int aYear, int aMonth, int aDay ) -> int
100 {
101 int totalDays = 0;
102
103 if( aYear >= epochYear )
104 {
105 for( int y = epochYear; y < aYear; ++y )
106 totalDays += daysInYear( y );
107 }
108 else
109 {
110 for( int y = aYear; y < epochYear; ++y )
111 totalDays -= daysInYear( y );
112 }
113
114 for( int m = 1; m < aMonth; ++m )
115 totalDays += daysInMonthForYear( m, aYear );
116
117 totalDays += aDay - 1;
118 return totalDays;
119 }
120
121 static auto ParseDate( const std::string& aDateStr ) -> std::optional<int>
122 {
123 std::istringstream iss( aDateStr );
124 std::string token;
125 std::vector<int> parts;
126
127 char separator = 0;
128 bool isCjkFormat = false;
129
130 // Check for CJK date formats first (Chinese, Korean, or mixed)
131 bool hasChineseYear = aDateStr.find( "年" ) != std::string::npos;
132 bool hasChineseMonth = aDateStr.find( "月" ) != std::string::npos;
133 bool hasChineseDay = aDateStr.find( "日" ) != std::string::npos;
134 bool hasKoreanYear = aDateStr.find( "년" ) != std::string::npos;
135 bool hasKoreanMonth = aDateStr.find( "월" ) != std::string::npos;
136 bool hasKoreanDay = aDateStr.find( "일" ) != std::string::npos;
137
138 // Check if we have any CJK date format (pure or mixed)
139 if( (hasChineseYear || hasKoreanYear) &&
140 (hasChineseMonth || hasKoreanMonth) &&
141 (hasChineseDay || hasKoreanDay) )
142 {
143 // CJK format: Support pure Chinese, pure Korean, or mixed formats
144 isCjkFormat = true;
145
146 size_t yearPos, monthPos, dayPos;
147
148 // Find year position and marker
149 if( hasChineseYear )
150 yearPos = aDateStr.find( "年" );
151 else
152 yearPos = aDateStr.find( "년" );
153
154 // Find month position and marker
155 if( hasChineseMonth )
156 monthPos = aDateStr.find( "月" );
157 else
158 monthPos = aDateStr.find( "월" );
159
160 // Find day position and marker
161 if( hasChineseDay )
162 dayPos = aDateStr.find( "日" );
163 else
164 dayPos = aDateStr.find( "일" );
165
166 try
167 {
168 int year = std::stoi( aDateStr.substr( 0, yearPos ) );
169 int month = std::stoi( aDateStr.substr( yearPos + 3, monthPos - yearPos - 3 ) ); // 3 bytes for CJK year marker
170 int day = std::stoi( aDateStr.substr( monthPos + 3, dayPos - monthPos - 3 ) ); // 3 bytes for CJK month marker
171
172 parts = { year, month, day };
173 }
174 catch( ... )
175 {
176 return std::nullopt;
177 }
178 }
179 else if( aDateStr.find( '-' ) != std::string::npos )
180 separator = '-';
181 else if( aDateStr.find( '/' ) != std::string::npos )
182 separator = '/';
183 else if( aDateStr.find( '.' ) != std::string::npos )
184 separator = '.';
185
186 if( separator )
187 {
188 while( std::getline( iss, token, separator ) )
189 {
190 try
191 {
192 parts.push_back( std::stoi( token ) );
193 }
194 catch( ... )
195 {
196 return std::nullopt;
197 }
198 }
199 }
200 else if( !isCjkFormat && aDateStr.length() == 8 )
201 {
202 try
203 {
204 int dateNum = std::stoi( aDateStr );
205 int year = dateNum / 10000;
206 int month = ( dateNum / 100 ) % 100;
207 int day = dateNum % 100;
208 return YmdToDays( year, month, day );
209 }
210 catch( ... )
211 {
212 return std::nullopt;
213 }
214 }
215 else if( !isCjkFormat )
216 {
217 return std::nullopt;
218 }
219
220 if( parts.empty() || parts.size() > 3 )
221 return std::nullopt;
222
223 int year, month, day;
224
225 if( parts.size() == 1 )
226 {
227 year = parts[0];
228 month = 1;
229 day = 1;
230 }
231 else if( parts.size() == 2 )
232 {
233 year = parts[0];
234 month = parts[1];
235 day = 1;
236 }
237 else
238 {
239 if( isCjkFormat )
240 {
241 // CJK formats are always in YYYY年MM月DD日 or YYYY년 MM월 DD일 order
242 year = parts[0];
243 month = parts[1];
244 day = parts[2];
245 }
246 else if( separator == '/' && parts[0] <= 12 && parts[1] <= 31 )
247 {
248 month = parts[0];
249 day = parts[1];
250 year = parts[2];
251 }
252 else if( separator == '/' && parts[1] <= 12 )
253 {
254 day = parts[0];
255 month = parts[1];
256 year = parts[2];
257 }
258 else
259 {
260 year = parts[0];
261 month = parts[1];
262 day = parts[2];
263 }
264 }
265
266 if( month < 1 || month > 12 )
267 return std::nullopt;
268 if( day < 1 || day > daysInMonthForYear( month, year ) )
269 return std::nullopt;
270
271 return YmdToDays( year, month, day );
272 }
273
274 static auto FormatDate( int aDaysSinceEpoch, const std::string& aFormat ) -> std::string
275 {
276 auto [year, month, day] = DaysToYmd( aDaysSinceEpoch );
277
278 if( aFormat == "ISO" || aFormat == "iso" )
279 return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
280 else if( aFormat == "US" || aFormat == "us" )
281 return fmt::format( "{:02d}/{:02d}/{:04d}", month, day, year );
282 else if( aFormat == "EU" || aFormat == "european" )
283 return fmt::format( "{:02d}/{:02d}/{:04d}", day, month, year );
284 else if( aFormat == "long" )
285 return fmt::format( "{} {}, {}", monthNames[month-1], day, year );
286 else if( aFormat == "short" )
287 return fmt::format( "{} {}, {}", monthAbbrev[month-1], day, year );
288 else if( aFormat == "Chinese" || aFormat == "chinese" || aFormat == "CN" || aFormat == "cn" || aFormat == "中文" )
289 return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
290 else if( aFormat == "Japanese" || aFormat == "japanese" || aFormat == "JP" || aFormat == "jp" || aFormat == "日本語" )
291 return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
292 else if( aFormat == "Korean" || aFormat == "korean" || aFormat == "KR" || aFormat == "kr" || aFormat == "한국어" )
293 return fmt::format( "{}년 {:02d}월 {:02d}일", year, month, day );
294 else
295 return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
296 }
297
298 static auto GetWeekdayName( int aDaysSinceEpoch ) -> std::string
299 {
300 int weekday = ( ( aDaysSinceEpoch + 3 ) % 7 ); // +3 because epoch was Thursday (Monday = 0)
301
302 if( weekday < 0 )
303 weekday += 7;
304
305 return std::string{ weekdayNames[weekday] };
306 }
307
308 static auto GetCurrentDays() -> int
309 {
310 auto now = std::chrono::system_clock::now();
311 auto timeT = std::chrono::system_clock::to_time_t( now );
312 return static_cast<int>( timeT / ( 24 * 3600 ) );
313 }
314
315 static auto GetCurrentTimestamp() -> double
316 {
317 auto now = std::chrono::system_clock::now();
318 auto timeT = std::chrono::system_clock::to_time_t( now );
319 return static_cast<double>( timeT );
320 }
321};
322
323EVAL_VISITOR::EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ) :
324 m_variableCallback( std::move( aVariableCallback ) ),
325 m_errors( aErrorCollector ),
326 m_gen( m_rd() )
327{}
328
329auto EVAL_VISITOR::operator()( const NODE& aNode ) const -> Result<Value>
330 {
331 switch( aNode.type )
332 {
333 case NodeType::Number:
334 return MakeValue<Value>( std::get<double>( aNode.data ) );
335
336 case NodeType::String:
337 return MakeValue<Value>( std::get<std::string>( aNode.data ) );
338
339 case NodeType::Var:
340 {
341 const auto& varName = std::get<std::string>( aNode.data );
342
343 // Use callback to resolve variable
345 return m_variableCallback( varName );
346
347 return MakeError<Value>( fmt::format( "No variable resolver configured for: {}", varName ) );
348 }
349
350 case NodeType::BinOp:
351 {
352 const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
353 auto leftResult = binop.left->Accept( *this );
354 if( !leftResult )
355 return leftResult;
356
357 auto rightResult = binop.right ?
358 binop.right->Accept( *this ) : MakeValue<Value>( 0.0 );
359 if( !rightResult )
360 return rightResult;
361
362 // Special handling for string concatenation with +
363 if( binop.op == '+' )
364 {
365 const auto& leftVal = leftResult.GetValue();
366 const auto& rightVal = rightResult.GetValue();
367
368 // If either operand is a string, concatenate
369 if( std::holds_alternative<std::string>( leftVal ) ||
370 std::holds_alternative<std::string>( rightVal ) )
371 {
372 return MakeValue<Value>( VALUE_UTILS::ConcatStrings( leftVal, rightVal ) );
373 }
374 }
375
376 // Otherwise, perform arithmetic
377 return VALUE_UTILS::ArithmeticOp( leftResult.GetValue(), rightResult.GetValue(), binop.op );
378 }
379
381 {
382 const auto& func = std::get<FUNC_DATA>( aNode.data );
383 return evaluateFunction( func );
384 }
385
386 default:
387 return MakeError<Value>( "Cannot evaluate this node type" );
388 }
389}
390
392{
393 const auto& name = aFunc.name;
394 const auto& args = aFunc.args;
395
396 // Zero-argument functions
397 if( args.empty() )
398 {
399 if( name == "today" )
400 return MakeValue<Value>( static_cast<double>( DATE_UTILS::GetCurrentDays() ) );
401 else if( name == "now" )
403 else if( name == "random" )
404 {
405 std::uniform_real_distribution<double> dis( 0.0, 1.0 );
406 return MakeValue<Value>( dis( m_gen ) );
407 }
408 }
409
410 // Evaluate arguments to mixed types
411 std::vector<Value> argValues;
412 argValues.reserve( args.size() );
413
414 for( const auto& arg : args )
415 {
416 auto result = arg->Accept( *this );
417 if( !result )
418 return result;
419
420 argValues.push_back( result.GetValue() );
421 }
422
423 const auto argc = argValues.size();
424
425 // String formatting functions (return strings!)
426 if( name == "format" && argc >= 1 )
427 {
428 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
429 if( !numResult )
430 return MakeError<Value>( numResult.GetError() );
431
432 const auto value = numResult.GetValue();
433 int decimals = 2;
434 if( argc > 1 )
435 {
436 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
437 if( decResult )
438 decimals = static_cast<int>( decResult.GetValue() );
439 }
440
441 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
442 }
443 else if( name == "currency" && argc >= 1 )
444 {
445 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
446 if( !numResult )
447 return MakeError<Value>( numResult.GetError() );
448
449 const auto amount = numResult.GetValue();
450 const auto symbol = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "$";
451
452 return MakeValue<Value>( fmt::format( "{}{:.2f}", symbol, amount ) );
453 }
454 else if( name == "fixed" && argc >= 1 )
455 {
456 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
457 if( !numResult )
458 return MakeError<Value>( numResult.GetError() );
459
460 const auto value = numResult.GetValue();
461 int decimals = 2;
462 if( argc > 1 )
463 {
464 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
465 if( decResult )
466 decimals = static_cast<int>( decResult.GetValue() );
467 }
468
469 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
470 }
471
472 // Date formatting functions (return strings!)
473 else if( name == "dateformat" && argc >= 1 )
474 {
475 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
476 if( !dateResult )
477 return MakeError<Value>( dateResult.GetError() );
478
479 const auto days = static_cast<int>( dateResult.GetValue() );
480 const auto format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
481
482 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
483 }
484 else if( name == "datestring" && argc == 1 )
485 {
486 auto dateStr = VALUE_UTILS::ToString( argValues[0] );
487 auto daysResult = DATE_UTILS::ParseDate( dateStr );
488
489 if( !daysResult )
490 return MakeError<Value>( "Invalid date format: " + dateStr );
491
492 return MakeValue<Value>( static_cast<double>( daysResult.value() ) );
493 }
494 else if( name == "weekdayname" && argc == 1 )
495 {
496 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
497 if( !dateResult )
498 return MakeError<Value>( dateResult.GetError() );
499
500 const auto days = static_cast<int>( dateResult.GetValue() );
502 }
503
504 // String functions (return strings!)
505 else if( name == "upper" && argc == 1 )
506 {
507 auto str = VALUE_UTILS::ToString( argValues[0] );
508 std::transform( str.begin(), str.end(), str.begin(), ::toupper );
509 return MakeValue<Value>( str );
510 }
511 else if( name == "lower" && argc == 1 )
512 {
513 auto str = VALUE_UTILS::ToString( argValues[0] );
514 std::transform( str.begin(), str.end(), str.begin(), ::tolower );
515 return MakeValue<Value>( str );
516 }
517 else if( name == "concat" && argc >= 2 )
518 {
519 std::string result;
520 for( const auto& val : argValues )
522
523 return MakeValue<Value>( result );
524 }
525 else if( name == "beforefirst" && argc == 2 )
526 {
527 wxString result = VALUE_UTILS::ToString( argValues[0] );
528
529 result = result.BeforeFirst( VALUE_UTILS::ToChar( argValues[1] ) );
530 return MakeValue<Value>( result.ToStdString() );
531 }
532 else if( name == "beforelast" && argc == 2 )
533 {
534 wxString result = VALUE_UTILS::ToString( argValues[0] );
535
536 result = result.BeforeLast( VALUE_UTILS::ToChar( argValues[1] ) );
537 return MakeValue<Value>( result.ToStdString() );
538 }
539 else if( name == "afterfirst" && argc == 2 )
540 {
541 wxString result = VALUE_UTILS::ToString( argValues[0] );
542
543 result = result.AfterFirst( VALUE_UTILS::ToChar( argValues[1] ) );
544 return MakeValue<Value>( result.ToStdString() );
545 }
546 else if( name == "afterlast" && argc == 2 )
547 {
548 wxString result = VALUE_UTILS::ToString( argValues[0] );
549
550 result = result.AfterLast( VALUE_UTILS::ToChar( argValues[1] ) );
551 return MakeValue<Value>( result.ToStdString() );
552 }
553
554 // Conditional functions (handle mixed types)
555 if( name == "if" && argc == 3 )
556 {
557 // Convert only the condition to a number
558 auto conditionResult = VALUE_UTILS::ToDouble( argValues[0] );
559 if( !conditionResult )
560 return MakeError<Value>( conditionResult.GetError() );
561
562 const auto condition = conditionResult.GetValue() != 0.0;
563 return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
564 }
565
566 // Mathematical functions (return numbers) - convert args to doubles first
567 std::vector<double> numArgs;
568 for( const auto& val : argValues )
569 {
570 auto numResult = VALUE_UTILS::ToDouble( val );
571 if( !numResult )
572 return MakeError<Value>( numResult.GetError() );
573
574 numArgs.push_back( numResult.GetValue() );
575 }
576
577 // Mathematical function implementations
578 if( name == "abs" && argc == 1 )
579 return MakeValue<Value>( std::abs( numArgs[0] ) );
580 else if( name == "sum" && argc >= 1 )
581 return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
582 else if( name == "round" && argc >= 1 )
583 {
584 const auto value = numArgs[0];
585 const auto precision = argc > 1 ? static_cast<int>( numArgs[1] ) : 0;
586 const auto multiplier = std::pow( 10.0, precision );
587 return MakeValue<Value>( std::round( value * multiplier ) / multiplier );
588 }
589 else if( name == "sqrt" && argc == 1 )
590 {
591 if( numArgs[0] < 0 )
592 return MakeError<Value>( "Square root of negative number" );
593
594 return MakeValue<Value>( std::sqrt( numArgs[0] ) );
595 }
596 else if( name == "pow" && argc == 2 )
597 return MakeValue<Value>( std::pow( numArgs[0], numArgs[1] ) );
598 else if( name == "floor" && argc == 1 )
599 return MakeValue<Value>( std::floor( numArgs[0] ) );
600 else if( name == "ceil" && argc == 1 )
601 return MakeValue<Value>( std::ceil( numArgs[0] ) );
602 else if( name == "min" && argc >= 1 )
603 return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
604 else if( name == "max" && argc >= 1 )
605 return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
606 else if( name == "avg" && argc >= 1 )
607 {
608 const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
609 return MakeValue<Value>( sum / static_cast<double>( argc ) );
610 }
611
612 return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
613}
614
615auto DOC_PROCESSOR::Process( const DOC& aDoc, VariableCallback aVariableCallback )
616 -> std::pair<std::string, bool>
617 {
618 std::string result;
619 auto localErrors = ERROR_COLLECTOR{};
620 EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
621 bool hadErrors = aDoc.HasErrors();
622
623 for( const auto& node : aDoc.GetNodes() )
624 {
625 switch( node->type )
626 {
627 case NodeType::Text:
628 result += std::get<std::string>( node->data );
629 break;
630
631 case NodeType::Calc:
632 {
633 const auto& calcData = std::get<BIN_OP_DATA>( node->data );
634 auto evalResult = calcData.left->Accept( evaluator );
635
636 if( evalResult )
637 result += VALUE_UTILS::ToString( evalResult.GetValue() );
638 else
639 {
640 // Don't add error formatting to result - errors go to error vector only
641 // The higher level will return original input unchanged if there are errors
642 hadErrors = true;
643 }
644 break;
645 }
646
647 default:
648 result += "[Unknown node type]";
649 hadErrors = true;
650 break;
651 }
652 }
653
654 return { std::move( result ), hadErrors || localErrors.HasErrors() };
655}
656
657auto DOC_PROCESSOR::ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
658 -> std::tuple<std::string, std::vector<std::string>, bool>
659{
660 auto [result, hadErrors] = Process( aDoc, std::move( aVariableCallback ) );
661 auto allErrors = aDoc.GetErrors();
662
663 return { std::move( result ), std::move( allErrors ), hadErrors };
664}
665
666} // namespace calc_parser
const char * name
static auto GetCurrentDays() -> int
static constexpr std::array< const char *, 12 > monthNames
static auto GetWeekdayName(int aDaysSinceEpoch) -> std::string
static auto FormatDate(int aDaysSinceEpoch, const std::string &aFormat) -> std::string
static constexpr std::array< const char *, 12 > monthAbbrev
static auto YmdToDays(int aYear, int aMonth, int aDay) -> int
static auto DaysToYmd(int aDaysSinceEpoch) -> std::tuple< int, int, int >
static auto ParseDate(const std::string &aDateStr) -> std::optional< int >
static auto daysInYear(int aYear) -> int
static constexpr int epochYear
static constexpr std::array< int, 12 > daysInMonth
static auto isLeapYear(int aYear) -> bool
static constexpr std::array< const char *, 7 > weekdayNames
static auto daysInMonthForYear(int aMonth, int aYear) -> int
static auto GetCurrentTimestamp() -> double
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 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 ArithmeticOp(const Value &aLeft, const Value &aRight, char aOp) -> Result< Value >
static auto ToString(const Value &aVal) -> std::string
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
thread_local ERROR_COLLECTOR * g_errorCollector
auto MakeValue(T aVal) -> Result< T >
auto MakeError(std::string aMsg) -> Result< T >
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
wxString result
Test unit parsing edge cases and error handling.