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
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
22#include <fmt/format.h>
23#include <array>
24#include <cctype>
25#include <wx/string.h>
26
27namespace calc_parser
28{
29thread_local ERROR_COLLECTOR* g_errorCollector = nullptr;
30
32{
33private:
34 static constexpr int epochYear = 1970;
35 static constexpr std::array<int, 12> daysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
36 static constexpr std::array<const char*, 12> monthNames = { "January", "February", "March", "April",
37 "May", "June", "July", "August",
38 "September", "October", "November", "December" };
39 static constexpr std::array<const char*, 12> monthAbbrev = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
40 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
41 static constexpr std::array<const char*, 7> weekdayNames = { "Monday", "Tuesday", "Wednesday", "Thursday",
42 "Friday", "Saturday", "Sunday" };
43
44 static auto isLeapYear( int aYear ) -> bool
45 {
46 return ( aYear % 4 == 0 && aYear % 100 != 0 ) || ( aYear % 400 == 0 );
47 }
48
49 static auto daysInYear( int aYear ) -> int { return isLeapYear( aYear ) ? 366 : 365; }
50
51 static auto daysInMonthForYear( int aMonth, int aYear ) -> int
52 {
53 if( aMonth == 2 && isLeapYear( aYear ) )
54 return 29;
55
56 return daysInMonth[aMonth - 1];
57 }
58
59public:
60 static auto DaysToYmd( int aDaysSinceEpoch ) -> std::tuple<int, int, int>
61 {
62 int year = epochYear;
63 int remainingDays = aDaysSinceEpoch;
64
65 if( remainingDays >= 0 )
66 {
67 while( remainingDays >= daysInYear( year ) )
68 {
69 remainingDays -= daysInYear( year );
70 year++;
71 }
72 }
73 else
74 {
75 while( remainingDays < 0 )
76 {
77 year--;
78 remainingDays += daysInYear( year );
79 }
80 }
81
82 int month = 1;
83 while( month <= 12 && remainingDays >= daysInMonthForYear( month, year ) )
84 {
85 remainingDays -= daysInMonthForYear( month, year );
86 month++;
87 }
88
89 int day = remainingDays + 1;
90 return { year, month, day };
91 }
92
93 static auto YmdToDays( int aYear, int aMonth, int aDay ) -> int
94 {
95 int totalDays = 0;
96
97 if( aYear >= epochYear )
98 {
99 for( int y = epochYear; y < aYear; ++y )
100 totalDays += daysInYear( y );
101 }
102 else
103 {
104 for( int y = aYear; y < epochYear; ++y )
105 totalDays -= daysInYear( y );
106 }
107
108 for( int m = 1; m < aMonth; ++m )
109 totalDays += daysInMonthForYear( m, aYear );
110
111 totalDays += aDay - 1;
112 return totalDays;
113 }
114
115 static auto ParseDate( const std::string& aDateStr ) -> std::optional<int>
116 {
117 std::istringstream iss( aDateStr );
118 std::string token;
119 std::vector<int> parts;
120
121 char separator = 0;
122 bool isCjkFormat = false;
123
124 // Check for CJK date formats first (Chinese, Korean, or mixed)
125 bool hasChineseYear = aDateStr.find( "年" ) != std::string::npos;
126 bool hasChineseMonth = aDateStr.find( "月" ) != std::string::npos;
127 bool hasChineseDay = aDateStr.find( "日" ) != std::string::npos;
128 bool hasKoreanYear = aDateStr.find( "년" ) != std::string::npos;
129 bool hasKoreanMonth = aDateStr.find( "월" ) != std::string::npos;
130 bool hasKoreanDay = aDateStr.find( "일" ) != std::string::npos;
131
132 // Check if we have any CJK date format (pure or mixed)
133 if( ( hasChineseYear || hasKoreanYear ) && ( hasChineseMonth || hasKoreanMonth )
134 && ( hasChineseDay || hasKoreanDay ) )
135 {
136 // CJK format: Support pure Chinese, pure Korean, or mixed formats
137 isCjkFormat = true;
138
139 size_t yearPos, monthPos, dayPos;
140
141 // Find year position and marker
142 if( hasChineseYear )
143 yearPos = aDateStr.find( "年" );
144 else
145 yearPos = aDateStr.find( "년" );
146
147 // Find month position and marker
148 if( hasChineseMonth )
149 monthPos = aDateStr.find( "月" );
150 else
151 monthPos = aDateStr.find( "월" );
152
153 // Find day position and marker
154 if( hasChineseDay )
155 dayPos = aDateStr.find( "日" );
156 else
157 dayPos = aDateStr.find( "일" );
158
159 try
160 {
161 int year = std::stoi( aDateStr.substr( 0, yearPos ) );
162 int month = std::stoi(
163 aDateStr.substr( yearPos + 3, monthPos - yearPos - 3 ) ); // 3 bytes for CJK year marker
164 int day = std::stoi(
165 aDateStr.substr( monthPos + 3, dayPos - monthPos - 3 ) ); // 3 bytes for CJK month marker
166
167 parts = { year, month, day };
168 }
169 catch( ... )
170 {
171 return std::nullopt;
172 }
173 }
174 else if( aDateStr.find( '-' ) != std::string::npos )
175 separator = '-';
176 else if( aDateStr.find( '/' ) != std::string::npos )
177 separator = '/';
178 else if( aDateStr.find( '.' ) != std::string::npos )
179 separator = '.';
180
181 if( separator )
182 {
183 while( std::getline( iss, token, separator ) )
184 {
185 try
186 {
187 parts.push_back( std::stoi( token ) );
188 }
189 catch( ... )
190 {
191 return std::nullopt;
192 }
193 }
194 }
195 else if( !isCjkFormat && aDateStr.length() == 8 )
196 {
197 try
198 {
199 int dateNum = std::stoi( aDateStr );
200 int year = dateNum / 10000;
201 int month = ( dateNum / 100 ) % 100;
202 int day = dateNum % 100;
203 return YmdToDays( year, month, day );
204 }
205 catch( ... )
206 {
207 return std::nullopt;
208 }
209 }
210 else if( !isCjkFormat )
211 {
212 return std::nullopt;
213 }
214
215 if( parts.empty() || parts.size() > 3 )
216 return std::nullopt;
217
218 int year, month, day;
219
220 if( parts.size() == 1 )
221 {
222 year = parts[0];
223 month = 1;
224 day = 1;
225 }
226 else if( parts.size() == 2 )
227 {
228 year = parts[0];
229 month = parts[1];
230 day = 1;
231 }
232 else
233 {
234 if( isCjkFormat )
235 {
236 // CJK formats are always in YYYY年MM月DD日 or YYYY년 MM월 DD일 order
237 year = parts[0];
238 month = parts[1];
239 day = parts[2];
240 }
241 else if( separator == '/' && parts[0] <= 12 && parts[1] <= 31 )
242 {
243 month = parts[0];
244 day = parts[1];
245 year = parts[2];
246 }
247 else if( separator == '/' && parts[1] <= 12 )
248 {
249 day = parts[0];
250 month = parts[1];
251 year = parts[2];
252 }
253 else
254 {
255 year = parts[0];
256 month = parts[1];
257 day = parts[2];
258 }
259 }
260
261 if( month < 1 || month > 12 )
262 return std::nullopt;
263 if( day < 1 || day > daysInMonthForYear( month, year ) )
264 return std::nullopt;
265
266 return YmdToDays( year, month, day );
267 }
268
269 static auto FormatDate( int aDaysSinceEpoch, const std::string& aFormat ) -> std::string
270 {
271 auto [year, month, day] = DaysToYmd( aDaysSinceEpoch );
272
273 if( aFormat == "ISO" || aFormat == "iso" )
274 return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
275 else if( aFormat == "US" || aFormat == "us" )
276 return fmt::format( "{:02d}/{:02d}/{:04d}", month, day, year );
277 else if( aFormat == "EU" || aFormat == "european" )
278 return fmt::format( "{:02d}/{:02d}/{:04d}", day, month, year );
279 else if( aFormat == "long" )
280 return fmt::format( "{} {}, {}", monthNames[month - 1], day, year );
281 else if( aFormat == "short" )
282 return fmt::format( "{} {}, {}", monthAbbrev[month - 1], day, year );
283 else if( aFormat == "Chinese" || aFormat == "chinese" || aFormat == "CN" || aFormat == "cn"
284 || aFormat == "中文" )
285 return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
286 else if( aFormat == "Japanese" || aFormat == "japanese" || aFormat == "JP" || aFormat == "jp"
287 || aFormat == "日本語" )
288 return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
289 else if( aFormat == "Korean" || aFormat == "korean" || aFormat == "KR" || aFormat == "kr"
290 || aFormat == "한국어" )
291 return fmt::format( "{}년 {:02d}월 {:02d}일", year, month, day );
292 else
293 return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
294 }
295
296 static auto GetWeekdayName( int aDaysSinceEpoch ) -> std::string
297 {
298 int weekday = ( ( aDaysSinceEpoch + 3 ) % 7 ); // +3 because epoch was Thursday (Monday = 0)
299
300 if( weekday < 0 )
301 weekday += 7;
302
303 return std::string{ weekdayNames[weekday] };
304 }
305
306 static auto GetCurrentDays() -> int
307 {
308 auto now = std::chrono::system_clock::now();
309 auto timeT = std::chrono::system_clock::to_time_t( now );
310 return static_cast<int>( timeT / ( 24 * 3600 ) );
311 }
312
313 static auto GetCurrentTimestamp() -> double
314 {
315 auto now = std::chrono::system_clock::now();
316 auto timeT = std::chrono::system_clock::to_time_t( now );
317 return static_cast<double>( timeT );
318 }
319
320 static auto FormatTime( double aSecondsSinceEpoch, const std::string& aFormat ) -> std::string
321 {
322 auto timeT = static_cast<time_t>( aSecondsSinceEpoch );
323 struct tm tmBuf;
324
325#ifdef _WIN32
326 localtime_s( &tmBuf, &timeT );
327#else
328 localtime_r( &timeT, &tmBuf );
329#endif
330
331 int hour = tmBuf.tm_hour;
332 int min = tmBuf.tm_min;
333 int sec = tmBuf.tm_sec;
334
335 if( aFormat == "24h" || aFormat == "ISO" || aFormat == "iso" )
336 return fmt::format( "{:02d}:{:02d}:{:02d}", hour, min, sec );
337 else if( aFormat == "12h" )
338 {
339 const char* ampm = hour >= 12 ? "PM" : "AM";
340 int hour12 = hour % 12;
341
342 if( hour12 == 0 )
343 hour12 = 12;
344
345 return fmt::format( "{}:{:02d}:{:02d} {}", hour12, min, sec, ampm );
346 }
347 else if( aFormat == "HH_MM_SS" || aFormat == "filename" )
348 return fmt::format( "{:02d}h{:02d}m{:02d}s", hour, min, sec );
349 else if( aFormat == "short" )
350 return fmt::format( "{:02d}:{:02d}", hour, min );
351 else
352 return fmt::format( "{:02d}:{:02d}:{:02d}", hour, min, sec );
353 }
354};
355
356
358{
359private:
360 // E24 series values in 100-999 decade (2 significant figures)
361 static constexpr std::array<uint16_t, 24> s_e24 = {
362 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300,
363 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910
364 };
365
366 // E192 series values in 100-999 decade (3 significant figures)
367 static constexpr std::array<uint16_t, 192> s_e192 = {
368 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 123,
369 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 142, 143, 145, 147, 149, 150, 152,
370 154, 156, 158, 160, 162, 164, 165, 167, 169, 172, 174, 176, 178, 180, 182, 184, 187, 189,
371 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 221, 223, 226, 229, 232, 234,
372 237, 240, 243, 246, 249, 252, 255, 258, 261, 264, 267, 271, 274, 277, 280, 284, 287, 291,
373 294, 298, 301, 305, 309, 312, 316, 320, 324, 328, 332, 336, 340, 344, 348, 352, 357, 361,
374 365, 370, 374, 379, 383, 388, 392, 397, 402, 407, 412, 417, 422, 427, 432, 437, 442, 448,
375 453, 459, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 530, 536, 542, 549, 556,
376 562, 569, 576, 583, 590, 597, 604, 612, 619, 626, 634, 642, 649, 657, 665, 673, 681, 690,
377 698, 706, 715, 723, 732, 741, 750, 759, 768, 777, 787, 796, 806, 816, 825, 835, 845, 856,
378 866, 876, 887, 898, 909, 920, 931, 942, 953, 965, 976, 988
379 };
380
381 static auto parseSeriesString( const std::string& aSeries ) -> int
382 {
383 if( aSeries == "E3" || aSeries == "e3" )
384 return 3;
385 else if( aSeries == "E6" || aSeries == "e6" )
386 return 6;
387 else if( aSeries == "E12" || aSeries == "e12" )
388 return 12;
389 else if( aSeries == "E24" || aSeries == "e24" )
390 return 24;
391 else if( aSeries == "E48" || aSeries == "e48" )
392 return 48;
393 else if( aSeries == "E96" || aSeries == "e96" )
394 return 96;
395 else if( aSeries == "E192" || aSeries == "e192" )
396 return 192;
397 else
398 return -1; // Invalid series
399 }
400
401 static auto getSeriesValue( int aSeries, size_t aIndex ) -> uint16_t
402 {
403 // E1, E3, E6, E12, E24 are derived from E24
404 if( aSeries <= 24 )
405 {
406 const size_t skipValue = 24 / aSeries;
407 return s_e24[aIndex * skipValue];
408 }
409 // E48, E96, E192 are derived from E192
410 else
411 {
412 const size_t skipValue = 192 / aSeries;
413 return s_e192[aIndex * skipValue];
414 }
415 }
416
417 static auto getSeriesSize( int aSeries ) -> size_t
418 {
419 return static_cast<size_t>( aSeries );
420 }
421
422public:
423 static auto FindNearest( double aValue, const std::string& aSeries ) -> std::optional<double>
424 {
425 const int series = parseSeriesString( aSeries );
426 if( series < 0 )
427 return std::nullopt;
428
429 if( aValue <= 0.0 )
430 return std::nullopt;
431
432 // Scale value to 100-999 decade
433 const double logValue = std::log10( aValue );
434 const int decade = static_cast<int>( std::floor( logValue ) );
435 const double scaledValue = aValue / std::pow( 10.0, decade );
436 const double normalized = scaledValue * 100.0;
437
438 // Find nearest value in series
439 const size_t seriesSize = getSeriesSize( series );
440 double minDiff = std::numeric_limits<double>::max();
441 uint16_t nearest = 100;
442
443 for( size_t i = 0; i < seriesSize; ++i )
444 {
445 const uint16_t val = getSeriesValue( series, i );
446 const double diff = std::abs( normalized - val );
447 if( diff < minDiff )
448 {
449 minDiff = diff;
450 nearest = val;
451 }
452 }
453
454 // Scale back to original decade
455 return ( nearest / 100.0 ) * std::pow( 10.0, decade );
456 }
457
458 static auto FindUp( double aValue, const std::string& aSeries ) -> std::optional<double>
459 {
460 const int series = parseSeriesString( aSeries );
461 if( series < 0 )
462 return std::nullopt;
463
464 if( aValue <= 0.0 )
465 return std::nullopt;
466
467 // Scale value to 100-999 decade
468 const double logValue = std::log10( aValue );
469 const int decade = static_cast<int>( std::floor( logValue ) );
470 const double scaledValue = aValue / std::pow( 10.0, decade );
471 const double normalized = scaledValue * 100.0;
472
473 // Find next higher value in series
474 const size_t seriesSize = getSeriesSize( series );
475
476 // Check current decade
477 for( size_t i = 0; i < seriesSize; ++i )
478 {
479 const uint16_t val = getSeriesValue( series, i );
480 if( val > normalized )
481 return ( val / 100.0 ) * std::pow( 10.0, decade );
482 }
483
484 // Wrap to next decade
485 const uint16_t firstVal = getSeriesValue( series, 0 );
486 return ( firstVal / 100.0 ) * std::pow( 10.0, decade + 1 );
487 }
488
489 static auto FindDown( double aValue, const std::string& aSeries ) -> std::optional<double>
490 {
491 const int series = parseSeriesString( aSeries );
492 if( series < 0 )
493 return std::nullopt;
494
495 if( aValue <= 0.0 )
496 return std::nullopt;
497
498 // Scale value to 100-999 decade
499 const double logValue = std::log10( aValue );
500 const int decade = static_cast<int>( std::floor( logValue ) );
501 const double scaledValue = aValue / std::pow( 10.0, decade );
502 const double normalized = scaledValue * 100.0;
503
504 // Find next lower value in series
505 const size_t seriesSize = getSeriesSize( series );
506
507 // Check current decade (search backwards)
508 for( int i = seriesSize - 1; i >= 0; --i )
509 {
510 const uint16_t val = getSeriesValue( series, i );
511 if( val < normalized )
512 return ( val / 100.0 ) * std::pow( 10.0, decade );
513 }
514
515 // Wrap to previous decade
516 const uint16_t lastVal = getSeriesValue( series, seriesSize - 1 );
517 return ( lastVal / 100.0 ) * std::pow( 10.0, decade - 1 );
518 }
519};
520
521
522EVAL_VISITOR::EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ) :
523 m_variableCallback( std::move( aVariableCallback ) ),
524 m_errors( aErrorCollector ),
525 m_gen( m_rd() )
526{
527}
528
529auto EVAL_VISITOR::operator()( const NODE& aNode ) const -> Result<Value>
530{
531 switch( aNode.type )
532 {
533 case NodeType::Number: return MakeValue<Value>( std::get<double>( aNode.data ) );
534
535 case NodeType::String: return MakeValue<Value>( std::get<std::string>( aNode.data ) );
536
537 case NodeType::Var:
538 {
539 const auto& varName = std::get<std::string>( aNode.data );
540
541 // Use callback to resolve variable
543 return m_variableCallback( varName );
544
545 return MakeError<Value>( fmt::format( "No variable resolver configured for: {}", varName ) );
546 }
547
548 case NodeType::BinOp:
549 {
550 const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
551 auto leftResult = binop.left->Accept( *this );
552 if( !leftResult )
553 return leftResult;
554
555 auto rightResult = binop.right ? binop.right->Accept( *this ) : MakeValue<Value>( 0.0 );
556 if( !rightResult )
557 return rightResult;
558
559 // Special handling for string concatenation with +
560 if( binop.op == '+' )
561 {
562 const auto& leftVal = leftResult.GetValue();
563 const auto& rightVal = rightResult.GetValue();
564
565 // If either operand is a string, concatenate
566 if( std::holds_alternative<std::string>( leftVal ) || std::holds_alternative<std::string>( rightVal ) )
567 {
568 return MakeValue<Value>( VALUE_UTILS::ConcatStrings( leftVal, rightVal ) );
569 }
570 }
571
572 // Special handling for string comparisons with == and !=
573 if( binop.op == 3 || binop.op == 4 ) // == or !=
574 {
575 const auto& leftVal = leftResult.GetValue();
576 const auto& rightVal = rightResult.GetValue();
577
578 // If both operands are strings, do string comparison
579 if( std::holds_alternative<std::string>( leftVal ) && std::holds_alternative<std::string>( rightVal ) )
580 {
581 bool equal = std::get<std::string>( leftVal ) == std::get<std::string>( rightVal );
582 double result = ( binop.op == 3 ) ? ( equal ? 1.0 : 0.0 ) : ( equal ? 0.0 : 1.0 );
583 return MakeValue<Value>( result );
584 }
585 }
586
587 // Otherwise, perform arithmetic
588 return VALUE_UTILS::ArithmeticOp( leftResult.GetValue(), rightResult.GetValue(), binop.op );
589 }
590
592 {
593 const auto& func = std::get<FUNC_DATA>( aNode.data );
594 return evaluateFunction( func );
595 }
596
597 default: return MakeError<Value>( "Cannot evaluate this node type" );
598 }
599}
600
602{
603 const auto& name = aFunc.name;
604 const auto& args = aFunc.args;
605
606 // Zero-argument functions
607 if( args.empty() )
608 {
609 if( name == "today" )
610 return MakeValue<Value>( static_cast<double>( DATE_UTILS::GetCurrentDays() ) );
611 else if( name == "now" )
613 else if( name == "random" )
614 {
615 std::uniform_real_distribution<double> dis( 0.0, 1.0 );
616 return MakeValue<Value>( dis( m_gen ) );
617 }
618 }
619
620 // Evaluate arguments to mixed types
621 std::vector<Value> argValues;
622 argValues.reserve( args.size() );
623
624 for( const auto& arg : args )
625 {
626 auto result = arg->Accept( *this );
627 if( !result )
628 return result;
629
630 argValues.push_back( result.GetValue() );
631 }
632
633 const auto argc = argValues.size();
634
635 // String formatting functions (return strings!)
636 if( name == "format" && argc >= 1 )
637 {
638 const auto& numResult = VALUE_UTILS::ToDouble( argValues[0] );
639
640 if( !numResult )
641 return MakeError<Value>( numResult.GetError() );
642
643 const auto& value = numResult.GetValue();
644 int decimals = 2;
645
646 if( argc > 1 )
647 {
648 const auto& decResult = VALUE_UTILS::ToDouble( argValues[1] );
649
650 if( decResult )
651 decimals = static_cast<int>( decResult.GetValue() );
652 }
653
654 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
655 }
656 else if( name == "currency" && argc >= 1 )
657 {
658 const auto& numResult = VALUE_UTILS::ToDouble( argValues[0] );
659
660 if( !numResult )
661 return MakeError<Value>( numResult.GetError() );
662
663 const auto& amount = numResult.GetValue();
664 const auto& symbol = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "$";
665
666 return MakeValue<Value>( fmt::format( "{}{:.2f}", symbol, amount ) );
667 }
668 else if( name == "fixed" && argc >= 1 )
669 {
670 const auto& numResult = VALUE_UTILS::ToDouble( argValues[0] );
671
672 if( !numResult )
673 return MakeError<Value>( numResult.GetError() );
674
675 const auto& value = numResult.GetValue();
676 int decimals = 2;
677
678 if( argc > 1 )
679 {
680 const auto& decResult = VALUE_UTILS::ToDouble( argValues[1] );
681
682 if( decResult )
683 decimals = static_cast<int>( decResult.GetValue() );
684 }
685
686 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
687 }
688
689 // Date formatting functions (return strings!)
690 else if( name == "dateformat" && argc >= 1 )
691 {
692 const auto& dateResult = VALUE_UTILS::ToDouble( argValues[0] );
693
694 if( !dateResult )
695 return MakeError<Value>( dateResult.GetError() );
696
697 const auto& days = static_cast<int>( dateResult.GetValue() );
698 const auto& format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
699
700 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
701 }
702 else if( name == "datestring" && argc == 1 )
703 {
704 const auto& dateStr = VALUE_UTILS::ToString( argValues[0] );
705 const auto& daysResult = DATE_UTILS::ParseDate( dateStr );
706
707 if( !daysResult )
708 return MakeError<Value>( "Invalid date format: " + dateStr );
709
710 return MakeValue<Value>( static_cast<double>( daysResult.value() ) );
711 }
712 else if( name == "weekdayname" && argc == 1 )
713 {
714 const auto& dateResult = VALUE_UTILS::ToDouble( argValues[0] );
715
716 if( !dateResult )
717 return MakeError<Value>( dateResult.GetError() );
718
719 const auto& days = static_cast<int>( dateResult.GetValue() );
721 }
722 else if( name == "timeformat" && argc >= 1 )
723 {
724 const auto& timeResult = VALUE_UTILS::ToDouble( argValues[0] );
725
726 if( !timeResult )
727 return MakeError<Value>( timeResult.GetError() );
728
729 const auto& timestamp = timeResult.GetValue();
730 const auto& format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
731
732 return MakeValue<Value>( DATE_UTILS::FormatTime( timestamp, format ) );
733 }
734
735 // VCS functions (return strings!)
736 // Empty results from the VCS layer mean "not in a repository" or "no data available"
737 auto vcsResult =
738 []( const std::string& aResult ) -> std::string
739 {
740 return aResult.empty() ? "<unknown>" : aResult;
741 };
742
743 if( name == "vcsidentifier" && argc <= 1 )
744 {
745 int length = 40; // Full identifier by default
746
747 if( argc == 1 )
748 {
749 const auto& lenResult = VALUE_UTILS::ToDouble( argValues[0] );
750
751 if( lenResult )
752 length = static_cast<int>( lenResult.GetValue() );
753 }
754
755 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitHash( ".", length ) ) );
756 }
757 else if( name == "vcsnearestlabel" && argc <= 2 )
758 {
759 std::string match;
760 bool anyTags = false;
761
762 if( argc >= 1 )
763 match = VALUE_UTILS::ToString( argValues[0] );
764
765 if( argc >= 2 )
766 {
767 auto tagsResult = VALUE_UTILS::ToDouble( argValues[1] );
768
769 if( tagsResult )
770 anyTags = tagsResult.GetValue() != 0.0;
771 }
772
773 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetNearestTag( match, anyTags ) ) );
774 }
775 else if( name == "vcslabeldistance" && argc <= 2 )
776 {
777 std::string match;
778 bool anyTags = false;
779
780 if( argc >= 1 )
781 match = VALUE_UTILS::ToString( argValues[0] );
782
783 if( argc >= 2 )
784 {
785 const auto& tagsResult = VALUE_UTILS::ToDouble( argValues[1] );
786
787 if( tagsResult )
788 anyTags = tagsResult.GetValue() != 0.0;
789 }
790
791 return MakeValue<Value>( std::to_string( TEXT_EVAL_VCS::GetDistanceFromTag( match, anyTags ) ) );
792 }
793 else if( name == "vcsdirty" && argc <= 1 )
794 {
795 bool includeUntracked = false;
796
797 if( argc == 1 )
798 {
799 const auto& utResult = VALUE_UTILS::ToDouble( argValues[0] );
800
801 if( utResult )
802 includeUntracked = utResult.GetValue() != 0.0;
803 }
804
805 return MakeValue<Value>( TEXT_EVAL_VCS::IsDirty( includeUntracked ) ? "1" : "0" );
806 }
807 else if( name == "vcsdirtysuffix" && argc <= 2 )
808 {
809 std::string suffix = "-dirty";
810 bool includeUntracked = false;
811
812 if( argc >= 1 )
813 suffix = VALUE_UTILS::ToString( argValues[0] );
814
815 if( argc >= 2 )
816 {
817 auto utResult = VALUE_UTILS::ToDouble( argValues[1] );
818
819 if( utResult )
820 includeUntracked = utResult.GetValue() != 0.0;
821 }
822
823 return MakeValue<Value>( TEXT_EVAL_VCS::IsDirty( includeUntracked ) ? suffix : "" );
824 }
825 else if( name == "vcsauthor" && argc == 0 )
826 {
827 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthor( "." ) ) );
828 }
829 else if( name == "vcsauthoremail" && argc == 0 )
830 {
831 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthorEmail( "." ) ) );
832 }
833 else if( name == "vcscommitter" && argc == 0 )
834 {
835 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitter( "." ) ) );
836 }
837 else if( name == "vcscommitteremail" && argc == 0 )
838 {
839 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitterEmail( "." ) ) );
840 }
841 else if( name == "vcsbranch" && argc == 0 )
842 {
843 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetBranch() ) );
844 }
845 else if( name == "vcscommitdate" && argc <= 1 )
846 {
847 std::string format = "ISO";
848
849 if( argc == 1 )
850 format = VALUE_UTILS::ToString( argValues[0] );
851
852 int64_t timestamp = TEXT_EVAL_VCS::GetCommitTimestamp( "." );
853
854 if( timestamp == 0 )
855 return MakeValue<Value>( vcsResult( std::string() ) );
856
857 int days = static_cast<int>( timestamp / ( 24 * 3600 ) );
858 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
859 }
860
861 // VCS file functions (file-specific versions)
862 else if( name == "vcsfileidentifier" && argc >= 1 && argc <= 2 )
863 {
864 const std::string& filePath = VALUE_UTILS::ToString( argValues[0] );
865 int length = 40;
866
867 if( argc == 2 )
868 {
869 const auto& lenResult = VALUE_UTILS::ToDouble( argValues[1] );
870
871 if( lenResult )
872 length = static_cast<int>( lenResult.GetValue() );
873 }
874
875 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitHash( filePath, length ) ) );
876 }
877 else if( name == "vcsfileauthor" && argc == 1 )
878 {
879 const std::string& filePath = VALUE_UTILS::ToString( argValues[0] );
880 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthor( filePath ) ) );
881 }
882 else if( name == "vcsfileauthoremail" && argc == 1 )
883 {
884 const std::string& filePath = VALUE_UTILS::ToString( argValues[0] );
885 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthorEmail( filePath ) ) );
886 }
887 else if( name == "vcsfilecommitter" && argc == 1 )
888 {
889 const std::string& filePath = VALUE_UTILS::ToString( argValues[0] );
890 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitter( filePath ) ) );
891 }
892 else if( name == "vcsfilecommitteremail" && argc == 1 )
893 {
894 const std::string& filePath = VALUE_UTILS::ToString( argValues[0] );
895 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitterEmail( filePath ) ) );
896 }
897 else if( name == "vcsfilecommitdate" && argc >= 1 && argc <= 2 )
898 {
899 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
900 std::string format = "ISO";
901
902 if( argc == 2 )
903 format = VALUE_UTILS::ToString( argValues[1] );
904
905 int64_t timestamp = TEXT_EVAL_VCS::GetCommitTimestamp( filePath );
906
907 if( timestamp == 0 )
908 return MakeValue<Value>( vcsResult( std::string() ) );
909
910 int days = static_cast<int>( timestamp / ( 24 * 3600 ) );
911 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
912 }
913
914 // String functions (return strings!)
915 else if( name == "upper" && argc == 1 )
916 {
917 std::string str = VALUE_UTILS::ToString( argValues[0] );
918 std::transform( str.begin(), str.end(), str.begin(), ::toupper );
919 return MakeValue<Value>( str );
920 }
921 else if( name == "lower" && argc == 1 )
922 {
923 std::string str = VALUE_UTILS::ToString( argValues[0] );
924 std::transform( str.begin(), str.end(), str.begin(), ::tolower );
925 return MakeValue<Value>( str );
926 }
927 else if( name == "concat" && argc >= 2 )
928 {
929 std::string result;
930
931 for( const auto& val : argValues )
933
934 return MakeValue<Value>( result );
935 }
936 else if( name == "beforefirst" && argc == 2 )
937 {
938 wxString result = VALUE_UTILS::ToString( argValues[0] );
939
940 result = result.BeforeFirst( VALUE_UTILS::ToChar( argValues[1] ) );
941 return MakeValue<Value>( result.ToStdString() );
942 }
943 else if( name == "beforelast" && argc == 2 )
944 {
945 wxString result = VALUE_UTILS::ToString( argValues[0] );
946
947 result = result.BeforeLast( VALUE_UTILS::ToChar( argValues[1] ) );
948 return MakeValue<Value>( result.ToStdString() );
949 }
950 else if( name == "afterfirst" && argc == 2 )
951 {
952 wxString result = VALUE_UTILS::ToString( argValues[0] );
953
954 result = result.AfterFirst( VALUE_UTILS::ToChar( argValues[1] ) );
955 return MakeValue<Value>( result.ToStdString() );
956 }
957 else if( name == "afterlast" && argc == 2 )
958 {
959 wxString result = VALUE_UTILS::ToString( argValues[0] );
960
961 result = result.AfterLast( VALUE_UTILS::ToChar( argValues[1] ) );
962 return MakeValue<Value>( result.ToStdString() );
963 }
964 else if( name == "replace" && argc == 3 )
965 {
966 wxString result = VALUE_UTILS::ToString( argValues[0] );
967 const wxString& search = VALUE_UTILS::ToString( argValues[1] );
968
969 if( !search.IsEmpty() )
970 result.Replace( search, VALUE_UTILS::ToString( argValues[2] ) );
971
972 return MakeValue<Value>( result.ToStdString() );
973 }
974
975 // Conditional functions (handle mixed types)
976 if( name == "if" && argc == 3 )
977 {
978 // Convert only the condition to a number
979 const auto& conditionResult = VALUE_UTILS::ToDouble( argValues[0] );
980
981 if( !conditionResult )
982 return MakeError<Value>( conditionResult.GetError() );
983
984 const auto& condition = conditionResult.GetValue() != 0.0;
985 return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
986 }
987
988 // E-series functions (handle value as number, series as string)
989 else if( ( name == "enearest" || name == "eup" || name == "edown" ) && argc >= 1 && argc <= 2 )
990 {
991 const auto& valueResult = VALUE_UTILS::ToDouble( argValues[0] );
992
993 if( !valueResult )
994 return MakeError<Value>( valueResult.GetError() );
995
996 const auto& value = valueResult.GetValue();
997 const auto& series = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "E24";
998 std::optional<double> result;
999
1000 if( name == "enearest" )
1001 result = ESERIES_UTILS::FindNearest( value, series );
1002 else if( name == "eup" )
1003 result = ESERIES_UTILS::FindUp( value, series );
1004 else if( name == "edown" )
1005 result = ESERIES_UTILS::FindDown( value, series );
1006
1007 if( !result )
1008 return MakeError<Value>( fmt::format( "Invalid E-series: {}", series ) );
1009
1010 return MakeValue<Value>( result.value() );
1011 }
1012
1013 // Mathematical functions (return numbers) - convert args to doubles first
1014 std::vector<double> numArgs;
1015
1016 for( const auto& val : argValues )
1017 {
1018 const auto& numResult = VALUE_UTILS::ToDouble( val );
1019
1020 if( !numResult )
1021 return MakeError<Value>( numResult.GetError() );
1022
1023 numArgs.push_back( numResult.GetValue() );
1024 }
1025
1026 // Mathematical function implementations
1027 if( name == "abs" && argc == 1 )
1028 return MakeValue<Value>( std::abs( numArgs[0] ) );
1029 else if( name == "sum" && argc >= 1 )
1030 return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
1031 else if( name == "round" && argc >= 1 )
1032 {
1033 const auto& value = numArgs[0];
1034 const auto& precision = argc > 1 ? static_cast<int>( numArgs[1] ) : 0;
1035 const auto& multiplier = std::pow( 10.0, precision );
1036 return MakeValue<Value>( std::round( value * multiplier ) / multiplier );
1037 }
1038 else if( name == "sqrt" && argc == 1 )
1039 {
1040 if( numArgs[0] < 0 )
1041 return MakeError<Value>( "Square root of negative number" );
1042
1043 return MakeValue<Value>( std::sqrt( numArgs[0] ) );
1044 }
1045 else if( name == "pow" && argc == 2 )
1046 return MakeValue<Value>( std::pow( numArgs[0], numArgs[1] ) );
1047 else if( name == "floor" && argc == 1 )
1048 return MakeValue<Value>( std::floor( numArgs[0] ) );
1049 else if( name == "ceil" && argc == 1 )
1050 return MakeValue<Value>( std::ceil( numArgs[0] ) );
1051 else if( name == "min" && argc >= 1 )
1052 return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
1053 else if( name == "max" && argc >= 1 )
1054 return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
1055 else if( name == "avg" && argc >= 1 )
1056 {
1057 const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
1058 return MakeValue<Value>( sum / static_cast<double>( argc ) );
1059 }
1060 else if( name == "shunt" && argc == 2 )
1061 {
1062 const auto r1 = numArgs[0];
1063 const auto r2 = numArgs[1];
1064 const auto sum = r1 + r2;
1065
1066 // Calculate parallel resistance: (r1*r2)/(r1+r2)
1067 // If sum is not positive, return 0.0 (handles edge cases like shunt(0,0))
1068 if( sum > 0.0 )
1069 return MakeValue<Value>( ( r1 * r2 ) / sum );
1070 else
1071 return MakeValue<Value>( 0.0 );
1072 }
1073 else if( name == "db" && argc == 1 )
1074 {
1075 // Power ratio to dB: 10*log10(ratio)
1076 if( numArgs[0] <= 0.0 )
1077 return MakeError<Value>( "db() argument must be positive" );
1078
1079 return MakeValue<Value>( 10.0 * std::log10( numArgs[0] ) );
1080 }
1081 else if( name == "dbv" && argc == 1 )
1082 {
1083 // Voltage/current ratio to dB: 20*log10(ratio)
1084 if( numArgs[0] <= 0.0 )
1085 return MakeError<Value>( "dbv() argument must be positive" );
1086
1087 return MakeValue<Value>( 20.0 * std::log10( numArgs[0] ) );
1088 }
1089 else if( name == "fromdb" && argc == 1 )
1090 {
1091 // dB to power ratio: 10^(dB/10)
1092 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 10.0 ) );
1093 }
1094 else if( name == "fromdbv" && argc == 1 )
1095 {
1096 // dB to voltage/current ratio: 10^(dB/20)
1097 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 20.0 ) );
1098 }
1099
1100 return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
1101}
1102
1103auto DOC_PROCESSOR::Process( const DOC& aDoc, VariableCallback aVariableCallback ) -> std::pair<std::string, bool>
1104{
1105 std::string result;
1106 auto localErrors = ERROR_COLLECTOR{};
1107 EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
1108 bool hadErrors = aDoc.HasErrors();
1109
1110 for( const auto& node : aDoc.GetNodes() )
1111 {
1112 switch( node->type )
1113 {
1114 case NodeType::Text: result += std::get<std::string>( node->data ); break;
1115
1116 case NodeType::Calc:
1117 {
1118 const auto& calcData = std::get<BIN_OP_DATA>( node->data );
1119 auto evalResult = calcData.left->Accept( evaluator );
1120
1121 if( evalResult )
1122 result += VALUE_UTILS::ToString( evalResult.GetValue() );
1123 else
1124 {
1125 // Don't add error formatting to result - errors go to error vector only
1126 // The higher level will return original input unchanged if there are errors
1127 hadErrors = true;
1128 }
1129 break;
1130 }
1131
1132 default:
1133 result += "[Unknown node type]";
1134 hadErrors = true;
1135 break;
1136 }
1137 }
1138
1139 return { std::move( result ), hadErrors || localErrors.HasErrors() };
1140}
1141
1142auto DOC_PROCESSOR::ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
1143 -> std::tuple<std::string, std::vector<std::string>, bool>
1144{
1145 auto [result, hadErrors] = Process( aDoc, std::move( aVariableCallback ) );
1146 auto allErrors = aDoc.GetErrors();
1147
1148 return { std::move( result ), std::move( allErrors ), hadErrors };
1149}
1150
1151} // 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 FormatTime(double aSecondsSinceEpoch, const std::string &aFormat) -> std::string
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
std::string GetAuthor(const std::string &aPath)
Get the author name of the HEAD commit.
bool IsDirty(bool aIncludeUntracked)
Check if the repository has uncommitted changes.
std::string GetCommitterEmail(const std::string &aPath)
Get the committer email of the HEAD commit.
std::string GetAuthorEmail(const std::string &aPath)
Get the author email of the HEAD commit.
std::string GetCommitter(const std::string &aPath)
Get the committer name of the HEAD commit.
std::string GetBranch()
Get the current branch name.
std::string GetNearestTag(const std::string &aMatch, bool aAnyTags)
Get the nearest tag/label from HEAD.
std::string GetCommitHash(const std::string &aPath, int aLength)
Get the current HEAD commit identifier (hash).
int64_t GetCommitTimestamp(const std::string &aPath)
Get the commit timestamp (Unix time) of the HEAD commit.
int GetDistanceFromTag(const std::string &aMatch, bool aAnyTags)
Get the number of commits since the nearest matching tag.
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.