KiCad PCB EDA Suite
Loading...
Searching...
No Matches
libeval_compiler.cpp
Go to the documentation of this file.
1/*
2 * This file is part of libeval, a simple math expression evaluator
3 *
4 * Copyright (C) 2017 Michael Geselbracht, [email protected]
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <memory>
22#include <set>
23#include <vector>
24#include <algorithm>
25
26#include <eda_units.h>
27#include <string_utils.h>
28#include <wx/log.h>
29
30#ifdef DEBUG
31#include <cstdarg>
32#endif
33
35
36/* The (generated) lemon parser is written in C.
37 * In order to keep its symbol from the global namespace include the parser code with
38 * a C++ namespace.
39 */
40namespace LIBEVAL
41{
42
43#ifdef __GNUC__
44#pragma GCC diagnostic push
45#pragma GCC diagnostic ignored "-Wunused-variable"
46#pragma GCC diagnostic ignored "-Wsign-compare"
47#endif
48
49#include <libeval_compiler/grammar.c>
50#include <libeval_compiler/grammar.h>
51
52#ifdef __GNUC__
53#pragma GCC diagnostic pop
54#endif
55
56
57#define libeval_dbg(level, fmt, ...) \
58 wxLogTrace( "libeval_compiler", fmt, __VA_ARGS__ );
59
60
61TREE_NODE* newNode( LIBEVAL::COMPILER* compiler, int op, const T_TOKEN_VALUE& value )
62{
63 TREE_NODE* t2 = new TREE_NODE();
64
65 t2->valid = true;
66 t2->value.str = value.str ? new wxString( *value.str ) : nullptr;
67 t2->value.num = value.num;
68 t2->value.idx = value.idx;
69 t2->op = op;
70 t2->leaf[0] = nullptr;
71 t2->leaf[1] = nullptr;
72 t2->isTerminal = false;
73 t2->srcPos = compiler->GetSourcePos();
74 t2->uop = nullptr;
75
76 libeval_dbg(10, " ostr %p nstr %p nnode %p op %d", value.str, t2->value.str, t2, t2->op );
77
78 if(t2->value.str)
79 compiler->GcItem( t2->value.str );
80
81 compiler->GcItem( t2 );
82
83 return t2;
84}
85
86
87static const wxString formatOpName( int op )
88{
89 static const struct
90 {
91 int op;
92 wxString mnemonic;
93 }
94 simpleOps[] =
95 {
96 { TR_OP_MUL, "MUL" },
97 { TR_OP_DIV, "DIV" },
98 { TR_OP_ADD, "ADD" },
99 { TR_OP_SUB, "SUB" },
100 { TR_OP_LESS, "LESS" },
101 { TR_OP_GREATER, "GREATER" },
102 { TR_OP_LESS_EQUAL, "LESS_EQUAL" },
103 { TR_OP_GREATER_EQUAL, "GREATER_EQUAL" },
104 { TR_OP_EQUAL, "EQUAL" },
105 { TR_OP_NOT_EQUAL, "NEQUAL" },
106 { TR_OP_BOOL_AND, "AND" },
107 { TR_OP_BOOL_OR, "OR" },
108 { TR_OP_BOOL_NOT, "NOT" },
109 { -1, "" }
110 };
111
112 for( int i = 0; simpleOps[i].op >= 0; i++ )
113 {
114 if( simpleOps[i].op == op )
115 return simpleOps[i].mnemonic;
116 }
117
118 return "???";
119}
120
121
122bool VALUE::EqualTo( CONTEXT* aCtx, const VALUE* b ) const
123{
124 if( m_type == VT_UNDEFINED || b->m_type == VT_UNDEFINED )
125 return false;
126
127 if( m_type == VT_NULL && b->m_type == VT_NULL )
128 return true;
129
130 if( m_type == VT_NUMERIC && b->m_type == VT_NUMERIC )
131 {
132 return AsDouble() == b->AsDouble();
133 }
134 else if( m_type == VT_STRING && b->m_type == VT_STRING )
135 {
136 if( b->m_stringIsWildcard )
137 return WildCompareString( b->AsString(), AsString(), false );
138 else
139 return AsString().IsSameAs( b->AsString(), false );
140 }
141
142 return false;
143}
144
145
146bool VALUE::NotEqualTo( CONTEXT* aCtx, const VALUE* b ) const
147{
148 if( m_type == VT_UNDEFINED || b->m_type == VT_UNDEFINED )
149 return false;
150
151 return !EqualTo( aCtx, b );
152}
153
154
155wxString UOP::Format() const
156{
157 wxString str;
158
159 switch( m_op )
160 {
161 case TR_UOP_PUSH_VAR:
162 str = wxString::Format( "PUSH VAR [%p]", m_ref.get() );
163 break;
164
166 if( !m_value )
167 str = wxString::Format( "PUSH nullptr" );
168 else if( m_value->GetType() == VT_NUMERIC )
169 str = wxString::Format( "PUSH NUM [%.10f]", m_value->AsDouble() );
170 else
171 str = wxString::Format( "PUSH STR [%ls]", m_value->AsString() );
172 break;
173
175 str = wxString::Format( "MCALL" );
176 break;
177
178 case TR_OP_FUNC_CALL:
179 str = wxString::Format( "FCALL" );
180 break;
181
182 case TR_OP_JZ:
183 str = wxString::Format( "JZ -> %d", m_jumpTarget );
184 break;
185
186 case TR_OP_JNZ:
187 str = wxString::Format( "JNZ -> %d", m_jumpTarget );
188 break;
189
190 default:
191 str = wxString::Format( "%s %d", formatOpName( m_op ).c_str(), m_op );
192 break;
193 }
194
195 return str;
196}
197
198
200{
201 for( UOP* op : m_ucode )
202 delete op;
203}
204
205
206wxString UCODE::Dump() const
207{
208 wxString rv;
209
210 for( UOP* op : m_ucode )
211 {
212 rv += op->Format();
213 rv += "\n";
214 }
215
216 return rv;
217};
218
219
221{
222 wxString rv;
223
224 while( m_pos < m_str.length() && m_str[ m_pos ] != '\'' )
225 {
226 if( m_str[ m_pos ] == '\\' && m_pos + 1 < m_str.length() && m_str[ m_pos + 1 ] == '\'' )
227 m_pos++;
228
229 rv.append( 1, m_str[ m_pos++ ] );
230 }
231
232 m_pos++;
233
234 return rv;
235}
236
237
238wxString TOKENIZER::GetChars( const std::function<bool( wxUniChar )>& cond ) const
239{
240 wxString rv;
241 size_t p = m_pos;
242
243 while( p < m_str.length() && cond( m_str[p] ) )
244 {
245 rv.append( 1, m_str[p] );
246 p++;
247 }
248
249 return rv;
250}
251
252
253bool TOKENIZER::MatchAhead( const wxString& match,
254 const std::function<bool( wxUniChar )>& stopCond ) const
255{
256 int remaining = (int) m_str.Length() - m_pos;
257
258 if( remaining < (int) match.length() )
259 return false;
260
261 if( m_str.substr( m_pos, match.length() ) == match )
262 return ( remaining == (int) match.length() || stopCond( m_str[m_pos + match.length()] ) );
263
264 return false;
265}
266
267
270{
272 m_sourcePos = 0;
273 m_parseFinished = false;
274 m_unitResolver = std::make_unique<UNIT_RESOLVER>();
275 m_parser = LIBEVAL::ParseAlloc( malloc );
276 m_tree = nullptr;
277 m_errorStatus.pendingError = false;
278}
279
280
282{
283 LIBEVAL::ParseFree( m_parser, free );
284
285 if( m_tree )
286 {
287 freeTree( m_tree );
288 m_tree = nullptr;
289 }
290
291 // Allow explicit call to destructor
292 m_parser = nullptr;
293
294 Clear();
295}
296
297
299{
300 //free( current.token );
301 m_tokenizer.Clear();
302
303 if( m_tree )
304 {
305 freeTree( m_tree );
306 m_tree = nullptr;
307 }
308
309 m_tree = nullptr;
310
311 for( TREE_NODE* tok : m_gcItems )
312 delete tok;
313
314 for( wxString* tok: m_gcStrings )
315 delete tok;
316
317 m_gcItems.clear();
318 m_gcStrings.clear();
319}
320
321
322void COMPILER::parseError( const char* s )
323{
325}
326
327
329{
330 m_parseFinished = true;
331}
332
333
334bool COMPILER::Compile( const wxString& aString, UCODE* aCode, CONTEXT* aPreflightContext )
335{
336 // Feed parser token after token until end of input.
337
338 newString( aString );
339
340 if( m_tree )
341 {
342 freeTree( m_tree );
343 m_tree = nullptr;
344 }
345
346 m_tree = nullptr;
347 m_parseFinished = false;
348 T_TOKEN tok( defaultToken );
349
350 libeval_dbg(0, "str: '%s' empty: %d\n", aString.c_str(), !!aString.empty() );
351
352 if( aString.empty() )
353 {
354 m_parseFinished = true;
355 return generateUCode( aCode, aPreflightContext );
356 }
357
358 do
359 {
360 m_sourcePos = m_tokenizer.GetPos();
361
362 tok = getToken();
363
364 if( tok.value.str )
365 GcItem( tok.value.str );
366
367 libeval_dbg(10, "parse: tok %d valstr %p\n", tok.token, tok.value.str );
368 Parse( m_parser, tok.token, tok, this );
369
370 if ( m_errorStatus.pendingError )
371 return false;
372
373 if( m_parseFinished || tok.token == G_ENDS )
374 {
375 // Reset parser by passing zero as token ID, value is ignored.
376 Parse( m_parser, 0, tok, this );
377 break;
378 }
379 } while( tok.token );
380
381 return generateUCode( aCode, aPreflightContext );
382}
383
384
385void COMPILER::newString( const wxString& aString )
386{
387 Clear();
388
390 m_tokenizer.Restart( aString );
391 m_parseFinished = false;
392}
393
394
396{
397 T_TOKEN rv;
399
400 bool done = false;
401
402 do
403 {
404 switch( m_lexerState )
405 {
406 case LS_DEFAULT:
407 done = lexDefault( rv );
408 break;
409
410 case LS_STRING:
411 done = lexString( rv );
412 break;
413 }
414 } while( !done );
415
416 return rv;
417}
418
419
421{
422 aToken.token = G_STRING;
423 aToken.value.str = new wxString( m_tokenizer.GetString() );
424
426 return true;
427}
428
429
431{
432 int unitId = 0;
433
434 for( const wxString& unitName : m_unitResolver->GetSupportedUnits() )
435 {
436 if( m_tokenizer.MatchAhead( unitName,
437 []( int c ) -> bool
438 {
439 return !isalnum( c );
440 } ) )
441 {
442 libeval_dbg(10, "Match unit '%s'\n", unitName.c_str() );
443 m_tokenizer.NextChar( unitName.length() );
444 return unitId;
445 }
446
447 unitId++;
448 }
449
450 return -1;
451}
452
453
455{
456 T_TOKEN retval;
457 wxString current;
458 int convertFrom;
459
460 retval.value.str = nullptr;
461 retval.value.num = 0.0;
462 retval.value.idx = -1;
463 retval.token = G_ENDS;
464
465 if( m_tokenizer.Done() )
466 {
467 aToken = retval;
468 return true;
469 }
470
471 auto isDecimalSeparator =
472 [&]( wxUniChar ch ) -> bool
473 {
474 return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
475 };
476
477 // Lambda: get value as string, store into clToken.token and update current index.
478 auto extractNumber =
479 [&]()
480 {
481 bool haveSeparator = false;
482 wxUniChar ch = m_tokenizer.GetChar();
483
484 do
485 {
486 if( isDecimalSeparator( ch ) && haveSeparator )
487 break;
488
489 current.append( 1, ch );
490
491 if( isDecimalSeparator( ch ) )
492 haveSeparator = true;
493
494 m_tokenizer.NextChar();
495 ch = m_tokenizer.GetChar();
496 } while( isdigit( ch ) || isDecimalSeparator( ch ) );
497
498 // Ensure that the systems decimal separator is used
499 for( int i = current.length(); i; i-- )
500 {
501 if( isDecimalSeparator( current[i - 1] ) )
502 current[i - 1] = m_localeDecimalSeparator;
503 }
504 };
505
506 int ch;
507
508 // Start processing of first/next token: Remove whitespace
509 for( ;; )
510 {
511 ch = m_tokenizer.GetChar();
512
513 if( ch == ' ' )
514 m_tokenizer.NextChar();
515 else
516 break;
517 }
518
519 libeval_dbg(10, "LEX ch '%c' pos %lu\n", ch, (unsigned long)m_tokenizer.GetPos() );
520
521 if( ch == 0 )
522 {
523 /* End of input */
524 }
525 else if( isdigit( ch ) )
526 {
527 // VALUE
528 extractNumber();
529 retval.token = G_VALUE;
530 retval.value.str = new wxString( current );
531 }
532 else if( ( convertFrom = resolveUnits() ) >= 0 )
533 {
534 // UNIT
535 // Units are appended to a VALUE.
536 // Determine factor to default unit if unit for value is given.
537 // Example: Default is mm, unit is inch: factor is 25.4
538 // The factor is assigned to the terminal UNIT. The actual
539 // conversion is done within a parser action.
540 retval.token = G_UNIT;
541 retval.value.idx = convertFrom;
542 }
543 else if( ch == '\'' ) // string literal
544 {
546 m_tokenizer.NextChar();
547 return false;
548 }
549 else if( isalpha( ch ) || ch == '_' )
550 {
551 current = m_tokenizer.GetChars( []( int c ) -> bool { return isalnum( c ) || c == '_'; } );
552 retval.token = G_IDENTIFIER;
553 retval.value.str = new wxString( current );
554 m_tokenizer.NextChar( current.length() );
555 }
556 else if( m_tokenizer.MatchAhead( "==", []( int c ) -> bool { return c != '='; } ) )
557 {
558 retval.token = G_EQUAL;
559 m_tokenizer.NextChar( 2 );
560 }
561 else if( m_tokenizer.MatchAhead( "!=", []( int c ) -> bool { return c != '='; } ) )
562 {
563 retval.token = G_NOT_EQUAL;
564 m_tokenizer.NextChar( 2 );
565 }
566 else if( m_tokenizer.MatchAhead( "<=", []( int c ) -> bool { return c != '='; } ) )
567 {
568 retval.token = G_LESS_EQUAL_THAN;
569 m_tokenizer.NextChar( 2 );
570 }
571 else if( m_tokenizer.MatchAhead( ">=", []( int c ) -> bool { return c != '='; } ) )
572 {
573 retval.token = G_GREATER_EQUAL_THAN;
574 m_tokenizer.NextChar( 2 );
575 }
576 else if( m_tokenizer.MatchAhead( "&&", []( int c ) -> bool { return c != '&'; } ) )
577 {
578 retval.token = G_BOOL_AND;
579 m_tokenizer.NextChar( 2 );
580 }
581 else if( m_tokenizer.MatchAhead( "||", []( int c ) -> bool { return c != '|'; } ) )
582 {
583 retval.token = G_BOOL_OR;
584 m_tokenizer.NextChar( 2 );
585 }
586 else
587 {
588 // Single char tokens
589 switch( ch )
590 {
591 case '+': retval.token = G_PLUS; break;
592 case '!': retval.token = G_BOOL_NOT; break;
593 case '-': retval.token = G_MINUS; break;
594 case '*': retval.token = G_MULT; break;
595 case '/': retval.token = G_DIVIDE; break;
596 case '<': retval.token = G_LESS_THAN; break;
597 case '>': retval.token = G_GREATER_THAN; break;
598 case '(': retval.token = G_PARENL; break;
599 case ')': retval.token = G_PARENR; break;
600 case ';': retval.token = G_SEMCOL; break;
601 case '.': retval.token = G_STRUCT_REF; break;
602 case ',': retval.token = G_COMMA; break;
603
604 default:
605 if( m_tokenizer.MatchAhead( "${", []( int c ) -> bool { return c != '{'; } ) )
606 reportError( CST_PARSE, _( "Unresolved text variable reference" ), m_sourcePos + 2 );
607 else
608 reportError( CST_PARSE, wxString::Format( _( "Unrecognized character '%c'" ), (char) ch ) );
609 break;
610 }
611
612 m_tokenizer.NextChar();
613 }
614
615 aToken = retval;
616 return true;
617}
618
619
620const wxString formatNode( TREE_NODE* node )
621{
622 return node->value.str ? *(node->value.str) : wxString( wxEmptyString );
623}
624
625
626void dumpNode( wxString& buf, TREE_NODE* tok, int depth = 0 )
627{
628 wxString str;
629
630 if( !tok )
631 return;
632
633 str.Printf( "\n[%p L0:%-20p L1:%-20p] ", tok, tok->leaf[0], tok->leaf[1] );
634 buf += str;
635
636 for( int i = 0; i < 2 * depth; i++ )
637 buf += " ";
638
639 if( tok->op & TR_OP_BINARY_MASK )
640 {
641 buf += formatOpName( tok->op );
642 dumpNode( buf, tok->leaf[0], depth + 1 );
643 dumpNode( buf, tok->leaf[1], depth + 1 );
644 }
645
646 switch( tok->op )
647 {
648 case TR_NUMBER:
649 buf += "NUMERIC: ";
650 buf += formatNode( tok );
651
652 if( tok->leaf[0] )
653 dumpNode( buf, tok->leaf[0], depth + 1 );
654
655 break;
656
657 case TR_ARG_LIST:
658 buf += "ARG_LIST: ";
659 buf += formatNode( tok );
660
661 if( tok->leaf[0] )
662 dumpNode( buf, tok->leaf[0], depth + 1 );
663 if( tok->leaf[1] )
664 dumpNode( buf, tok->leaf[1], depth + 1 );
665
666 break;
667
668 case TR_STRING:
669 buf += "STRING: ";
670 buf += formatNode( tok );
671 break;
672
673 case TR_IDENTIFIER:
674 buf += "ID: ";
675 buf += formatNode( tok );
676 break;
677
678 case TR_STRUCT_REF:
679 buf += "SREF: ";
680 dumpNode( buf, tok->leaf[0], depth + 1 );
681 dumpNode( buf, tok->leaf[1], depth + 1 );
682 break;
683
684 case TR_OP_FUNC_CALL:
685 buf += "CALL '";
686 buf += formatNode( tok->leaf[0] );
687 buf += "': ";
688 dumpNode( buf, tok->leaf[1], depth + 1 );
689 break;
690
691 case TR_UNIT:
692 str.Printf( "UNIT: %d ", tok->value.idx );
693 buf += str;
694 break;
695 }
696}
697
698
699void CONTEXT::ReportError( const wxString& aErrorMsg )
700{
701 if( m_errorCallback )
702 m_errorCallback( aErrorMsg, -1 );
703}
704
705
706void COMPILER::reportError( COMPILATION_STAGE stage, const wxString& aErrorMsg, int aPos )
707{
708 if( aPos == -1 )
709 aPos = m_sourcePos;
710
711 m_errorStatus.pendingError = true;
712 m_errorStatus.stage = stage;
713 m_errorStatus.message = aErrorMsg;
714 m_errorStatus.srcPos = aPos;
715
716 if( m_errorCallback )
717 m_errorCallback( aErrorMsg, aPos );
718}
719
720
722{
723 m_tree = root;
724}
725
726
728{
729 if ( tree->leaf[0] )
730 freeTree( tree->leaf[0] );
731
732 if ( tree->leaf[1] )
733 freeTree( tree->leaf[1] );
734
735 delete tree->uop;
736 tree->uop = nullptr;
737}
738
739
740void TREE_NODE::SetUop( int aOp, double aValue, EDA_UNITS aUnits )
741{
742 delete uop;
743
744 std::unique_ptr<VALUE> val = std::make_unique<VALUE>( aValue );
745 val->SetUnits( aUnits );
746 uop = new UOP( aOp, std::move( val ) );
747}
748
749
750void TREE_NODE::SetUop( int aOp, const wxString& aValue, bool aStringIsWildcard )
751{
752 delete uop;
753
754 std::unique_ptr<VALUE> val = std::make_unique<VALUE>( aValue, aStringIsWildcard );
755 uop = new UOP( aOp, std::move( val ) );
756}
757
758
759void TREE_NODE::SetUop( int aOp, std::unique_ptr<VAR_REF> aRef )
760{
761 delete uop;
762
763 uop = new UOP( aOp, std::move( aRef ) );
764}
765
766
767void TREE_NODE::SetUop( int aOp, FUNC_CALL_REF aFunc, std::unique_ptr<VAR_REF> aRef )
768{
769 delete uop;
770
771 uop = new UOP( aOp, std::move( aFunc ), std::move( aRef ) );
772}
773
774
775static void prepareTree( LIBEVAL::TREE_NODE *node )
776{
777 node->isVisited = false;
778
779 // fixme: for reasons I don't understand the lemon parser isn't initializing the
780 // leaf node pointers of function name nodes. -JY
781 if( node->op == TR_OP_FUNC_CALL && node->leaf[0] )
782 {
783 node->leaf[0]->leaf[0] = nullptr;
784 node->leaf[0]->leaf[1] = nullptr;
785 }
786
787 if ( node->leaf[0] )
788 prepareTree( node->leaf[0] );
789
790 if ( node->leaf[1] )
791 prepareTree( node->leaf[1] );
792}
793
794
795static std::vector<TREE_NODE*> squashParamList( TREE_NODE* root )
796{
797 std::vector<TREE_NODE*> args;
798
799 if( !root )
800 return args;
801
802 if( root->op != TR_ARG_LIST && root->op != TR_NULL )
803 {
804 args.push_back( root );
805 }
806 else
807 {
808 TREE_NODE *n = root;
809 do
810 {
811 if( n->leaf[1] )
812 args.push_back(n->leaf[1]);
813
814 n = n->leaf[0];
815 } while ( n && n->op == TR_ARG_LIST );
816
817 if( n )
818 args.push_back( n );
819 }
820
821 std::reverse( args.begin(), args.end() );
822
823 for( size_t i = 0; i < args.size(); i++ )
824 libeval_dbg( 10, "squash arg%d: %s\n", int( i ), formatNode( args[i] ) );
825
826 return args;
827}
828
829
830// Flatten a pure identifier chain (e.g. "A" or "A.Parent") that forms the receiver of a
831// property or method access into a dotted variable name. Returns false when the receiver
832// is anything else (for example the result of another method call), which is unsupported.
833// Every node consumed is marked visited so the code generator does not re-process it.
834static bool flattenVarChain( TREE_NODE* aNode, wxString& aResult )
835{
836 if( aNode->op == TR_IDENTIFIER )
837 {
838 aResult = formatNode( aNode );
839 aNode->isVisited = true;
840 return true;
841 }
842
843 if( aNode->op == TR_STRUCT_REF && aNode->leaf[0] && aNode->leaf[1]
844 && aNode->leaf[1]->op == TR_IDENTIFIER )
845 {
846 wxString base;
847
848 if( !flattenVarChain( aNode->leaf[0], base ) )
849 return false;
850
851 aResult = base + wxT( "." ) + formatNode( aNode->leaf[1] );
852 aNode->leaf[1]->isVisited = true;
853 aNode->isVisited = true;
854 return true;
855 }
856
857 return false;
858}
859
860
861bool COMPILER::generateUCode( UCODE* aCode, CONTEXT* aPreflightContext )
862{
863 std::vector<TREE_NODE*> stack;
864 wxString msg;
865 int numericValueCount = 0;
866 wxString missingUnitsMsg;
867 int missingUnitsSrcPos = 0;
868
869 // Short-circuit jumps for && / || awaiting their target backpatched once the right-hand
870 // operand's microcode has been emitted.
871 std::map<TREE_NODE*, UOP*> scJumps;
872
873 if( !m_tree )
874 {
875 std::unique_ptr<VALUE> val = std::make_unique<VALUE>( 1.0 );
876 // Empty expression returns true
877 aCode->AddOp( new UOP( TR_UOP_PUSH_VALUE, std::move(val) ) );
878 return true;
879 }
880
882
883 stack.push_back( m_tree );
884
885 wxString dump;
886
887 dumpNode( dump, m_tree, 0 );
888 libeval_dbg( 3, "Tree dump:\n%s\n\n", (const char*) dump.c_str() );
889
890 while( !stack.empty() )
891 {
892 TREE_NODE* node = stack.back();
893
894 libeval_dbg( 4, "process node %p [op %d] [stack %lu]\n", node, node->op, (unsigned long)stack.size() );
895
896 // process terminal nodes first
897 switch( node->op )
898 {
899 case TR_OP_FUNC_CALL:
900 // Function call's uop was generated inside TR_STRUCT_REF
901 if( !node->uop )
902 {
903 // This function call is bare so we don't know who to apply it to
904 // Set a safe default value to exit gracefully with an error
905 reportError( CST_CODEGEN, _( "Unknown parent of function parameters" ), node->srcPos );
907 }
908
909 node->isTerminal = true;
910 break;
911
912 case TR_STRUCT_REF:
913 {
914 // leaf[0]: object (a variable name, or a "." chain such as "A.Parent")
915 // leaf[1]: field (TR_IDENTIFIER) or TR_OP_FUNC_CALL
916
917 wxString itemName;
918
919 if( !flattenVarChain( node->leaf[0], itemName ) )
920 {
921 int pos = node->leaf[0]->srcPos;
922
923 if( node->leaf[0]->value.str )
924 pos -= static_cast<int>( formatNode( node->leaf[0] ).length() );
925
926 reportError( CST_CODEGEN, _( "Unknown parent of property" ), pos );
927
928 node->leaf[0]->isVisited = true;
929 node->leaf[1]->isVisited = true;
930
932 node->isTerminal = true;
933 break;
934 }
935
936 switch( node->leaf[1]->op )
937 {
938 case TR_IDENTIFIER:
939 {
940 // leaf[0]: object
941 // leaf[1]: field
942
943 wxString propName = formatNode( node->leaf[1] );
944 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( itemName, propName );
945
946 if( !vref )
947 {
948 msg.Printf( _( "Unrecognized item '%s'" ), itemName );
949 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos - (int) itemName.length() );
950 }
951 else if( vref->GetType() == VT_PARSE_ERROR )
952 {
953 msg.Printf( _( "Unrecognized property '%s'" ), propName );
954 reportError( CST_CODEGEN, msg, node->leaf[1]->srcPos - (int) propName.length() );
955 }
956
957 node->leaf[1]->isVisited = true;
958
959 node->SetUop( TR_UOP_PUSH_VAR, std::move( vref ) );
960 node->isTerminal = true;
961 break;
962 }
963 case TR_OP_FUNC_CALL:
964 {
965 // leaf[0]: object
966 // leaf[1]: TR_OP_FUNC_CALL
967 // leaf[0]: function name
968 // leaf[1]: parameter
969
970 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( itemName, "" );
971
972 if( !vref )
973 {
974 msg.Printf( _( "Unrecognized item '%s'" ), itemName );
975 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos - (int) itemName.length() );
976 }
977
978 wxString functionName = formatNode( node->leaf[1]->leaf[0] );
979 auto func = aCode->CreateFuncCall( functionName );
980 std::vector<TREE_NODE*> params = squashParamList( node->leaf[1]->leaf[1] );
981
982 libeval_dbg( 10, "emit func call: %s\n", functionName );
983
984 if( !func )
985 {
986 msg.Printf( _( "Unrecognized function '%s'" ), functionName );
987 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos + 1 );
988 }
989
990 // Expressions have no value until runtime; formatNode() would turn
991 // them into empty strings and falsely report missing arguments
992 const bool literalArgs = std::all_of( params.begin(), params.end(),
993 []( const TREE_NODE* param )
994 {
995 return param->op == TR_STRING || param->op == TR_NUMBER;
996 } );
997
998 if( func && literalArgs )
999 {
1000 for( TREE_NODE* pnode : params )
1001 {
1002 VALUE* param = aPreflightContext->AllocValue();
1003 param->Set( formatNode( pnode ) );
1004 aPreflightContext->Push( param );
1005 }
1006
1007 aPreflightContext->SetErrorCallback(
1008 [&]( const wxString& aMessage, int aOffset )
1009 {
1010 size_t loc = node->leaf[1]->leaf[1]->srcPos;
1011 reportError( CST_CODEGEN, aMessage, (int) loc - 1 );
1012 } );
1013
1014 try
1015 {
1016 func( aPreflightContext, vref.get() );
1017 aPreflightContext->Pop(); // return value
1018 }
1019 catch( ... )
1020 {
1021 }
1022 }
1023
1024 node->leaf[0]->isVisited = true;
1025 node->leaf[1]->isVisited = true;
1026 node->leaf[1]->leaf[0]->isVisited = true;
1027 node->leaf[1]->leaf[1]->isVisited = true;
1028
1029 // Our non-terminal-node stacking algorithm can't handle doubly-nested
1030 // structures so we need to pop a level by replacing the TR_STRUCT_REF with
1031 // a TR_OP_FUNC_CALL and its function parameter
1032 stack.pop_back();
1033 stack.push_back( node->leaf[1] );
1034
1035 for( TREE_NODE* pnode : params )
1036 stack.push_back( pnode );
1037
1038 node->leaf[1]->SetUop( TR_OP_METHOD_CALL, std::move( func ), std::move( vref ) );
1039 node->isTerminal = false;
1040 break;
1041 }
1042
1043 default:
1044 // leaf[0]: object
1045 // leaf[1]: malformed syntax
1046
1047 wxString propName = formatNode( node->leaf[1] );
1048 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( itemName, propName );
1049
1050 if( !vref )
1051 {
1052 msg.Printf( _( "Unrecognized item '%s'" ), itemName );
1053 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos - (int) itemName.length() );
1054 }
1055
1056 msg.Printf( _( "Unrecognized property '%s'" ), propName );
1057 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos + 1 );
1058
1059 node->leaf[0]->isVisited = true;
1060 node->leaf[1]->isVisited = true;
1061
1063 node->isTerminal = true;
1064 break;
1065 }
1066
1067 break;
1068 }
1069
1070 case TR_NUMBER:
1071 {
1072 TREE_NODE* son = node->leaf[0];
1073 double value;
1075
1076 if( !node->value.str )
1077 {
1078 value = 0.0;
1079 }
1080 else if( son && son->op == TR_UNIT )
1081 {
1082 if( m_unitResolver->GetSupportedUnits().empty() )
1083 {
1084 msg.Printf( _( "Unexpected units for '%s'" ), formatNode( node ) );
1085 reportError( CST_CODEGEN, msg, node->srcPos );
1086 }
1087
1088 int units = son->value.idx;
1089 unitsType = m_unitResolver->GetSupportedUnitsTypes().at( units );
1090 value = m_unitResolver->Convert( formatNode( node ), units );
1091 son->isVisited = true;
1092 }
1093 else
1094 {
1095 if( !m_unitResolver->GetSupportedUnitsMessage().empty() )
1096 {
1097 missingUnitsMsg.Printf( _( "Missing units for '%s'| (%s)" ),
1098 formatNode( node ),
1099 m_unitResolver->GetSupportedUnitsMessage() );
1100 missingUnitsSrcPos = node->srcPos;
1101 }
1102
1104 }
1105
1106 node->SetUop( TR_UOP_PUSH_VALUE, value, unitsType );
1107 node->isTerminal = true;
1108 numericValueCount++;
1109 break;
1110 }
1111
1112 case TR_STRING:
1113 {
1114 wxString str = formatNode( node );
1115 bool isWildcard = str.Contains("?") || str.Contains("*");
1116 node->SetUop( TR_UOP_PUSH_VALUE, str, isWildcard );
1117 node->isTerminal = true;
1118 break;
1119 }
1120
1121 case TR_IDENTIFIER:
1122 {
1123 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( formatNode( node ), "" );
1124
1125 if( !vref )
1126 {
1127 msg.Printf( _( "Unrecognized item '%s'" ), formatNode( node ) );
1128 reportError( CST_CODEGEN, msg, node->srcPos - (int) formatNode( node ).length() );
1129 }
1130
1131 node->SetUop( TR_UOP_PUSH_VAR, std::move( vref ) );
1132 node->isTerminal = true;
1133 break;
1134 }
1135
1136 default:
1137 node->SetUop( node->op );
1138 node->isTerminal = ( !node->leaf[0] || node->leaf[0]->isVisited )
1139 && ( !node->leaf[1] || node->leaf[1]->isVisited );
1140 break;
1141 }
1142
1143 if( !node->isTerminal )
1144 {
1145 if( node->leaf[0] && !node->leaf[0]->isVisited )
1146 {
1147 stack.push_back( node->leaf[0] );
1148 node->leaf[0]->isVisited = true;
1149 }
1150 else if( node->leaf[1] && !node->leaf[1]->isVisited )
1151 {
1152 // The left operand's microcode is now fully emitted. For && / || insert the
1153 // short-circuit jump here, before the right operand, so the right side can be
1154 // skipped at run time when the left already decides the result.
1155 if( node->op == TR_OP_BOOL_AND || node->op == TR_OP_BOOL_OR )
1156 {
1157 UOP* jump = new UOP( node->op == TR_OP_BOOL_AND ? TR_OP_JZ : TR_OP_JNZ );
1158 aCode->AddOp( jump );
1159 aCode->MarkHasJumps();
1160 scJumps[node] = jump;
1161 }
1162
1163 stack.push_back( node->leaf[1] );
1164 node->leaf[1]->isVisited = true;
1165 }
1166
1167 continue;
1168 }
1169
1170 node->isVisited = true;
1171
1172 if( node->uop )
1173 {
1174 aCode->AddOp( node->uop );
1175 node->uop = nullptr;
1176
1177 // Backpatch the matching short-circuit jump to land just past this combine op.
1178 auto jumpIt = scJumps.find( node );
1179
1180 if( jumpIt != scJumps.end() )
1181 jumpIt->second->SetJumpTarget( aCode->GetSize() );
1182 }
1183
1184 stack.pop_back();
1185 }
1186
1187 // Report the common error condition of a single numeric value with no units (which will result in
1188 // nanometers, and rarely leads to anything useful).
1189 // Reporting missing units with multiple numeric values is far more complicated as we have to
1190 // separate comparison operators from multiplicative operators from additive operators. Consider:
1191 // 2 * 1.5mm
1192 // 2mm + 105um
1193 // 2mm > 20um
1194 // (2mm + 1.5mm) * 3
1195 if( !missingUnitsMsg.IsEmpty() && numericValueCount == 1 )
1196 reportError( CST_CODEGEN, missingUnitsMsg, missingUnitsSrcPos );
1197
1198 libeval_dbg(2,"dump: \n%s\n", aCode->Dump().c_str() );
1199
1200 return true;
1201}
1202
1203
1204// Normalized boolean results pushed when && / || short-circuits, so the skipped combine op's output
1205// contract (an UNSCALED numeric 1/0) is met for any enclosing operator without allocating a fresh
1206// value on the hot path. A numeric VALUE is immutable once set (only deferred values mutate on
1207// read), so DRC's worker threads can share these by reference, just as numeric literals are.
1208static const VALUE g_shortCircuitFalse( 0.0 );
1209static const VALUE g_shortCircuitTrue( 1.0 );
1210
1211
1213{
1214 switch( m_op )
1215 {
1216 case TR_OP_JZ:
1217 // Short-circuit && when the left side is false, then jump past the right-hand microcode.
1218 if( ctx->Top()->AsDouble() == 0.0 )
1219 {
1220 ctx->Pop();
1221 ctx->Push( const_cast<VALUE*>( &g_shortCircuitFalse ) );
1222 return m_jumpTarget;
1223 }
1224
1225 return -1;
1226
1227 case TR_OP_JNZ:
1228 // Short-circuit || when the left side is true, then jump past the right-hand microcode.
1229 if( ctx->Top()->AsDouble() != 0.0 )
1230 {
1231 ctx->Pop();
1232 ctx->Push( const_cast<VALUE*>( &g_shortCircuitTrue ) );
1233 return m_jumpTarget;
1234 }
1235
1236 return -1;
1237
1238 case TR_UOP_PUSH_VAR:
1239 {
1240 VALUE* value = nullptr;
1241
1242 if( m_ref )
1243 value = ctx->StoreValue( m_ref->GetValue( ctx ) );
1244 else
1245 value = ctx->AllocValue();
1246
1247 ctx->Push( value );
1248 break;
1249 }
1250
1251 case TR_UOP_PUSH_VALUE:
1252 // String literals contain a wxString whose internal mb_str cache is mutated by
1253 // ToUTF8/utf8_str. DRC evaluates compiled rules from many threads concurrently,
1254 // so push a per-thread copy to avoid racing on that cache.
1255 if( m_value && m_value->GetType() == VT_STRING )
1256 {
1257 VALUE* copy = ctx->AllocValue();
1258 copy->Set( *m_value );
1259 ctx->Push( copy );
1260 }
1261 else
1262 {
1263 ctx->Push( m_value.get() );
1264 }
1265
1266 return -1;
1267
1268 case TR_OP_METHOD_CALL:
1269 if( m_func )
1270 m_func( ctx, m_ref.get() );
1271
1272 return -1;
1273
1274 default:
1275 break;
1276 }
1277
1278#define AS_DOUBLE( arg ) ( arg ? arg->AsDouble() : 0.0 )
1279
1280 if( m_op & TR_OP_BINARY_MASK )
1281 {
1282 LIBEVAL::VALUE* arg2 = ctx->Pop();
1283 LIBEVAL::VALUE* arg1 = ctx->Pop();
1284 double result;
1285
1286 if( ctx->HasErrorCallback() )
1287 {
1288 if( arg1 && arg1->GetType() == VT_STRING && arg2 && arg2->GetType() == VT_NUMERIC )
1289 {
1290 ctx->ReportError( wxString::Format( _( "Type mismatch between '%s' and %lf" ),
1291 arg1->AsString(),
1292 arg2->AsDouble() ) );
1293 }
1294 else if( arg1 && arg1->GetType() == VT_NUMERIC && arg2 && arg2->GetType() == VT_STRING )
1295 {
1296 ctx->ReportError( wxString::Format( _( "Type mismatch between %lf and '%s'" ),
1297 arg1->AsDouble(),
1298 arg2->AsString() ) );
1299 }
1300 }
1301
1302 // TODO: This doesn't fully calculate units correctly yet. We really need to work out the dimensional analysis
1303 // TODO: (and do this independently of unit specifics - e.g. MM + INCH needs to return one of the dimension
1304 // TODO: types, but our units framework doesn't currently allow this. Therefore, use some heuristics to
1305 // TODO: determine the resulting operation unit type for now
1306 auto getOpResultUnits =
1307 []( const VALUE* aVal1, const VALUE* aVal2 )
1308 {
1309 wxCHECK( aVal1 && aVal2, EDA_UNITS::UNSCALED );
1310
1311 // This condition can occur in, e.g., a unary negation operation
1312 if( aVal1->GetUnits() == EDA_UNITS::UNSCALED && aVal2->GetUnits() != EDA_UNITS::UNSCALED )
1313 return aVal2->GetUnits();
1314
1315 if( aVal1->GetUnits() != EDA_UNITS::UNSCALED && aVal2->GetUnits() == EDA_UNITS::UNSCALED )
1316 return aVal1->GetUnits();
1317
1318 return aVal2->GetUnits();
1319 };
1320
1321 EDA_UNITS resultUnits = EDA_UNITS::UNSCALED;
1322
1323 switch( m_op )
1324 {
1325 case TR_OP_ADD:
1326 result = AS_DOUBLE( arg1 ) + AS_DOUBLE( arg2 );
1327 resultUnits = getOpResultUnits( arg1, arg2 );
1328 break;
1329
1330 case TR_OP_SUB:
1331 result = AS_DOUBLE( arg1 ) - AS_DOUBLE( arg2 );
1332 resultUnits = getOpResultUnits( arg1, arg2 );
1333 break;
1334
1335 case TR_OP_MUL:
1336 result = AS_DOUBLE( arg1 ) * AS_DOUBLE( arg2 );
1337 resultUnits = getOpResultUnits( arg1, arg2 );
1338 break;
1339
1340 case TR_OP_DIV:
1341 result = AS_DOUBLE( arg1 ) / AS_DOUBLE( arg2 );
1342 resultUnits = getOpResultUnits( arg1, arg2 );
1343 break;
1344
1345 case TR_OP_LESS_EQUAL:
1346 result = AS_DOUBLE( arg1 ) <= AS_DOUBLE( arg2 ) ? 1 : 0;
1347 break;
1348
1350 result = AS_DOUBLE( arg1 ) >= AS_DOUBLE( arg2 ) ? 1 : 0;
1351 break;
1352
1353 case TR_OP_LESS:
1354 result = AS_DOUBLE( arg1 ) < AS_DOUBLE( arg2 ) ? 1 : 0;
1355 break;
1356 case TR_OP_GREATER:
1357 result = AS_DOUBLE( arg1 ) > AS_DOUBLE( arg2 ) ? 1 : 0;
1358 break;
1359
1360 case TR_OP_EQUAL:
1361 if( !arg1 || !arg2 )
1362 result = arg1 == arg2 ? 1 : 0;
1363 else if( arg2->GetType() == VT_UNDEFINED )
1364 result = arg2->EqualTo( ctx, arg1 ) ? 1 : 0;
1365 else
1366 result = arg1->EqualTo( ctx, arg2 ) ? 1 : 0;
1367 break;
1368
1369 case TR_OP_NOT_EQUAL:
1370 if( !arg1 || !arg2 )
1371 result = arg1 != arg2 ? 1 : 0;
1372 else if( arg2->GetType() == VT_UNDEFINED )
1373 result = arg2->NotEqualTo( ctx, arg1 ) ? 1 : 0;
1374 else
1375 result = arg1->NotEqualTo( ctx, arg2 ) ? 1 : 0;
1376 break;
1377
1378 case TR_OP_BOOL_AND:
1379 result = AS_DOUBLE( arg1 ) != 0.0 && AS_DOUBLE( arg2 ) != 0.0 ? 1 : 0;
1380 break;
1381
1382 case TR_OP_BOOL_OR:
1383 result = AS_DOUBLE( arg1 ) != 0.0 || AS_DOUBLE( arg2 ) != 0.0 ? 1 : 0;
1384 break;
1385
1386 default:
1387 result = 0.0;
1388 break;
1389 }
1390
1391 VALUE* rp = ctx->AllocValue();
1392 rp->Set( result );
1393 rp->SetUnits( resultUnits );
1394 ctx->Push( rp );
1395 return -1;
1396 }
1397 else if( m_op & TR_OP_UNARY_MASK )
1398 {
1399 LIBEVAL::VALUE* arg1 = ctx->Pop();
1400 double ARG1VALUE = arg1 ? arg1->AsDouble() : 0.0;
1401 double result;
1402 EDA_UNITS resultUnits = arg1 ? arg1->GetUnits() : EDA_UNITS::UNSCALED;
1403
1404 switch( m_op )
1405 {
1406 case TR_OP_BOOL_NOT:
1407 result = ARG1VALUE != 0.0 ? 0 : 1;
1408 break;
1409 default:
1410 result = ARG1VALUE != 0.0 ? 1 : 0;
1411 break;
1412 }
1413
1414 VALUE* rp = ctx->AllocValue();
1415 rp->Set( result );
1416 rp->SetUnits( resultUnits );
1417 ctx->Push( rp );
1418 return -1;
1419 }
1420
1421 return -1;
1422}
1423
1424
1426{
1427 try
1428 {
1429 if( m_hasJumps )
1430 {
1431 for( std::size_t ip = 0; ip < m_ucode.size(); )
1432 {
1433 int next = m_ucode[ip]->Exec( ctx );
1434
1435 // Backpatched jump targets are always forward, past the combine op; a non-forward
1436 // or stale target would spin or read out of range, so reject it.
1437 wxASSERT( next < 0 || static_cast<std::size_t>( next ) > ip );
1438
1440 }
1441 }
1442 else
1443 {
1444 for( UOP* op : m_ucode )
1445 op->Exec( ctx );
1446 }
1447 }
1448 catch(...)
1449 {
1450 // rules which fail outright should not be fired; return 0/false
1451 return ctx->StoreValue( new VALUE( 0 ) );
1452 }
1453
1454 if( ctx->SP() == 1 )
1455 {
1456 return ctx->Pop();
1457 }
1458 else
1459 {
1460 // If stack is corrupted after execution it suggests a problem with the compiler, not
1461 // the rule....
1462
1463 // do not use "assert"; it crashes outright on OSX
1464 wxASSERT( ctx->SP() == 1 );
1465
1466 // non-well-formed rules should not be fired on a release build
1467 return ctx->StoreValue( new VALUE( 0 ) );
1468 }
1469}
1470
1471
1472} // namespace LIBEVAL
std::unique_ptr< UNIT_RESOLVER > m_unitResolver
void newString(const wxString &aString)
void freeTree(LIBEVAL::TREE_NODE *tree)
bool lexString(T_TOKEN &aToken)
void GcItem(TREE_NODE *aItem)
bool generateUCode(UCODE *aCode, CONTEXT *aPreflightContext)
std::function< void(const wxString &aMessage, int aOffset)> m_errorCallback
bool lexDefault(T_TOKEN &aToken)
std::vector< TREE_NODE * > m_gcItems
void reportError(COMPILATION_STAGE stage, const wxString &aErrorMsg, int aPos=-1)
void setRoot(LIBEVAL::TREE_NODE *root)
std::vector< wxString * > m_gcStrings
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
ERROR_STATUS m_errorStatus
void parseError(const char *s)
VALUE * StoreValue(VALUE *aValue)
void ReportError(const wxString &aErrorMsg)
std::function< void(const wxString &aMessage, int aOffset)> m_errorCallback
void SetErrorCallback(std::function< void(const wxString &aMessage, int aOffset)> aCallback)
VALUE * Top()
Peek the top of the stack without popping (used by the short-circuit jumps).
void Push(VALUE *v)
wxString GetChars(const std::function< bool(wxUniChar)> &cond) const
bool MatchAhead(const wxString &match, const std::function< bool(wxUniChar)> &stopCond) const
void SetUop(int aOp, double aValue, EDA_UNITS aUnits)
virtual std::unique_ptr< VAR_REF > CreateVarRef(const wxString &var, const wxString &field)
void AddOp(UOP *uop)
std::vector< UOP * > m_ucode
wxString Dump() const
void MarkHasJumps()
Flag that this ucode contains short-circuit jumps, so Run() uses the jump-aware loop.
int GetSize() const
Index of the next op to be added (used to backpatch short-circuit jump targets).
VALUE * Run(CONTEXT *ctx)
virtual FUNC_CALL_REF CreateFuncCall(const wxString &name)
std::unique_ptr< VAR_REF > m_ref
FUNC_CALL_REF m_func
wxString Format() const
int Exec(CONTEXT *ctx)
Execute the op.
std::unique_ptr< VALUE > m_value
void Set(double aValue)
virtual const wxString & AsString() const
void SetUnits(const EDA_UNITS aUnits)
virtual bool NotEqualTo(CONTEXT *aCtx, const VALUE *b) const
virtual double AsDouble() const
EDA_UNITS GetUnits() const
VAR_TYPE_T GetType() const
virtual bool EqualTo(CONTEXT *aCtx, const VALUE *b) const
#define _(s)
EDA_UNITS
Definition eda_units.h:44
unitsType
#define libeval_dbg(level, fmt,...)
#define AS_DOUBLE(arg)
#define TR_OP_GREATER_EQUAL
#define TR_OP_BOOL_AND
#define TR_OP_MUL
#define TR_UOP_PUSH_VAR
#define TR_UOP_PUSH_VALUE
#define TR_OP_BOOL_OR
#define TR_OP_GREATER
#define TR_OP_EQUAL
#define TR_OP_JZ
#define TR_OP_ADD
#define TR_OP_FUNC_CALL
#define TR_OP_SUB
#define TR_OP_METHOD_CALL
#define TR_OP_UNARY_MASK
#define TR_OP_LESS_EQUAL
#define TR_OP_BINARY_MASK
#define TR_OP_LESS
#define TR_OP_DIV
#define TR_OP_JNZ
#define TR_OP_NOT_EQUAL
#define TR_OP_BOOL_NOT
KICOMMON_API double DoubleValueFromString(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Convert aTextValue to a double.
TREE_NODE * newNode(LIBEVAL::COMPILER *compiler, int op, const T_TOKEN_VALUE &value)
static bool flattenVarChain(TREE_NODE *aNode, wxString &aResult)
static std::vector< TREE_NODE * > squashParamList(TREE_NODE *root)
constexpr T_TOKEN defaultToken
void dumpNode(wxString &buf, TREE_NODE *tok, int depth=0)
static const VALUE g_shortCircuitFalse(0.0)
constexpr T_TOKEN_VALUE defaultTokenValue
std::function< void(CONTEXT *, void *)> FUNC_CALL_REF
static const wxString formatOpName(int op)
static const VALUE g_shortCircuitTrue(1.0)
const wxString formatNode(TREE_NODE *node)
static void prepareTree(LIBEVAL::TREE_NODE *node)
CITER next(CITER it)
Definition ptree.cpp:120
bool WildCompareString(const wxString &pattern, const wxString &string_to_tst, bool case_sensitive)
Compare a string against wild card (* and ?) pattern using the usual rules.
T_TOKEN_VALUE value
@ VALUE
Field Value of part, i.e. "3.3K".
wxString result
Test unit parsing edge cases and error handling.
wxString dump(const wxArrayString &aArray)
Debug helper for printing wxArrayString contents.