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 = { "January", "February", "March", "April",
36 "May", "June", "July", "August",
37 "September", "October", "November", "December" };
38 static constexpr std::array<const char*, 12> monthAbbrev = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
39 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
40 static constexpr std::array<const char*, 7> weekdayNames = { "Monday", "Tuesday", "Wednesday", "Thursday",
41 "Friday", "Saturday", "Sunday" };
42
43 static auto isLeapYear( int aYear ) -> bool
44 {
45 return ( aYear % 4 == 0 && aYear % 100 != 0 ) || ( aYear % 400 == 0 );
46 }
47
48 static auto daysInYear( int aYear ) -> int { return isLeapYear( aYear ) ? 366 : 365; }
49
50 static auto daysInMonthForYear( int aMonth, int aYear ) -> int
51 {
52 if( aMonth == 2 && isLeapYear( aYear ) )
53 return 29;
54
55 return daysInMonth[aMonth - 1];
56 }
57
58public:
59 static auto DaysToYmd( int aDaysSinceEpoch ) -> std::tuple<int, int, int>
60 {
61 int year = epochYear;
62 int remainingDays = aDaysSinceEpoch;
63
64 if( remainingDays >= 0 )
65 {
66 while( remainingDays >= daysInYear( year ) )
67 {
68 remainingDays -= daysInYear( year );
69 year++;
70 }
71 }
72 else
73 {
74 while( remainingDays < 0 )
75 {
76 year--;
77 remainingDays += daysInYear( year );
78 }
79 }
80
81 int month = 1;
82 while( month <= 12 && remainingDays >= daysInMonthForYear( month, year ) )
83 {
84 remainingDays -= daysInMonthForYear( month, year );
85 month++;
86 }
87
88 int day = remainingDays + 1;
89 return { year, month, day };
90 }
91
92 static auto YmdToDays( int aYear, int aMonth, int aDay ) -> int
93 {
94 int totalDays = 0;
95
96 if( aYear >= epochYear )
97 {
98 for( int y = epochYear; y < aYear; ++y )
99 totalDays += daysInYear( y );
100 }
101 else
102 {
103 for( int y = aYear; y < epochYear; ++y )
104 totalDays -= daysInYear( y );
105 }
106
107 for( int m = 1; m < aMonth; ++m )
108 totalDays += daysInMonthForYear( m, aYear );
109
110 totalDays += aDay - 1;
111 return totalDays;
112 }
113
114 static auto ParseDate( const std::string& aDateStr ) -> std::optional<int>
115 {
116 std::istringstream iss( aDateStr );
117 std::string token;
118 std::vector<int> parts;
119
120 char separator = 0;
121 bool isCjkFormat = false;
122
123 // Check for CJK date formats first (Chinese, Korean, or mixed)
124 bool hasChineseYear = aDateStr.find( "年" ) != std::string::npos;
125 bool hasChineseMonth = aDateStr.find( "月" ) != std::string::npos;
126 bool hasChineseDay = aDateStr.find( "日" ) != std::string::npos;
127 bool hasKoreanYear = aDateStr.find( "년" ) != std::string::npos;
128 bool hasKoreanMonth = aDateStr.find( "월" ) != std::string::npos;
129 bool hasKoreanDay = aDateStr.find( "일" ) != std::string::npos;
130
131 // Check if we have any CJK date format (pure or mixed)
132 if( ( hasChineseYear || hasKoreanYear ) && ( hasChineseMonth || hasKoreanMonth )
133 && ( hasChineseDay || hasKoreanDay ) )
134 {
135 // CJK format: Support pure Chinese, pure Korean, or mixed formats
136 isCjkFormat = true;
137
138 size_t yearPos, monthPos, dayPos;
139
140 // Find year position and marker
141 if( hasChineseYear )
142 yearPos = aDateStr.find( "年" );
143 else
144 yearPos = aDateStr.find( "년" );
145
146 // Find month position and marker
147 if( hasChineseMonth )
148 monthPos = aDateStr.find( "月" );
149 else
150 monthPos = aDateStr.find( "월" );
151
152 // Find day position and marker
153 if( hasChineseDay )
154 dayPos = aDateStr.find( "日" );
155 else
156 dayPos = aDateStr.find( "일" );
157
158 try
159 {
160 int year = std::stoi( aDateStr.substr( 0, yearPos ) );
161 int month = std::stoi(
162 aDateStr.substr( yearPos + 3, monthPos - yearPos - 3 ) ); // 3 bytes for CJK year marker
163 int day = std::stoi(
164 aDateStr.substr( monthPos + 3, dayPos - monthPos - 3 ) ); // 3 bytes for CJK month marker
165
166 parts = { year, month, day };
167 }
168 catch( ... )
169 {
170 return std::nullopt;
171 }
172 }
173 else if( aDateStr.find( '-' ) != std::string::npos )
174 separator = '-';
175 else if( aDateStr.find( '/' ) != std::string::npos )
176 separator = '/';
177 else if( aDateStr.find( '.' ) != std::string::npos )
178 separator = '.';
179
180 if( separator )
181 {
182 while( std::getline( iss, token, separator ) )
183 {
184 try
185 {
186 parts.push_back( std::stoi( token ) );
187 }
188 catch( ... )
189 {
190 return std::nullopt;
191 }
192 }
193 }
194 else if( !isCjkFormat && aDateStr.length() == 8 )
195 {
196 try
197 {
198 int dateNum = std::stoi( aDateStr );
199 int year = dateNum / 10000;
200 int month = ( dateNum / 100 ) % 100;
201 int day = dateNum % 100;
202 return YmdToDays( year, month, day );
203 }
204 catch( ... )
205 {
206 return std::nullopt;
207 }
208 }
209 else if( !isCjkFormat )
210 {
211 return std::nullopt;
212 }
213
214 if( parts.empty() || parts.size() > 3 )
215 return std::nullopt;
216
217 int year, month, day;
218
219 if( parts.size() == 1 )
220 {
221 year = parts[0];
222 month = 1;
223 day = 1;
224 }
225 else if( parts.size() == 2 )
226 {
227 year = parts[0];
228 month = parts[1];
229 day = 1;
230 }
231 else
232 {
233 if( isCjkFormat )
234 {
235 // CJK formats are always in YYYY年MM月DD日 or YYYY년 MM월 DD일 order
236 year = parts[0];
237 month = parts[1];
238 day = parts[2];
239 }
240 else if( separator == '/' && parts[0] <= 12 && parts[1] <= 31 )
241 {
242 month = parts[0];
243 day = parts[1];
244 year = parts[2];
245 }
246 else if( separator == '/' && parts[1] <= 12 )
247 {
248 day = parts[0];
249 month = parts[1];
250 year = parts[2];
251 }
252 else
253 {
254 year = parts[0];
255 month = parts[1];
256 day = parts[2];
257 }
258 }
259
260 if( month < 1 || month > 12 )
261 return std::nullopt;
262 if( day < 1 || day > daysInMonthForYear( month, year ) )
263 return std::nullopt;
264
265 return YmdToDays( year, month, day );
266 }
267
268 static auto FormatDate( int aDaysSinceEpoch, const std::string& aFormat ) -> std::string
269 {
270 auto [year, month, day] = DaysToYmd( aDaysSinceEpoch );
271
272 if( aFormat == "ISO" || aFormat == "iso" )
273 return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
274 else if( aFormat == "US" || aFormat == "us" )
275 return fmt::format( "{:02d}/{:02d}/{:04d}", month, day, year );
276 else if( aFormat == "EU" || aFormat == "european" )
277 return fmt::format( "{:02d}/{:02d}/{:04d}", day, month, year );
278 else if( aFormat == "long" )
279 return fmt::format( "{} {}, {}", monthNames[month - 1], day, year );
280 else if( aFormat == "short" )
281 return fmt::format( "{} {}, {}", monthAbbrev[month - 1], day, year );
282 else if( aFormat == "Chinese" || aFormat == "chinese" || aFormat == "CN" || aFormat == "cn"
283 || aFormat == "中文" )
284 return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
285 else if( aFormat == "Japanese" || aFormat == "japanese" || aFormat == "JP" || aFormat == "jp"
286 || aFormat == "日本語" )
287 return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
288 else if( aFormat == "Korean" || aFormat == "korean" || aFormat == "KR" || aFormat == "kr"
289 || aFormat == "한국어" )
290 return fmt::format( "{}년 {:02d}월 {:02d}일", year, month, day );
291 else
292 return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
293 }
294
295 static auto GetWeekdayName( int aDaysSinceEpoch ) -> std::string
296 {
297 int weekday = ( ( aDaysSinceEpoch + 3 ) % 7 ); // +3 because epoch was Thursday (Monday = 0)
298
299 if( weekday < 0 )
300 weekday += 7;
301
302 return std::string{ weekdayNames[weekday] };
303 }
304
305 static auto GetCurrentDays() -> int
306 {
307 auto now = std::chrono::system_clock::now();
308 auto timeT = std::chrono::system_clock::to_time_t( now );
309 return static_cast<int>( timeT / ( 24 * 3600 ) );
310 }
311
312 static auto GetCurrentTimestamp() -> double
313 {
314 auto now = std::chrono::system_clock::now();
315 auto timeT = std::chrono::system_clock::to_time_t( now );
316 return static_cast<double>( timeT );
317 }
318};
319
320
322{
323private:
324 // E24 series values in 100-999 decade (2 significant figures)
325 static constexpr std::array<uint16_t, 24> s_e24 = {
326 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300,
327 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910
328 };
329
330 // E192 series values in 100-999 decade (3 significant figures)
331 static constexpr std::array<uint16_t, 192> s_e192 = {
332 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 123,
333 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 142, 143, 145, 147, 149, 150, 152,
334 154, 156, 158, 160, 162, 164, 165, 167, 169, 172, 174, 176, 178, 180, 182, 184, 187, 189,
335 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 221, 223, 226, 229, 232, 234,
336 237, 240, 243, 246, 249, 252, 255, 258, 261, 264, 267, 271, 274, 277, 280, 284, 287, 291,
337 294, 298, 301, 305, 309, 312, 316, 320, 324, 328, 332, 336, 340, 344, 348, 352, 357, 361,
338 365, 370, 374, 379, 383, 388, 392, 397, 402, 407, 412, 417, 422, 427, 432, 437, 442, 448,
339 453, 459, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 530, 536, 542, 549, 556,
340 562, 569, 576, 583, 590, 597, 604, 612, 619, 626, 634, 642, 649, 657, 665, 673, 681, 690,
341 698, 706, 715, 723, 732, 741, 750, 759, 768, 777, 787, 796, 806, 816, 825, 835, 845, 856,
342 866, 876, 887, 898, 909, 920, 931, 942, 953, 965, 976, 988
343 };
344
345 static auto parseSeriesString( const std::string& aSeries ) -> int
346 {
347 if( aSeries == "E3" || aSeries == "e3" )
348 return 3;
349 else if( aSeries == "E6" || aSeries == "e6" )
350 return 6;
351 else if( aSeries == "E12" || aSeries == "e12" )
352 return 12;
353 else if( aSeries == "E24" || aSeries == "e24" )
354 return 24;
355 else if( aSeries == "E48" || aSeries == "e48" )
356 return 48;
357 else if( aSeries == "E96" || aSeries == "e96" )
358 return 96;
359 else if( aSeries == "E192" || aSeries == "e192" )
360 return 192;
361 else
362 return -1; // Invalid series
363 }
364
365 static auto getSeriesValue( int aSeries, size_t aIndex ) -> uint16_t
366 {
367 // E1, E3, E6, E12, E24 are derived from E24
368 if( aSeries <= 24 )
369 {
370 const size_t skipValue = 24 / aSeries;
371 return s_e24[aIndex * skipValue];
372 }
373 // E48, E96, E192 are derived from E192
374 else
375 {
376 const size_t skipValue = 192 / aSeries;
377 return s_e192[aIndex * skipValue];
378 }
379 }
380
381 static auto getSeriesSize( int aSeries ) -> size_t
382 {
383 return static_cast<size_t>( aSeries );
384 }
385
386public:
387 static auto FindNearest( double aValue, const std::string& aSeries ) -> std::optional<double>
388 {
389 const int series = parseSeriesString( aSeries );
390 if( series < 0 )
391 return std::nullopt;
392
393 if( aValue <= 0.0 )
394 return std::nullopt;
395
396 // Scale value to 100-999 decade
397 const double logValue = std::log10( aValue );
398 const int decade = static_cast<int>( std::floor( logValue ) );
399 const double scaledValue = aValue / std::pow( 10.0, decade );
400 const double normalized = scaledValue * 100.0;
401
402 // Find nearest value in series
403 const size_t seriesSize = getSeriesSize( series );
404 double minDiff = std::numeric_limits<double>::max();
405 uint16_t nearest = 100;
406
407 for( size_t i = 0; i < seriesSize; ++i )
408 {
409 const uint16_t val = getSeriesValue( series, i );
410 const double diff = std::abs( normalized - val );
411 if( diff < minDiff )
412 {
413 minDiff = diff;
414 nearest = val;
415 }
416 }
417
418 // Scale back to original decade
419 return ( nearest / 100.0 ) * std::pow( 10.0, decade );
420 }
421
422 static auto FindUp( double aValue, const std::string& aSeries ) -> std::optional<double>
423 {
424 const int series = parseSeriesString( aSeries );
425 if( series < 0 )
426 return std::nullopt;
427
428 if( aValue <= 0.0 )
429 return std::nullopt;
430
431 // Scale value to 100-999 decade
432 const double logValue = std::log10( aValue );
433 const int decade = static_cast<int>( std::floor( logValue ) );
434 const double scaledValue = aValue / std::pow( 10.0, decade );
435 const double normalized = scaledValue * 100.0;
436
437 // Find next higher value in series
438 const size_t seriesSize = getSeriesSize( series );
439
440 // Check current decade
441 for( size_t i = 0; i < seriesSize; ++i )
442 {
443 const uint16_t val = getSeriesValue( series, i );
444 if( val > normalized )
445 return ( val / 100.0 ) * std::pow( 10.0, decade );
446 }
447
448 // Wrap to next decade
449 const uint16_t firstVal = getSeriesValue( series, 0 );
450 return ( firstVal / 100.0 ) * std::pow( 10.0, decade + 1 );
451 }
452
453 static auto FindDown( double aValue, const std::string& aSeries ) -> std::optional<double>
454 {
455 const int series = parseSeriesString( aSeries );
456 if( series < 0 )
457 return std::nullopt;
458
459 if( aValue <= 0.0 )
460 return std::nullopt;
461
462 // Scale value to 100-999 decade
463 const double logValue = std::log10( aValue );
464 const int decade = static_cast<int>( std::floor( logValue ) );
465 const double scaledValue = aValue / std::pow( 10.0, decade );
466 const double normalized = scaledValue * 100.0;
467
468 // Find next lower value in series
469 const size_t seriesSize = getSeriesSize( series );
470
471 // Check current decade (search backwards)
472 for( int i = seriesSize - 1; i >= 0; --i )
473 {
474 const uint16_t val = getSeriesValue( series, i );
475 if( val < normalized )
476 return ( val / 100.0 ) * std::pow( 10.0, decade );
477 }
478
479 // Wrap to previous decade
480 const uint16_t lastVal = getSeriesValue( series, seriesSize - 1 );
481 return ( lastVal / 100.0 ) * std::pow( 10.0, decade - 1 );
482 }
483};
484
485
486EVAL_VISITOR::EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ) :
487 m_variableCallback( std::move( aVariableCallback ) ),
488 m_errors( aErrorCollector ),
489 m_gen( m_rd() )
490{
491}
492
493auto EVAL_VISITOR::operator()( const NODE& aNode ) const -> Result<Value>
494{
495 switch( aNode.type )
496 {
497 case NodeType::Number: return MakeValue<Value>( std::get<double>( aNode.data ) );
498
499 case NodeType::String: return MakeValue<Value>( std::get<std::string>( aNode.data ) );
500
501 case NodeType::Var:
502 {
503 const auto& varName = std::get<std::string>( aNode.data );
504
505 // Use callback to resolve variable
507 return m_variableCallback( varName );
508
509 return MakeError<Value>( fmt::format( "No variable resolver configured for: {}", varName ) );
510 }
511
512 case NodeType::BinOp:
513 {
514 const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
515 auto leftResult = binop.left->Accept( *this );
516 if( !leftResult )
517 return leftResult;
518
519 auto rightResult = binop.right ? binop.right->Accept( *this ) : MakeValue<Value>( 0.0 );
520 if( !rightResult )
521 return rightResult;
522
523 // Special handling for string concatenation with +
524 if( binop.op == '+' )
525 {
526 const auto& leftVal = leftResult.GetValue();
527 const auto& rightVal = rightResult.GetValue();
528
529 // If either operand is a string, concatenate
530 if( std::holds_alternative<std::string>( leftVal ) || std::holds_alternative<std::string>( rightVal ) )
531 {
532 return MakeValue<Value>( VALUE_UTILS::ConcatStrings( leftVal, rightVal ) );
533 }
534 }
535
536 // Special handling for string comparisons with == and !=
537 if( binop.op == 3 || binop.op == 4 ) // == or !=
538 {
539 const auto& leftVal = leftResult.GetValue();
540 const auto& rightVal = rightResult.GetValue();
541
542 // If both operands are strings, do string comparison
543 if( std::holds_alternative<std::string>( leftVal ) && std::holds_alternative<std::string>( rightVal ) )
544 {
545 bool equal = std::get<std::string>( leftVal ) == std::get<std::string>( rightVal );
546 double result = ( binop.op == 3 ) ? ( equal ? 1.0 : 0.0 ) : ( equal ? 0.0 : 1.0 );
547 return MakeValue<Value>( result );
548 }
549 }
550
551 // Otherwise, perform arithmetic
552 return VALUE_UTILS::ArithmeticOp( leftResult.GetValue(), rightResult.GetValue(), binop.op );
553 }
554
556 {
557 const auto& func = std::get<FUNC_DATA>( aNode.data );
558 return evaluateFunction( func );
559 }
560
561 default: return MakeError<Value>( "Cannot evaluate this node type" );
562 }
563}
564
566{
567 const auto& name = aFunc.name;
568 const auto& args = aFunc.args;
569
570 // Zero-argument functions
571 if( args.empty() )
572 {
573 if( name == "today" )
574 return MakeValue<Value>( static_cast<double>( DATE_UTILS::GetCurrentDays() ) );
575 else if( name == "now" )
577 else if( name == "random" )
578 {
579 std::uniform_real_distribution<double> dis( 0.0, 1.0 );
580 return MakeValue<Value>( dis( m_gen ) );
581 }
582 }
583
584 // Evaluate arguments to mixed types
585 std::vector<Value> argValues;
586 argValues.reserve( args.size() );
587
588 for( const auto& arg : args )
589 {
590 auto result = arg->Accept( *this );
591 if( !result )
592 return result;
593
594 argValues.push_back( result.GetValue() );
595 }
596
597 const auto argc = argValues.size();
598
599 // String formatting functions (return strings!)
600 if( name == "format" && argc >= 1 )
601 {
602 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
603 if( !numResult )
604 return MakeError<Value>( numResult.GetError() );
605
606 const auto value = numResult.GetValue();
607 int decimals = 2;
608 if( argc > 1 )
609 {
610 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
611 if( decResult )
612 decimals = static_cast<int>( decResult.GetValue() );
613 }
614
615 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
616 }
617 else if( name == "currency" && argc >= 1 )
618 {
619 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
620 if( !numResult )
621 return MakeError<Value>( numResult.GetError() );
622
623 const auto amount = numResult.GetValue();
624 const auto symbol = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "$";
625
626 return MakeValue<Value>( fmt::format( "{}{:.2f}", symbol, amount ) );
627 }
628 else if( name == "fixed" && argc >= 1 )
629 {
630 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
631 if( !numResult )
632 return MakeError<Value>( numResult.GetError() );
633
634 const auto value = numResult.GetValue();
635 int decimals = 2;
636 if( argc > 1 )
637 {
638 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
639 if( decResult )
640 decimals = static_cast<int>( decResult.GetValue() );
641 }
642
643 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
644 }
645
646 // Date formatting functions (return strings!)
647 else if( name == "dateformat" && argc >= 1 )
648 {
649 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
650 if( !dateResult )
651 return MakeError<Value>( dateResult.GetError() );
652
653 const auto days = static_cast<int>( dateResult.GetValue() );
654 const auto format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
655
656 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
657 }
658 else if( name == "datestring" && argc == 1 )
659 {
660 auto dateStr = VALUE_UTILS::ToString( argValues[0] );
661 auto daysResult = DATE_UTILS::ParseDate( dateStr );
662
663 if( !daysResult )
664 return MakeError<Value>( "Invalid date format: " + dateStr );
665
666 return MakeValue<Value>( static_cast<double>( daysResult.value() ) );
667 }
668 else if( name == "weekdayname" && argc == 1 )
669 {
670 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
671 if( !dateResult )
672 return MakeError<Value>( dateResult.GetError() );
673
674 const auto days = static_cast<int>( dateResult.GetValue() );
676 }
677
678 // String functions (return strings!)
679 else if( name == "upper" && argc == 1 )
680 {
681 auto str = VALUE_UTILS::ToString( argValues[0] );
682 std::transform( str.begin(), str.end(), str.begin(), ::toupper );
683 return MakeValue<Value>( str );
684 }
685 else if( name == "lower" && argc == 1 )
686 {
687 auto str = VALUE_UTILS::ToString( argValues[0] );
688 std::transform( str.begin(), str.end(), str.begin(), ::tolower );
689 return MakeValue<Value>( str );
690 }
691 else if( name == "concat" && argc >= 2 )
692 {
693 std::string result;
694 for( const auto& val : argValues )
696
697 return MakeValue<Value>( result );
698 }
699 else if( name == "beforefirst" && argc == 2 )
700 {
701 wxString result = VALUE_UTILS::ToString( argValues[0] );
702
703 result = result.BeforeFirst( VALUE_UTILS::ToChar( argValues[1] ) );
704 return MakeValue<Value>( result.ToStdString() );
705 }
706 else if( name == "beforelast" && argc == 2 )
707 {
708 wxString result = VALUE_UTILS::ToString( argValues[0] );
709
710 result = result.BeforeLast( VALUE_UTILS::ToChar( argValues[1] ) );
711 return MakeValue<Value>( result.ToStdString() );
712 }
713 else if( name == "afterfirst" && argc == 2 )
714 {
715 wxString result = VALUE_UTILS::ToString( argValues[0] );
716
717 result = result.AfterFirst( VALUE_UTILS::ToChar( argValues[1] ) );
718 return MakeValue<Value>( result.ToStdString() );
719 }
720 else if( name == "afterlast" && argc == 2 )
721 {
722 wxString result = VALUE_UTILS::ToString( argValues[0] );
723
724 result = result.AfterLast( VALUE_UTILS::ToChar( argValues[1] ) );
725 return MakeValue<Value>( result.ToStdString() );
726 }
727
728 // Conditional functions (handle mixed types)
729 if( name == "if" && argc == 3 )
730 {
731 // Convert only the condition to a number
732 auto conditionResult = VALUE_UTILS::ToDouble( argValues[0] );
733 if( !conditionResult )
734 return MakeError<Value>( conditionResult.GetError() );
735
736 const auto condition = conditionResult.GetValue() != 0.0;
737 return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
738 }
739
740 // E-series functions (handle value as number, series as string)
741 else if( ( name == "enearest" || name == "eup" || name == "edown" ) && argc >= 1 && argc <= 2 )
742 {
743 auto valueResult = VALUE_UTILS::ToDouble( argValues[0] );
744 if( !valueResult )
745 return MakeError<Value>( valueResult.GetError() );
746
747 const auto value = valueResult.GetValue();
748 const auto series = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "E24";
749 std::optional<double> result;
750
751 if( name == "enearest" )
752 result = ESERIES_UTILS::FindNearest( value, series );
753 else if( name == "eup" )
754 result = ESERIES_UTILS::FindUp( value, series );
755 else if( name == "edown" )
756 result = ESERIES_UTILS::FindDown( value, series );
757
758 if( !result )
759 return MakeError<Value>( fmt::format( "Invalid E-series: {}", series ) );
760
761 return MakeValue<Value>( result.value() );
762 }
763
764 // Mathematical functions (return numbers) - convert args to doubles first
765 std::vector<double> numArgs;
766 for( const auto& val : argValues )
767 {
768 auto numResult = VALUE_UTILS::ToDouble( val );
769 if( !numResult )
770 return MakeError<Value>( numResult.GetError() );
771
772 numArgs.push_back( numResult.GetValue() );
773 }
774
775 // Mathematical function implementations
776 if( name == "abs" && argc == 1 )
777 return MakeValue<Value>( std::abs( numArgs[0] ) );
778 else if( name == "sum" && argc >= 1 )
779 return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
780 else if( name == "round" && argc >= 1 )
781 {
782 const auto value = numArgs[0];
783 const auto precision = argc > 1 ? static_cast<int>( numArgs[1] ) : 0;
784 const auto multiplier = std::pow( 10.0, precision );
785 return MakeValue<Value>( std::round( value * multiplier ) / multiplier );
786 }
787 else if( name == "sqrt" && argc == 1 )
788 {
789 if( numArgs[0] < 0 )
790 return MakeError<Value>( "Square root of negative number" );
791
792 return MakeValue<Value>( std::sqrt( numArgs[0] ) );
793 }
794 else if( name == "pow" && argc == 2 )
795 return MakeValue<Value>( std::pow( numArgs[0], numArgs[1] ) );
796 else if( name == "floor" && argc == 1 )
797 return MakeValue<Value>( std::floor( numArgs[0] ) );
798 else if( name == "ceil" && argc == 1 )
799 return MakeValue<Value>( std::ceil( numArgs[0] ) );
800 else if( name == "min" && argc >= 1 )
801 return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
802 else if( name == "max" && argc >= 1 )
803 return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
804 else if( name == "avg" && argc >= 1 )
805 {
806 const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
807 return MakeValue<Value>( sum / static_cast<double>( argc ) );
808 }
809 else if( name == "shunt" && argc == 2 )
810 {
811 const auto r1 = numArgs[0];
812 const auto r2 = numArgs[1];
813 const auto sum = r1 + r2;
814
815 // Calculate parallel resistance: (r1*r2)/(r1+r2)
816 // If sum is not positive, return 0.0 (handles edge cases like shunt(0,0))
817 if( sum > 0.0 )
818 return MakeValue<Value>( ( r1 * r2 ) / sum );
819 else
820 return MakeValue<Value>( 0.0 );
821 }
822 else if( name == "db" && argc == 1 )
823 {
824 // Power ratio to dB: 10*log10(ratio)
825 if( numArgs[0] <= 0.0 )
826 return MakeError<Value>( "db() argument must be positive" );
827
828 return MakeValue<Value>( 10.0 * std::log10( numArgs[0] ) );
829 }
830 else if( name == "dbv" && argc == 1 )
831 {
832 // Voltage/current ratio to dB: 20*log10(ratio)
833 if( numArgs[0] <= 0.0 )
834 return MakeError<Value>( "dbv() argument must be positive" );
835
836 return MakeValue<Value>( 20.0 * std::log10( numArgs[0] ) );
837 }
838 else if( name == "fromdb" && argc == 1 )
839 {
840 // dB to power ratio: 10^(dB/10)
841 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 10.0 ) );
842 }
843 else if( name == "fromdbv" && argc == 1 )
844 {
845 // dB to voltage/current ratio: 10^(dB/20)
846 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 20.0 ) );
847 }
848
849 return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
850}
851
852auto DOC_PROCESSOR::Process( const DOC& aDoc, VariableCallback aVariableCallback ) -> std::pair<std::string, bool>
853{
854 std::string result;
855 auto localErrors = ERROR_COLLECTOR{};
856 EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
857 bool hadErrors = aDoc.HasErrors();
858
859 for( const auto& node : aDoc.GetNodes() )
860 {
861 switch( node->type )
862 {
863 case NodeType::Text: result += std::get<std::string>( node->data ); break;
864
865 case NodeType::Calc:
866 {
867 const auto& calcData = std::get<BIN_OP_DATA>( node->data );
868 auto evalResult = calcData.left->Accept( evaluator );
869
870 if( evalResult )
871 result += VALUE_UTILS::ToString( evalResult.GetValue() );
872 else
873 {
874 // Don't add error formatting to result - errors go to error vector only
875 // The higher level will return original input unchanged if there are errors
876 hadErrors = true;
877 }
878 break;
879 }
880
881 default:
882 result += "[Unknown node type]";
883 hadErrors = true;
884 break;
885 }
886 }
887
888 return { std::move( result ), hadErrors || localErrors.HasErrors() };
889}
890
891auto DOC_PROCESSOR::ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
892 -> std::tuple<std::string, std::vector<std::string>, bool>
893{
894 auto [result, hadErrors] = Process( aDoc, std::move( aVariableCallback ) );
895 auto allErrors = aDoc.GetErrors();
896
897 return { std::move( result ), std::move( allErrors ), hadErrors };
898}
899
900} // 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.
static auto parseSeriesString(const std::string &aSeries) -> int
static auto getSeriesSize(int aSeries) -> size_t
static auto FindDown(double aValue, const std::string &aSeries) -> std::optional< double >
static auto getSeriesValue(int aSeries, size_t aIndex) -> uint16_t
static constexpr std::array< uint16_t, 24 > s_e24
static auto FindUp(double aValue, const std::string &aSeries) -> std::optional< double >
static auto FindNearest(double aValue, const std::string &aSeries) -> std::optional< double >
static constexpr std::array< uint16_t, 192 > s_e192
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.