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
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 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
639 if( !numResult )
640 return MakeError<Value>( numResult.GetError() );
641
642 const auto value = numResult.GetValue();
643 int decimals = 2;
644 if( argc > 1 )
645 {
646 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
647 if( decResult )
648 decimals = static_cast<int>( decResult.GetValue() );
649 }
650
651 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
652 }
653 else if( name == "currency" && argc >= 1 )
654 {
655 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
656 if( !numResult )
657 return MakeError<Value>( numResult.GetError() );
658
659 const auto amount = numResult.GetValue();
660 const auto symbol = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "$";
661
662 return MakeValue<Value>( fmt::format( "{}{:.2f}", symbol, amount ) );
663 }
664 else if( name == "fixed" && argc >= 1 )
665 {
666 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
667 if( !numResult )
668 return MakeError<Value>( numResult.GetError() );
669
670 const auto value = numResult.GetValue();
671 int decimals = 2;
672 if( argc > 1 )
673 {
674 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
675 if( decResult )
676 decimals = static_cast<int>( decResult.GetValue() );
677 }
678
679 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
680 }
681
682 // Date formatting functions (return strings!)
683 else if( name == "dateformat" && argc >= 1 )
684 {
685 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
686 if( !dateResult )
687 return MakeError<Value>( dateResult.GetError() );
688
689 const auto days = static_cast<int>( dateResult.GetValue() );
690 const auto format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
691
692 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
693 }
694 else if( name == "datestring" && argc == 1 )
695 {
696 auto dateStr = VALUE_UTILS::ToString( argValues[0] );
697 auto daysResult = DATE_UTILS::ParseDate( dateStr );
698
699 if( !daysResult )
700 return MakeError<Value>( "Invalid date format: " + dateStr );
701
702 return MakeValue<Value>( static_cast<double>( daysResult.value() ) );
703 }
704 else if( name == "weekdayname" && argc == 1 )
705 {
706 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
707 if( !dateResult )
708 return MakeError<Value>( dateResult.GetError() );
709
710 const auto days = static_cast<int>( dateResult.GetValue() );
712 }
713 else if( name == "timeformat" && argc >= 1 )
714 {
715 auto timeResult = VALUE_UTILS::ToDouble( argValues[0] );
716 if( !timeResult )
717 return MakeError<Value>( timeResult.GetError() );
718
719 const auto timestamp = timeResult.GetValue();
720 const auto format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
721
722 return MakeValue<Value>( DATE_UTILS::FormatTime( timestamp, format ) );
723 }
724
725 // VCS functions (return strings!)
726 // Empty results from the VCS layer mean "not in a repository" or "no data available"
727 auto vcsResult = []( const std::string& aResult ) -> std::string
728 {
729 return aResult.empty() ? "<unknown>" : aResult;
730 };
731
732 if( name == "vcsidentifier" && argc <= 1 )
733 {
734 int length = 40; // Full identifier by default
735
736 if( argc == 1 )
737 {
738 auto lenResult = VALUE_UTILS::ToDouble( argValues[0] );
739
740 if( lenResult )
741 length = static_cast<int>( lenResult.GetValue() );
742 }
743
744 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitHash( ".", length ) ) );
745 }
746 else if( name == "vcsnearestlabel" && argc <= 2 )
747 {
748 std::string match;
749 bool anyTags = false;
750
751 if( argc >= 1 )
752 match = VALUE_UTILS::ToString( argValues[0] );
753
754 if( argc >= 2 )
755 {
756 auto tagsResult = VALUE_UTILS::ToDouble( argValues[1] );
757
758 if( tagsResult )
759 anyTags = tagsResult.GetValue() != 0.0;
760 }
761
762 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetNearestTag( match, anyTags ) ) );
763 }
764 else if( name == "vcslabeldistance" && argc <= 2 )
765 {
766 std::string match;
767 bool anyTags = false;
768
769 if( argc >= 1 )
770 match = VALUE_UTILS::ToString( argValues[0] );
771
772 if( argc >= 2 )
773 {
774 auto tagsResult = VALUE_UTILS::ToDouble( argValues[1] );
775
776 if( tagsResult )
777 anyTags = tagsResult.GetValue() != 0.0;
778 }
779
780 return MakeValue<Value>( std::to_string( TEXT_EVAL_VCS::GetDistanceFromTag( match, anyTags ) ) );
781 }
782 else if( name == "vcsdirty" && argc <= 1 )
783 {
784 bool includeUntracked = false;
785
786 if( argc == 1 )
787 {
788 auto utResult = VALUE_UTILS::ToDouble( argValues[0] );
789
790 if( utResult )
791 includeUntracked = utResult.GetValue() != 0.0;
792 }
793
794 return MakeValue<Value>( TEXT_EVAL_VCS::IsDirty( includeUntracked ) ? "1" : "0" );
795 }
796 else if( name == "vcsdirtysuffix" && argc <= 2 )
797 {
798 std::string suffix = "-dirty";
799 bool includeUntracked = false;
800
801 if( argc >= 1 )
802 suffix = VALUE_UTILS::ToString( argValues[0] );
803
804 if( argc >= 2 )
805 {
806 auto utResult = VALUE_UTILS::ToDouble( argValues[1] );
807
808 if( utResult )
809 includeUntracked = utResult.GetValue() != 0.0;
810 }
811
812 return MakeValue<Value>( TEXT_EVAL_VCS::IsDirty( includeUntracked ) ? suffix : "" );
813 }
814 else if( name == "vcsauthor" && argc == 0 )
815 {
816 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthor( "." ) ) );
817 }
818 else if( name == "vcsauthoremail" && argc == 0 )
819 {
820 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthorEmail( "." ) ) );
821 }
822 else if( name == "vcscommitter" && argc == 0 )
823 {
824 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitter( "." ) ) );
825 }
826 else if( name == "vcscommitteremail" && argc == 0 )
827 {
828 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitterEmail( "." ) ) );
829 }
830 else if( name == "vcsbranch" && argc == 0 )
831 {
832 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetBranch() ) );
833 }
834 else if( name == "vcscommitdate" && argc <= 1 )
835 {
836 std::string format = "ISO";
837
838 if( argc == 1 )
839 format = VALUE_UTILS::ToString( argValues[0] );
840
841 int64_t timestamp = TEXT_EVAL_VCS::GetCommitTimestamp( "." );
842
843 if( timestamp == 0 )
844 return MakeValue<Value>( vcsResult( std::string() ) );
845
846 int days = static_cast<int>( timestamp / ( 24 * 3600 ) );
847 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
848 }
849
850 // VCS file functions (file-specific versions)
851 else if( name == "vcsfileidentifier" && argc >= 1 && argc <= 2 )
852 {
853 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
854 int length = 40;
855
856 if( argc == 2 )
857 {
858 auto lenResult = VALUE_UTILS::ToDouble( argValues[1] );
859
860 if( lenResult )
861 length = static_cast<int>( lenResult.GetValue() );
862 }
863
864 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitHash( filePath, length ) ) );
865 }
866 else if( name == "vcsfileauthor" && argc == 1 )
867 {
868 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
869 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthor( filePath ) ) );
870 }
871 else if( name == "vcsfileauthoremail" && argc == 1 )
872 {
873 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
874 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetAuthorEmail( filePath ) ) );
875 }
876 else if( name == "vcsfilecommitter" && argc == 1 )
877 {
878 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
879 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitter( filePath ) ) );
880 }
881 else if( name == "vcsfilecommitteremail" && argc == 1 )
882 {
883 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
884 return MakeValue<Value>( vcsResult( TEXT_EVAL_VCS::GetCommitterEmail( filePath ) ) );
885 }
886 else if( name == "vcsfilecommitdate" && argc >= 1 && argc <= 2 )
887 {
888 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
889 std::string format = "ISO";
890
891 if( argc == 2 )
892 format = VALUE_UTILS::ToString( argValues[1] );
893
894 int64_t timestamp = TEXT_EVAL_VCS::GetCommitTimestamp( filePath );
895
896 if( timestamp == 0 )
897 return MakeValue<Value>( vcsResult( std::string() ) );
898
899 int days = static_cast<int>( timestamp / ( 24 * 3600 ) );
900 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
901 }
902
903 // String functions (return strings!)
904 else if( name == "upper" && argc == 1 )
905 {
906 auto str = VALUE_UTILS::ToString( argValues[0] );
907 std::transform( str.begin(), str.end(), str.begin(), ::toupper );
908 return MakeValue<Value>( str );
909 }
910 else if( name == "lower" && argc == 1 )
911 {
912 auto str = VALUE_UTILS::ToString( argValues[0] );
913 std::transform( str.begin(), str.end(), str.begin(), ::tolower );
914 return MakeValue<Value>( str );
915 }
916 else if( name == "concat" && argc >= 2 )
917 {
918 std::string result;
919
920 for( const auto& val : argValues )
922
923 return MakeValue<Value>( result );
924 }
925 else if( name == "beforefirst" && argc == 2 )
926 {
927 wxString result = VALUE_UTILS::ToString( argValues[0] );
928
929 result = result.BeforeFirst( VALUE_UTILS::ToChar( argValues[1] ) );
930 return MakeValue<Value>( result.ToStdString() );
931 }
932 else if( name == "beforelast" && argc == 2 )
933 {
934 wxString result = VALUE_UTILS::ToString( argValues[0] );
935
936 result = result.BeforeLast( VALUE_UTILS::ToChar( argValues[1] ) );
937 return MakeValue<Value>( result.ToStdString() );
938 }
939 else if( name == "afterfirst" && argc == 2 )
940 {
941 wxString result = VALUE_UTILS::ToString( argValues[0] );
942
943 result = result.AfterFirst( VALUE_UTILS::ToChar( argValues[1] ) );
944 return MakeValue<Value>( result.ToStdString() );
945 }
946 else if( name == "afterlast" && argc == 2 )
947 {
948 wxString result = VALUE_UTILS::ToString( argValues[0] );
949
950 result = result.AfterLast( VALUE_UTILS::ToChar( argValues[1] ) );
951 return MakeValue<Value>( result.ToStdString() );
952 }
953 else if( name == "replace" && argc == 3 )
954 {
955 wxString result = VALUE_UTILS::ToString( argValues[0] );
956 wxString search = VALUE_UTILS::ToString( argValues[1] );
957
958 if( !search.IsEmpty() )
959 {
960 result.Replace( search, VALUE_UTILS::ToString( argValues[2] ) );
961 }
962
963 return MakeValue<Value>( result.ToStdString() );
964 }
965
966 // Conditional functions (handle mixed types)
967 if( name == "if" && argc == 3 )
968 {
969 // Convert only the condition to a number
970 auto conditionResult = VALUE_UTILS::ToDouble( argValues[0] );
971 if( !conditionResult )
972 return MakeError<Value>( conditionResult.GetError() );
973
974 const auto condition = conditionResult.GetValue() != 0.0;
975 return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
976 }
977
978 // E-series functions (handle value as number, series as string)
979 else if( ( name == "enearest" || name == "eup" || name == "edown" ) && argc >= 1 && argc <= 2 )
980 {
981 auto valueResult = VALUE_UTILS::ToDouble( argValues[0] );
982 if( !valueResult )
983 return MakeError<Value>( valueResult.GetError() );
984
985 const auto value = valueResult.GetValue();
986 const auto series = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "E24";
987 std::optional<double> result;
988
989 if( name == "enearest" )
990 result = ESERIES_UTILS::FindNearest( value, series );
991 else if( name == "eup" )
992 result = ESERIES_UTILS::FindUp( value, series );
993 else if( name == "edown" )
994 result = ESERIES_UTILS::FindDown( value, series );
995
996 if( !result )
997 return MakeError<Value>( fmt::format( "Invalid E-series: {}", series ) );
998
999 return MakeValue<Value>( result.value() );
1000 }
1001
1002 // Mathematical functions (return numbers) - convert args to doubles first
1003 std::vector<double> numArgs;
1004 for( const auto& val : argValues )
1005 {
1006 auto numResult = VALUE_UTILS::ToDouble( val );
1007 if( !numResult )
1008 return MakeError<Value>( numResult.GetError() );
1009
1010 numArgs.push_back( numResult.GetValue() );
1011 }
1012
1013 // Mathematical function implementations
1014 if( name == "abs" && argc == 1 )
1015 return MakeValue<Value>( std::abs( numArgs[0] ) );
1016 else if( name == "sum" && argc >= 1 )
1017 return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
1018 else if( name == "round" && argc >= 1 )
1019 {
1020 const auto value = numArgs[0];
1021 const auto precision = argc > 1 ? static_cast<int>( numArgs[1] ) : 0;
1022 const auto multiplier = std::pow( 10.0, precision );
1023 return MakeValue<Value>( std::round( value * multiplier ) / multiplier );
1024 }
1025 else if( name == "sqrt" && argc == 1 )
1026 {
1027 if( numArgs[0] < 0 )
1028 return MakeError<Value>( "Square root of negative number" );
1029
1030 return MakeValue<Value>( std::sqrt( numArgs[0] ) );
1031 }
1032 else if( name == "pow" && argc == 2 )
1033 return MakeValue<Value>( std::pow( numArgs[0], numArgs[1] ) );
1034 else if( name == "floor" && argc == 1 )
1035 return MakeValue<Value>( std::floor( numArgs[0] ) );
1036 else if( name == "ceil" && argc == 1 )
1037 return MakeValue<Value>( std::ceil( numArgs[0] ) );
1038 else if( name == "min" && argc >= 1 )
1039 return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
1040 else if( name == "max" && argc >= 1 )
1041 return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
1042 else if( name == "avg" && argc >= 1 )
1043 {
1044 const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
1045 return MakeValue<Value>( sum / static_cast<double>( argc ) );
1046 }
1047 else if( name == "shunt" && argc == 2 )
1048 {
1049 const auto r1 = numArgs[0];
1050 const auto r2 = numArgs[1];
1051 const auto sum = r1 + r2;
1052
1053 // Calculate parallel resistance: (r1*r2)/(r1+r2)
1054 // If sum is not positive, return 0.0 (handles edge cases like shunt(0,0))
1055 if( sum > 0.0 )
1056 return MakeValue<Value>( ( r1 * r2 ) / sum );
1057 else
1058 return MakeValue<Value>( 0.0 );
1059 }
1060 else if( name == "db" && argc == 1 )
1061 {
1062 // Power ratio to dB: 10*log10(ratio)
1063 if( numArgs[0] <= 0.0 )
1064 return MakeError<Value>( "db() argument must be positive" );
1065
1066 return MakeValue<Value>( 10.0 * std::log10( numArgs[0] ) );
1067 }
1068 else if( name == "dbv" && argc == 1 )
1069 {
1070 // Voltage/current ratio to dB: 20*log10(ratio)
1071 if( numArgs[0] <= 0.0 )
1072 return MakeError<Value>( "dbv() argument must be positive" );
1073
1074 return MakeValue<Value>( 20.0 * std::log10( numArgs[0] ) );
1075 }
1076 else if( name == "fromdb" && argc == 1 )
1077 {
1078 // dB to power ratio: 10^(dB/10)
1079 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 10.0 ) );
1080 }
1081 else if( name == "fromdbv" && argc == 1 )
1082 {
1083 // dB to voltage/current ratio: 10^(dB/20)
1084 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 20.0 ) );
1085 }
1086
1087 return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
1088}
1089
1090auto DOC_PROCESSOR::Process( const DOC& aDoc, VariableCallback aVariableCallback ) -> std::pair<std::string, bool>
1091{
1092 std::string result;
1093 auto localErrors = ERROR_COLLECTOR{};
1094 EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
1095 bool hadErrors = aDoc.HasErrors();
1096
1097 for( const auto& node : aDoc.GetNodes() )
1098 {
1099 switch( node->type )
1100 {
1101 case NodeType::Text: result += std::get<std::string>( node->data ); break;
1102
1103 case NodeType::Calc:
1104 {
1105 const auto& calcData = std::get<BIN_OP_DATA>( node->data );
1106 auto evalResult = calcData.left->Accept( evaluator );
1107
1108 if( evalResult )
1109 result += VALUE_UTILS::ToString( evalResult.GetValue() );
1110 else
1111 {
1112 // Don't add error formatting to result - errors go to error vector only
1113 // The higher level will return original input unchanged if there are errors
1114 hadErrors = true;
1115 }
1116 break;
1117 }
1118
1119 default:
1120 result += "[Unknown node type]";
1121 hadErrors = true;
1122 break;
1123 }
1124 }
1125
1126 return { std::move( result ), hadErrors || localErrors.HasErrors() };
1127}
1128
1129auto DOC_PROCESSOR::ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
1130 -> std::tuple<std::string, std::vector<std::string>, bool>
1131{
1132 auto [result, hadErrors] = Process( aDoc, std::move( aVariableCallback ) );
1133 auto allErrors = aDoc.GetErrors();
1134
1135 return { std::move( result ), std::move( allErrors ), hadErrors };
1136}
1137
1138} // 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.