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 wxString msg;
460
461 retval.value.str = nullptr;
462 retval.value.num = 0.0;
463 retval.value.idx = -1;
464 retval.token = G_ENDS;
465
466 if( m_tokenizer.Done() )
467 {
468 aToken = retval;
469 return true;
470 }
471
472 auto isDecimalSeparator =
473 [&]( wxUniChar ch ) -> bool
474 {
475 return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
476 };
477
478 // Lambda: get value as string, store into clToken.token and update current index.
479 auto extractNumber =
480 [&]()
481 {
482 bool haveSeparator = false;
483 wxUniChar ch = m_tokenizer.GetChar();
484
485 do
486 {
487 if( isDecimalSeparator( ch ) && haveSeparator )
488 break;
489
490 current.append( 1, ch );
491
492 if( isDecimalSeparator( ch ) )
493 haveSeparator = true;
494
495 m_tokenizer.NextChar();
496 ch = m_tokenizer.GetChar();
497 } while( isdigit( ch ) || isDecimalSeparator( ch ) );
498
499 // Ensure that the systems decimal separator is used
500 for( int i = current.length(); i; i-- )
501 {
502 if( isDecimalSeparator( current[i - 1] ) )
503 current[i - 1] = m_localeDecimalSeparator;
504 }
505 };
506
507 int ch;
508
509 // Start processing of first/next token: Remove whitespace
510 for( ;; )
511 {
512 ch = m_tokenizer.GetChar();
513
514 if( ch == ' ' )
515 m_tokenizer.NextChar();
516 else
517 break;
518 }
519
520 libeval_dbg(10, "LEX ch '%c' pos %lu\n", ch, (unsigned long)m_tokenizer.GetPos() );
521
522 if( ch == 0 )
523 {
524 /* End of input */
525 }
526 else if( isdigit( ch ) )
527 {
528 // VALUE
529 extractNumber();
530 retval.token = G_VALUE;
531 retval.value.str = new wxString( current );
532 }
533 else if( ( convertFrom = resolveUnits() ) >= 0 )
534 {
535 // UNIT
536 // Units are appended to a VALUE.
537 // Determine factor to default unit if unit for value is given.
538 // Example: Default is mm, unit is inch: factor is 25.4
539 // The factor is assigned to the terminal UNIT. The actual
540 // conversion is done within a parser action.
541 retval.token = G_UNIT;
542 retval.value.idx = convertFrom;
543 }
544 else if( ch == '\'' ) // string literal
545 {
547 m_tokenizer.NextChar();
548 return false;
549 }
550 else if( isalpha( ch ) || ch == '_' )
551 {
552 current = m_tokenizer.GetChars( []( int c ) -> bool { return isalnum( c ) || c == '_'; } );
553 retval.token = G_IDENTIFIER;
554 retval.value.str = new wxString( current );
555 m_tokenizer.NextChar( current.length() );
556 }
557 else if( m_tokenizer.MatchAhead( "==", []( int c ) -> bool { return c != '='; } ) )
558 {
559 retval.token = G_EQUAL;
560 m_tokenizer.NextChar( 2 );
561 }
562 else if( m_tokenizer.MatchAhead( "!=", []( int c ) -> bool { return c != '='; } ) )
563 {
564 retval.token = G_NOT_EQUAL;
565 m_tokenizer.NextChar( 2 );
566 }
567 else if( m_tokenizer.MatchAhead( "<=", []( int c ) -> bool { return c != '='; } ) )
568 {
569 retval.token = G_LESS_EQUAL_THAN;
570 m_tokenizer.NextChar( 2 );
571 }
572 else if( m_tokenizer.MatchAhead( ">=", []( int c ) -> bool { return c != '='; } ) )
573 {
574 retval.token = G_GREATER_EQUAL_THAN;
575 m_tokenizer.NextChar( 2 );
576 }
577 else if( m_tokenizer.MatchAhead( "&&", []( int c ) -> bool { return c != '&'; } ) )
578 {
579 retval.token = G_BOOL_AND;
580 m_tokenizer.NextChar( 2 );
581 }
582 else if( m_tokenizer.MatchAhead( "||", []( int c ) -> bool { return c != '|'; } ) )
583 {
584 retval.token = G_BOOL_OR;
585 m_tokenizer.NextChar( 2 );
586 }
587 else
588 {
589 // Single char tokens
590 switch( ch )
591 {
592 case '+': retval.token = G_PLUS; break;
593 case '!': retval.token = G_BOOL_NOT; break;
594 case '-': retval.token = G_MINUS; break;
595 case '*': retval.token = G_MULT; break;
596 case '/': retval.token = G_DIVIDE; break;
597 case '<': retval.token = G_LESS_THAN; break;
598 case '>': retval.token = G_GREATER_THAN; break;
599 case '(': retval.token = G_PARENL; break;
600 case ')': retval.token = G_PARENR; break;
601 case ';': retval.token = G_SEMCOL; break;
602 case '.': retval.token = G_STRUCT_REF; break;
603 case ',': retval.token = G_COMMA; break;
604
605 default:
606 if( m_tokenizer.MatchAhead( "${", []( int c ) -> bool { return c != '{'; } ) )
607 reportError( CST_PARSE, _( "Unresolved text variable reference" ), m_sourcePos + 2 );
608 else
609 reportError( CST_PARSE, wxString::Format( _( "Unrecognized character '%c'" ), (char) ch ) );
610 break;
611 }
612
613 m_tokenizer.NextChar();
614 }
615
616 aToken = retval;
617 return true;
618}
619
620
621const wxString formatNode( TREE_NODE* node )
622{
623 return node->value.str ? *(node->value.str) : wxString( wxEmptyString );
624}
625
626
627void dumpNode( wxString& buf, TREE_NODE* tok, int depth = 0 )
628{
629 wxString str;
630
631 if( !tok )
632 return;
633
634 str.Printf( "\n[%p L0:%-20p L1:%-20p] ", tok, tok->leaf[0], tok->leaf[1] );
635 buf += str;
636
637 for( int i = 0; i < 2 * depth; i++ )
638 buf += " ";
639
640 if( tok->op & TR_OP_BINARY_MASK )
641 {
642 buf += formatOpName( tok->op );
643 dumpNode( buf, tok->leaf[0], depth + 1 );
644 dumpNode( buf, tok->leaf[1], depth + 1 );
645 }
646
647 switch( tok->op )
648 {
649 case TR_NUMBER:
650 buf += "NUMERIC: ";
651 buf += formatNode( tok );
652
653 if( tok->leaf[0] )
654 dumpNode( buf, tok->leaf[0], depth + 1 );
655
656 break;
657
658 case TR_ARG_LIST:
659 buf += "ARG_LIST: ";
660 buf += formatNode( tok );
661
662 if( tok->leaf[0] )
663 dumpNode( buf, tok->leaf[0], depth + 1 );
664 if( tok->leaf[1] )
665 dumpNode( buf, tok->leaf[1], depth + 1 );
666
667 break;
668
669 case TR_STRING:
670 buf += "STRING: ";
671 buf += formatNode( tok );
672 break;
673
674 case TR_IDENTIFIER:
675 buf += "ID: ";
676 buf += formatNode( tok );
677 break;
678
679 case TR_STRUCT_REF:
680 buf += "SREF: ";
681 dumpNode( buf, tok->leaf[0], depth + 1 );
682 dumpNode( buf, tok->leaf[1], depth + 1 );
683 break;
684
685 case TR_OP_FUNC_CALL:
686 buf += "CALL '";
687 buf += formatNode( tok->leaf[0] );
688 buf += "': ";
689 dumpNode( buf, tok->leaf[1], depth + 1 );
690 break;
691
692 case TR_UNIT:
693 str.Printf( "UNIT: %d ", tok->value.idx );
694 buf += str;
695 break;
696 }
697}
698
699
700void CONTEXT::ReportError( const wxString& aErrorMsg )
701{
702 if( m_errorCallback )
703 m_errorCallback( aErrorMsg, -1 );
704}
705
706
707void COMPILER::reportError( COMPILATION_STAGE stage, const wxString& aErrorMsg, int aPos )
708{
709 if( aPos == -1 )
710 aPos = m_sourcePos;
711
712 m_errorStatus.pendingError = true;
713 m_errorStatus.stage = stage;
714 m_errorStatus.message = aErrorMsg;
715 m_errorStatus.srcPos = aPos;
716
717 if( m_errorCallback )
718 m_errorCallback( aErrorMsg, aPos );
719}
720
721
723{
724 m_tree = root;
725}
726
727
729{
730 if ( tree->leaf[0] )
731 freeTree( tree->leaf[0] );
732
733 if ( tree->leaf[1] )
734 freeTree( tree->leaf[1] );
735
736 delete tree->uop;
737 tree->uop = nullptr;
738}
739
740
741void TREE_NODE::SetUop( int aOp, double aValue, EDA_UNITS aUnits )
742{
743 delete uop;
744
745 std::unique_ptr<VALUE> val = std::make_unique<VALUE>( aValue );
746 val->SetUnits( aUnits );
747 uop = new UOP( aOp, std::move( val ) );
748}
749
750
751void TREE_NODE::SetUop( int aOp, const wxString& aValue, bool aStringIsWildcard )
752{
753 delete uop;
754
755 std::unique_ptr<VALUE> val = std::make_unique<VALUE>( aValue, aStringIsWildcard );
756 uop = new UOP( aOp, std::move( val ) );
757}
758
759
760void TREE_NODE::SetUop( int aOp, std::unique_ptr<VAR_REF> aRef )
761{
762 delete uop;
763
764 uop = new UOP( aOp, std::move( aRef ) );
765}
766
767
768void TREE_NODE::SetUop( int aOp, FUNC_CALL_REF aFunc, std::unique_ptr<VAR_REF> aRef )
769{
770 delete uop;
771
772 uop = new UOP( aOp, std::move( aFunc ), std::move( aRef ) );
773}
774
775
776static void prepareTree( LIBEVAL::TREE_NODE *node )
777{
778 node->isVisited = false;
779
780 // fixme: for reasons I don't understand the lemon parser isn't initializing the
781 // leaf node pointers of function name nodes. -JY
782 if( node->op == TR_OP_FUNC_CALL && node->leaf[0] )
783 {
784 node->leaf[0]->leaf[0] = nullptr;
785 node->leaf[0]->leaf[1] = nullptr;
786 }
787
788 if ( node->leaf[0] )
789 prepareTree( node->leaf[0] );
790
791 if ( node->leaf[1] )
792 prepareTree( node->leaf[1] );
793}
794
795
796static std::vector<TREE_NODE*> squashParamList( TREE_NODE* root )
797{
798 std::vector<TREE_NODE*> args;
799
800 if( !root )
801 return args;
802
803 if( root->op != TR_ARG_LIST && root->op != TR_NULL )
804 {
805 args.push_back( root );
806 }
807 else
808 {
809 TREE_NODE *n = root;
810 do
811 {
812 if( n->leaf[1] )
813 args.push_back(n->leaf[1]);
814
815 n = n->leaf[0];
816 } while ( n && n->op == TR_ARG_LIST );
817
818 if( n )
819 args.push_back( n );
820 }
821
822 std::reverse( args.begin(), args.end() );
823
824 for( size_t i = 0; i < args.size(); i++ )
825 libeval_dbg( 10, "squash arg%d: %s\n", int( i ), formatNode( args[i] ) );
826
827 return args;
828}
829
830
831// Flatten a pure identifier chain (e.g. "A" or "A.Parent") that forms the receiver of a
832// property or method access into a dotted variable name. Returns false when the receiver
833// is anything else (for example the result of another method call), which is unsupported.
834// Every node consumed is marked visited so the code generator does not re-process it.
835static bool flattenVarChain( TREE_NODE* aNode, wxString& aResult )
836{
837 if( aNode->op == TR_IDENTIFIER )
838 {
839 aResult = formatNode( aNode );
840 aNode->isVisited = true;
841 return true;
842 }
843
844 if( aNode->op == TR_STRUCT_REF && aNode->leaf[0] && aNode->leaf[1]
845 && aNode->leaf[1]->op == TR_IDENTIFIER )
846 {
847 wxString base;
848
849 if( !flattenVarChain( aNode->leaf[0], base ) )
850 return false;
851
852 aResult = base + wxT( "." ) + formatNode( aNode->leaf[1] );
853 aNode->leaf[1]->isVisited = true;
854 aNode->isVisited = true;
855 return true;
856 }
857
858 return false;
859}
860
861
862bool COMPILER::generateUCode( UCODE* aCode, CONTEXT* aPreflightContext )
863{
864 std::vector<TREE_NODE*> stack;
865 wxString msg;
866 int numericValueCount = 0;
867 wxString missingUnitsMsg;
868 int missingUnitsSrcPos = 0;
869
870 // Short-circuit jumps for && / || awaiting their target backpatched once the right-hand
871 // operand's microcode has been emitted.
872 std::map<TREE_NODE*, UOP*> scJumps;
873
874 if( !m_tree )
875 {
876 std::unique_ptr<VALUE> val = std::make_unique<VALUE>( 1.0 );
877 // Empty expression returns true
878 aCode->AddOp( new UOP( TR_UOP_PUSH_VALUE, std::move(val) ) );
879 return true;
880 }
881
883
884 stack.push_back( m_tree );
885
886 wxString dump;
887
888 dumpNode( dump, m_tree, 0 );
889 libeval_dbg( 3, "Tree dump:\n%s\n\n", (const char*) dump.c_str() );
890
891 while( !stack.empty() )
892 {
893 TREE_NODE* node = stack.back();
894
895 libeval_dbg( 4, "process node %p [op %d] [stack %lu]\n", node, node->op, (unsigned long)stack.size() );
896
897 // process terminal nodes first
898 switch( node->op )
899 {
900 case TR_OP_FUNC_CALL:
901 // Function call's uop was generated inside TR_STRUCT_REF
902 if( !node->uop )
903 {
904 // This function call is bare so we don't know who to apply it to
905 // Set a safe default value to exit gracefully with an error
906 reportError( CST_CODEGEN, _( "Unknown parent of function parameters" ), node->srcPos );
908 }
909
910 node->isTerminal = true;
911 break;
912
913 case TR_STRUCT_REF:
914 {
915 // leaf[0]: object (a variable name, or a "." chain such as "A.Parent")
916 // leaf[1]: field (TR_IDENTIFIER) or TR_OP_FUNC_CALL
917
918 wxString itemName;
919
920 if( !flattenVarChain( node->leaf[0], itemName ) )
921 {
922 int pos = node->leaf[0]->srcPos;
923
924 if( node->leaf[0]->value.str )
925 pos -= static_cast<int>( formatNode( node->leaf[0] ).length() );
926
927 reportError( CST_CODEGEN, _( "Unknown parent of property" ), pos );
928
929 node->leaf[0]->isVisited = true;
930 node->leaf[1]->isVisited = true;
931
933 node->isTerminal = true;
934 break;
935 }
936
937 switch( node->leaf[1]->op )
938 {
939 case TR_IDENTIFIER:
940 {
941 // leaf[0]: object
942 // leaf[1]: field
943
944 wxString propName = formatNode( node->leaf[1] );
945 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( itemName, propName );
946
947 if( !vref )
948 {
949 msg.Printf( _( "Unrecognized item '%s'" ), itemName );
950 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos - (int) itemName.length() );
951 }
952 else if( vref->GetType() == VT_PARSE_ERROR )
953 {
954 msg.Printf( _( "Unrecognized property '%s'" ), propName );
955 reportError( CST_CODEGEN, msg, node->leaf[1]->srcPos - (int) propName.length() );
956 }
957
958 node->leaf[1]->isVisited = true;
959
960 node->SetUop( TR_UOP_PUSH_VAR, std::move( vref ) );
961 node->isTerminal = true;
962 break;
963 }
964 case TR_OP_FUNC_CALL:
965 {
966 // leaf[0]: object
967 // leaf[1]: TR_OP_FUNC_CALL
968 // leaf[0]: function name
969 // leaf[1]: parameter
970
971 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( itemName, "" );
972
973 if( !vref )
974 {
975 msg.Printf( _( "Unrecognized item '%s'" ), itemName );
976 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos - (int) itemName.length() );
977 }
978
979 wxString functionName = formatNode( node->leaf[1]->leaf[0] );
980 auto func = aCode->CreateFuncCall( functionName );
981 std::vector<TREE_NODE*> params = squashParamList( node->leaf[1]->leaf[1] );
982
983 libeval_dbg( 10, "emit func call: %s\n", functionName );
984
985 if( !func )
986 {
987 msg.Printf( _( "Unrecognized function '%s'" ), functionName );
988 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos + 1 );
989 }
990
991 if( func )
992 {
993 // Preflight the function call
994
995 for( TREE_NODE* pnode : params )
996 {
997 VALUE* param = aPreflightContext->AllocValue();
998 param->Set( formatNode( pnode ) );
999 aPreflightContext->Push( param );
1000 }
1001
1002 aPreflightContext->SetErrorCallback(
1003 [&]( const wxString& aMessage, int aOffset )
1004 {
1005 size_t loc = node->leaf[1]->leaf[1]->srcPos;
1006 reportError( CST_CODEGEN, aMessage, (int) loc - 1 );
1007 } );
1008
1009 try
1010 {
1011 func( aPreflightContext, vref.get() );
1012 aPreflightContext->Pop(); // return value
1013 }
1014 catch( ... )
1015 {
1016 }
1017 }
1018
1019 node->leaf[0]->isVisited = true;
1020 node->leaf[1]->isVisited = true;
1021 node->leaf[1]->leaf[0]->isVisited = true;
1022 node->leaf[1]->leaf[1]->isVisited = true;
1023
1024 // Our non-terminal-node stacking algorithm can't handle doubly-nested
1025 // structures so we need to pop a level by replacing the TR_STRUCT_REF with
1026 // a TR_OP_FUNC_CALL and its function parameter
1027 stack.pop_back();
1028 stack.push_back( node->leaf[1] );
1029
1030 for( TREE_NODE* pnode : params )
1031 stack.push_back( pnode );
1032
1033 node->leaf[1]->SetUop( TR_OP_METHOD_CALL, std::move( func ), std::move( vref ) );
1034 node->isTerminal = false;
1035 break;
1036 }
1037
1038 default:
1039 // leaf[0]: object
1040 // leaf[1]: malformed syntax
1041
1042 wxString propName = formatNode( node->leaf[1] );
1043 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( itemName, propName );
1044
1045 if( !vref )
1046 {
1047 msg.Printf( _( "Unrecognized item '%s'" ), itemName );
1048 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos - (int) itemName.length() );
1049 }
1050
1051 msg.Printf( _( "Unrecognized property '%s'" ), propName );
1052 reportError( CST_CODEGEN, msg, node->leaf[0]->srcPos + 1 );
1053
1054 node->leaf[0]->isVisited = true;
1055 node->leaf[1]->isVisited = true;
1056
1058 node->isTerminal = true;
1059 break;
1060 }
1061
1062 break;
1063 }
1064
1065 case TR_NUMBER:
1066 {
1067 TREE_NODE* son = node->leaf[0];
1068 double value;
1070
1071 if( !node->value.str )
1072 {
1073 value = 0.0;
1074 }
1075 else if( son && son->op == TR_UNIT )
1076 {
1077 if( m_unitResolver->GetSupportedUnits().empty() )
1078 {
1079 msg.Printf( _( "Unexpected units for '%s'" ), formatNode( node ) );
1080 reportError( CST_CODEGEN, msg, node->srcPos );
1081 }
1082
1083 int units = son->value.idx;
1084 unitsType = m_unitResolver->GetSupportedUnitsTypes().at( units );
1085 value = m_unitResolver->Convert( formatNode( node ), units );
1086 son->isVisited = true;
1087 }
1088 else
1089 {
1090 if( !m_unitResolver->GetSupportedUnitsMessage().empty() )
1091 {
1092 missingUnitsMsg.Printf( _( "Missing units for '%s'| (%s)" ),
1093 formatNode( node ),
1094 m_unitResolver->GetSupportedUnitsMessage() );
1095 missingUnitsSrcPos = node->srcPos;
1096 }
1097
1099 }
1100
1101 node->SetUop( TR_UOP_PUSH_VALUE, value, unitsType );
1102 node->isTerminal = true;
1103 numericValueCount++;
1104 break;
1105 }
1106
1107 case TR_STRING:
1108 {
1109 wxString str = formatNode( node );
1110 bool isWildcard = str.Contains("?") || str.Contains("*");
1111 node->SetUop( TR_UOP_PUSH_VALUE, str, isWildcard );
1112 node->isTerminal = true;
1113 break;
1114 }
1115
1116 case TR_IDENTIFIER:
1117 {
1118 std::unique_ptr<VAR_REF> vref = aCode->CreateVarRef( formatNode( node ), "" );
1119
1120 if( !vref )
1121 {
1122 msg.Printf( _( "Unrecognized item '%s'" ), formatNode( node ) );
1123 reportError( CST_CODEGEN, msg, node->srcPos - (int) formatNode( node ).length() );
1124 }
1125
1126 node->SetUop( TR_UOP_PUSH_VAR, std::move( vref ) );
1127 node->isTerminal = true;
1128 break;
1129 }
1130
1131 default:
1132 node->SetUop( node->op );
1133 node->isTerminal = ( !node->leaf[0] || node->leaf[0]->isVisited )
1134 && ( !node->leaf[1] || node->leaf[1]->isVisited );
1135 break;
1136 }
1137
1138 if( !node->isTerminal )
1139 {
1140 if( node->leaf[0] && !node->leaf[0]->isVisited )
1141 {
1142 stack.push_back( node->leaf[0] );
1143 node->leaf[0]->isVisited = true;
1144 }
1145 else if( node->leaf[1] && !node->leaf[1]->isVisited )
1146 {
1147 // The left operand's microcode is now fully emitted. For && / || insert the
1148 // short-circuit jump here, before the right operand, so the right side can be
1149 // skipped at run time when the left already decides the result.
1150 if( node->op == TR_OP_BOOL_AND || node->op == TR_OP_BOOL_OR )
1151 {
1152 UOP* jump = new UOP( node->op == TR_OP_BOOL_AND ? TR_OP_JZ : TR_OP_JNZ );
1153 aCode->AddOp( jump );
1154 aCode->MarkHasJumps();
1155 scJumps[node] = jump;
1156 }
1157
1158 stack.push_back( node->leaf[1] );
1159 node->leaf[1]->isVisited = true;
1160 }
1161
1162 continue;
1163 }
1164
1165 node->isVisited = true;
1166
1167 if( node->uop )
1168 {
1169 aCode->AddOp( node->uop );
1170 node->uop = nullptr;
1171
1172 // Backpatch the matching short-circuit jump to land just past this combine op.
1173 auto jumpIt = scJumps.find( node );
1174
1175 if( jumpIt != scJumps.end() )
1176 jumpIt->second->SetJumpTarget( aCode->GetSize() );
1177 }
1178
1179 stack.pop_back();
1180 }
1181
1182 // Report the common error condition of a single numeric value with no units (which will result in
1183 // nanometers, and rarely leads to anything useful).
1184 // Reporting missing units with multiple numeric values is far more complicated as we have to
1185 // separate comparison operators from multiplicative operators from additive operators. Consider:
1186 // 2 * 1.5mm
1187 // 2mm + 105um
1188 // 2mm > 20um
1189 // (2mm + 1.5mm) * 3
1190 if( !missingUnitsMsg.IsEmpty() && numericValueCount == 1 )
1191 reportError( CST_CODEGEN, missingUnitsMsg, missingUnitsSrcPos );
1192
1193 libeval_dbg(2,"dump: \n%s\n", aCode->Dump().c_str() );
1194
1195 return true;
1196}
1197
1198
1199// Normalized boolean results pushed when && / || short-circuits, so the skipped combine op's output
1200// contract (an UNSCALED numeric 1/0) is met for any enclosing operator without allocating a fresh
1201// value on the hot path. A numeric VALUE is immutable once set (only deferred values mutate on
1202// read), so DRC's worker threads can share these by reference, just as numeric literals are.
1203static const VALUE g_shortCircuitFalse( 0.0 );
1204static const VALUE g_shortCircuitTrue( 1.0 );
1205
1206
1208{
1209 switch( m_op )
1210 {
1211 case TR_OP_JZ:
1212 // Short-circuit && when the left side is false, then jump past the right-hand microcode.
1213 if( ctx->Top()->AsDouble() == 0.0 )
1214 {
1215 ctx->Pop();
1216 ctx->Push( const_cast<VALUE*>( &g_shortCircuitFalse ) );
1217 return m_jumpTarget;
1218 }
1219
1220 return -1;
1221
1222 case TR_OP_JNZ:
1223 // Short-circuit || when the left side is true, then jump past the right-hand microcode.
1224 if( ctx->Top()->AsDouble() != 0.0 )
1225 {
1226 ctx->Pop();
1227 ctx->Push( const_cast<VALUE*>( &g_shortCircuitTrue ) );
1228 return m_jumpTarget;
1229 }
1230
1231 return -1;
1232
1233 case TR_UOP_PUSH_VAR:
1234 {
1235 VALUE* value = nullptr;
1236
1237 if( m_ref )
1238 value = ctx->StoreValue( m_ref->GetValue( ctx ) );
1239 else
1240 value = ctx->AllocValue();
1241
1242 ctx->Push( value );
1243 break;
1244 }
1245
1246 case TR_UOP_PUSH_VALUE:
1247 // String literals contain a wxString whose internal mb_str cache is mutated by
1248 // ToUTF8/utf8_str. DRC evaluates compiled rules from many threads concurrently,
1249 // so push a per-thread copy to avoid racing on that cache.
1250 if( m_value && m_value->GetType() == VT_STRING )
1251 {
1252 VALUE* copy = ctx->AllocValue();
1253 copy->Set( *m_value );
1254 ctx->Push( copy );
1255 }
1256 else
1257 {
1258 ctx->Push( m_value.get() );
1259 }
1260
1261 return -1;
1262
1263 case TR_OP_METHOD_CALL:
1264 if( m_func )
1265 m_func( ctx, m_ref.get() );
1266
1267 return -1;
1268
1269 default:
1270 break;
1271 }
1272
1273#define AS_DOUBLE( arg ) ( arg ? arg->AsDouble() : 0.0 )
1274
1275 if( m_op & TR_OP_BINARY_MASK )
1276 {
1277 LIBEVAL::VALUE* arg2 = ctx->Pop();
1278 LIBEVAL::VALUE* arg1 = ctx->Pop();
1279 double result;
1280
1281 if( ctx->HasErrorCallback() )
1282 {
1283 if( arg1 && arg1->GetType() == VT_STRING && arg2 && arg2->GetType() == VT_NUMERIC )
1284 {
1285 ctx->ReportError( wxString::Format( _( "Type mismatch between '%s' and %lf" ),
1286 arg1->AsString(),
1287 arg2->AsDouble() ) );
1288 }
1289 else if( arg1 && arg1->GetType() == VT_NUMERIC && arg2 && arg2->GetType() == VT_STRING )
1290 {
1291 ctx->ReportError( wxString::Format( _( "Type mismatch between %lf and '%s'" ),
1292 arg1->AsDouble(),
1293 arg2->AsString() ) );
1294 }
1295 }
1296
1297 // TODO: This doesn't fully calculate units correctly yet. We really need to work out the dimensional analysis
1298 // TODO: (and do this independently of unit specifics - e.g. MM + INCH needs to return one of the dimension
1299 // TODO: types, but our units framework doesn't currently allow this. Therefore, use some heuristics to
1300 // TODO: determine the resulting operation unit type for now
1301 auto getOpResultUnits =
1302 []( const VALUE* aVal1, const VALUE* aVal2 )
1303 {
1304 wxCHECK( aVal1 && aVal2, EDA_UNITS::UNSCALED );
1305
1306 // This condition can occur in, e.g., a unary negation operation
1307 if( aVal1->GetUnits() == EDA_UNITS::UNSCALED && aVal2->GetUnits() != EDA_UNITS::UNSCALED )
1308 return aVal2->GetUnits();
1309
1310 if( aVal1->GetUnits() != EDA_UNITS::UNSCALED && aVal2->GetUnits() == EDA_UNITS::UNSCALED )
1311 return aVal1->GetUnits();
1312
1313 return aVal2->GetUnits();
1314 };
1315
1316 EDA_UNITS resultUnits = EDA_UNITS::UNSCALED;
1317
1318 switch( m_op )
1319 {
1320 case TR_OP_ADD:
1321 result = AS_DOUBLE( arg1 ) + AS_DOUBLE( arg2 );
1322 resultUnits = getOpResultUnits( arg1, arg2 );
1323 break;
1324
1325 case TR_OP_SUB:
1326 result = AS_DOUBLE( arg1 ) - AS_DOUBLE( arg2 );
1327 resultUnits = getOpResultUnits( arg1, arg2 );
1328 break;
1329
1330 case TR_OP_MUL:
1331 result = AS_DOUBLE( arg1 ) * AS_DOUBLE( arg2 );
1332 resultUnits = getOpResultUnits( arg1, arg2 );
1333 break;
1334
1335 case TR_OP_DIV:
1336 result = AS_DOUBLE( arg1 ) / AS_DOUBLE( arg2 );
1337 resultUnits = getOpResultUnits( arg1, arg2 );
1338 break;
1339
1340 case TR_OP_LESS_EQUAL:
1341 result = AS_DOUBLE( arg1 ) <= AS_DOUBLE( arg2 ) ? 1 : 0;
1342 break;
1343
1345 result = AS_DOUBLE( arg1 ) >= AS_DOUBLE( arg2 ) ? 1 : 0;
1346 break;
1347
1348 case TR_OP_LESS:
1349 result = AS_DOUBLE( arg1 ) < AS_DOUBLE( arg2 ) ? 1 : 0;
1350 break;
1351 case TR_OP_GREATER:
1352 result = AS_DOUBLE( arg1 ) > AS_DOUBLE( arg2 ) ? 1 : 0;
1353 break;
1354
1355 case TR_OP_EQUAL:
1356 if( !arg1 || !arg2 )
1357 result = arg1 == arg2 ? 1 : 0;
1358 else if( arg2->GetType() == VT_UNDEFINED )
1359 result = arg2->EqualTo( ctx, arg1 ) ? 1 : 0;
1360 else
1361 result = arg1->EqualTo( ctx, arg2 ) ? 1 : 0;
1362 break;
1363
1364 case TR_OP_NOT_EQUAL:
1365 if( !arg1 || !arg2 )
1366 result = arg1 != arg2 ? 1 : 0;
1367 else if( arg2->GetType() == VT_UNDEFINED )
1368 result = arg2->NotEqualTo( ctx, arg1 ) ? 1 : 0;
1369 else
1370 result = arg1->NotEqualTo( ctx, arg2 ) ? 1 : 0;
1371 break;
1372
1373 case TR_OP_BOOL_AND:
1374 result = AS_DOUBLE( arg1 ) != 0.0 && AS_DOUBLE( arg2 ) != 0.0 ? 1 : 0;
1375 break;
1376
1377 case TR_OP_BOOL_OR:
1378 result = AS_DOUBLE( arg1 ) != 0.0 || AS_DOUBLE( arg2 ) != 0.0 ? 1 : 0;
1379 break;
1380
1381 default:
1382 result = 0.0;
1383 break;
1384 }
1385
1386 VALUE* rp = ctx->AllocValue();
1387 rp->Set( result );
1388 rp->SetUnits( resultUnits );
1389 ctx->Push( rp );
1390 return -1;
1391 }
1392 else if( m_op & TR_OP_UNARY_MASK )
1393 {
1394 LIBEVAL::VALUE* arg1 = ctx->Pop();
1395 double ARG1VALUE = arg1 ? arg1->AsDouble() : 0.0;
1396 double result;
1397 EDA_UNITS resultUnits = arg1 ? arg1->GetUnits() : EDA_UNITS::UNSCALED;
1398
1399 switch( m_op )
1400 {
1401 case TR_OP_BOOL_NOT:
1402 result = ARG1VALUE != 0.0 ? 0 : 1;
1403 break;
1404 default:
1405 result = ARG1VALUE != 0.0 ? 1 : 0;
1406 break;
1407 }
1408
1409 VALUE* rp = ctx->AllocValue();
1410 rp->Set( result );
1411 rp->SetUnits( resultUnits );
1412 ctx->Push( rp );
1413 return -1;
1414 }
1415
1416 return -1;
1417}
1418
1419
1421{
1422 try
1423 {
1424 if( m_hasJumps )
1425 {
1426 for( std::size_t ip = 0; ip < m_ucode.size(); )
1427 {
1428 int next = m_ucode[ip]->Exec( ctx );
1429
1430 // Backpatched jump targets are always forward, past the combine op; a non-forward
1431 // or stale target would spin or read out of range, so reject it.
1432 wxASSERT( next < 0 || static_cast<std::size_t>( next ) > ip );
1433
1435 }
1436 }
1437 else
1438 {
1439 for( UOP* op : m_ucode )
1440 op->Exec( ctx );
1441 }
1442 }
1443 catch(...)
1444 {
1445 // rules which fail outright should not be fired; return 0/false
1446 return ctx->StoreValue( new VALUE( 0 ) );
1447 }
1448
1449 if( ctx->SP() == 1 )
1450 {
1451 return ctx->Pop();
1452 }
1453 else
1454 {
1455 // If stack is corrupted after execution it suggests a problem with the compiler, not
1456 // the rule....
1457
1458 // do not use "assert"; it crashes outright on OSX
1459 wxASSERT( ctx->SP() == 1 );
1460
1461 // non-well-formed rules should not be fired on a release build
1462 return ctx->StoreValue( new VALUE( 0 ) );
1463 }
1464}
1465
1466
1467} // 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.