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