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 auto prefixes = std::to_array<PREFIX>( { { 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'R', 1.0 }, // RKM unity markers
336 { U'F', 1.0 },
337 { U'k', 1e3 },
338 { U'K', 1e3 },
339 { U'M', 1e6 },
340 { U'G', 1e9 },
341 { U'T', 1e12 },
342 { U'P', 1e15 },
343 { U'E', 1e18 } } );
344
345 static constexpr bool is_si_prefix( UnicodeCodepoint auto cp ) noexcept
346 {
347 return std::ranges::any_of( prefixes,
348 [cp]( const PREFIX& p )
349 {
350 return p.symbol == cp;
351 } );
352 }
353
354 static constexpr double get_multiplier( UnicodeCodepoint auto cp ) noexcept
355 {
356 auto it = std::ranges::find_if( prefixes,
357 [cp]( const PREFIX& p )
358 {
359 return p.symbol == cp;
360 } );
361 return it != prefixes.end() ? it->multiplier : 1.0;
362 }
363};
364} // namespace utf8_utils
365
366
367// Unit conversion utilities for the text evaluator
369{
370
371
372// Internal unit enum matching NUMERIC_EVALUATOR
389
390
391// Convert EDA_UNITS to internal Unit enum
393{
394 switch( aUnits )
395 {
396 case EDA_UNITS::MM: return UNIT::MM;
397 case EDA_UNITS::MILS: return UNIT::Mil;
398 case EDA_UNITS::INCH: return UNIT::Inch;
401 case EDA_UNITS::PS: return UNIT::Picoseconds;
405 case EDA_UNITS::UM: return UNIT::UM;
406 case EDA_UNITS::CM: return UNIT::CM;
407 case EDA_UNITS::UNSCALED: return UNIT::SI;
408 default: return UNIT::MM;
409 }
410}
411
412
413// Parse unit from string using centralized registry
414UNIT parseUnit( const std::string& aUnitStr )
415{
416 auto evalUnit = text_eval_units::UnitRegistry::parseUnit( aUnitStr );
417
418 // Convert text_eval_units::Unit to KIEVAL_UNIT_CONV::Unit
419 switch( evalUnit )
420 {
435 default: return UNIT::Invalid;
436 }
437}
438
439// Get conversion factor from one unit to another (based on numeric_evaluator logic)
440double getConversionFactor( UNIT aFromUnit, UNIT aToUnit )
441{
442 if( aFromUnit == aToUnit )
443 return 1.0;
444
445 // Convert to MM first, then to target unit
446 double toMM = 1.0;
447
448 switch( aFromUnit )
449 {
450 case UNIT::Inch: toMM = 25.4; break;
451 case UNIT::Mil: toMM = 25.4 / 1000.0; break;
452 case UNIT::UM: toMM = 1.0 / 1000.0; break;
453 case UNIT::MM: toMM = 1.0; break;
454 case UNIT::CM: toMM = 10.0; break;
455 default: return 1.0; // No conversion for other units
456 }
457
458 double fromMM = 1.0;
459
460 switch( aToUnit )
461 {
462 case UNIT::Inch: fromMM = 1.0 / 25.4; break;
463 case UNIT::Mil: fromMM = 1000.0 / 25.4; break;
464 case UNIT::UM: fromMM = 1000.0; break;
465 case UNIT::MM: fromMM = 1.0; break;
466 case UNIT::CM: fromMM = 1.0 / 10.0; break;
467 default: return 1.0; // No conversion for other units
468 }
469
470 return toMM * fromMM;
471}
472
473
474// Convert a value with units to the default units using centralized registry
475double convertToDefaultUnits( double aValue, const std::string& aUnitStr, EDA_UNITS aDefaultUnits )
476{
477 return text_eval_units::UnitRegistry::convertToEdaUnits( aValue, aUnitStr, aDefaultUnits );
478}
479} // namespace KIEVAL_UNIT_CONV
480
481
483{
484private:
487
488 char32_t current_char() const noexcept
489 {
490 return m_pos < m_text.size() ? m_text[m_pos] : U'\0';
491 }
492
493 char32_t peek_char( size_t offset = 1 ) const noexcept
494 {
495 size_t peek_pos = m_pos + offset;
496 return peek_pos < m_text.size() ? m_text[peek_pos] : U'\0';
497 }
498
499 bool at_exponent() const noexcept
500 {
501 char32_t c = current_char();
502
503 if( c != U'e' && c != U'E' )
504 return false;
505
506 size_t offset = 1;
507
508 if( peek_char( offset ) == U'+' || peek_char( offset ) == U'-' )
509 ++offset;
510
511 return CLASSIFIER::is_digit( peek_char( offset ) );
512 }
513
514 void advance_position( size_t count = 1 ) noexcept
515 {
516 for( size_t i = 0; i < count && m_pos < m_text.size(); ++i )
517 {
518 if( m_text[m_pos] == U'\n' )
519 {
520 ++m_line;
521 m_column = 1;
522 }
523 else
524 {
525 ++m_column;
526 }
527
528 ++m_pos;
529 }
530 }
531
532 void skip_whitespace() noexcept
533 {
534 while( m_pos < m_text.size() && CLASSIFIER::is_whitespace( current_char() ) )
536 }
537
538 void add_error( std::string_view message ) const
539 {
540 if( m_errorCollector )
541 {
542 auto error_msg = fmt::format( "Line {}, Column {}: {}", m_line, m_column, message );
543 m_errorCollector->AddError( error_msg );
544 }
545 }
546
547 static calc_parser::TOKEN_TYPE make_string_token( const std::string &value ) noexcept
548 {
550 token.isString = true;
551 std::strncpy( token.text, value.c_str(), sizeof( token.text ) - 1 );
552 token.text[sizeof( token.text ) - 1] = '\0';
553 token.dValue = 0.0;
554 return token;
555 }
556
557 static constexpr calc_parser::TOKEN_TYPE make_number_token( double value ) noexcept
558 {
560 token.isString = false;
561 token.dValue = value;
562 return token;
563 }
564
566 {
567 advance_position(); // Skip opening quote
568
569 std::u32string content;
570 content.reserve( 64 ); // Reasonable default
571
572 while( m_pos < m_text.size() && current_char() != quote_char )
573 {
574 char32_t c = current_char();
575
576 if( c == U'\\' && m_pos + 1 < m_text.size() )
577 {
578 char32_t escaped = peek_char();
579 advance_position( 2 );
580
581 switch( escaped )
582 {
583 case U'n':
584 content.push_back( U'\n' );
585 break;
586 case U't':
587 content.push_back( U'\t' );
588 break;
589 case U'r':
590 content.push_back( U'\r' );
591 break;
592 case U'\\':
593 content.push_back( U'\\' );
594 break;
595 case U'"':
596 content.push_back( U'"' );
597 break;
598 case U'\'':
599 content.push_back( U'\'' );
600 break;
601 case U'0':
602 content.push_back( U'\0' );
603 break;
604 case U'x':
605 {
606 // Hexadecimal escape \xHH
607 std::u32string hex;
608
609 for( int i = 0; i < 2 && m_pos < m_text.size(); ++i )
610 {
611 char32_t hex_char = current_char();
612 if( ( hex_char >= U'0' && hex_char <= U'9' )
613 || ( hex_char >= U'A' && hex_char <= U'F' )
614 || ( hex_char >= U'a' && hex_char <= U'f' ) )
615 {
616 hex.push_back( hex_char );
618 }
619 else
620 {
621 break;
622 }
623 }
624
625 if( !hex.empty() )
626 {
627 try
628 {
629 std::string hex_str = utf8_utils::UTF8_CONVERTER::to_utf8( hex );
630 unsigned long value = std::stoul( hex_str, nullptr, 16 );
631
632 if( value <= 0x10FFFF )
633 content.push_back( static_cast<char32_t>( value ) );
634 else
635 content.push_back( U'\uFFFD' );
636 }
637 catch( ... )
638 {
639 content.push_back( U'\uFFFD' );
640 }
641 }
642 else
643 {
644 content.append( U"\\x" );
645 }
646
647 break;
648 }
649 default:
650 content.push_back( U'\\' );
651 content.push_back( escaped );
652 break;
653 }
654 }
655 else if( c == U'\n' )
656 {
657 add_error( "Unterminated string literal" );
658 break;
659 }
660 else
661 {
662 content.push_back( c );
664 }
665 }
666
667 if( m_pos < m_text.size() && current_char() == quote_char )
668 {
669 advance_position(); // Skip closing quote
670 }
671 else
672 {
673 add_error( "Missing closing quote in string literal" );
674 }
675
677 }
678
680 {
681 std::u32string number_text;
682 number_text.reserve( 32 );
683
684 double multiplier = 1.0;
685 bool hasPrefix = false;
686
687 // R and F carry no scale, so only accept them as RKM markers, never ahead of a unit like 1Rin
688 auto canConsumePrefix = [&]( char32_t aPrefix )
689 {
690 if( !SI_HANDLER::is_si_prefix( aPrefix ) )
691 return false;
692
693 if( aPrefix == U'R' || aPrefix == U'F' )
694 return !CLASSIFIER::is_alpha( peek_char() ) && peek_char() != U'_';
695
696 return true;
697 };
698
699 auto consumePrefix = [&]( char32_t aPrefix )
700 {
701 multiplier = SI_HANDLER::get_multiplier( aPrefix );
702 hasPrefix = true;
704 };
705
706 // Parse integer part
707 while( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
708 {
709 number_text.push_back( current_char() );
711 }
712
713 // Handle decimal point, SI prefix, or unit suffix
714 if( m_pos < m_text.size() )
715 {
716 char32_t c = current_char();
717
718 // Only treat comma as decimal separator in text context, not expression context
719 // This prevents comma from interfering with function argument separation
720 if( c == U'.' || ( c == U',' && m_context != TOKENIZER_CONTEXT::EXPRESSION ) )
721 {
722 number_text.push_back( U'.' );
724 }
725 else if( at_exponent() )
726 {
727 // Leave 3E3 for the exponent parser rather than reading E as exa
728 }
730 {
731 // In expression context, check for unit first before SI prefix (unit strings are longer)
732 // Look ahead to see if we have a complete unit string
733 std::u32string potential_unit;
734 size_t temp_pos = m_pos;
735
736 while( temp_pos < m_text.size() )
737 {
738 char32_t unit_char = m_text[temp_pos];
739
740 if( CLASSIFIER::is_alpha( unit_char ) || unit_char == U'"' || unit_char == U'\'' )
741 {
742 potential_unit.push_back( unit_char );
743 temp_pos++;
744 }
745 else
746 {
747 break;
748 }
749 }
750
751 // Check if we have a valid unit
752 if( !potential_unit.empty() )
753 {
754 std::string unit_str = utf8_utils::UTF8_CONVERTER::to_utf8( potential_unit );
755 KIEVAL_UNIT_CONV::UNIT parsed_unit = KIEVAL_UNIT_CONV::parseUnit( unit_str );
756
757 if( parsed_unit != KIEVAL_UNIT_CONV::UNIT::Invalid )
758 {
759 // This is a valid unit - don't treat the first character as SI prefix
760 // The unit parsing will happen later
761 }
762 else if( canConsumePrefix( c ) )
763 {
764 // Not a valid unit, so treat as SI prefix
765 consumePrefix( c );
766 }
767 }
768 else if( canConsumePrefix( c ) )
769 {
770 // No alphabetic characters following, so treat as SI prefix
771 consumePrefix( c );
772 }
773 }
774 else if( canConsumePrefix( c ) )
775 {
776 // In text context, treat as SI prefix
777 consumePrefix( c );
778 }
779
780 // RKM notation (3k3, 4R7) puts the multiplier where the decimal point goes
781 if( hasPrefix && m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
782 number_text.push_back( U'.' );
783 }
784
785 // Parse fractional part
786 while( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
787 {
788 number_text.push_back( current_char() );
790 }
791
792 // Check for scientific notation (e.g., 1e-3, 3.5E6)
793 if( at_exponent() )
794 {
795 // Parse scientific notation exponent
796 number_text.push_back( current_char() ); // Add 'e' or 'E'
798
799 // Optional sign
800 if( m_pos < m_text.size() && ( current_char() == U'+' || current_char() == U'-' ) )
801 {
802 number_text.push_back( current_char() );
804 }
805
806 // Exponent digits (required)
807 if( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
808 {
809 while( m_pos < m_text.size() && CLASSIFIER::is_digit( current_char() ) )
810 {
811 number_text.push_back( current_char() );
813 }
814 }
815 else
816 {
817 // Invalid scientific notation - will fail in conversion
818 add_error( "Invalid scientific notation: missing exponent digits" );
819 }
820 }
821
822 // Check for SI prefix after fractional part (for numbers like 0.3M)
823 if( m_pos < m_text.size() && !hasPrefix )
824 {
825 char32_t c = current_char();
826
828 {
829 // Look ahead to check for unit vs SI prefix
830 std::u32string potential_unit;
831 size_t temp_pos = m_pos;
832
833 while( temp_pos < m_text.size() )
834 {
835 char32_t unit_char = m_text[temp_pos];
836
837 if( CLASSIFIER::is_alpha( unit_char ) || unit_char == U'"' || unit_char == U'\'' )
838 {
839 potential_unit.push_back( unit_char );
840 temp_pos++;
841 }
842 else
843 {
844 break;
845 }
846 }
847
848 if( !potential_unit.empty() )
849 {
850 std::string unit_str = utf8_utils::UTF8_CONVERTER::to_utf8( potential_unit );
851 KIEVAL_UNIT_CONV::UNIT parsed_unit = KIEVAL_UNIT_CONV::parseUnit( unit_str );
852
853 if( parsed_unit == KIEVAL_UNIT_CONV::UNIT::Invalid && canConsumePrefix( c ) )
854 {
855 // Not a valid unit, so treat as SI prefix
856 consumePrefix( c );
857 }
858 }
859 }
860 else if( canConsumePrefix( c ) )
861 {
862 // In text context, treat as SI prefix
863 consumePrefix( c );
864 }
865 }
866
867 // Convert to double safely
868 std::string number_str = utf8_utils::UTF8_CONVERTER::to_utf8( number_text );
869 double value = 0.0;
870
871 try
872 {
873 if( !number_str.empty() && number_str != "." )
874 {
875 auto result = fast_float::from_chars( number_str.data(), number_str.data() + number_str.size(), value );
876
877 if( result.ec != std::errc() || result.ptr != number_str.data() + number_str.size() )
878 throw std::invalid_argument( fmt::format( "Cannot convert '{}' to number", number_str ) );
879
880 value *= multiplier;
881
882 if( !std::isfinite( value ) )
883 {
884 add_error( "Number out of range" );
885 value = 0.0;
886 }
887 }
888 }
889 catch( const std::exception& e )
890 {
891 add_error( fmt::format( "Invalid number format: {}", e.what() ) );
892 value = 0.0;
893 }
894
895 // Look for unit suffix
897 {
898 // Skip any whitespace between number and unit
899 size_t whitespace_start = m_pos;
900
901 while( m_pos < m_text.size() && CLASSIFIER::is_whitespace( current_char() ) )
903
904 // Parse potential unit suffix
905 std::u32string unit_text;
906
907 // Look ahead to parse potential unit (letters, quotes, etc.)
908 while( m_pos < m_text.size() )
909 {
910 char32_t c = current_char();
911
912 // Unit characters: letters, quotes for inches
913 if( CLASSIFIER::is_alpha( c ) || c == U'"' || c == U'\'' )
914 {
915 unit_text.push_back( c );
917 }
918 else
919 {
920 break;
921 }
922 }
923
924 if( !unit_text.empty() )
925 {
926 // Convert unit text to string and try to parse it
927 std::string unit_str = utf8_utils::UTF8_CONVERTER::to_utf8( unit_text );
928 KIEVAL_UNIT_CONV::UNIT parsed_unit = KIEVAL_UNIT_CONV::parseUnit( unit_str );
929
930 if( parsed_unit != KIEVAL_UNIT_CONV::UNIT::Invalid )
931 {
932 // Successfully parsed unit - convert value to default units
933 double converted_value = KIEVAL_UNIT_CONV::convertToDefaultUnits( value, unit_str, m_defaultUnits );
934 value = converted_value;
935 }
936 else
937 {
938 // Not a valid unit - backtrack to before the whitespace
939 m_pos = whitespace_start;
940 }
941 }
942 else
943 {
944 // No unit found - backtrack to before the whitespace
945 m_pos = whitespace_start;
946 }
947 }
948
949 return make_number_token( value );
950 }
951
953 {
954 std::u32string identifier;
955 identifier.reserve( 64 );
956
957 while( m_pos < m_text.size() && ( CLASSIFIER::is_alnum( current_char() ) || current_char() == U'_' ) )
958 {
959 identifier.push_back( current_char() );
961 }
962
964 }
965
967 {
968 std::u32string text;
969 text.reserve( 256 );
970
971 while( m_pos < m_text.size() )
972 {
973 char32_t current = current_char();
974 char32_t next = peek_char();
975
976 // Stop at special sequences
977 if( ( current == U'@' && next == U'{' ) || ( current == U'$' && next == U'{' ) )
978 break;
979
980 text.push_back( current );
982 }
983
985 }
986
987public:
988 explicit KIEVAL_TEXT_TOKENIZER( std::string_view input, calc_parser::ERROR_COLLECTOR* error_collector = nullptr,
989 EDA_UNITS default_units = EDA_UNITS::MM ) :
990 m_errorCollector( error_collector ),
991 m_defaultUnits( default_units )
992 {
994 }
995
997 {
998 token_value = calc_parser::TOKEN_TYPE{};
999
1000 if( m_pos >= m_text.size() )
1001 return TEXT_EVAL_TOKEN::ENDS;
1002
1003 // Only skip whitespace in expression context
1005 {
1007
1008 if( m_pos >= m_text.size() )
1009 return TEXT_EVAL_TOKEN::ENDS;
1010 }
1011
1012 char32_t current = current_char();
1013 char32_t next = peek_char();
1014
1015 // Multi-character tokens that switch to expression context
1016 if( current == U'@' && next == U'{' )
1017 {
1018 advance_position( 2 );
1019 m_context = TOKENIZER_CONTEXT::EXPRESSION; // Switch to expression context
1020 m_braceNestingLevel++; // Increment nesting level
1021 token_value = make_string_token( "@{" );
1023 }
1024
1025 if( current == U'$' && next == U'{' )
1026 {
1027 advance_position( 2 );
1028 m_context = TOKENIZER_CONTEXT::EXPRESSION; // Switch to expression context
1029 m_braceNestingLevel++; // Increment nesting level
1030 token_value = make_string_token( "${" );
1032 }
1033
1034 // Handle closing brace specially to manage context correctly
1035 if( current == U'}' )
1036 {
1038 m_braceNestingLevel--; // Decrement nesting level
1039
1040 if( m_braceNestingLevel <= 0 )
1041 {
1042 m_braceNestingLevel = 0; // Clamp to zero
1043 m_context = TOKENIZER_CONTEXT::TEXT; // Switch back to text context only when fully unnested
1044 }
1045
1046 token_value = make_string_token( "}" );
1048 }
1049
1050 // Multi-character comparison operators
1051 if( current == U'<' && next == U'=' )
1052 {
1053 advance_position( 2 );
1054 token_value = make_string_token( "<=" );
1055 return TEXT_EVAL_TOKEN::LE;
1056 }
1057 if( current == U'>' && next == U'=' )
1058 {
1059 advance_position( 2 );
1060 token_value = make_string_token( ">=" );
1061 return TEXT_EVAL_TOKEN::GE;
1062 }
1063 if( current == U'=' && next == U'=' )
1064 {
1065 advance_position( 2 );
1066 token_value = make_string_token( "==" );
1067 return TEXT_EVAL_TOKEN::EQ;
1068 }
1069 if( current == U'!' && next == U'=' )
1070 {
1071 advance_position( 2 );
1072 token_value = make_string_token( "!=" );
1073 return TEXT_EVAL_TOKEN::NE;
1074 }
1075
1076 // Single character tokens using structured binding
1077 // Single character tokens (only in expression context)
1079 {
1080 static constexpr std::array<std::pair<char32_t, TEXT_EVAL_TOKEN>, 11> single_char_tokens{
1081 { { U'(', TEXT_EVAL_TOKEN::LPAREN },
1082 { U')', TEXT_EVAL_TOKEN::RPAREN },
1083 { U'+', TEXT_EVAL_TOKEN::PLUS },
1084 { U'-', TEXT_EVAL_TOKEN::MINUS },
1085 { U'*', TEXT_EVAL_TOKEN::MULTIPLY },
1086 { U'/', TEXT_EVAL_TOKEN::DIVIDE },
1087 { U'%', TEXT_EVAL_TOKEN::MODULO },
1088 { U'^', TEXT_EVAL_TOKEN::POWER },
1089 { U',', TEXT_EVAL_TOKEN::COMMA },
1090 { U'<', TEXT_EVAL_TOKEN::LT },
1091 { U'>', TEXT_EVAL_TOKEN::GT } }
1092 };
1093
1094 if( auto it = std::ranges::find_if( single_char_tokens,
1095 [current]( const auto& pair )
1096 {
1097 return pair.first == current;
1098 } );
1099 it != single_char_tokens.end() )
1100 {
1102 token_value = make_string_token( utf8_utils::UTF8_CONVERTER::to_utf8( std::u32string{ current } ) );
1103 return it->second;
1104 }
1105 }
1106
1107 // Complex tokens
1108 if( current == U'"' || current == U'\'' )
1109 {
1110 token_value = parse_string_literal( current );
1112 }
1113
1114 if( CLASSIFIER::is_digit( current ) || ( current == U'.' && CLASSIFIER::is_digit( next ) ) )
1115 {
1116 token_value = parse_number();
1118 }
1119
1120 // Context-aware handling of alphabetic content
1121 if( CLASSIFIER::is_alpha( current ) || current == U'_' )
1122 {
1124 {
1125 // In expression context, alphabetic content is an identifier
1126 token_value = parse_identifier();
1128 }
1129 else
1130 {
1131 // In text context, alphabetic content is part of regular text
1132 token_value = parse_text_content();
1133 return TEXT_EVAL_TOKEN::TEXT;
1134 }
1135 }
1136
1137 // Default to text content
1138 token_value = parse_text_content();
1139 return token_value.text[0] == U'\0' ? TEXT_EVAL_TOKEN::ENDS : TEXT_EVAL_TOKEN::TEXT;
1140 }
1141
1142 bool has_more_tokens() const noexcept { return m_pos < m_text.size(); }
1143 constexpr size_t get_line() const noexcept { return m_line; }
1144 constexpr size_t get_column() const noexcept { return m_column; }
1145
1146private:
1148 {
1149 TEXT, // Regular text content - alphabetic should be TEXT tokens
1150 EXPRESSION // Inside @{...} or ${...} - alphabetic should be IDENTIFIER tokens
1151 };
1152
1153 std::u32string m_text;
1154 size_t m_pos{ 0 };
1155 size_t m_line{ 1 };
1156 size_t m_column{ 1 };
1158 int m_braceNestingLevel{ 0 }; // Track nesting level of expressions
1160 EDA_UNITS m_defaultUnits{ EDA_UNITS::MM }; // Add default units for conversion
1161};
1162
1163
1164EXPRESSION_EVALUATOR::EXPRESSION_EVALUATOR( bool aClearVariablesOnEvaluate ) :
1165 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1166 m_useCustomCallback( false ),
1167 m_defaultUnits( EDA_UNITS::MM ) // Default to millimeters
1168{
1169 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1170}
1171
1172
1173EXPRESSION_EVALUATOR::EXPRESSION_EVALUATOR( VAR_CALLBACK aVariableCallback, bool aClearVariablesOnEvaluate ) :
1174 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1175 m_customCallback( std::move( aVariableCallback ) ),
1176 m_useCustomCallback( true ),
1177 m_defaultUnits( EDA_UNITS::MM ) // Default to millimeters
1178{
1179 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1180}
1181
1182
1183EXPRESSION_EVALUATOR::EXPRESSION_EVALUATOR( EDA_UNITS aUnits, bool aClearVariablesOnEvaluate ) :
1184 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1185 m_useCustomCallback( false ),
1186 m_defaultUnits( aUnits )
1187{
1188 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1189}
1190
1191
1193 bool aClearVariablesOnEvaluate ) :
1194 m_clearVariablesOnEvaluate( aClearVariablesOnEvaluate ),
1195 m_customCallback( std::move( aVariableCallback ) ),
1196 m_useCustomCallback( true ),
1197 m_defaultUnits( aUnits )
1198{
1199 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1200}
1201
1202
1204
1205
1207 m_variables( aOther.m_variables ),
1212{
1213 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1214
1215 if( aOther.m_lastErrors )
1216 {
1217 // Copy error state
1218 for( const auto& error : aOther.m_lastErrors->GetErrors() )
1219 m_lastErrors->AddError( error );
1220 }
1221}
1222
1223
1225{
1226 if( this != &aOther )
1227 {
1228 m_variables = aOther.m_variables;
1233
1234 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1235 if( aOther.m_lastErrors )
1236 {
1237 for( const auto& error : aOther.m_lastErrors->GetErrors() )
1238 m_lastErrors->AddError( error );
1239 }
1240 }
1241 return *this;
1242}
1243
1244
1246 m_variables( std::move( aOther.m_variables ) ),
1247 m_lastErrors( std::move( aOther.m_lastErrors ) ),
1248 m_clearVariablesOnEvaluate( aOther.m_clearVariablesOnEvaluate ),
1249 m_customCallback( std::move( aOther.m_customCallback ) ),
1250 m_useCustomCallback( aOther.m_useCustomCallback ),
1251 m_defaultUnits( aOther.m_defaultUnits )
1252{
1253}
1254
1255
1257{
1258 if( this != &aOther )
1259 {
1260 m_variables = std::move( aOther.m_variables );
1261 m_lastErrors = std::move( aOther.m_lastErrors );
1262 m_clearVariablesOnEvaluate = aOther.m_clearVariablesOnEvaluate;
1263 m_customCallback = std::move( aOther.m_customCallback );
1264 m_useCustomCallback = aOther.m_useCustomCallback;
1265 m_defaultUnits = aOther.m_defaultUnits;
1266 }
1267
1268 return *this;
1269}
1270
1271
1273{
1274 m_customCallback = std::move( aCallback );
1275 m_useCustomCallback = true;
1276}
1277
1278
1284
1285
1290
1291
1293{
1294 m_defaultUnits = aUnits;
1295}
1296
1297
1302
1303
1304void EXPRESSION_EVALUATOR::SetVariable( const wxString& aName, double aValue )
1305{
1306 std::string name = wxStringToStdString( aName );
1307 m_variables[name] = calc_parser::VALUE{ aValue };
1308}
1309
1310
1311void EXPRESSION_EVALUATOR::SetVariable( const wxString& aName, const wxString& aValue )
1312{
1313 std::string name = wxStringToStdString( aName );
1314 std::string value = wxStringToStdString( aValue );
1316}
1317
1318
1319void EXPRESSION_EVALUATOR::SetVariable( const std::string& aName, const std::string& aValue )
1320{
1321 m_variables[aName] = calc_parser::VALUE{ aValue };
1322}
1323
1324
1325bool EXPRESSION_EVALUATOR::RemoveVariable( const wxString& aName )
1326{
1327 std::string name = wxStringToStdString( aName );
1328 return m_variables.erase( name ) > 0;
1329}
1330
1331
1333{
1334 m_variables.clear();
1335}
1336
1337
1338bool EXPRESSION_EVALUATOR::HasVariable( const wxString& aName ) const
1339{
1340 std::string name = wxStringToStdString( aName );
1341 return m_variables.find( name ) != m_variables.end();
1342}
1343
1344
1345wxString EXPRESSION_EVALUATOR::GetVariable( const wxString& aName ) const
1346{
1347 std::string name = wxStringToStdString( aName );
1348 auto it = m_variables.find( name );
1349
1350 if( it != m_variables.end() )
1351 {
1352 if( std::holds_alternative<double>( it->second ) )
1353 {
1354 double val = std::get<double>( it->second );
1355
1356 // Smart formatting - whole numbers don't need decimal places
1357 if( val == std::floor( val ) && std::abs( val ) < 1e15 )
1358 return wxString::Format( "%.0f", val );
1359 // Otherwise match highest precision from EDA_UNIT_UTILS::UI::StringFromValue()
1360 else
1361 return wxString::Format( "%.10g", val );
1362 }
1363 else
1364 {
1365 return stdStringToWxString( std::get<std::string>( it->second ) );
1366 }
1367 }
1368
1369 return wxString{};
1370}
1371
1372
1373std::vector<wxString> EXPRESSION_EVALUATOR::GetVariableNames() const
1374{
1375 std::vector<wxString> names;
1376 names.reserve( m_variables.size() );
1377
1378 for( const auto& [name, value] : m_variables )
1379 names.push_back( stdStringToWxString( name ) );
1380
1381 return names;
1382}
1383
1384
1385void EXPRESSION_EVALUATOR::SetVariables( const std::unordered_map<wxString, double>& aVariables )
1386{
1387 for( const auto& [name, value] : aVariables )
1388 SetVariable( name, value );
1389}
1390
1391
1392void EXPRESSION_EVALUATOR::SetVariables( const std::unordered_map<wxString, wxString>& aVariables )
1393{
1394 for( const auto& [name, value] : aVariables )
1395 SetVariable( name, value );
1396}
1397
1398
1399wxString EXPRESSION_EVALUATOR::Evaluate( const wxString& aInput )
1400{
1401 std::unordered_map<wxString, double> emptyNumVars;
1402 std::unordered_map<wxString, wxString> emptyStringVars;
1403 return Evaluate( aInput, emptyNumVars, emptyStringVars );
1404}
1405
1406
1407wxString EXPRESSION_EVALUATOR::Evaluate( const wxString& aInput,
1408 const std::unordered_map<wxString, double>& aTempVariables )
1409{
1410 std::unordered_map<wxString, wxString> emptyStringVars;
1411 return Evaluate( aInput, aTempVariables, emptyStringVars );
1412}
1413
1414
1415wxString EXPRESSION_EVALUATOR::Evaluate( const wxString& aInput,
1416 const std::unordered_map<wxString, double>& aTempNumericVars,
1417 const std::unordered_map<wxString, wxString>& aTempStringVars )
1418{
1419 // Clear previous errors
1420 ClearErrors();
1421
1422 // Expand ${variable} patterns that are OUTSIDE of @{} expressions
1423 wxString processedInput = expandVariablesOutsideExpressions( aInput, aTempNumericVars, aTempStringVars );
1424
1425 // Convert processed input to std::string
1426 std::string input = wxStringToStdString( processedInput ); // Create combined callback for all variable sources
1427 VAR_CALLBACK combinedCallback = createCombinedCallback( &aTempNumericVars, &aTempStringVars );
1428
1429 // Evaluate using parser
1430 auto [result, hadErrors] = evaluateWithParser( input, combinedCallback );
1431
1432 // Update error state if evaluation had errors
1433 if( hadErrors && !m_lastErrors )
1434 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1435
1436 if( hadErrors )
1437 m_lastErrors->AddError( "Evaluation failed" );
1438
1439 // Clear variables if requested
1442
1443 // Convert result back to wxString
1444 return stdStringToWxString( result );
1445}
1446
1447
1449{
1450 return m_lastErrors && m_lastErrors->HasErrors();
1451}
1452
1453
1455{
1456 if( !m_lastErrors )
1457 return wxString{};
1458
1459 return stdStringToWxString( m_lastErrors->GetAllMessages() );
1460}
1461
1462
1464{
1465 if( !m_lastErrors )
1466 return 0;
1467
1468 return m_lastErrors->GetErrors().size();
1469}
1470
1471
1472std::vector<wxString> EXPRESSION_EVALUATOR::GetErrors() const
1473{
1474 std::vector<wxString> result;
1475
1476 if( m_lastErrors )
1477 {
1478 const std::vector<std::string>& errors = m_lastErrors->GetErrors();
1479 result.reserve( errors.size() );
1480
1481 for( const std::string& error : errors )
1482 result.push_back( stdStringToWxString( error ) );
1483 }
1484
1485 return result;
1486}
1487
1488
1490{
1491 if( m_lastErrors )
1492 m_lastErrors->Clear();
1493}
1494
1495
1500
1501
1506
1507
1508bool EXPRESSION_EVALUATOR::TestExpression( const wxString& aExpression )
1509{
1510 // Create a test input with the expression wrapped in @{}
1511 wxString testInput = "@{" + aExpression + "}";
1512
1513 // Create a minimal callback that returns errors for all variables
1514 auto testCallback =
1515 []( const std::string& aVarName ) -> calc_parser::RESULT<calc_parser::VALUE>
1516 {
1517 return calc_parser::MakeError<calc_parser::VALUE>( "Test mode - no variables available" );
1518 };
1519
1520 // Try to parse it
1521 ClearErrors();
1522 std::string input = wxStringToStdString( testInput );
1523 auto [result, hadErrors] = evaluateWithParser( input, testCallback );
1524
1525 // Check if there were parsing errors (ignore evaluation errors for undefined variables)
1526 if( m_lastErrors )
1527 {
1528 // Filter out "Test mode - no variables available" errors, look for syntax errors
1529 for( const std::string& error : m_lastErrors->GetErrors() )
1530 {
1531 if( error.find( "Syntax error" ) != std::string::npos
1532 || error.find( "Parser failed" ) != std::string::npos )
1533 {
1534 return false; // Found syntax error
1535 }
1536 }
1537 }
1538
1539 return true; // No syntax errors found
1540}
1541
1542
1543size_t EXPRESSION_EVALUATOR::CountExpressions( const wxString& aInput ) const
1544{
1545 size_t count = 0;
1546 size_t pos = 0;
1547
1548 while( ( pos = aInput.find( "@{", pos ) ) != wxString::npos )
1549 {
1550 count++;
1551 pos += 2; // Move past "@{"
1552 }
1553
1554 return count;
1555}
1556
1557
1558std::vector<wxString> EXPRESSION_EVALUATOR::ExtractExpressions( const wxString& aInput ) const
1559{
1560 std::vector<wxString> expressions;
1561 size_t pos = 0;
1562
1563 while( ( pos = aInput.find( "@{", pos ) ) != wxString::npos )
1564 {
1565 size_t start = pos + 2; // Skip "@{"
1566 size_t end = aInput.find( "}", start );
1567
1568 if( end != wxString::npos )
1569 {
1570 expressions.push_back( aInput.substr( start, end - start ) );
1571 pos = end + 1;
1572 }
1573 else
1574 {
1575 break; // No closing brace found
1576 }
1577 }
1578
1579 return expressions;
1580}
1581
1582
1583std::string EXPRESSION_EVALUATOR::wxStringToStdString( const wxString& aWxStr ) const
1584{
1585 return aWxStr.ToStdString( wxConvUTF8 );
1586}
1587
1588
1589wxString EXPRESSION_EVALUATOR::stdStringToWxString( const std::string& aStdStr ) const
1590{
1591 return wxString( aStdStr.c_str(), wxConvUTF8 );
1592}
1593
1594
1596 const wxString& aInput,
1597 const std::unordered_map<wxString, double>& aTempNumericVars,
1598 const std::unordered_map<wxString, wxString>& aTempStringVars ) const
1599{
1600 wxString result = aInput;
1601 size_t pos = 0;
1602
1603 // Track positions of @{} expressions to avoid substituting inside them
1604 std::vector<std::pair<size_t, size_t>> expressionRanges;
1605
1606 // Find all @{} expression ranges
1607 while( ( pos = result.find( "@{", pos ) ) != std::string::npos )
1608 {
1609 size_t start = pos;
1610 size_t braceCount = 1;
1611 size_t searchPos = start + 2; // Skip "@{"
1612
1613 // Find matching closing brace
1614 while( searchPos < result.length() && braceCount > 0 )
1615 {
1616 if( result[searchPos] == '{' )
1617 braceCount++;
1618 else if( result[searchPos] == '}' )
1619 braceCount--;
1620 searchPos++;
1621 }
1622
1623 if( braceCount == 0 )
1624 expressionRanges.emplace_back( start, searchPos ); // searchPos is after '}'
1625
1626 pos = searchPos;
1627 }
1628
1629 // Now find and replace ${variable} patterns that are NOT inside @{} expressions
1630 pos = 0;
1631
1632 while( ( pos = result.find( "${", pos ) ) != std::string::npos )
1633 {
1634 // Check if this ${} is inside any @{} expression
1635 bool insideExpression = false;
1636
1637 for( const auto& range : expressionRanges )
1638 {
1639 if( pos >= range.first && pos < range.second )
1640 {
1641 insideExpression = true;
1642 break;
1643 }
1644 }
1645
1646 if( insideExpression )
1647 {
1648 // Special case: if this variable is immediately followed by unit text,
1649 // we should expand it to allow proper unit parsing
1650 size_t closePos = result.find( "}", pos + 2 );
1651
1652 if( closePos != std::string::npos )
1653 {
1654 // Check what comes after the closing brace
1655 size_t afterBrace = closePos + 1;
1656 bool followedByUnit = false;
1657
1658 if( afterBrace < result.length() )
1659 {
1660 // Check if followed by any supported unit strings using centralized registry
1662
1663 for( const auto& unit : units )
1664 {
1665 if( afterBrace + unit.length() <= result.length()
1666 && result.substr( afterBrace, unit.length() ) == unit )
1667 {
1668 followedByUnit = true;
1669 break;
1670 }
1671 }
1672 }
1673
1674 if( !followedByUnit )
1675 {
1676 pos += 2; // Skip this ${} since it's inside an expression and not followed by units
1677 continue;
1678 }
1679 // If followed by units, continue with variable expansion below
1680 }
1681 else
1682 {
1683 pos += 2; // Invalid pattern, skip
1684 continue;
1685 }
1686 }
1687
1688 // Find the closing brace
1689 size_t closePos = result.find( "}", pos + 2 );
1690
1691 if( closePos == std::string::npos )
1692 {
1693 pos += 2; // Invalid ${} pattern, skip
1694 continue;
1695 }
1696
1697 // Extract variable name
1698 wxString varName = result.substr( pos + 2, closePos - pos - 2 );
1699 wxString replacement;
1700 bool found = false;
1701
1702 // Check temporary string variables first
1703 auto stringIt = aTempStringVars.find( varName );
1704
1705 if( stringIt != aTempStringVars.end() )
1706 {
1707 replacement = stringIt->second;
1708 found = true;
1709 }
1710 else
1711 {
1712 // Check temporary numeric variables
1713 auto numIt = aTempNumericVars.find( varName );
1714
1715 if( numIt != aTempNumericVars.end() )
1716 {
1717 replacement = wxString::FromDouble( numIt->second );
1718 found = true;
1719 }
1720 else
1721 {
1722 // Check instance variables
1723 std::string stdVarName = wxStringToStdString( varName );
1724 auto instIt = m_variables.find( stdVarName );
1725
1726 if( instIt != m_variables.end() )
1727 {
1728 const calc_parser::VALUE& value = instIt->second;
1729
1730 if( std::holds_alternative<std::string>( value ) )
1731 {
1732 replacement = stdStringToWxString( std::get<std::string>( value ) );
1733 found = true;
1734 }
1735 else if( std::holds_alternative<double>( value ) )
1736 {
1737 replacement = wxString::FromDouble( std::get<double>( value ) );
1738 found = true;
1739 }
1740 }
1741 }
1742 }
1743
1744 if( found )
1745 {
1746 // Replace ${variable} with its value
1747 result.replace( pos, closePos - pos + 1, replacement );
1748 pos += replacement.length();
1749 }
1750 else
1751 {
1752 // Variable not found, record error but leave ${variable} unchanged
1753 if( !m_lastErrors )
1754 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1755
1756 m_lastErrors->AddError( fmt::format( "Undefined variable: {}", wxStringToStdString( varName ) ) );
1757 pos = closePos + 1;
1758 }
1759 }
1760
1761 return result;
1762}
1763
1764
1766EXPRESSION_EVALUATOR::createCombinedCallback( const std::unordered_map<wxString, double>* aTempNumericVars,
1767 const std::unordered_map<wxString, wxString>* aTempStringVars ) const
1768{
1769 return [this, aTempNumericVars, aTempStringVars]
1770 ( const std::string& aVarName ) -> calc_parser::RESULT<calc_parser::VALUE>
1771 {
1772 // Priority 1: Custom callback (if set)
1774 {
1775 auto customResult = m_customCallback( aVarName );
1776
1777 if( customResult.HasValue() )
1778 return customResult;
1779
1780 // If custom callback returned an error, continue to fallback options
1781 // unless the error indicates a definitive "not found" vs "lookup failed"
1782 // For simplicity, we'll always try fallbacks
1783 }
1784
1785 // Priority 2: Temporary string variables
1786 if( aTempStringVars )
1787 {
1788 wxString wxVarName = stdStringToWxString( aVarName );
1789
1790 if( auto it = aTempStringVars->find( wxVarName ); it != aTempStringVars->end() )
1791 {
1792 std::string stdValue = wxStringToStdString( it->second );
1794 }
1795 }
1796
1797 // Priority 3: Temporary numeric variables
1798 if( aTempNumericVars )
1799 {
1800 wxString wxVarName = stdStringToWxString( aVarName );
1801
1802 if( auto it = aTempNumericVars->find( wxVarName ); it != aTempNumericVars->end() )
1803 return calc_parser::MakeValue<calc_parser::VALUE>( it->second );
1804 }
1805
1806 // Priority 4: Stored variables
1807 if( auto it = m_variables.find( aVarName ); it != m_variables.end() )
1808 {
1809 return calc_parser::MakeValue<calc_parser::VALUE>( it->second );
1810 }
1811
1812 // Priority 5: Use KiCad's ExpandTextVars for system/project variables
1813 try
1814 {
1815 wxString varName = stdStringToWxString( aVarName );
1816 wxString testString = wxString::Format( "${%s}", varName );
1817
1818 // Create a resolver that will return true if the variable was found
1819 bool wasResolved = false;
1820 std::function<bool( wxString* )> resolver =
1821 [&wasResolved]( wxString* token ) -> bool
1822 {
1823 // If we get here, ExpandTextVars found the variable and wants to resolve it
1824 // For our purposes, we just want to know if it exists, so return false
1825 // to keep the original ${varname} format, and set our flag
1826 wasResolved = true;
1827 return false; // Don't replace, just detect
1828 };
1829
1830 wxString expandedResult = ExpandTextVars( testString, &resolver, INTERNAL );
1831
1832 if( wasResolved )
1833 {
1834 // Variable exists in KiCad's system, now get its actual value
1835 std::function<bool( wxString* )> valueResolver =
1836 []( wxString* token ) -> bool
1837 {
1838 // Let ExpandTextVars resolve this normally
1839 // We'll get the resolved value in token
1840 return false; // Use default resolution
1841 };
1842
1843 wxString resolvedValue = ExpandTextVars( testString, &valueResolver, INTERNAL );
1844
1845 // Check if it was actually resolved (not still ${varname})
1846 if( resolvedValue != testString )
1847 {
1848 std::string resolvedStd = wxStringToStdString( resolvedValue );
1849
1850 // Try to parse as number first
1851 try
1852 {
1853 double numValue;
1854 auto result = fast_float::from_chars( resolvedStd.data(),
1855 resolvedStd.data() + resolvedStd.size(),
1856 numValue );
1857
1858 if( result.ec != std::errc()
1859 || result.ptr != resolvedStd.data() + resolvedStd.size() )
1860 {
1861 throw std::invalid_argument( fmt::format( "Cannot convert '{}' to number",
1862 resolvedStd ) );
1863 }
1864
1866 }
1867 catch( ... )
1868 {
1869 // Not a number, return as string
1870 return calc_parser::MakeValue<calc_parser::VALUE>( resolvedStd );
1871 }
1872 }
1873 }
1874 }
1875 catch( const std::exception& )
1876 {
1877 // ExpandTextVars failed, continue to error
1878 }
1879
1880 // Priority 6: If custom callback was tried and failed, return its error
1882 {
1883 return m_customCallback( aVarName ); // Return the original error
1884 }
1885
1886 // No variable found anywhere
1887 return calc_parser::MakeError<calc_parser::VALUE>( fmt::format( "Undefined variable: {}", aVarName ) );
1888 };
1889}
1890
1891
1892std::pair<std::string, bool> EXPRESSION_EVALUATOR::evaluateWithParser( const std::string& aInput,
1893 VAR_CALLBACK aVariableCallback )
1894{
1895 try
1896 {
1897 // Try partial error recovery first
1898 auto [partialResult, partialHadErrors] = evaluateWithPartialErrorRecovery( aInput, aVariableCallback );
1899
1900 // If partial recovery made any progress (result differs from input), use it
1901 if( partialResult != aInput )
1902 {
1903 // Partial recovery made progress - always report errors collected during partial recovery
1904 return { std::move( partialResult ), partialHadErrors };
1905 }
1906
1907 // If no progress was made, try original full parsing approach as fallback
1908 return evaluateWithFullParser( aInput, std::move( aVariableCallback ) );
1909 }
1910 catch( const std::bad_alloc& )
1911 {
1912 if( m_lastErrors )
1913 m_lastErrors->AddError( "Out of memory" );
1914
1915 return { aInput, true };
1916 }
1917 catch( const std::exception& e )
1918 {
1919 if( m_lastErrors )
1920 m_lastErrors->AddError( fmt::format( "Exception: {}", e.what() ) );
1921
1922 return { aInput, true };
1923 }
1924}
1925
1926
1927std::pair<std::string, bool>
1928EXPRESSION_EVALUATOR::evaluateWithPartialErrorRecovery( const std::string& aInput, VAR_CALLBACK aVariableCallback )
1929{
1930 std::string result = aInput;
1931 bool hadAnyErrors = false;
1932 size_t pos = 0;
1933
1934 // Process expressions from right to left to avoid position shifts
1935 std::vector<std::pair<size_t, size_t>> expressionRanges;
1936
1937 // Find all expression ranges
1938 while( ( pos = result.find( "@{", pos ) ) != std::string::npos )
1939 {
1940 size_t start = pos;
1941 size_t exprStart = pos + 2; // Skip "@{"
1942 size_t braceCount = 1;
1943 size_t searchPos = exprStart;
1944
1945 // Find matching closing brace, handling nested braces
1946 while( searchPos < result.length() && braceCount > 0 )
1947 {
1948 if( result[searchPos] == '{' )
1949 braceCount++;
1950 else if( result[searchPos] == '}' )
1951 braceCount--;
1952
1953 searchPos++;
1954 }
1955
1956 if( braceCount == 0 )
1957 {
1958 size_t end = searchPos; // Position after the '}'
1959 expressionRanges.emplace_back( start, end );
1960 pos = end;
1961 }
1962 else
1963 {
1964 pos = exprStart; // Skip this malformed expression
1965 }
1966 }
1967
1968 // Process expressions from right to left to avoid position shifts
1969 for( auto it = expressionRanges.rbegin(); it != expressionRanges.rend(); ++it )
1970 {
1971 auto [start, end] = *it;
1972 std::string fullExpr = result.substr( start, end - start );
1973 std::string innerExpr = result.substr( start + 2, end - start - 3 ); // Remove @{ and }
1974
1975 // Try to evaluate this single expression
1976 try
1977 {
1978 // Create a simple expression for evaluation
1979 std::string testExpr = "@{" + innerExpr + "}";
1980
1981 // Create a temporary error collector to capture errors for this specific expression
1982 auto tempErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
1983 auto oldErrors = std::move( m_lastErrors );
1984 m_lastErrors = std::move( tempErrors );
1985
1986 // Use the full parser for this single expression
1987 auto [evalResult, evalHadErrors] = evaluateWithFullParser( testExpr, aVariableCallback );
1988
1989 if( !evalHadErrors )
1990 {
1991 // Successful evaluation, replace in result
1992 result.replace( start, end - start, evalResult );
1993 }
1994 else
1995 {
1996 // Expression failed - add a specific error for this expression
1997 hadAnyErrors = true;
1998
1999 // Restore main error collector and add error
2000 if( !oldErrors )
2001 oldErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
2002
2003 oldErrors->AddError( fmt::format( "Failed to evaluate expression: {}", fullExpr ) );
2004 }
2005
2006 // Restore the main error collector
2007 m_lastErrors = std::move( oldErrors );
2008 }
2009 catch( ... )
2010 {
2011 // Report exception as an error for this expression
2012 if( !m_lastErrors )
2013 m_lastErrors = std::make_unique<calc_parser::ERROR_COLLECTOR>();
2014
2015 m_lastErrors->AddError( fmt::format( "Exception in expression: {}", fullExpr ) );
2016 hadAnyErrors = true;
2017 }
2018 }
2019
2020 return { std::move( result ), hadAnyErrors };
2021}
2022
2023
2024std::pair<std::string, bool> EXPRESSION_EVALUATOR::evaluateWithFullParser( const std::string& aInput,
2025 VAR_CALLBACK aVariableCallback )
2026{
2027 if( aInput.empty() )
2028 {
2029 return { std::string{}, false };
2030 }
2031
2032 // RAII guard for error collector cleanup
2033 struct ERROR_COLLECTOR_GUARD
2034 {
2035 ~ERROR_COLLECTOR_GUARD()
2036 {
2038 }
2039 } guard;
2040
2041 try
2042 {
2043 // Clear previous errors
2044 if( m_lastErrors )
2045 m_lastErrors->Clear();
2046
2047 // Set up error collector
2049
2050 // Create tokenizer with default units
2051 KIEVAL_TEXT_TOKENIZER tokenizer{ aInput, m_lastErrors.get(), m_defaultUnits };
2052
2053 // Create parser deleter function
2054 auto parser_deleter =
2055 []( void* p )
2056 {
2057 KI_EVAL::ParseFree( p, free );
2058 };
2059
2060 // Allocate parser with RAII cleanup
2061 std::unique_ptr<void, decltype( parser_deleter )> parser{ KI_EVAL::ParseAlloc( malloc ), parser_deleter };
2062
2063 if( !parser )
2064 {
2065 if( m_lastErrors )
2066 m_lastErrors->AddError( "Failed to allocate parser" );
2067
2068 return { aInput, true };
2069 }
2070
2071 // Parse document
2072 std::unique_ptr<calc_parser::DOC> document;
2073
2074 calc_parser::TOKEN_TYPE token_value;
2075 TEXT_EVAL_TOKEN token_type;
2076
2077 do
2078 {
2079 token_type = tokenizer.get_next_token( token_value );
2080
2081 // Send token to parser
2082 KI_EVAL::Parse( parser.get(), (int) token_type, token_value, &document );
2083
2084 // Early exit on errors
2085 if( m_lastErrors && m_lastErrors->HasErrors() )
2086 break;
2087
2088 } while( token_type != TEXT_EVAL_TOKEN::ENDS && tokenizer.has_more_tokens() );
2089
2090 // Finalize parsing
2091 KI_EVAL::Parse( parser.get(), (int) TEXT_EVAL_TOKEN::ENDS, calc_parser::TOKEN_TYPE{}, &document );
2092
2093 // Process document if parsing succeeded
2094 if( document && ( !m_lastErrors || !m_lastErrors->HasErrors() ) )
2095 {
2096 auto [result, had_errors] = calc_parser::DOC_PROCESSOR::Process( *document,
2097 std::move( aVariableCallback ) );
2098
2099 // If processing had any evaluation errors, return original input unchanged
2100 // This preserves the original expression syntax while still reporting errors
2101 if( had_errors )
2102 return { aInput, true };
2103
2104 return { std::move( result ), had_errors };
2105 }
2106
2107 // Cleanup and return original on error
2108 return { aInput, true };
2109 }
2110 catch( const std::bad_alloc& )
2111 {
2112 if( m_lastErrors )
2113 m_lastErrors->AddError( "Out of memory" );
2114
2115 return { aInput, true };
2116 }
2117 catch( const std::exception& e )
2118 {
2119 if( m_lastErrors )
2120 m_lastErrors->AddError( fmt::format( "Exception: {}", e.what() ) );
2121
2122 return { aInput, true };
2123 }
2124}
2125
2126
2128 m_evaluator( aUnits ),
2129 m_lastValid( false )
2130{
2131}
2132
2133
2135
2136
2138{
2139 m_lastInput.clear();
2140 m_lastResult.clear();
2141 m_lastValid = false;
2142 m_evaluator.ClearErrors();
2143}
2144
2145
2147{
2148 m_evaluator.SetDefaultUnits( aUnits );
2149}
2150
2151
2153{
2154 // No-op: EXPRESSION_EVALUATOR handles locale properly internally
2155}
2156
2157
2159{
2160 return m_lastValid;
2161}
2162
2163
2165{
2166 return m_lastResult;
2167}
2168
2169
2170bool NUMERIC_EVALUATOR_COMPAT::Process( const wxString& aString )
2171{
2172 m_lastInput = aString;
2173 m_evaluator.ClearErrors();
2174
2175 // Convert bare variable names to ${variable} syntax for compatibility
2176 // This allows NUMERIC_EVALUATOR-style variable access to work with EXPRESSION_EVALUATOR
2177 wxString processedExpression = aString;
2178
2179 // Get all variable names that are currently defined
2180 std::vector<wxString> varNames = m_evaluator.GetVariableNames();
2181
2182 // Sort variable names by length (longest first) to avoid partial replacements
2183 std::sort( varNames.begin(), varNames.end(),
2184 []( const wxString& a, const wxString& b )
2185 {
2186 return a.length() > b.length();
2187 } );
2188
2189 // Replace bare variable names with ${variable} syntax
2190 for( const wxString& varName : varNames )
2191 {
2192 // Create a regex to match the variable name as a whole word
2193 // This avoids replacing parts of other words
2194 wxString pattern = "\\b" + varName + "\\b";
2195 wxString replacement = "${" + varName + "}";
2196
2197 // Simple string replacement (not regex for now to avoid complexity)
2198 // Look for the variable name surrounded by non-alphanumeric characters
2199 size_t pos = 0;
2200
2201 while( ( pos = processedExpression.find( varName, pos ) ) != wxString::npos )
2202 {
2203 // Check if this is a whole word (not part of another identifier)
2204 bool isWholeWord = true;
2205
2206 // Check character before
2207 if( pos > 0 )
2208 {
2209 wxChar before = processedExpression[pos - 1];
2210
2211 if( wxIsalnum( before ) || before == '_' || before == '$' )
2212 isWholeWord = false;
2213 }
2214
2215 // Check character after
2216 if( isWholeWord && pos + varName.length() < processedExpression.length() )
2217 {
2218 wxChar after = processedExpression[pos + varName.length()];
2219
2220 if( wxIsalnum( after ) || after == '_' )
2221 isWholeWord = false;
2222 }
2223
2224 if( isWholeWord )
2225 {
2226 processedExpression.replace( pos, varName.length(), replacement );
2227 pos += replacement.length();
2228 }
2229 else
2230 {
2231 pos += varName.length();
2232 }
2233 }
2234 }
2235
2236 // Wrap the processed expression in @{...} syntax for EXPRESSION_EVALUATOR
2237 wxString wrappedExpression = "@{" + processedExpression + "}";
2238
2239 m_lastResult = m_evaluator.Evaluate( wrappedExpression );
2240 m_lastValid = !m_evaluator.HasErrors();
2241
2242 // Additional check: if the result is exactly the wrapped expression,
2243 // it means the expression wasn't evaluated (likely due to errors)
2244 if( m_lastResult == wrappedExpression )
2245 {
2246 m_lastValid = false;
2247 m_lastResult = "NaN";
2248 }
2249
2250 // If there were errors, set result to "NaN" to match NUMERIC_EVALUATOR behavior
2251 if( !m_lastValid )
2252 {
2253 m_lastResult = "NaN";
2254 return false;
2255 }
2256
2257 return true;
2258}
2259
2260
2262{
2263 return m_lastInput;
2264}
2265
2266
2267void NUMERIC_EVALUATOR_COMPAT::SetVar( const wxString& aString, double aValue )
2268{
2269 m_evaluator.SetVariable( aString, aValue );
2270}
2271
2272
2273double NUMERIC_EVALUATOR_COMPAT::GetVar( const wxString& aString )
2274{
2275 if( !m_evaluator.HasVariable( aString ) )
2276 return 0.0;
2277
2278 wxString value = m_evaluator.GetVariable( aString );
2279
2280 // Try to convert to double
2281 double result = 0.0;
2282
2283 if( !value.ToDouble( &result ) )
2284 return 0.0;
2285
2286 return result;
2287}
2288
2289
2290void NUMERIC_EVALUATOR_COMPAT::RemoveVar( const wxString& aString )
2291{
2292 m_evaluator.RemoveVariable( aString );
2293}
2294
2295
2297{
2298 m_evaluator.ClearVariables();
2299}
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()
bool at_exponent() const noexcept
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:96
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
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 bool is_si_prefix(UnicodeCodepoint auto cp) noexcept
static std::vector< uint8_t > bytes(const std::string &aText)
static const long long MM
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
TEXT_EVAL_TOKEN