KiCad PCB EDA Suite
Loading...
Searching...
No Matches
text_eval_wrapper.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
21#include <fmt/format.h>
22
23// Include the KiCad common functionality
24#include <common.h>
25#include <fast_float/fast_float.h>
26#include <text_eval/text_eval_types.h> // Parser types
28#include <text_eval/text_eval_units.h> // Centralized unit registry
29
30namespace KI_EVAL
31{
32
33#ifdef __GNUC__
34#pragma GCC diagnostic push
35#pragma GCC diagnostic ignored "-Wunused-variable"
36#pragma GCC diagnostic ignored "-Wsign-compare"
37#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
38#endif
39
40#include <text_eval/text_eval.c>
41
42#ifdef __GNUC__
43#pragma GCC diagnostic pop
44#endif
45} // namespace KI_EVAL
46
47
48#include <wx/log.h>
49#include <algorithm>
50#include <regex>
51#include <span>
52
53
54// // Token type enum matching the generated parser
55enum class TEXT_EVAL_TOKEN : int
56{
57 ENDS = KI_EVAL_LT - 1,
58 LT = KI_EVAL_LT,
59 GT = KI_EVAL_GT,
60 LE = KI_EVAL_LE,
61 GE = KI_EVAL_GE,
62 EQ = KI_EVAL_EQ,
63 NE = KI_EVAL_NE,
64 PLUS = KI_EVAL_PLUS,
65 MINUS = KI_EVAL_MINUS,
66 MULTIPLY = KI_EVAL_MULTIPLY,
67 DIVIDE = KI_EVAL_DIVIDE,
68 MODULO = KI_EVAL_MODULO,
69 UMINUS = KI_EVAL_UMINUS,
70 POWER = KI_EVAL_POWER,
71 COMMA = KI_EVAL_COMMA,
72 TEXT = KI_EVAL_TEXT,
73 AT_OPEN = KI_EVAL_AT_OPEN,
74 CLOSE_BRACE = KI_EVAL_CLOSE_BRACE,
75 LPAREN = KI_EVAL_LPAREN,
76 RPAREN = KI_EVAL_RPAREN,
77 NUMBER = KI_EVAL_NUMBER,
78 STRING = KI_EVAL_STRING,
79 IDENTIFIER = KI_EVAL_IDENTIFIER,
80 DOLLAR_OPEN = KI_EVAL_DOLLAR_OPEN,
81};
82
83
84// UTF-8 <-> UTF-32 conversion utilities
85namespace utf8_utils
86{
87
88// Concept for UTF-8 byte validation
89template <typename T>
90concept Utf8Byte = std::same_as<T, char> || std::same_as<T, unsigned char> || std::same_as<T, std::byte>;
91
92
93// UTF-8 validation and conversion
95{
96private:
97 // UTF-8 byte classification using bit operations
98 static constexpr bool is_ascii( std::byte b ) noexcept
99 {
100 return ( b & std::byte{ 0x80 } ) == std::byte{ 0x00 };
101 }
102
103 static constexpr bool is_continuation( std::byte b ) noexcept
104 {
105 return ( b & std::byte{ 0xC0 } ) == std::byte{ 0x80 };
106 }
107
108 static constexpr int sequence_length( std::byte first ) noexcept
109 {
110 if( is_ascii( first ) )
111 return 1;
112
113 if( ( first & std::byte{ 0xE0 } ) == std::byte{ 0xC0 } )
114 return 2;
115
116 if( ( first & std::byte{ 0xF0 } ) == std::byte{ 0xE0 } )
117 return 3;
118
119 if( ( first & std::byte{ 0xF8 } ) == std::byte{ 0xF0 } )
120 return 4;
121
122 return 0; // Invalid
123 }
124
125public:
126 // Convert UTF-8 string to UTF-32 codepoints using C++20 ranges
127 static std::u32string to_utf32( std::string_view utf8 )
128 {
129 std::u32string result;
130 result.reserve( utf8.size() ); // Conservative estimate
131
132 auto bytes = std::as_bytes( std::span{ utf8.data(), utf8.size() } );
133
134 for( size_t i = 0; i < bytes.size(); )
135 {
136 std::byte first = bytes[i];
137 int len = sequence_length( first );
138
139 if( len == 0 || i + len > bytes.size() )
140 {
141 // Invalid sequence - insert replacement character
142 result.push_back( U'\uFFFD' );
143 i++;
144 continue;
145 }
146
147 char32_t codepoint = 0;
148
149 switch( len )
150 {
151 case 1: codepoint = std::to_integer<char32_t>( first ); break;
152
153 case 2:
154 {
155 if( !is_continuation( bytes[i + 1] ) )
156 {
157 result.push_back( U'\uFFFD' );
158 i++;
159 continue;
160 }
161 codepoint = ( std::to_integer<char32_t>( first & std::byte{ 0x1F } ) << 6 )
162 | std::to_integer<char32_t>( bytes[i + 1] & std::byte{ 0x3F } );
163 break;
164 }
165
166 case 3:
167 {
168 if( !is_continuation( bytes[i + 1] ) || !is_continuation( bytes[i + 2] ) )
169 {
170 result.push_back( U'\uFFFD' );
171 i++;
172 continue;
173 }
174 codepoint = ( std::to_integer<char32_t>( first & std::byte{ 0x0F } ) << 12 )
175 | ( std::to_integer<char32_t>( bytes[i + 1] & std::byte{ 0x3F } ) << 6 )
176 | std::to_integer<char32_t>( bytes[i + 2] & std::byte{ 0x3F } );
177 break;
178 }
179
180 case 4:
181 {
182 if( !is_continuation( bytes[i + 1] ) || !is_continuation( bytes[i + 2] )
183 || !is_continuation( bytes[i + 3] ) )
184 {
185 result.push_back( U'\uFFFD' );
186 i++;
187 continue;
188 }
189 codepoint = ( std::to_integer<char32_t>( first & std::byte{ 0x07 } ) << 18 )
190 | ( std::to_integer<char32_t>( bytes[i + 1] & std::byte{ 0x3F } ) << 12 )
191 | ( std::to_integer<char32_t>( bytes[i + 2] & std::byte{ 0x3F } ) << 6 )
192 | std::to_integer<char32_t>( bytes[i + 3] & std::byte{ 0x3F } );
193 break;
194 }
195 }
196
197 // Validate codepoint range
198 if( codepoint > 0x10FFFF || ( codepoint >= 0xD800 && codepoint <= 0xDFFF ) )
199 {
200 result.push_back( U'\uFFFD' ); // Replacement character
201 }
202 else if( len == 2 && codepoint < 0x80 )
203 {
204 result.push_back( U'\uFFFD' ); // Overlong encoding
205 }
206 else if( len == 3 && codepoint < 0x800 )
207 {
208 result.push_back( U'\uFFFD' ); // Overlong encoding
209 }
210 else if( len == 4 && codepoint < 0x10000 )
211 {
212 result.push_back( U'\uFFFD' ); // Overlong encoding
213 }
214 else
215 {
216 result.push_back( codepoint );
217 }
218
219 i += len;
220 }
221
222 return result;
223 }
224
225 // Convert UTF-32 to UTF-8
226 static std::string to_utf8( std::u32string_view utf32 )
227 {
228 std::string result;
229 result.reserve( utf32.size() * 4 ); // Maximum possible size
230
231 for( char32_t cp : utf32 )
232 {
233 if( cp <= 0x7F )
234 {
235 // 1-byte sequence
236 result.push_back( static_cast<char>( cp ) );
237 }
238 else if( cp <= 0x7FF )
239 {
240 // 2-byte sequence
241 result.push_back( static_cast<char>( 0xC0 | ( cp >> 6 ) ) );
242 result.push_back( static_cast<char>( 0x80 | ( cp & 0x3F ) ) );
243 }
244 else if( cp <= 0xFFFF )
245 {
246 // 3-byte sequence
247 if( cp >= 0xD800 && cp <= 0xDFFF )
248 {
249 // Surrogate pair - invalid in UTF-32
250 result.append( "\uFFFD" ); // Replacement character in UTF-8
251 }
252 else
253 {
254 result.push_back( static_cast<char>( 0xE0 | ( cp >> 12 ) ) );
255 result.push_back( static_cast<char>( 0x80 | ( ( cp >> 6 ) & 0x3F ) ) );
256 result.push_back( static_cast<char>( 0x80 | ( cp & 0x3F ) ) );
257 }
258 }
259 else if( cp <= 0x10FFFF )
260 {
261 // 4-byte sequence
262 result.push_back( static_cast<char>( 0xF0 | ( cp >> 18 ) ) );
263 result.push_back( static_cast<char>( 0x80 | ( ( cp >> 12 ) & 0x3F ) ) );
264 result.push_back( static_cast<char>( 0x80 | ( ( cp >> 6 ) & 0x3F ) ) );
265 result.push_back( static_cast<char>( 0x80 | ( cp & 0x3F ) ) );
266 }
267 else
268 {
269 // Invalid codepoint
270 result.append( "\uFFFD" ); // Replacement character in UTF-8
271 }
272 }
273
274 return result;
275 }
276};
277
278
279template <typename T>
280concept UnicodeCodepoint = std::same_as<T, char32_t>;
281
282
284{
285 static constexpr bool is_whitespace( UnicodeCodepoint auto cp ) noexcept
286 {
287 // Unicode whitespace categories
288 return cp == U' ' || cp == U'\t' || cp == U'\r' || cp == U'\n' || cp == U'\f' || cp == U'\v' || cp == U'\u00A0'
289 || // Non-breaking space
290 cp == U'\u2000' || cp == U'\u2001' || cp == U'\u2002' || cp == U'\u2003' || cp == U'\u2004'
291 || cp == U'\u2005' || cp == U'\u2006' || cp == U'\u2007' || cp == U'\u2008' || cp == U'\u2009'
292 || cp == U'\u200A' || cp == U'\u2028' || cp == U'\u2029' || cp == U'\u202F' || cp == U'\u205F'
293 || cp == U'\u3000';
294 }
295
296 static constexpr bool is_digit( UnicodeCodepoint auto cp ) noexcept
297 {
298 return cp >= U'0' && cp <= U'9';
299 }
300
301 static constexpr bool is_ascii_alpha( UnicodeCodepoint auto cp ) noexcept
302 {
303 return ( cp >= U'a' && cp <= U'z' ) || ( cp >= U'A' && cp <= U'Z' );
304 }
305
306 static constexpr bool is_alpha( UnicodeCodepoint auto cp ) noexcept
307 {
308 // Basic Latin + extended Unicode letter ranges
309 return is_ascii_alpha( cp ) || ( cp >= 0x80 && cp <= 0x10FFFF && cp != 0xFFFD && !is_whitespace( cp ) );
310 }
311
312 static constexpr bool is_alnum( UnicodeCodepoint auto cp ) noexcept
313 {
314 return is_alpha( cp ) || is_digit( cp );
315 }
316};
317
318
320{
321 struct PREFIX
322 {
323 char32_t symbol;
325 };
326
327 static constexpr std::array<PREFIX, 18> prefixes = { { { U'a', 1e-18 },
328 { U'f', 1e-15 },
329 { U'p', 1e-12 },
330 { U'n', 1e-9 },
331 { U'u', 1e-6 },
332 { U'µ', 1e-6 },
333 { U'μ', 1e-6 }, // Various micro symbols
334 { U'm', 1e-3 },
335 { U'k', 1e3 },
336 { U'K', 1e3 },
337 { U'M', 1e6 },
338 { U'G', 1e9 },
339 { U'T', 1e12 },
340 { U'P', 1e15 },
341 { U'E', 1e18 } } };
342
343 static constexpr bool is_si_prefix( UnicodeCodepoint auto cp ) noexcept
344 {
345 return std::ranges::any_of( prefixes,
346 [cp]( const PREFIX& p )
347 {
348 return p.symbol == cp;
349 } );
350 }
351
352 static constexpr double get_multiplier( UnicodeCodepoint auto cp ) noexcept
353 {
354 auto it = std::ranges::find_if( prefixes,
355 [cp]( const PREFIX& p )
356 {
357 return p.symbol == cp;
358 } );
359 return it != prefixes.end() ? it->multiplier : 1.0;
360 }
361};
362} // namespace utf8_utils
363
364
365// Unit conversion utilities for the text evaluator
367{
368
369
370// Internal unit enum matching NUMERIC_EVALUATOR
387
388
389// Convert EDA_UNITS to internal Unit enum
391{
392 switch( aUnits )
393 {
394 case EDA_UNITS::MM: return UNIT::MM;
395 case EDA_UNITS::MILS: return UNIT::Mil;
396 case EDA_UNITS::INCH: return UNIT::Inch;
399 case EDA_UNITS::PS: return UNIT::Picoseconds;
403 case EDA_UNITS::UM: return UNIT::UM;
404 case EDA_UNITS::CM: return UNIT::CM;
405 case EDA_UNITS::UNSCALED: return UNIT::SI;
406 default: return UNIT::MM;
407 }
408}
409
410
411// Parse unit from string using centralized registry
412UNIT parseUnit( const std::string& aUnitStr )
413{
414 auto evalUnit = text_eval_units::UnitRegistry::parseUnit( aUnitStr );
415
416 // Convert text_eval_units::Unit to KIEVAL_UNIT_CONV::Unit
417 switch( evalUnit )
418 {
433 default: return UNIT::Invalid;
434 }
435}
436
437// Get conversion factor from one unit to another (based on numeric_evaluator logic)
438double getConversionFactor( UNIT aFromUnit, UNIT aToUnit )
439{
440 if( aFromUnit == aToUnit )
441 return 1.0;
442
443 // Convert to MM first, then to target unit
444 double toMM = 1.0;
445
446 switch( aFromUnit )
447 {
448 case UNIT::Inch: toMM = 25.4; break;
449 case UNIT::Mil: toMM = 25.4 / 1000.0; break;
450 case UNIT::UM: toMM = 1.0 / 1000.0; break;
451 case UNIT::MM: toMM = 1.0; break;
452 case UNIT::CM: toMM = 10.0; break;
453 default: return 1.0; // No conversion for other units
454 }
455
456 double fromMM = 1.0;
457
458 switch( aToUnit )
459 {
460 case UNIT::Inch: fromMM = 1.0 / 25.4; break;
461 case UNIT::Mil: fromMM = 1000.0 / 25.4; break;
462 case UNIT::UM: fromMM = 1000.0; break;
463 case UNIT::MM: fromMM = 1.0; break;
464 case UNIT::CM: fromMM = 1.0 / 10.0; break;
465 default: return 1.0; // No conversion for other units
466 }
467
468 return toMM * fromMM;
469}
470
471
472// Convert a value with units to the default units using centralized registry
473double convertToDefaultUnits( double aValue, const std::string& aUnitStr, EDA_UNITS aDefaultUnits )
474{
475 return text_eval_units::UnitRegistry::convertToEdaUnits( aValue, aUnitStr, aDefaultUnits );
476}
477} // namespace KIEVAL_UNIT_CONV
478
479
481{
482private:
485
486 char32_t current_char() const noexcept
487 {
488 return m_pos < m_text.size() ? m_text[m_pos] : U'\0';
489 }
490
491 char32_t peek_char( size_t offset = 1 ) const noexcept
492 {
493 size_t peek_pos = m_pos + offset;
494 return peek_pos < m_text.size() ? m_text[peek_pos] : U'\0';
495 }
496
497 void advance_position( size_t count = 1 ) noexcept
498 {
499 for( size_t i = 0; i < count && m_pos < m_text.size(); ++i )
500 {
501 if( m_text[m_pos] == U'\n' )
502 {
503 ++m_line;
504 m_column = 1;
505 }
506 else
507 {
508 ++m_column;
509 }
510
511 ++m_pos;
512 }
513 }
514
515 void skip_whitespace() noexcept
516 {
517 while( m_pos < m_text.size() && CLASSIFIER::is_whitespace( current_char() ) )
519 }
520
521 void add_error( std::string_view message ) const
522 {
523 if( m_errorCollector )
524 {
525 auto error_msg = fmt::format( "Line {}, Column {}: {}", m_line, m_column, message );
526 m_errorCollector->AddError( error_msg );
527 }
528 }
529
530 static calc_parser::TOKEN_TYPE make_string_token( const std::string &value ) noexcept
531 {
533 token.isString = true;
534 std::strncpy( token.text, value.c_str(), sizeof( token.text ) - 1 );
535 token.text[sizeof( token.text ) - 1] = '\0';
536 token.dValue = 0.0;
537 return token;
538 }
539
540 static constexpr calc_parser::TOKEN_TYPE make_number_token( double value ) noexcept
541 {
543 token.isString = false;
544 token.dValue = value;
545 return token;
546 }
547
549 {
550 advance_position(); // Skip opening quote
551
552 std::u32string content;
553 content.reserve( 64 ); // Reasonable default
554
555 while( m_pos < m_text.size() && current_char() != quote_char )
556 {
557 char32_t c = current_char();
558
559 if( c == U'\\' && m_pos + 1 < m_text.size() )
560 {
561 char32_t escaped = peek_char();
562 advance_position( 2 );
563
564 switch( escaped )
565 {
566 case U'n':
567 content.push_back( U'\n' );
568 break;
569 case U't':
570 content.push_back( U'\t' );
571 break;
572 case U'r':
573 content.push_back( U'\r' );
574 break;
575 case U'\\':
576 content.push_back( U'\\' );
577 break;
578 case U'"':
579 content.push_back( U'"' );
580 break;
581 case U'\'':
582 content.push_back( U'\'' );
583 break;
584 case U'0':
585 content.push_back( U'\0' );
586 break;
587 case U'x':
588 {
589 // Hexadecimal escape \xHH
590 std::u32string hex;
591
592 for( int i = 0; i < 2 && m_pos < m_text.size(); ++i )
593 {
594 char32_t hex_char = current_char();
595 if( ( hex_char >= U'0' && hex_char <= U'9' )
596 || ( hex_char >= U'A' && hex_char <= U'F' )
597 || ( hex_char >= U'a' && hex_char <= U'f' ) )
598 {
599 hex.push_back( hex_char );
601 }
602 else
603 {
604 break;
605 }
606 }
607
608 if( !hex.empty() )
609 {
610 try
611 {
612 std::string hex_str = utf8_utils::UTF8_CONVERTER::to_utf8( hex );
613 unsigned long value = std::stoul( hex_str, nullptr, 16 );
614
615 if( value <= 0x10FFFF )
616 content.push_back( static_cast<char32_t>( value ) );
617 else
618 content.push_back( U'\uFFFD' );
619 }
620 catch( ... )
621 {
622 content.push_back( U'\uFFFD' );
623 }
624 }
625 else
626 {
627 content.append( U"\\x" );
628 }
629
630 break;
631 }
632 default:
633 content.push_back( U'\\' );
634 content.push_back( escaped );
635 break;
636 }
637 }
638 else if( c == U'\n' )
639 {
640 add_error( "Unterminated string literal" );
641 break;
642 }
643 else
644 {
645 content.push_back( c );
647 }
648 }
649
650 if( m_pos < m_text.size() && current_char() == quote_char )
651 {
652 advance_position(); // Skip closing quote
653 }
654 else
655 {
656 add_error( "Missing closing quote in string literal" );
657 }
658
660 }
661
663 {
664 std::u32string number_text;
665 number_text.reserve( 32 );
666
667 double multiplier = 1.0;
668
669 // Parse integer part
670 while( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
671 {
672 number_text.push_back( current_char() );
674 }
675
676 // Handle decimal point, SI prefix, or unit suffix
677 if( m_pos < m_text.size() )
678 {
679 char32_t c = current_char();
680
681 // Only treat comma as decimal separator in text context, not expression context
682 // This prevents comma from interfering with function argument separation
683 if( c == U'.' || ( c == U',' && m_context != TOKENIZER_CONTEXT::EXPRESSION ) )
684 {
685 number_text.push_back( U'.' );
687 }
689 {
690 // In expression context, check for unit first before SI prefix (unit strings are longer)
691 // Look ahead to see if we have a complete unit string
692 std::u32string potential_unit;
693 size_t temp_pos = m_pos;
694
695 while( temp_pos < m_text.size() )
696 {
697 char32_t unit_char = m_text[temp_pos];
698
699 if( CLASSIFIER::is_alpha( unit_char ) || unit_char == U'"' || unit_char == U'\'' )
700 {
701 potential_unit.push_back( unit_char );
702 temp_pos++;
703 }
704 else
705 {
706 break;
707 }
708 }
709
710 // Check if we have a valid unit
711 if( !potential_unit.empty() )
712 {
713 std::string unit_str = utf8_utils::UTF8_CONVERTER::to_utf8( potential_unit );
714 KIEVAL_UNIT_CONV::UNIT parsed_unit = KIEVAL_UNIT_CONV::parseUnit( unit_str );
715
716 if( parsed_unit != KIEVAL_UNIT_CONV::UNIT::Invalid )
717 {
718 // This is a valid unit - don't treat the first character as SI prefix
719 // The unit parsing will happen later
720 }
721 else if( SI_HANDLER::is_si_prefix( c ) )
722 {
723 // Not a valid unit, so treat as SI prefix
724 multiplier = SI_HANDLER::get_multiplier( c );
726 }
727 }
728 else if( SI_HANDLER::is_si_prefix( c ) )
729 {
730 // No alphabetic characters following, so treat as SI prefix
731 multiplier = SI_HANDLER::get_multiplier( c );
733 }
734 }
735 else if( SI_HANDLER::is_si_prefix( c ) )
736 {
737 // In text context, treat as SI prefix
738 multiplier = SI_HANDLER::get_multiplier( c );
740 }
741 }
742
743 // Parse fractional part
744 while( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
745 {
746 number_text.push_back( current_char() );
748 }
749
750 // Check for scientific notation (e.g., 1e-3, 3.5E6)
751 if( m_pos < m_text.size() )
752 {
753 char32_t c = current_char();
754
755 if( c == U'e' || c == U'E' )
756 {
757 // Look ahead to see if this is scientific notation (followed by +, -, or digit)
758 size_t temp_pos = m_pos + 1;
759 bool is_scientific = false;
760
761 if( temp_pos < m_text.size() )
762 {
763 char32_t next = m_text[temp_pos];
764
765 if( next == U'+' || next == U'-' || CLASSIFIER::is_digit( next ) )
766 is_scientific = true;
767 }
768
769 if( is_scientific )
770 {
771 // Parse scientific notation exponent
772 number_text.push_back( c ); // Add 'e' or 'E'
774
775 // Optional sign
776 if( m_pos < m_text.size() && ( current_char() == U'+' || current_char() == U'-' ) )
777 {
778 number_text.push_back( current_char() );
780 }
781
782 // Exponent digits (required)
783 if( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
784 {
785 while( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
786 {
787 number_text.push_back( current_char() );
789 }
790 }
791 else
792 {
793 // Invalid scientific notation - will fail in conversion
794 add_error( "Invalid scientific notation: missing exponent digits" );
795 }
796 }
797 }
798 }
799
800 // Check for SI prefix after fractional part (for numbers like 0.3M)
801 if( m_pos < m_text.size() && multiplier == 1.0 )
802 {
803 char32_t c = current_char();
804
806 {
807 // Look ahead to check for unit vs SI prefix
808 std::u32string potential_unit;
809 size_t temp_pos = m_pos;
810
811 while( temp_pos < m_text.size() )
812 {
813 char32_t unit_char = m_text[temp_pos];
814
815 if( CLASSIFIER::is_alpha( unit_char ) || unit_char == U'"' || unit_char == U'\'' )
816 {
817 potential_unit.push_back( unit_char );
818 temp_pos++;
819 }
820 else
821 {
822 break;
823 }
824 }
825
826 if( !potential_unit.empty() )
827 {
828 std::string unit_str = utf8_utils::UTF8_CONVERTER::to_utf8( potential_unit );
829 KIEVAL_UNIT_CONV::UNIT parsed_unit = KIEVAL_UNIT_CONV::parseUnit( unit_str );
830
832 {
833 // Not a valid unit, so treat as SI prefix
834 multiplier = SI_HANDLER::get_multiplier( c );
836 }
837 }
838 }
839 else if( SI_HANDLER::is_si_prefix( c ) )
840 {
841 // In text context, treat as SI prefix
842 multiplier = SI_HANDLER::get_multiplier( c );
844 }
845 }
846
847 // Convert to double safely
848 std::string number_str = utf8_utils::UTF8_CONVERTER::to_utf8( number_text );
849 double value = 0.0;
850
851 try
852 {
853 if( !number_str.empty() && number_str != "." )
854 {
855 auto result = fast_float::from_chars( number_str.data(), number_str.data() + number_str.size(), value );
856
857 if( result.ec != std::errc() || result.ptr != number_str.data() + number_str.size() )
858 throw std::invalid_argument( fmt::format( "Cannot convert '{}' to number", number_str ) );
859
860 value *= multiplier;
861
862 if( !std::isfinite( value ) )
863 {
864 add_error( "Number out of range" );
865 value = 0.0;
866 }
867 }
868 }
869 catch( const std::exception& e )
870 {
871 add_error( fmt::format( "Invalid number format: {}", e.what() ) );
872 value = 0.0;
873 }
874
875 // Look for unit suffix
877 {
878 // Skip any whitespace between number and unit
879 size_t whitespace_start = m_pos;
880
881 while( m_pos < m_text.size() && CLASSIFIER::is_whitespace( current_char() ) )
883
884 // Parse potential unit suffix
885 std::u32string unit_text;
886
887 // Look ahead to parse potential unit (letters, quotes, etc.)
888 while( m_pos < m_text.size() )
889 {
890 char32_t c = current_char();
891
892 // Unit characters: letters, quotes for inches
893 if( CLASSIFIER::is_alpha( c ) || c == U'"' || c == U'\'' )
894 {
895 unit_text.push_back( c );
897 }
898 else
899 {
900 break;
901 }
902 }
903
904 if( !unit_text.empty() )
905 {
906 // Convert unit text to string and try to parse it
907 std::string unit_str = utf8_utils::UTF8_CONVERTER::to_utf8( unit_text );
908 KIEVAL_UNIT_CONV::UNIT parsed_unit = KIEVAL_UNIT_CONV::parseUnit( unit_str );
909
910 if( parsed_unit != KIEVAL_UNIT_CONV::UNIT::Invalid )
911 {
912 // Successfully parsed unit - convert value to default units
913 double converted_value = KIEVAL_UNIT_CONV::convertToDefaultUnits( value, unit_str, m_defaultUnits );
914 value = converted_value;
915 }
916 else
917 {
918 // Not a valid unit - backtrack to before the whitespace
919 m_pos = whitespace_start;
920 }
921 }
922 else
923 {
924 // No unit found - backtrack to before the whitespace
925 m_pos = whitespace_start;
926 }
927 }
928
929 return make_number_token( value );
930 }
931
933 {
934 std::u32string identifier;
935 identifier.reserve( 64 );
936
937 while( m_pos < m_text.size() && ( CLASSIFIER::is_alnum( current_char() ) || current_char() == U'_' ) )
938 {
939 identifier.push_back( current_char() );
941 }
942
944 }
945
947 {
948 std::u32string text;
949 text.reserve( 256 );
950
951 while( m_pos < m_text.size() )
952 {
953 char32_t current = current_char();
954 char32_t next = peek_char();
955
956 // Stop at special sequences
957 if( ( current == U'@' && next == U'{' ) || ( current == U'$' && next == U'{' ) )
958 break;
959
960 text.push_back( current );
962 }
963
965 }
966
967public:
968 explicit KIEVAL_TEXT_TOKENIZER( std::string_view input, calc_parser::ERROR_COLLECTOR* error_collector = nullptr,
969 EDA_UNITS default_units = EDA_UNITS::MM ) :
970 m_errorCollector( error_collector ),
971 m_defaultUnits( default_units )
972 {
974 }
975
977 {
978 token_value = calc_parser::TOKEN_TYPE{};
979
980 if( m_pos >= m_text.size() )
982
983 // Only skip whitespace in expression context
985 {
987
988 if( m_pos >= m_text.size() )
990 }
991
992 char32_t current = current_char();
993 char32_t next = peek_char();
994
995 // Multi-character tokens that switch to expression context
996 if( current == U'@' && next == U'{' )
997 {
998 advance_position( 2 );
999 m_context = TOKENIZER_CONTEXT::EXPRESSION; // Switch to expression context
1000 m_braceNestingLevel++; // Increment nesting level
1001 token_value = make_string_token( "@{" );
1003 }
1004
1005 if( current == U'$' && next == U'{' )
1006 {
1007 advance_position( 2 );
1008 m_context = TOKENIZER_CONTEXT::EXPRESSION; // Switch to expression context
1009 m_braceNestingLevel++; // Increment nesting level
1010 token_value = make_string_token( "${" );
1012 }
1013
1014 // Handle closing brace specially to manage context correctly
1015 if( current == U'}' )
1016 {
1018 m_braceNestingLevel--; // Decrement nesting level
1019
1020 if( m_braceNestingLevel <= 0 )
1021 {
1022 m_braceNestingLevel = 0; // Clamp to zero
1023 m_context = TOKENIZER_CONTEXT::TEXT; // Switch back to text context only when fully unnested
1024 }
1025
1026 token_value = make_string_token( "}" );
1028 }
1029
1030 // Multi-character comparison operators
1031 if( current == U'<' && next == U'=' )
1032 {
1033 advance_position( 2 );
1034 token_value = make_string_token( "<=" );
1035 return TEXT_EVAL_TOKEN::LE;
1036 }
1037 if( current == U'>' && next == U'=' )
1038 {
1039 advance_position( 2 );
1040 token_value = make_string_token( ">=" );
1041 return TEXT_EVAL_TOKEN::GE;
1042 }
1043 if( current == U'=' && next == U'=' )
1044 {
1045 advance_position( 2 );
1046 token_value = make_string_token( "==" );
1047 return TEXT_EVAL_TOKEN::EQ;
1048 }
1049 if( current == U'!' && next == U'=' )
1050 {
1051 advance_position( 2 );
1052 token_value = make_string_token( "!=" );
1053 return TEXT_EVAL_TOKEN::NE;
1054 }
1055
1056 // Single character tokens using structured binding
1057 // Single character tokens (only in expression context)
1059 {
1060 static constexpr std::array<std::pair<char32_t, TEXT_EVAL_TOKEN>, 11> single_char_tokens{
1061 { { U'(', TEXT_EVAL_TOKEN::LPAREN },
1062 { U')', TEXT_EVAL_TOKEN::RPAREN },
1063 { U'+', TEXT_EVAL_TOKEN::PLUS },
1064 { U'-', TEXT_EVAL_TOKEN::MINUS },
1065 { U'*', TEXT_EVAL_TOKEN::MULTIPLY },
1066 { U'/', TEXT_EVAL_TOKEN::DIVIDE },
1067 { U'%', TEXT_EVAL_TOKEN::MODULO },
1068 { U'^', TEXT_EVAL_TOKEN::POWER },
1069 { U',', TEXT_EVAL_TOKEN::COMMA },
1070 { U'<', TEXT_EVAL_TOKEN::LT },
1071 { U'>', TEXT_EVAL_TOKEN::GT } }
1072 };
1073
1074 if( auto it = std::ranges::find_if( single_char_tokens,
1075 [current]( const auto& pair )
1076 {
1077 return pair.first == current;
1078 } );
1079 it != single_char_tokens.end() )
1080 {
1082 token_value = make_string_token( utf8_utils::UTF8_CONVERTER::to_utf8( std::u32string{ current } ) );
1083 return it->second;
1084 }
1085 }
1086
1087 // Complex tokens
1088 if( current == U'"' || current == U'\'' )
1089 {
1090 token_value = parse_string_literal( current );
1092 }
1093
1094 if( CLASSIFIER::is_digit( current ) || ( current == U'.' && CLASSIFIER::is_digit( next ) ) )
1095 {
1096 token_value = parse_number();
1098 }
1099
1100 // Context-aware handling of alphabetic content
1101 if( CLASSIFIER::is_alpha( current ) || current == U'_' )
1102 {
1104 {
1105 // In expression context, alphabetic content is an identifier
1106 token_value = parse_identifier();
1108 }
1109 else
1110 {
1111 // In text context, alphabetic content is part of regular text
1112 token_value = parse_text_content();
1113 return TEXT_EVAL_TOKEN::TEXT;
1114 }
1115 }
1116
1117 // Default to text content
1118 token_value = parse_text_content();
1119 return token_value.text[0] == U'\0' ? TEXT_EVAL_TOKEN::ENDS : TEXT_EVAL_TOKEN::TEXT;
1120 }
1121
1122 bool has_more_tokens() const noexcept { return m_pos < m_text.size(); }
1123 constexpr size_t get_line() const noexcept { return m_line; }
1124 constexpr size_t get_column() const noexcept { return m_column; }
1125
1126private:
1128 {
1129 TEXT, // Regular text content - alphabetic should be TEXT tokens
1130 EXPRESSION // Inside @{...} or ${...} - alphabetic should be IDENTIFIER tokens
1131 };
1132
1133 std::u32string m_text;
1134 size_t m_pos{ 0 };
1135 size_t m_line{ 1 };
1136 size_t m_column{ 1 };
1138 int m_braceNestingLevel{ 0 }; // Track nesting level of expressions
1140 EDA_UNITS m_defaultUnits{ EDA_UNITS::MM }; // Add default units for conversion
1141};
1142
1143
1144EXPRESSION_EVALUATOR::EXPRESSION_EVALUATOR( bool aClearVariablesOnEvaluate ) :
1145 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1146 m_useCustomCallback( false ),
1147 m_defaultUnits( EDA_UNITS::MM ) // Default to millimeters
1148{
1149 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1150}
1151
1152
1153EXPRESSION_EVALUATOR::EXPRESSION_EVALUATOR( VAR_CALLBACK aVariableCallback, bool aClearVariablesOnEvaluate ) :
1154 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1155 m_customCallback( std::move( aVariableCallback ) ),
1156 m_useCustomCallback( true ),
1157 m_defaultUnits( EDA_UNITS::MM ) // Default to millimeters
1158{
1159 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1160}
1161
1162
1163EXPRESSION_EVALUATOR::EXPRESSION_EVALUATOR( EDA_UNITS aUnits, bool aClearVariablesOnEvaluate ) :
1164 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1165 m_useCustomCallback( false ),
1166 m_defaultUnits( aUnits )
1167{
1168 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1169}
1170
1171
1173 bool aClearVariablesOnEvaluate ) :
1174 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1175 m_customCallback( std::move( aVariableCallback ) ),
1176 m_useCustomCallback( true ),
1177 m_defaultUnits( aUnits )
1178{
1179 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1180}
1181
1182
1184
1185
1187 m_variables( aOther.m_variables ),
1192{
1193 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1194
1195 if( aOther.m_lastErrors )
1196 {
1197 // Copy error state
1198 for( const auto& error : aOther.m_lastErrors->GetErrors() )
1199 m_lastErrors->AddError( error );
1200 }
1201}
1202
1203
1205{
1206 if( this != &aOther )
1207 {
1208 m_variables = aOther.m_variables;
1213
1214 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1215 if( aOther.m_lastErrors )
1216 {
1217 for( const auto& error : aOther.m_lastErrors->GetErrors() )
1218 m_lastErrors->AddError( error );
1219 }
1220 }
1221 return *this;
1222}
1223
1224
1226 m_variables( std::move( aOther.m_variables ) ),
1227 m_lastErrors( std::move( aOther.m_lastErrors ) ),
1228 m_clearVariablesOnEvaluate( aOther.m_clearVariablesOnEvaluate ),
1229 m_customCallback( std::move( aOther.m_customCallback ) ),
1230 m_useCustomCallback( aOther.m_useCustomCallback ),
1231 m_defaultUnits( aOther.m_defaultUnits )
1232{
1233}
1234
1235
1237{
1238 if( this != &aOther )
1239 {
1240 m_variables = std::move( aOther.m_variables );
1241 m_lastErrors = std::move( aOther.m_lastErrors );
1242 m_clearVariablesOnEvaluate = aOther.m_clearVariablesOnEvaluate;
1243 m_customCallback = std::move( aOther.m_customCallback );
1244 m_useCustomCallback = aOther.m_useCustomCallback;
1245 m_defaultUnits = aOther.m_defaultUnits;
1246 }
1247
1248 return *this;
1249}
1250
1251
1253{
1254 m_customCallback = std::move( aCallback );
1255 m_useCustomCallback = true;
1256}
1257
1258
1264
1265
1270
1271
1273{
1274 m_defaultUnits = aUnits;
1275}
1276
1277
1282
1283
1284void EXPRESSION_EVALUATOR::SetVariable( const wxString& aName, double aValue )
1285{
1286 std::string name = wxStringToStdString( aName );
1287 m_variables[name] = calc_parser::VALUE{ aValue };
1288}
1289
1290
1291void EXPRESSION_EVALUATOR::SetVariable( const wxString& aName, const wxString& aValue )
1292{
1293 std::string name = wxStringToStdString( aName );
1294 std::string value = wxStringToStdString( aValue );
1296}
1297
1298
1299void EXPRESSION_EVALUATOR::SetVariable( const std::string& aName, const std::string& aValue )
1300{
1301 m_variables[aName] = calc_parser::VALUE{ aValue };
1302}
1303
1304
1305bool EXPRESSION_EVALUATOR::RemoveVariable( const wxString& aName )
1306{
1307 std::string name = wxStringToStdString( aName );
1308 return m_variables.erase( name ) > 0;
1309}
1310
1311
1313{
1314 m_variables.clear();
1315}
1316
1317
1318bool EXPRESSION_EVALUATOR::HasVariable( const wxString& aName ) const
1319{
1320 std::string name = wxStringToStdString( aName );
1321 return m_variables.find( name ) != m_variables.end();
1322}
1323
1324
1325wxString EXPRESSION_EVALUATOR::GetVariable( const wxString& aName ) const
1326{
1327 std::string name = wxStringToStdString( aName );
1328 auto it = m_variables.find( name );
1329
1330 if( it != m_variables.end() )
1331 {
1332 if( std::holds_alternative<double>( it->second ) )
1333 {
1334 double val = std::get<double>( it->second );
1335
1336 // Smart formatting - whole numbers don't need decimal places
1337 if( val == std::floor( val ) && std::abs( val ) < 1e15 )
1338 return wxString::Format( "%.0f", val );
1339 // Otherwise match highest precision from EDA_UNIT_UTILS::UI::StringFromValue()
1340 else
1341 return wxString::Format( "%.10g", val );
1342 }
1343 else
1344 {
1345 return stdStringToWxString( std::get<std::string>( it->second ) );
1346 }
1347 }
1348
1349 return wxString{};
1350}
1351
1352
1353std::vector<wxString> EXPRESSION_EVALUATOR::GetVariableNames() const
1354{
1355 std::vector<wxString> names;
1356 names.reserve( m_variables.size() );
1357
1358 for( const auto& [name, value] : m_variables )
1359 names.push_back( stdStringToWxString( name ) );
1360
1361 return names;
1362}
1363
1364
1365void EXPRESSION_EVALUATOR::SetVariables( const std::unordered_map<wxString, double>& aVariables )
1366{
1367 for( const auto& [name, value] : aVariables )
1368 SetVariable( name, value );
1369}
1370
1371
1372void EXPRESSION_EVALUATOR::SetVariables( const std::unordered_map<wxString, wxString>& aVariables )
1373{
1374 for( const auto& [name, value] : aVariables )
1375 SetVariable( name, value );
1376}
1377
1378
1379wxString EXPRESSION_EVALUATOR::Evaluate( const wxString& aInput )
1380{
1381 std::unordered_map<wxString, double> emptyNumVars;
1382 std::unordered_map<wxString, wxString> emptyStringVars;
1383 return Evaluate( aInput, emptyNumVars, emptyStringVars );
1384}
1385
1386
1387wxString EXPRESSION_EVALUATOR::Evaluate( const wxString& aInput,
1388 const std::unordered_map<wxString, double>& aTempVariables )
1389{
1390 std::unordered_map<wxString, wxString> emptyStringVars;
1391 return Evaluate( aInput, aTempVariables, emptyStringVars );
1392}
1393
1394
1395wxString EXPRESSION_EVALUATOR::Evaluate( const wxString& aInput,
1396 const std::unordered_map<wxString, double>& aTempNumericVars,
1397 const std::unordered_map<wxString, wxString>& aTempStringVars )
1398{
1399 // Clear previous errors
1400 ClearErrors();
1401
1402 // Expand ${variable} patterns that are OUTSIDE of @{} expressions
1403 wxString processedInput = expandVariablesOutsideExpressions( aInput, aTempNumericVars, aTempStringVars );
1404
1405 // Convert processed input to std::string
1406 std::string input = wxStringToStdString( processedInput ); // Create combined callback for all variable sources
1407 VAR_CALLBACK combinedCallback = createCombinedCallback( &aTempNumericVars, &aTempStringVars );
1408
1409 // Evaluate using parser
1410 auto [result, hadErrors] = evaluateWithParser( input, combinedCallback );
1411
1412 // Update error state if evaluation had errors
1413 if( hadErrors && !m_lastErrors )
1414 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1415
1416 if( hadErrors )
1417 m_lastErrors->AddError( "Evaluation failed" );
1418
1419 // Clear variables if requested
1422
1423 // Convert result back to wxString
1424 return stdStringToWxString( result );
1425}
1426
1427
1429{
1430 return m_lastErrors && m_lastErrors->HasErrors();
1431}
1432
1433
1435{
1436 if( !m_lastErrors )
1437 return wxString{};
1438
1439 return stdStringToWxString( m_lastErrors->GetAllMessages() );
1440}
1441
1442
1444{
1445 if( !m_lastErrors )
1446 return 0;
1447
1448 return m_lastErrors->GetErrors().size();
1449}
1450
1451
1452std::vector<wxString> EXPRESSION_EVALUATOR::GetErrors() const
1453{
1454 std::vector<wxString> result;
1455
1456 if( m_lastErrors )
1457 {
1458 const std::vector<std::string>& errors = m_lastErrors->GetErrors();
1459 result.reserve( errors.size() );
1460
1461 for( const std::string& error : errors )
1462 result.push_back( stdStringToWxString( error ) );
1463 }
1464
1465 return result;
1466}
1467
1468
1470{
1471 if( m_lastErrors )
1472 m_lastErrors->Clear();
1473}
1474
1475
1480
1481
1486
1487
1488bool EXPRESSION_EVALUATOR::TestExpression( const wxString& aExpression )
1489{
1490 // Create a test input with the expression wrapped in @{}
1491 wxString testInput = "@{" + aExpression + "}";
1492
1493 // Create a minimal callback that returns errors for all variables
1494 auto testCallback =
1495 []( const std::string& aVarName ) -> calc_parser::RESULT<calc_parser::VALUE>
1496 {
1497 return calc_parser::MakeError<calc_parser::VALUE>( "Test mode - no variables available" );
1498 };
1499
1500 // Try to parse it
1501 m_lastErrors->Clear();
1502 std::string input = wxStringToStdString( testInput );
1503 auto [result, hadErrors] = evaluateWithParser( input, testCallback );
1504
1505 // Check if there were parsing errors (ignore evaluation errors for undefined variables)
1506 if( m_lastErrors )
1507 {
1508 // Filter out "Test mode - no variables available" errors, look for syntax errors
1509 for( const std::string& error : m_lastErrors->GetErrors() )
1510 {
1511 if( error.find( "Syntax error" ) != std::string::npos
1512 || error.find( "Parser failed" ) != std::string::npos )
1513 {
1514 return false; // Found syntax error
1515 }
1516 }
1517 }
1518
1519 return true; // No syntax errors found
1520}
1521
1522
1523size_t EXPRESSION_EVALUATOR::CountExpressions( const wxString& aInput ) const
1524{
1525 size_t count = 0;
1526 size_t pos = 0;
1527
1528 while( ( pos = aInput.find( "@{", pos ) ) != wxString::npos )
1529 {
1530 count++;
1531 pos += 2; // Move past "@{"
1532 }
1533
1534 return count;
1535}
1536
1537
1538std::vector<wxString> EXPRESSION_EVALUATOR::ExtractExpressions( const wxString& aInput ) const
1539{
1540 std::vector<wxString> expressions;
1541 size_t pos = 0;
1542
1543 while( ( pos = aInput.find( "@{", pos ) ) != wxString::npos )
1544 {
1545 size_t start = pos + 2; // Skip "@{"
1546 size_t end = aInput.find( "}", start );
1547
1548 if( end != wxString::npos )
1549 {
1550 expressions.push_back( aInput.substr( start, end - start ) );
1551 pos = end + 1;
1552 }
1553 else
1554 {
1555 break; // No closing brace found
1556 }
1557 }
1558
1559 return expressions;
1560}
1561
1562
1563std::string EXPRESSION_EVALUATOR::wxStringToStdString( const wxString& aWxStr ) const
1564{
1565 return aWxStr.ToStdString( wxConvUTF8 );
1566}
1567
1568
1569wxString EXPRESSION_EVALUATOR::stdStringToWxString( const std::string& aStdStr ) const
1570{
1571 return wxString( aStdStr.c_str(), wxConvUTF8 );
1572}
1573
1574
1576 const wxString& aInput,
1577 const std::unordered_map<wxString, double>& aTempNumericVars,
1578 const std::unordered_map<wxString, wxString>& aTempStringVars ) const
1579{
1580 wxString result = aInput;
1581 size_t pos = 0;
1582
1583 // Track positions of @{} expressions to avoid substituting inside them
1584 std::vector<std::pair<size_t, size_t>> expressionRanges;
1585
1586 // Find all @{} expression ranges
1587 while( ( pos = result.find( "@{", pos ) ) != std::string::npos )
1588 {
1589 size_t start = pos;
1590 size_t braceCount = 1;
1591 size_t searchPos = start + 2; // Skip "@{"
1592
1593 // Find matching closing brace
1594 while( searchPos < result.length() && braceCount > 0 )
1595 {
1596 if( result[searchPos] == '{' )
1597 braceCount++;
1598 else if( result[searchPos] == '}' )
1599 braceCount--;
1600 searchPos++;
1601 }
1602
1603 if( braceCount == 0 )
1604 expressionRanges.emplace_back( start, searchPos ); // searchPos is after '}'
1605
1606 pos = searchPos;
1607 }
1608
1609 // Now find and replace ${variable} patterns that are NOT inside @{} expressions
1610 pos = 0;
1611
1612 while( ( pos = result.find( "${", pos ) ) != std::string::npos )
1613 {
1614 // Check if this ${} is inside any @{} expression
1615 bool insideExpression = false;
1616
1617 for( const auto& range : expressionRanges )
1618 {
1619 if( pos >= range.first && pos < range.second )
1620 {
1621 insideExpression = true;
1622 break;
1623 }
1624 }
1625
1626 if( insideExpression )
1627 {
1628 // Special case: if this variable is immediately followed by unit text,
1629 // we should expand it to allow proper unit parsing
1630 size_t closePos = result.find( "}", pos + 2 );
1631
1632 if( closePos != std::string::npos )
1633 {
1634 // Check what comes after the closing brace
1635 size_t afterBrace = closePos + 1;
1636 bool followedByUnit = false;
1637
1638 if( afterBrace < result.length() )
1639 {
1640 // Check if followed by any supported unit strings using centralized registry
1642
1643 for( const auto& unit : units )
1644 {
1645 if( afterBrace + unit.length() <= result.length()
1646 && result.substr( afterBrace, unit.length() ) == unit )
1647 {
1648 followedByUnit = true;
1649 break;
1650 }
1651 }
1652 }
1653
1654 if( !followedByUnit )
1655 {
1656 pos += 2; // Skip this ${} since it's inside an expression and not followed by units
1657 continue;
1658 }
1659 // If followed by units, continue with variable expansion below
1660 }
1661 else
1662 {
1663 pos += 2; // Invalid pattern, skip
1664 continue;
1665 }
1666 }
1667
1668 // Find the closing brace
1669 size_t closePos = result.find( "}", pos + 2 );
1670
1671 if( closePos == std::string::npos )
1672 {
1673 pos += 2; // Invalid ${} pattern, skip
1674 continue;
1675 }
1676
1677 // Extract variable name
1678 wxString varName = result.substr( pos + 2, closePos - pos - 2 );
1679 wxString replacement;
1680 bool found = false;
1681
1682 // Check temporary string variables first
1683 auto stringIt = aTempStringVars.find( varName );
1684
1685 if( stringIt != aTempStringVars.end() )
1686 {
1687 replacement = stringIt->second;
1688 found = true;
1689 }
1690 else
1691 {
1692 // Check temporary numeric variables
1693 auto numIt = aTempNumericVars.find( varName );
1694
1695 if( numIt != aTempNumericVars.end() )
1696 {
1697 replacement = wxString::FromDouble( numIt->second );
1698 found = true;
1699 }
1700 else
1701 {
1702 // Check instance variables
1703 std::string stdVarName = wxStringToStdString( varName );
1704 auto instIt = m_variables.find( stdVarName );
1705
1706 if( instIt != m_variables.end() )
1707 {
1708 const calc_parser::VALUE& value = instIt->second;
1709
1710 if( std::holds_alternative<std::string>( value ) )
1711 {
1712 replacement = stdStringToWxString( std::get<std::string>( value ) );
1713 found = true;
1714 }
1715 else if( std::holds_alternative<double>( value ) )
1716 {
1717 replacement = wxString::FromDouble( std::get<double>( value ) );
1718 found = true;
1719 }
1720 }
1721 }
1722 }
1723
1724 if( found )
1725 {
1726 // Replace ${variable} with its value
1727 result.replace( pos, closePos - pos + 1, replacement );
1728 pos += replacement.length();
1729 }
1730 else
1731 {
1732 // Variable not found, record error but leave ${variable} unchanged
1733 if( !m_lastErrors )
1734 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1735
1736 m_lastErrors->AddError( fmt::format( "Undefined variable: {}", wxStringToStdString( varName ) ) );
1737 pos = closePos + 1;
1738 }
1739 }
1740
1741 return result;
1742}
1743
1744
1746EXPRESSION_EVALUATOR::createCombinedCallback( const std::unordered_map<wxString, double>* aTempNumericVars,
1747 const std::unordered_map<wxString, wxString>* aTempStringVars ) const
1748{
1749 return [this, aTempNumericVars, aTempStringVars]
1750 ( const std::string& aVarName ) -> calc_parser::RESULT<calc_parser::VALUE>
1751 {
1752 // Priority 1: Custom callback (if set)
1754 {
1755 auto customResult = m_customCallback( aVarName );
1756
1757 if( customResult.HasValue() )
1758 return customResult;
1759
1760 // If custom callback returned an error, continue to fallback options
1761 // unless the error indicates a definitive "not found" vs "lookup failed"
1762 // For simplicity, we'll always try fallbacks
1763 }
1764
1765 // Priority 2: Temporary string variables
1766 if( aTempStringVars )
1767 {
1768 wxString wxVarName = stdStringToWxString( aVarName );
1769
1770 if( auto it = aTempStringVars->find( wxVarName ); it != aTempStringVars->end() )
1771 {
1772 std::string stdValue = wxStringToStdString( it->second );
1774 }
1775 }
1776
1777 // Priority 3: Temporary numeric variables
1778 if( aTempNumericVars )
1779 {
1780 wxString wxVarName = stdStringToWxString( aVarName );
1781
1782 if( auto it = aTempNumericVars->find( wxVarName ); it != aTempNumericVars->end() )
1783 return calc_parser::MakeValue<calc_parser::VALUE>( it->second );
1784 }
1785
1786 // Priority 4: Stored variables
1787 if( auto it = m_variables.find( aVarName ); it != m_variables.end() )
1788 {
1789 return calc_parser::MakeValue<calc_parser::VALUE>( it->second );
1790 }
1791
1792 // Priority 5: Use KiCad's ExpandTextVars for system/project variables
1793 try
1794 {
1795 wxString varName = stdStringToWxString( aVarName );
1796 wxString testString = wxString::Format( "${%s}", varName );
1797
1798 // Create a resolver that will return true if the variable was found
1799 bool wasResolved = false;
1800 std::function<bool( wxString* )> resolver =
1801 [&wasResolved]( wxString* token ) -> bool
1802 {
1803 // If we get here, ExpandTextVars found the variable and wants to resolve it
1804 // For our purposes, we just want to know if it exists, so return false
1805 // to keep the original ${varname} format, and set our flag
1806 wasResolved = true;
1807 return false; // Don't replace, just detect
1808 };
1809
1810 wxString expandedResult = ExpandTextVars( testString, &resolver, INTERNAL );
1811
1812 if( wasResolved )
1813 {
1814 // Variable exists in KiCad's system, now get its actual value
1815 std::function<bool( wxString* )> valueResolver =
1816 []( wxString* token ) -> bool
1817 {
1818 // Let ExpandTextVars resolve this normally
1819 // We'll get the resolved value in token
1820 return false; // Use default resolution
1821 };
1822
1823 wxString resolvedValue = ExpandTextVars( testString, &valueResolver, INTERNAL );
1824
1825 // Check if it was actually resolved (not still ${varname})
1826 if( resolvedValue != testString )
1827 {
1828 std::string resolvedStd = wxStringToStdString( resolvedValue );
1829
1830 // Try to parse as number first
1831 try
1832 {
1833 double numValue;
1834 auto result = fast_float::from_chars( resolvedStd.data(),
1835 resolvedStd.data() + resolvedStd.size(),
1836 numValue );
1837
1838 if( result.ec != std::errc()
1839 || result.ptr != resolvedStd.data() + resolvedStd.size() )
1840 {
1841 throw std::invalid_argument( fmt::format( "Cannot convert '{}' to number",
1842 resolvedStd ) );
1843 }
1844
1846 }
1847 catch( ... )
1848 {
1849 // Not a number, return as string
1850 return calc_parser::MakeValue<calc_parser::VALUE>( resolvedStd );
1851 }
1852 }
1853 }
1854 }
1855 catch( const std::exception& )
1856 {
1857 // ExpandTextVars failed, continue to error
1858 }
1859
1860 // Priority 6: If custom callback was tried and failed, return its error
1862 {
1863 return m_customCallback( aVarName ); // Return the original error
1864 }
1865
1866 // No variable found anywhere
1867 return calc_parser::MakeError<calc_parser::VALUE>( fmt::format( "Undefined variable: {}", aVarName ) );
1868 };
1869}
1870
1871
1872std::pair<std::string, bool> EXPRESSION_EVALUATOR::evaluateWithParser( const std::string& aInput,
1873 VAR_CALLBACK aVariableCallback )
1874{
1875 try
1876 {
1877 // Try partial error recovery first
1878 auto [partialResult, partialHadErrors] = evaluateWithPartialErrorRecovery( aInput, aVariableCallback );
1879
1880 // If partial recovery made any progress (result differs from input), use it
1881 if( partialResult != aInput )
1882 {
1883 // Partial recovery made progress - always report errors collected during partial recovery
1884 return { std::move( partialResult ), partialHadErrors };
1885 }
1886
1887 // If no progress was made, try original full parsing approach as fallback
1888 return evaluateWithFullParser( aInput, std::move( aVariableCallback ) );
1889 }
1890 catch( const std::bad_alloc& )
1891 {
1892 if( m_lastErrors )
1893 m_lastErrors->AddError( "Out of memory" );
1894
1895 return { aInput, true };
1896 }
1897 catch( const std::exception& e )
1898 {
1899 if( m_lastErrors )
1900 m_lastErrors->AddError( fmt::format( "Exception: {}", e.what() ) );
1901
1902 return { aInput, true };
1903 }
1904}
1905
1906
1907std::pair<std::string, bool>
1908EXPRESSION_EVALUATOR::evaluateWithPartialErrorRecovery( const std::string& aInput, VAR_CALLBACK aVariableCallback )
1909{
1910 std::string result = aInput;
1911 bool hadAnyErrors = false;
1912 size_t pos = 0;
1913
1914 // Process expressions from right to left to avoid position shifts
1915 std::vector<std::pair<size_t, size_t>> expressionRanges;
1916
1917 // Find all expression ranges
1918 while( ( pos = result.find( "@{", pos ) ) != std::string::npos )
1919 {
1920 size_t start = pos;
1921 size_t exprStart = pos + 2; // Skip "@{"
1922 size_t braceCount = 1;
1923 size_t searchPos = exprStart;
1924
1925 // Find matching closing brace, handling nested braces
1926 while( searchPos < result.length() && braceCount > 0 )
1927 {
1928 if( result[searchPos] == '{' )
1929 braceCount++;
1930 else if( result[searchPos] == '}' )
1931 braceCount--;
1932
1933 searchPos++;
1934 }
1935
1936 if( braceCount == 0 )
1937 {
1938 size_t end = searchPos; // Position after the '}'
1939 expressionRanges.emplace_back( start, end );
1940 pos = end;
1941 }
1942 else
1943 {
1944 pos = exprStart; // Skip this malformed expression
1945 }
1946 }
1947
1948 // Process expressions from right to left to avoid position shifts
1949 for( auto it = expressionRanges.rbegin(); it != expressionRanges.rend(); ++it )
1950 {
1951 auto [start, end] = *it;
1952 std::string fullExpr = result.substr( start, end - start );
1953 std::string innerExpr = result.substr( start + 2, end - start - 3 ); // Remove @{ and }
1954
1955 // Try to evaluate this single expression
1956 try
1957 {
1958 // Create a simple expression for evaluation
1959 std::string testExpr = "@{" + innerExpr + "}";
1960
1961 // Create a temporary error collector to capture errors for this specific expression
1962 auto tempErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1963 auto oldErrors = std::move( m_lastErrors );
1964 m_lastErrors = std::move( tempErrors );
1965
1966 // Use the full parser for this single expression
1967 auto [evalResult, evalHadErrors] = evaluateWithFullParser( testExpr, aVariableCallback );
1968
1969 if( !evalHadErrors )
1970 {
1971 // Successful evaluation, replace in result
1972 result.replace( start, end - start, evalResult );
1973 }
1974 else
1975 {
1976 // Expression failed - add a specific error for this expression
1977 hadAnyErrors = true;
1978
1979 // Restore main error collector and add error
1980 if( !oldErrors )
1981 oldErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1982
1983 oldErrors->AddError( fmt::format( "Failed to evaluate expression: {}", fullExpr ) );
1984 }
1985
1986 // Restore the main error collector
1987 m_lastErrors = std::move( oldErrors );
1988 }
1989 catch( ... )
1990 {
1991 // Report exception as an error for this expression
1992 if( !m_lastErrors )
1993 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1994
1995 m_lastErrors->AddError( fmt::format( "Exception in expression: {}", fullExpr ) );
1996 hadAnyErrors = true;
1997 }
1998 }
1999
2000 return { std::move( result ), hadAnyErrors };
2001}
2002
2003
2004std::pair<std::string, bool> EXPRESSION_EVALUATOR::evaluateWithFullParser( const std::string& aInput,
2005 VAR_CALLBACK aVariableCallback )
2006{
2007 if( aInput.empty() )
2008 {
2009 return { std::string{}, false };
2010 }
2011
2012 calc_parser::DOC* document = nullptr;
2013
2014 // RAII guard for error collector and parser document cleanup
2015 struct RAII_GUARD
2016 {
2017 RAII_GUARD( calc_parser::DOC* aDocument ) :
2018 m_document( aDocument )
2019 {}
2020
2021 ~RAII_GUARD()
2022 {
2024 delete m_document;
2025 }
2026
2027 calc_parser::DOC* m_document;
2028 } guard( document );
2029
2030 try
2031 {
2032 // Clear previous errors
2033 if( m_lastErrors )
2034 m_lastErrors->Clear();
2035
2036 // Set up error collector
2038
2039 // Create tokenizer with default units
2040 KIEVAL_TEXT_TOKENIZER tokenizer{ aInput, m_lastErrors.get(), m_defaultUnits };
2041
2042 // Create parser deleter function
2043 auto parser_deleter =
2044 []( void* p )
2045 {
2046 KI_EVAL::ParseFree( p, free );
2047 };
2048
2049 // Allocate parser with RAII cleanup
2050 std::unique_ptr<void, decltype( parser_deleter )> parser{ KI_EVAL::ParseAlloc( malloc ), parser_deleter };
2051
2052 if( !parser )
2053 {
2054 if( m_lastErrors )
2055 m_lastErrors->AddError( "Failed to allocate parser" );
2056
2057 return { aInput, true };
2058 }
2059
2060 // Parse document
2061 calc_parser::TOKEN_TYPE token_value;
2062 TEXT_EVAL_TOKEN token_type;
2063
2064 do
2065 {
2066 token_type = tokenizer.get_next_token( token_value );
2067
2068 // Send token to parser
2069 KI_EVAL::Parse( parser.get(), (int) token_type, token_value, &document );
2070
2071 // Early exit on errors
2072 if( m_lastErrors && m_lastErrors->HasErrors() )
2073 break;
2074
2075 } while( token_type != TEXT_EVAL_TOKEN::ENDS && tokenizer.has_more_tokens() );
2076
2077 // Finalize parsing
2078 KI_EVAL::Parse( parser.get(), (int) TEXT_EVAL_TOKEN::ENDS, calc_parser::TOKEN_TYPE{}, &document );
2079
2080 // Process document if parsing succeeded
2081 if( document && ( !m_lastErrors || !m_lastErrors->HasErrors() ) )
2082 {
2083 auto [result, had_errors] = calc_parser::DOC_PROCESSOR::Process( *document,
2084 std::move( aVariableCallback ) );
2085
2086 // If processing had any evaluation errors, return original input unchanged
2087 // This preserves the original expression syntax while still reporting errors
2088 if( had_errors )
2089 return { aInput, true };
2090
2091 return { std::move( result ), had_errors };
2092 }
2093
2094 // Cleanup and return original on error
2095 return { aInput, true };
2096 }
2097 catch( const std::bad_alloc& )
2098 {
2099 if( m_lastErrors )
2100 m_lastErrors->AddError( "Out of memory" );
2101
2102 return { aInput, true };
2103 }
2104 catch( const std::exception& e )
2105 {
2106 if( m_lastErrors )
2107 m_lastErrors->AddError( fmt::format( "Exception: {}", e.what() ) );
2108
2109 return { aInput, true };
2110 }
2111}
2112
2113
2115 m_evaluator( aUnits ),
2116 m_lastValid( false )
2117{
2118}
2119
2120
2122
2123
2125{
2126 m_lastInput.clear();
2127 m_lastResult.clear();
2128 m_lastValid = false;
2129 m_evaluator.ClearErrors();
2130}
2131
2132
2134{
2135 m_evaluator.SetDefaultUnits( aUnits );
2136}
2137
2138
2140{
2141 // No-op: EXPRESSION_EVALUATOR handles locale properly internally
2142}
2143
2144
2146{
2147 return m_lastValid;
2148}
2149
2150
2152{
2153 return m_lastResult;
2154}
2155
2156
2157bool NUMERIC_EVALUATOR_COMPAT::Process( const wxString& aString )
2158{
2159 m_lastInput = aString;
2160 m_evaluator.ClearErrors();
2161
2162 // Convert bare variable names to ${variable} syntax for compatibility
2163 // This allows NUMERIC_EVALUATOR-style variable access to work with EXPRESSION_EVALUATOR
2164 wxString processedExpression = aString;
2165
2166 // Get all variable names that are currently defined
2167 std::vector<wxString> varNames = m_evaluator.GetVariableNames();
2168
2169 // Sort variable names by length (longest first) to avoid partial replacements
2170 std::sort( varNames.begin(), varNames.end(),
2171 []( const wxString& a, const wxString& b )
2172 {
2173 return a.length() > b.length();
2174 } );
2175
2176 // Replace bare variable names with ${variable} syntax
2177 for( const wxString& varName : varNames )
2178 {
2179 // Create a regex to match the variable name as a whole word
2180 // This avoids replacing parts of other words
2181 wxString pattern = "\\b" + varName + "\\b";
2182 wxString replacement = "${" + varName + "}";
2183
2184 // Simple string replacement (not regex for now to avoid complexity)
2185 // Look for the variable name surrounded by non-alphanumeric characters
2186 size_t pos = 0;
2187
2188 while( ( pos = processedExpression.find( varName, pos ) ) != wxString::npos )
2189 {
2190 // Check if this is a whole word (not part of another identifier)
2191 bool isWholeWord = true;
2192
2193 // Check character before
2194 if( pos > 0 )
2195 {
2196 wxChar before = processedExpression[pos - 1];
2197
2198 if( wxIsalnum( before ) || before == '_' || before == '$' )
2199 isWholeWord = false;
2200 }
2201
2202 // Check character after
2203 if( isWholeWord && pos + varName.length() < processedExpression.length() )
2204 {
2205 wxChar after = processedExpression[pos + varName.length()];
2206
2207 if( wxIsalnum( after ) || after == '_' )
2208 isWholeWord = false;
2209 }
2210
2211 if( isWholeWord )
2212 {
2213 processedExpression.replace( pos, varName.length(), replacement );
2214 pos += replacement.length();
2215 }
2216 else
2217 {
2218 pos += varName.length();
2219 }
2220 }
2221 }
2222
2223 // Wrap the processed expression in @{...} syntax for EXPRESSION_EVALUATOR
2224 wxString wrappedExpression = "@{" + processedExpression + "}";
2225
2226 m_lastResult = m_evaluator.Evaluate( wrappedExpression );
2227 m_lastValid = !m_evaluator.HasErrors();
2228
2229 // Additional check: if the result is exactly the wrapped expression,
2230 // it means the expression wasn't evaluated (likely due to errors)
2231 if( m_lastResult == wrappedExpression )
2232 {
2233 m_lastValid = false;
2234 m_lastResult = "NaN";
2235 }
2236
2237 // If there were errors, set result to "NaN" to match NUMERIC_EVALUATOR behavior
2238 if( !m_lastValid )
2239 {
2240 m_lastResult = "NaN";
2241 return false;
2242 }
2243
2244 return true;
2245}
2246
2247
2249{
2250 return m_lastInput;
2251}
2252
2253
2254void NUMERIC_EVALUATOR_COMPAT::SetVar( const wxString& aString, double aValue )
2255{
2256 m_evaluator.SetVariable( aString, aValue );
2257}
2258
2259
2260double NUMERIC_EVALUATOR_COMPAT::GetVar( const wxString& aString )
2261{
2262 if( !m_evaluator.HasVariable( aString ) )
2263 return 0.0;
2264
2265 wxString value = m_evaluator.GetVariable( aString );
2266
2267 // Try to convert to double
2268 double result = 0.0;
2269
2270 if( !value.ToDouble( &result ) )
2271 return 0.0;
2272
2273 return result;
2274}
2275
2276
2277void NUMERIC_EVALUATOR_COMPAT::RemoveVar( const wxString& aString )
2278{
2279 m_evaluator.RemoveVariable( aString );
2280}
2281
2282
2284{
2285 m_evaluator.ClearVariables();
2286}
const char * name
EDA_UNITS GetDefaultUnits() const
Get the current default units.
bool HasVariableCallback() const
Check if a custom variable callback is set.
wxString Evaluate(const wxString &aInput)
Main evaluation function - processes input string and evaluates all '@{}' expressions.
bool TestExpression(const wxString &aExpression)
Test if an expression can be parsed without evaluating it.
bool RemoveVariable(const wxString &aName)
Remove a variable from the evaluator.
bool HasErrors() const
Check if the last evaluation had errors.
wxString GetErrorSummary() const
Get detailed error information from the last evaluation.
void SetDefaultUnits(EDA_UNITS aUnits)
Set the default units for expressions.
bool GetClearVariablesOnEvaluate() const
Check if automatic variable clearing is enabled.
void SetVariables(const std::unordered_map< wxString, double > &aVariables)
Set multiple variables at once from a map.
std::pair< std::string, bool > evaluateWithFullParser(const std::string &aInput, VAR_CALLBACK aVariableCallback)
Full parser evaluation (original behavior) - fails completely on any error.
std::string wxStringToStdString(const wxString &aWxStr) const
Convert wxString to std::string using UTF-8 encoding.
std::unordered_map< std::string, calc_parser::VALUE > m_variables
std::pair< std::string, bool > evaluateWithParser(const std::string &aInput, VAR_CALLBACK aVariableCallback)
Parse and evaluate the input string using the expression parser.
size_t GetErrorCount() const
Get count of errors from the last evaluation.
std::vector< wxString > ExtractExpressions(const wxString &aInput) const
Extract all '@{}' expressions from input without evaluating.
void ClearErrors()
Clear any stored error information.
std::unique_ptr< calc_parser::ERROR_COLLECTOR > m_lastErrors
wxString stdStringToWxString(const std::string &aStdStr) const
Convert std::string to wxString using UTF-8 encoding.
std::vector< wxString > GetErrors() const
Get individual error messages from the last evaluation.
void SetVariableCallback(VAR_CALLBACK aCallback)
Set a custom variable resolver callback.
void ClearVariableCallback()
Clear the custom variable resolver callback.
void ClearVariables()
Clear all stored variables.
bool HasVariable(const wxString &aName) const
Check if a variable exists in stored variables.
std::vector< wxString > GetVariableNames() const
Get all stored variable names currently defined.
~EXPRESSION_EVALUATOR()
Destructor.
EXPRESSION_EVALUATOR & operator=(const EXPRESSION_EVALUATOR &aOther)
VAR_CALLBACK createCombinedCallback(const std::unordered_map< wxString, double > *aTempNumericVars=nullptr, const std::unordered_map< wxString, wxString > *aTempStringVars=nullptr) const
Create a callback function that combines all variable sources.
std::function< calc_parser::RESULT< calc_parser::VALUE >(const std::string &aVariableName)> VAR_CALLBACK
void SetVariable(const wxString &aName, double aValue)
Set a numeric variable for use in expressions.
void SetClearVariablesOnEvaluate(bool aEnable)
Enable or disable automatic variable clearing after evaluation.
wxString GetVariable(const wxString &aName) const
Get the current value of a stored variable.
std::pair< std::string, bool > evaluateWithPartialErrorRecovery(const std::string &aInput, VAR_CALLBACK aVariableCallback)
Parse and evaluate with partial error recovery - malformed expressions left unchanged.
size_t CountExpressions(const wxString &aInput) const
Count the number of '@{}' expressions in input string.
wxString expandVariablesOutsideExpressions(const wxString &aInput, const std::unordered_map< wxString, double > &aTempNumericVars, const std::unordered_map< wxString, wxString > &aTempStringVars) const
Expand ${variable} patterns that are outside '@{}' expressions.
EXPRESSION_EVALUATOR(bool aClearVariablesOnEvaluate=false)
Construct a new Expression Evaluator in static variable mode.
static calc_parser::TOKEN_TYPE make_string_token(const std::string &value) noexcept
calc_parser::TOKEN_TYPE parse_number()
TEXT_EVAL_TOKEN get_next_token(calc_parser::TOKEN_TYPE &token_value)
void add_error(std::string_view message) const
static constexpr calc_parser::TOKEN_TYPE make_number_token(double value) noexcept
void advance_position(size_t count=1) noexcept
utf8_utils::SI_PREFIX_HANDLER SI_HANDLER
constexpr size_t get_column() const noexcept
void skip_whitespace() noexcept
bool has_more_tokens() const noexcept
calc_parser::ERROR_COLLECTOR * m_errorCollector
char32_t peek_char(size_t offset=1) const noexcept
calc_parser::TOKEN_TYPE parse_string_literal(char32_t quote_char)
KIEVAL_TEXT_TOKENIZER(std::string_view input, calc_parser::ERROR_COLLECTOR *error_collector=nullptr, EDA_UNITS default_units=EDA_UNITS::MM)
utf8_utils::CHARACTER_CLASSIFIER CLASSIFIER
calc_parser::TOKEN_TYPE parse_text_content()
calc_parser::TOKEN_TYPE parse_identifier()
constexpr size_t get_line() const noexcept
char32_t current_char() const noexcept
void LocaleChanged()
Handle locale changes (for decimal separator)
~NUMERIC_EVALUATOR_COMPAT()
Destructor.
void SetDefaultUnits(EDA_UNITS aUnits)
Set default units for evaluation.
bool Process(const wxString &aString)
Process and evaluate an expression.
wxString Result() const
Get the result of the last evaluation.
void RemoveVar(const wxString &aString)
Remove a single variable.
void SetVar(const wxString &aString, double aValue)
Set a variable value.
void ClearVar()
Remove all variables.
EXPRESSION_EVALUATOR m_evaluator
double GetVar(const wxString &aString)
Get a variable value.
void Clear()
Clear parser state but retain variables.
NUMERIC_EVALUATOR_COMPAT(EDA_UNITS aUnits)
Constructor with default units.
bool IsValid() const
Check if the last evaluation was successful.
wxString OriginalText() const
Get the original input text.
static std::pair< std::string, bool > Process(const DOC &aDoc, VAR_CALLBACK aVariableCallback)
Process document using callback for variable resolution.
const std::vector< std::string > & GetErrors() const
static constexpr Unit parseUnit(std::string_view unitStr) noexcept
Parse a unit string and return the corresponding Unit enum.
static double convertToEdaUnits(double value, std::string_view unitStr, EDA_UNITS targetUnits)
Convert a value with unit string to target EDA_UNITS.
static std::vector< std::string > getAllUnitStrings()
Get all unit strings in parsing order (longest first)
static constexpr int sequence_length(std::byte first) noexcept
static std::string to_utf8(std::u32string_view utf32)
static constexpr bool is_ascii(std::byte b) noexcept
static std::u32string to_utf32(std::string_view utf8)
static constexpr bool is_continuation(std::byte b) noexcept
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:94
EDA_UNITS
Definition eda_units.h:44
@ PS_PER_INCH
Definition eda_units.h:55
std::string message
double convertToDefaultUnits(double aValue, const std::string &aUnitStr, EDA_UNITS aDefaultUnits)
UNIT parseUnit(const std::string &aUnitStr)
double getConversionFactor(UNIT aFromUnit, UNIT aToUnit)
UNIT edaUnitsToInternal(EDA_UNITS aUnits)
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
double fromMM(double aMMValue)
double toMM(double aIUValue)
CITER next(CITER it)
Definition ptree.cpp:120
static constexpr bool is_digit(UnicodeCodepoint auto cp) noexcept
static constexpr bool is_alpha(UnicodeCodepoint auto cp) noexcept
static constexpr bool is_whitespace(UnicodeCodepoint auto cp) noexcept
static constexpr bool is_ascii_alpha(UnicodeCodepoint auto cp) noexcept
static constexpr bool is_alnum(UnicodeCodepoint auto cp) noexcept
static constexpr double get_multiplier(UnicodeCodepoint auto cp) noexcept
static constexpr std::array< PREFIX, 18 > prefixes
static constexpr bool is_si_prefix(UnicodeCodepoint auto cp) noexcept
static const long long MM
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
TEXT_EVAL_TOKEN