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