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