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
321
323{
324private:
325 // E24 series values in 100-999 decade (2 significant figures)
326 static constexpr std::array<uint16_t, 24> s_e24 = {
327 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300,
328 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910
329 };
330
331 // E192 series values in 100-999 decade (3 significant figures)
332 static constexpr std::array<uint16_t, 192> s_e192 = {
333 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 123,
334 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 142, 143, 145, 147, 149, 150, 152,
335 154, 156, 158, 160, 162, 164, 165, 167, 169, 172, 174, 176, 178, 180, 182, 184, 187, 189,
336 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 221, 223, 226, 229, 232, 234,
337 237, 240, 243, 246, 249, 252, 255, 258, 261, 264, 267, 271, 274, 277, 280, 284, 287, 291,
338 294, 298, 301, 305, 309, 312, 316, 320, 324, 328, 332, 336, 340, 344, 348, 352, 357, 361,
339 365, 370, 374, 379, 383, 388, 392, 397, 402, 407, 412, 417, 422, 427, 432, 437, 442, 448,
340 453, 459, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 530, 536, 542, 549, 556,
341 562, 569, 576, 583, 590, 597, 604, 612, 619, 626, 634, 642, 649, 657, 665, 673, 681, 690,
342 698, 706, 715, 723, 732, 741, 750, 759, 768, 777, 787, 796, 806, 816, 825, 835, 845, 856,
343 866, 876, 887, 898, 909, 920, 931, 942, 953, 965, 976, 988
344 };
345
346 static auto parseSeriesString( const std::string& aSeries ) -> int
347 {
348 if( aSeries == "E3" || aSeries == "e3" )
349 return 3;
350 else if( aSeries == "E6" || aSeries == "e6" )
351 return 6;
352 else if( aSeries == "E12" || aSeries == "e12" )
353 return 12;
354 else if( aSeries == "E24" || aSeries == "e24" )
355 return 24;
356 else if( aSeries == "E48" || aSeries == "e48" )
357 return 48;
358 else if( aSeries == "E96" || aSeries == "e96" )
359 return 96;
360 else if( aSeries == "E192" || aSeries == "e192" )
361 return 192;
362 else
363 return -1; // Invalid series
364 }
365
366 static auto getSeriesValue( int aSeries, size_t aIndex ) -> uint16_t
367 {
368 // E1, E3, E6, E12, E24 are derived from E24
369 if( aSeries <= 24 )
370 {
371 const size_t skipValue = 24 / aSeries;
372 return s_e24[aIndex * skipValue];
373 }
374 // E48, E96, E192 are derived from E192
375 else
376 {
377 const size_t skipValue = 192 / aSeries;
378 return s_e192[aIndex * skipValue];
379 }
380 }
381
382 static auto getSeriesSize( int aSeries ) -> size_t
383 {
384 return static_cast<size_t>( aSeries );
385 }
386
387public:
388 static auto FindNearest( double aValue, const std::string& aSeries ) -> std::optional<double>
389 {
390 const int series = parseSeriesString( aSeries );
391 if( series < 0 )
392 return std::nullopt;
393
394 if( aValue <= 0.0 )
395 return std::nullopt;
396
397 // Scale value to 100-999 decade
398 const double logValue = std::log10( aValue );
399 const int decade = static_cast<int>( std::floor( logValue ) );
400 const double scaledValue = aValue / std::pow( 10.0, decade );
401 const double normalized = scaledValue * 100.0;
402
403 // Find nearest value in series
404 const size_t seriesSize = getSeriesSize( series );
405 double minDiff = std::numeric_limits<double>::max();
406 uint16_t nearest = 100;
407
408 for( size_t i = 0; i < seriesSize; ++i )
409 {
410 const uint16_t val = getSeriesValue( series, i );
411 const double diff = std::abs( normalized - val );
412 if( diff < minDiff )
413 {
414 minDiff = diff;
415 nearest = val;
416 }
417 }
418
419 // Scale back to original decade
420 return ( nearest / 100.0 ) * std::pow( 10.0, decade );
421 }
422
423 static auto FindUp( 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 next higher value in series
439 const size_t seriesSize = getSeriesSize( series );
440
441 // Check current decade
442 for( size_t i = 0; i < seriesSize; ++i )
443 {
444 const uint16_t val = getSeriesValue( series, i );
445 if( val > normalized )
446 return ( val / 100.0 ) * std::pow( 10.0, decade );
447 }
448
449 // Wrap to next decade
450 const uint16_t firstVal = getSeriesValue( series, 0 );
451 return ( firstVal / 100.0 ) * std::pow( 10.0, decade + 1 );
452 }
453
454 static auto FindDown( double aValue, const std::string& aSeries ) -> std::optional<double>
455 {
456 const int series = parseSeriesString( aSeries );
457 if( series < 0 )
458 return std::nullopt;
459
460 if( aValue <= 0.0 )
461 return std::nullopt;
462
463 // Scale value to 100-999 decade
464 const double logValue = std::log10( aValue );
465 const int decade = static_cast<int>( std::floor( logValue ) );
466 const double scaledValue = aValue / std::pow( 10.0, decade );
467 const double normalized = scaledValue * 100.0;
468
469 // Find next lower value in series
470 const size_t seriesSize = getSeriesSize( series );
471
472 // Check current decade (search backwards)
473 for( int i = seriesSize - 1; i >= 0; --i )
474 {
475 const uint16_t val = getSeriesValue( series, i );
476 if( val < normalized )
477 return ( val / 100.0 ) * std::pow( 10.0, decade );
478 }
479
480 // Wrap to previous decade
481 const uint16_t lastVal = getSeriesValue( series, seriesSize - 1 );
482 return ( lastVal / 100.0 ) * std::pow( 10.0, decade - 1 );
483 }
484};
485
486
487EVAL_VISITOR::EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ) :
488 m_variableCallback( std::move( aVariableCallback ) ),
489 m_errors( aErrorCollector ),
490 m_gen( m_rd() )
491{
492}
493
494auto EVAL_VISITOR::operator()( const NODE& aNode ) const -> Result<Value>
495{
496 switch( aNode.type )
497 {
498 case NodeType::Number: return MakeValue<Value>( std::get<double>( aNode.data ) );
499
500 case NodeType::String: return MakeValue<Value>( std::get<std::string>( aNode.data ) );
501
502 case NodeType::Var:
503 {
504 const auto& varName = std::get<std::string>( aNode.data );
505
506 // Use callback to resolve variable
508 return m_variableCallback( varName );
509
510 return MakeError<Value>( fmt::format( "No variable resolver configured for: {}", varName ) );
511 }
512
513 case NodeType::BinOp:
514 {
515 const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
516 auto leftResult = binop.left->Accept( *this );
517 if( !leftResult )
518 return leftResult;
519
520 auto rightResult = binop.right ? binop.right->Accept( *this ) : MakeValue<Value>( 0.0 );
521 if( !rightResult )
522 return rightResult;
523
524 // Special handling for string concatenation with +
525 if( binop.op == '+' )
526 {
527 const auto& leftVal = leftResult.GetValue();
528 const auto& rightVal = rightResult.GetValue();
529
530 // If either operand is a string, concatenate
531 if( std::holds_alternative<std::string>( leftVal ) || std::holds_alternative<std::string>( rightVal ) )
532 {
533 return MakeValue<Value>( VALUE_UTILS::ConcatStrings( leftVal, rightVal ) );
534 }
535 }
536
537 // Special handling for string comparisons with == and !=
538 if( binop.op == 3 || binop.op == 4 ) // == or !=
539 {
540 const auto& leftVal = leftResult.GetValue();
541 const auto& rightVal = rightResult.GetValue();
542
543 // If both operands are strings, do string comparison
544 if( std::holds_alternative<std::string>( leftVal ) && std::holds_alternative<std::string>( rightVal ) )
545 {
546 bool equal = std::get<std::string>( leftVal ) == std::get<std::string>( rightVal );
547 double result = ( binop.op == 3 ) ? ( equal ? 1.0 : 0.0 ) : ( equal ? 0.0 : 1.0 );
548 return MakeValue<Value>( result );
549 }
550 }
551
552 // Otherwise, perform arithmetic
553 return VALUE_UTILS::ArithmeticOp( leftResult.GetValue(), rightResult.GetValue(), binop.op );
554 }
555
557 {
558 const auto& func = std::get<FUNC_DATA>( aNode.data );
559 return evaluateFunction( func );
560 }
561
562 default: return MakeError<Value>( "Cannot evaluate this node type" );
563 }
564}
565
567{
568 const auto& name = aFunc.name;
569 const auto& args = aFunc.args;
570
571 // Zero-argument functions
572 if( args.empty() )
573 {
574 if( name == "today" )
575 return MakeValue<Value>( static_cast<double>( DATE_UTILS::GetCurrentDays() ) );
576 else if( name == "now" )
578 else if( name == "random" )
579 {
580 std::uniform_real_distribution<double> dis( 0.0, 1.0 );
581 return MakeValue<Value>( dis( m_gen ) );
582 }
583 }
584
585 // Evaluate arguments to mixed types
586 std::vector<Value> argValues;
587 argValues.reserve( args.size() );
588
589 for( const auto& arg : args )
590 {
591 auto result = arg->Accept( *this );
592 if( !result )
593 return result;
594
595 argValues.push_back( result.GetValue() );
596 }
597
598 const auto argc = argValues.size();
599
600 // String formatting functions (return strings!)
601 if( name == "format" && argc >= 1 )
602 {
603 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
604 if( !numResult )
605 return MakeError<Value>( numResult.GetError() );
606
607 const auto value = numResult.GetValue();
608 int decimals = 2;
609 if( argc > 1 )
610 {
611 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
612 if( decResult )
613 decimals = static_cast<int>( decResult.GetValue() );
614 }
615
616 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
617 }
618 else if( name == "currency" && argc >= 1 )
619 {
620 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
621 if( !numResult )
622 return MakeError<Value>( numResult.GetError() );
623
624 const auto amount = numResult.GetValue();
625 const auto symbol = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "$";
626
627 return MakeValue<Value>( fmt::format( "{}{:.2f}", symbol, amount ) );
628 }
629 else if( name == "fixed" && argc >= 1 )
630 {
631 auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
632 if( !numResult )
633 return MakeError<Value>( numResult.GetError() );
634
635 const auto value = numResult.GetValue();
636 int decimals = 2;
637 if( argc > 1 )
638 {
639 auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
640 if( decResult )
641 decimals = static_cast<int>( decResult.GetValue() );
642 }
643
644 return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
645 }
646
647 // Date formatting functions (return strings!)
648 else if( name == "dateformat" && argc >= 1 )
649 {
650 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
651 if( !dateResult )
652 return MakeError<Value>( dateResult.GetError() );
653
654 const auto days = static_cast<int>( dateResult.GetValue() );
655 const auto format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
656
657 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
658 }
659 else if( name == "datestring" && argc == 1 )
660 {
661 auto dateStr = VALUE_UTILS::ToString( argValues[0] );
662 auto daysResult = DATE_UTILS::ParseDate( dateStr );
663
664 if( !daysResult )
665 return MakeError<Value>( "Invalid date format: " + dateStr );
666
667 return MakeValue<Value>( static_cast<double>( daysResult.value() ) );
668 }
669 else if( name == "weekdayname" && argc == 1 )
670 {
671 auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
672 if( !dateResult )
673 return MakeError<Value>( dateResult.GetError() );
674
675 const auto days = static_cast<int>( dateResult.GetValue() );
677 }
678
679 // VCS functions (return strings!)
680 else if( name == "vcsidentifier" && argc <= 1 )
681 {
682 int length = 40; // Full identifier by default
683 if( argc == 1 )
684 {
685 auto lenResult = VALUE_UTILS::ToDouble( argValues[0] );
686 if( lenResult )
687 length = static_cast<int>( lenResult.GetValue() );
688 }
689
690 return MakeValue<Value>( TEXT_EVAL_VCS::GetCommitHash( ".", length ) );
691 }
692 else if( name == "vcsnearestlabel" && argc <= 2 )
693 {
694 std::string match;
695 bool anyTags = false;
696
697 if( argc >= 1 )
698 match = VALUE_UTILS::ToString( argValues[0] );
699 if( argc >= 2 )
700 {
701 auto tagsResult = VALUE_UTILS::ToDouble( argValues[1] );
702 if( tagsResult )
703 anyTags = tagsResult.GetValue() != 0.0;
704 }
705
706 return MakeValue<Value>( TEXT_EVAL_VCS::GetNearestTag( match, anyTags ) );
707 }
708 else if( name == "vcslabeldistance" && argc <= 2 )
709 {
710 std::string match;
711 bool anyTags = false;
712
713 if( argc >= 1 )
714 match = VALUE_UTILS::ToString( argValues[0] );
715 if( argc >= 2 )
716 {
717 auto tagsResult = VALUE_UTILS::ToDouble( argValues[1] );
718 if( tagsResult )
719 anyTags = tagsResult.GetValue() != 0.0;
720 }
721
722 return MakeValue<Value>( std::to_string( TEXT_EVAL_VCS::GetDistanceFromTag( match, anyTags ) ) );
723 }
724 else if( name == "vcsdirty" && argc <= 1 )
725 {
726 bool includeUntracked = false;
727 if( argc == 1 )
728 {
729 auto utResult = VALUE_UTILS::ToDouble( argValues[0] );
730 if( utResult )
731 includeUntracked = utResult.GetValue() != 0.0;
732 }
733
734 return MakeValue<Value>( TEXT_EVAL_VCS::IsDirty( includeUntracked ) ? "1" : "0" );
735 }
736 else if( name == "vcsdirtysuffix" && argc <= 2 )
737 {
738 std::string suffix = "-dirty";
739 bool includeUntracked = false;
740
741 if( argc >= 1 )
742 suffix = VALUE_UTILS::ToString( argValues[0] );
743 if( argc >= 2 )
744 {
745 auto utResult = VALUE_UTILS::ToDouble( argValues[1] );
746 if( utResult )
747 includeUntracked = utResult.GetValue() != 0.0;
748 }
749
750 return MakeValue<Value>( TEXT_EVAL_VCS::IsDirty( includeUntracked ) ? suffix : "" );
751 }
752 else if( name == "vcsauthor" && argc == 0 )
753 {
755 }
756 else if( name == "vcsauthoremail" && argc == 0 )
757 {
759 }
760 else if( name == "vcscommitter" && argc == 0 )
761 {
763 }
764 else if( name == "vcscommitteremail" && argc == 0 )
765 {
767 }
768 else if( name == "vcsbranch" && argc == 0 )
769 {
771 }
772 else if( name == "vcscommitdate" && argc <= 1 )
773 {
774 std::string format = "ISO";
775 if( argc == 1 )
776 format = VALUE_UTILS::ToString( argValues[0] );
777
778 int64_t timestamp = TEXT_EVAL_VCS::GetCommitTimestamp( "." );
779 if( timestamp == 0 )
780 return MakeValue<Value>( "" );
781
782 int days = static_cast<int>( timestamp / ( 24 * 3600 ) );
783 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
784 }
785
786 // VCS file functions (file-specific versions)
787 else if( name == "vcsfileidentifier" && argc >= 1 && argc <= 2 )
788 {
789 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
790 int length = 40;
791
792 if( argc == 2 )
793 {
794 auto lenResult = VALUE_UTILS::ToDouble( argValues[1] );
795 if( lenResult )
796 length = static_cast<int>( lenResult.GetValue() );
797 }
798
799 return MakeValue<Value>( TEXT_EVAL_VCS::GetCommitHash( filePath, length ) );
800 }
801 else if( name == "vcsfileauthor" && argc == 1 )
802 {
803 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
804 return MakeValue<Value>( TEXT_EVAL_VCS::GetAuthor( filePath ) );
805 }
806 else if( name == "vcsfileauthoremail" && argc == 1 )
807 {
808 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
810 }
811 else if( name == "vcsfilecommitter" && argc == 1 )
812 {
813 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
814 return MakeValue<Value>( TEXT_EVAL_VCS::GetCommitter( filePath ) );
815 }
816 else if( name == "vcsfilecommitteremail" && argc == 1 )
817 {
818 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
820 }
821 else if( name == "vcsfilecommitdate" && argc >= 1 && argc <= 2 )
822 {
823 std::string filePath = VALUE_UTILS::ToString( argValues[0] );
824 std::string format = "ISO";
825 if( argc == 2 )
826 format = VALUE_UTILS::ToString( argValues[1] );
827
828 int64_t timestamp = TEXT_EVAL_VCS::GetCommitTimestamp( filePath );
829 if( timestamp == 0 )
830 return MakeValue<Value>( "" );
831
832 int days = static_cast<int>( timestamp / ( 24 * 3600 ) );
833 return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
834 }
835
836 // String functions (return strings!)
837 else if( name == "upper" && argc == 1 )
838 {
839 auto str = VALUE_UTILS::ToString( argValues[0] );
840 std::transform( str.begin(), str.end(), str.begin(), ::toupper );
841 return MakeValue<Value>( str );
842 }
843 else if( name == "lower" && argc == 1 )
844 {
845 auto str = VALUE_UTILS::ToString( argValues[0] );
846 std::transform( str.begin(), str.end(), str.begin(), ::tolower );
847 return MakeValue<Value>( str );
848 }
849 else if( name == "concat" && argc >= 2 )
850 {
851 std::string result;
852 for( const auto& val : argValues )
854
855 return MakeValue<Value>( result );
856 }
857 else if( name == "beforefirst" && argc == 2 )
858 {
859 wxString result = VALUE_UTILS::ToString( argValues[0] );
860
861 result = result.BeforeFirst( VALUE_UTILS::ToChar( argValues[1] ) );
862 return MakeValue<Value>( result.ToStdString() );
863 }
864 else if( name == "beforelast" && argc == 2 )
865 {
866 wxString result = VALUE_UTILS::ToString( argValues[0] );
867
868 result = result.BeforeLast( VALUE_UTILS::ToChar( argValues[1] ) );
869 return MakeValue<Value>( result.ToStdString() );
870 }
871 else if( name == "afterfirst" && argc == 2 )
872 {
873 wxString result = VALUE_UTILS::ToString( argValues[0] );
874
875 result = result.AfterFirst( VALUE_UTILS::ToChar( argValues[1] ) );
876 return MakeValue<Value>( result.ToStdString() );
877 }
878 else if( name == "afterlast" && argc == 2 )
879 {
880 wxString result = VALUE_UTILS::ToString( argValues[0] );
881
882 result = result.AfterLast( VALUE_UTILS::ToChar( argValues[1] ) );
883 return MakeValue<Value>( result.ToStdString() );
884 }
885
886 // Conditional functions (handle mixed types)
887 if( name == "if" && argc == 3 )
888 {
889 // Convert only the condition to a number
890 auto conditionResult = VALUE_UTILS::ToDouble( argValues[0] );
891 if( !conditionResult )
892 return MakeError<Value>( conditionResult.GetError() );
893
894 const auto condition = conditionResult.GetValue() != 0.0;
895 return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
896 }
897
898 // E-series functions (handle value as number, series as string)
899 else if( ( name == "enearest" || name == "eup" || name == "edown" ) && argc >= 1 && argc <= 2 )
900 {
901 auto valueResult = VALUE_UTILS::ToDouble( argValues[0] );
902 if( !valueResult )
903 return MakeError<Value>( valueResult.GetError() );
904
905 const auto value = valueResult.GetValue();
906 const auto series = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "E24";
907 std::optional<double> result;
908
909 if( name == "enearest" )
910 result = ESERIES_UTILS::FindNearest( value, series );
911 else if( name == "eup" )
912 result = ESERIES_UTILS::FindUp( value, series );
913 else if( name == "edown" )
914 result = ESERIES_UTILS::FindDown( value, series );
915
916 if( !result )
917 return MakeError<Value>( fmt::format( "Invalid E-series: {}", series ) );
918
919 return MakeValue<Value>( result.value() );
920 }
921
922 // Mathematical functions (return numbers) - convert args to doubles first
923 std::vector<double> numArgs;
924 for( const auto& val : argValues )
925 {
926 auto numResult = VALUE_UTILS::ToDouble( val );
927 if( !numResult )
928 return MakeError<Value>( numResult.GetError() );
929
930 numArgs.push_back( numResult.GetValue() );
931 }
932
933 // Mathematical function implementations
934 if( name == "abs" && argc == 1 )
935 return MakeValue<Value>( std::abs( numArgs[0] ) );
936 else if( name == "sum" && argc >= 1 )
937 return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
938 else if( name == "round" && argc >= 1 )
939 {
940 const auto value = numArgs[0];
941 const auto precision = argc > 1 ? static_cast<int>( numArgs[1] ) : 0;
942 const auto multiplier = std::pow( 10.0, precision );
943 return MakeValue<Value>( std::round( value * multiplier ) / multiplier );
944 }
945 else if( name == "sqrt" && argc == 1 )
946 {
947 if( numArgs[0] < 0 )
948 return MakeError<Value>( "Square root of negative number" );
949
950 return MakeValue<Value>( std::sqrt( numArgs[0] ) );
951 }
952 else if( name == "pow" && argc == 2 )
953 return MakeValue<Value>( std::pow( numArgs[0], numArgs[1] ) );
954 else if( name == "floor" && argc == 1 )
955 return MakeValue<Value>( std::floor( numArgs[0] ) );
956 else if( name == "ceil" && argc == 1 )
957 return MakeValue<Value>( std::ceil( numArgs[0] ) );
958 else if( name == "min" && argc >= 1 )
959 return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
960 else if( name == "max" && argc >= 1 )
961 return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
962 else if( name == "avg" && argc >= 1 )
963 {
964 const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
965 return MakeValue<Value>( sum / static_cast<double>( argc ) );
966 }
967 else if( name == "shunt" && argc == 2 )
968 {
969 const auto r1 = numArgs[0];
970 const auto r2 = numArgs[1];
971 const auto sum = r1 + r2;
972
973 // Calculate parallel resistance: (r1*r2)/(r1+r2)
974 // If sum is not positive, return 0.0 (handles edge cases like shunt(0,0))
975 if( sum > 0.0 )
976 return MakeValue<Value>( ( r1 * r2 ) / sum );
977 else
978 return MakeValue<Value>( 0.0 );
979 }
980 else if( name == "db" && argc == 1 )
981 {
982 // Power ratio to dB: 10*log10(ratio)
983 if( numArgs[0] <= 0.0 )
984 return MakeError<Value>( "db() argument must be positive" );
985
986 return MakeValue<Value>( 10.0 * std::log10( numArgs[0] ) );
987 }
988 else if( name == "dbv" && argc == 1 )
989 {
990 // Voltage/current ratio to dB: 20*log10(ratio)
991 if( numArgs[0] <= 0.0 )
992 return MakeError<Value>( "dbv() argument must be positive" );
993
994 return MakeValue<Value>( 20.0 * std::log10( numArgs[0] ) );
995 }
996 else if( name == "fromdb" && argc == 1 )
997 {
998 // dB to power ratio: 10^(dB/10)
999 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 10.0 ) );
1000 }
1001 else if( name == "fromdbv" && argc == 1 )
1002 {
1003 // dB to voltage/current ratio: 10^(dB/20)
1004 return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 20.0 ) );
1005 }
1006
1007 return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
1008}
1009
1010auto DOC_PROCESSOR::Process( const DOC& aDoc, VariableCallback aVariableCallback ) -> std::pair<std::string, bool>
1011{
1012 std::string result;
1013 auto localErrors = ERROR_COLLECTOR{};
1014 EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
1015 bool hadErrors = aDoc.HasErrors();
1016
1017 for( const auto& node : aDoc.GetNodes() )
1018 {
1019 switch( node->type )
1020 {
1021 case NodeType::Text: result += std::get<std::string>( node->data ); break;
1022
1023 case NodeType::Calc:
1024 {
1025 const auto& calcData = std::get<BIN_OP_DATA>( node->data );
1026 auto evalResult = calcData.left->Accept( evaluator );
1027
1028 if( evalResult )
1029 result += VALUE_UTILS::ToString( evalResult.GetValue() );
1030 else
1031 {
1032 // Don't add error formatting to result - errors go to error vector only
1033 // The higher level will return original input unchanged if there are errors
1034 hadErrors = true;
1035 }
1036 break;
1037 }
1038
1039 default:
1040 result += "[Unknown node type]";
1041 hadErrors = true;
1042 break;
1043 }
1044 }
1045
1046 return { std::move( result ), hadErrors || localErrors.HasErrors() };
1047}
1048
1049auto DOC_PROCESSOR::ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
1050 -> std::tuple<std::string, std::vector<std::string>, bool>
1051{
1052 auto [result, hadErrors] = Process( aDoc, std::move( aVariableCallback ) );
1053 auto allErrors = aDoc.GetErrors();
1054
1055 return { std::move( result ), std::move( allErrors ), hadErrors };
1056}
1057
1058} // namespace calc_parser
const char * name
static auto GetCurrentDays() -> int
static constexpr std::array< const char *, 12 > monthNames
static auto GetWeekdayName(int aDaysSinceEpoch) -> std::string
static auto FormatDate(int aDaysSinceEpoch, const std::string &aFormat) -> std::string
static constexpr std::array< const char *, 12 > monthAbbrev
static auto YmdToDays(int aYear, int aMonth, int aDay) -> int
static auto DaysToYmd(int aDaysSinceEpoch) -> std::tuple< int, int, int >
static auto ParseDate(const std::string &aDateStr) -> std::optional< int >
static auto daysInYear(int aYear) -> int
static constexpr int epochYear
static constexpr std::array< int, 12 > daysInMonth
static auto isLeapYear(int aYear) -> bool
static constexpr std::array< const char *, 7 > weekdayNames
static auto daysInMonthForYear(int aMonth, int aYear) -> int
static auto GetCurrentTimestamp() -> double
static auto ProcessWithDetails(const DOC &aDoc, VariableCallback aVariableCallback) -> std::tuple< std::string, std::vector< std::string >, bool >
Process document with detailed error reporting.
EVAL_VISITOR::VariableCallback VariableCallback
static auto Process(const DOC &aDoc, VariableCallback aVariableCallback) -> std::pair< std::string, bool >
Process document using callback for variable resolution.
static auto parseSeriesString(const std::string &aSeries) -> int
static auto getSeriesSize(int aSeries) -> size_t
static auto FindDown(double aValue, const std::string &aSeries) -> std::optional< double >
static auto getSeriesValue(int aSeries, size_t aIndex) -> uint16_t
static constexpr std::array< uint16_t, 24 > s_e24
static auto FindUp(double aValue, const std::string &aSeries) -> std::optional< double >
static auto FindNearest(double aValue, const std::string &aSeries) -> std::optional< double >
static constexpr std::array< uint16_t, 192 > s_e192
auto operator()(const NODE &aNode) const -> Result< Value >
VariableCallback m_variableCallback
std::function< Result< Value >(const std::string &aVariableName)> VariableCallback
EVAL_VISITOR(VariableCallback aVariableCallback, ERROR_COLLECTOR &aErrorCollector)
Construct evaluator with variable callback function.
auto evaluateFunction(const FUNC_DATA &aFunc) const -> Result< Value >
static auto ArithmeticOp(const Value &aLeft, const Value &aRight, char aOp) -> Result< Value >
static auto ToString(const Value &aVal) -> std::string
static auto ToDouble(const Value &aVal) -> Result< double >
static auto ConcatStrings(const Value &aLeft, const Value &aRight) -> Value
static auto ToChar(const Value &aVal) -> char
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.