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