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