diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt index bb379e7ff5..4dc8336668 100644 --- a/qa/CMakeLists.txt +++ b/qa/CMakeLists.txt @@ -53,6 +53,7 @@ add_subdirectory( eeschema ) add_subdirectory( libs ) add_subdirectory( utils/kicad2step ) +add_subdirectory( libeval_compiler ) # Utility/debugging/profiling programs add_subdirectory( common_tools ) diff --git a/qa/libeval_compiler/CMakeLists.txt b/qa/libeval_compiler/CMakeLists.txt new file mode 100644 index 0000000000..6e6a27baf9 --- /dev/null +++ b/qa/libeval_compiler/CMakeLists.txt @@ -0,0 +1,107 @@ +# +# This program source code file is part of KiCad, a free EDA CAD application. +# +# Copyright (C) 2017 CERN +# @author Alejandro GarcĂ­a Montoro +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, you may find one here: +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# or you may search the http://www.gnu.org website for the version 2 license, +# or you may write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +find_package(Boost COMPONENTS unit_test_framework REQUIRED) +find_package( wxWidgets 3.0.0 COMPONENTS gl aui adv html core net base xml stc REQUIRED ) + +find_program(LEMON lemon) + +if( LEMON ) + + macro( generate_lemon_grammar TGT_NAME GRAMMAR_LEMON GRAMMAR_C ) + add_custom_target( ${TGT_NAME} + DEPENDS ${GRAMMAR_LEMON} + COMMAND ${LEMON} -q ${GRAMMAR_LEMON} + COMMENT "Running Lemon on ${GRAMMAR_LEMON} -> ${GRAMMAR_C}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + endmacro() + + generate_lemon_grammar( libeval_grammar2 grammar.lemon grammar.c ) + +endif() + +add_definitions(-DBOOST_TEST_DYN_LINK -DPCBNEW) + +if( BUILD_GITHUB_PLUGIN ) + set( GITHUB_PLUGIN_LIBRARIES github_plugin ) +endif() + +add_dependencies( pnsrouter pcbcommon pcad2kicadpcb libeval_grammar2 ${GITHUB_PLUGIN_LIBRARIES} ) + +add_executable( libeval_compiler_test + libeval_compiler_test.cpp + libeval_compiler.cpp + ../qa_utils/mocks.cpp + ../../common/base_units.cpp + ../../3d-viewer/3d_viewer/3d_viewer_settings.cpp +) + +include_directories( BEFORE ${INC_BEFORE} ) +include_directories( + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/3d-viewer + ${CMAKE_SOURCE_DIR}/common + ${CMAKE_SOURCE_DIR}/pcbnew + ${CMAKE_SOURCE_DIR}/pcbnew/router + ${CMAKE_SOURCE_DIR}/pcbnew/tools + ${CMAKE_SOURCE_DIR}/pcbnew/dialogs + ${CMAKE_SOURCE_DIR}/polygon + ${CMAKE_SOURCE_DIR}/common/geometry + ${CMAKE_SOURCE_DIR}/qa/common + ${CMAKE_SOURCE_DIR}/qa/qa_utils + ${Boost_INCLUDE_DIR} + ${INC_AFTER} +) + +target_link_libraries( libeval_compiler_test + pnsrouter + common + pcbcommon + bitmaps + pnsrouter + common + pcbcommon + bitmaps + pnsrouter + common + pcbcommon + bitmaps + pnsrouter + common + pcbcommon + bitmaps + gal + pcad2kicadpcb + altium2kicadpcb + common + pcbcommon + ${GITHUB_PLUGIN_LIBRARIES} + common + pcbcommon + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${wxWidgets_LIBRARIES} +) diff --git a/qa/libeval_compiler/grammar.c b/qa/libeval_compiler/grammar.c new file mode 100644 index 0000000000..7cb716b9ca --- /dev/null +++ b/qa/libeval_compiler/grammar.c @@ -0,0 +1,1387 @@ +/* +** 2000-05-29 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Driver template for the LEMON parser generator. +** +** The "lemon" program processes an LALR(1) input grammar file, then uses +** this template to construct a parser. The "lemon" program inserts text +** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the +** interstitial "-" characters) contained in this template is changed into +** the value of the %name directive from the grammar. Otherwise, the content +** of this template is copied straight through into the generate parser +** source file. +** +** The following is the concatenation of all %include directives from the +** input grammar file: +*/ +#include +#include +/************ Begin %include sections from the grammar ************************/ +#line 33 "grammar.lemon" + +#include +#include "libeval_compiler.h" +#line 33 "grammar.c" +/**************** End of %include directives **********************************/ +/* These constants specify the various numeric values for terminal symbols +** in a format understandable to "makeheaders". This section is blank unless +** "lemon" is run with the "-m" command-line option. +***************** Begin makeheaders token definitions *************************/ +/**************** End makeheaders token definitions ***************************/ + +/* The next sections is a series of control #defines. +** various aspects of the generated parser. +** YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** YYNOCODE is a number of type YYCODETYPE that is not used for +** any terminal or nonterminal symbol. +** YYFALLBACK If defined, this indicates that one or more tokens +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** YYACTIONTYPE is the data type used for "action codes" - numbers +** that indicate what to do in response to the next +** token. +** ParseTOKENTYPE is the data type used for minor type for terminal +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** symbols. +** YYMINORTYPE is the data type used for all minor types. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for terminal symbols is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_PARAM Code to pass %extra_argument as a subroutine parameter +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** ParseCTX_* As ParseARG_ except for %extra_context +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYNTOKEN Number of terminal symbols +** YY_MAX_SHIFT Maximum value for shift actions +** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** YY_ERROR_ACTION The yy_action[] code for syntax error +** YY_ACCEPT_ACTION The yy_action[] code for accept +** YY_NO_ACTION The yy_action[] code for no-op +** YY_MIN_REDUCE Minimum value for reduce actions +** YY_MAX_REDUCE Maximum value for reduce actions +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ +#define YYCODETYPE unsigned char +#define YYNOCODE 29 +#define YYACTIONTYPE unsigned char +#define ParseTOKENTYPE LIBEVAL::TREE_NODE +typedef union { + int yyinit; + ParseTOKENTYPE yy0; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define ParseARG_SDECL LIBEVAL::COMPILER* pEval ; +#define ParseARG_PDECL , LIBEVAL::COMPILER* pEval +#define ParseARG_PARAM ,pEval +#define ParseARG_FETCH LIBEVAL::COMPILER* pEval =yypParser->pEval ; +#define ParseARG_STORE yypParser->pEval =pEval ; +#define ParseCTX_SDECL +#define ParseCTX_PDECL +#define ParseCTX_PARAM +#define ParseCTX_FETCH +#define ParseCTX_STORE +#define YYNSTATE 35 +#define YYNRULE 24 +#define YYNTOKEN 25 +#define YY_MAX_SHIFT 34 +#define YY_MIN_SHIFTREDUCE 43 +#define YY_MAX_SHIFTREDUCE 66 +#define YY_ERROR_ACTION 67 +#define YY_ACCEPT_ACTION 68 +#define YY_NO_ACTION 69 +#define YY_MIN_REDUCE 70 +#define YY_MAX_REDUCE 93 +/************* End control #defines *******************************************/ +#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. +** +** N == YY_ERROR_ACTION A syntax error has occurred. +** +** N == YY_ACCEPT_ACTION The parser accepts its input. +** +** N == YY_NO_ACTION No such action. Denotes unused +** slots in the yy_action[] table. +** +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as either: +** +** (A) N = yy_action[ yy_shift_ofst[S] + X ] +** (B) N = yy_default[S] +** +** The (A) formula is preferred. The B formula is used instead if +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X. +** +** The formulas above are for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +** +*********** Begin parsing tables **********************************************/ +#define YY_ACTTAB_COUNT (118) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 68, 1, 1, 19, 9, 10, 17, 16, 14, 13, + /* 10 */ 12, 4, 11, 92, 19, 8, 7, 5, 6, 3, + /* 20 */ 87, 26, 9, 10, 61, 16, 14, 13, 12, 4, + /* 30 */ 11, 31, 32, 8, 7, 5, 6, 3, 28, 29, + /* 40 */ 9, 10, 62, 16, 14, 13, 12, 4, 11, 20, + /* 50 */ 21, 8, 7, 5, 6, 3, 43, 67, 67, 27, + /* 60 */ 16, 14, 13, 12, 4, 11, 23, 24, 8, 7, + /* 70 */ 5, 6, 3, 67, 67, 67, 67, 4, 11, 90, + /* 80 */ 33, 8, 7, 5, 6, 3, 67, 67, 33, 25, + /* 90 */ 8, 7, 5, 6, 3, 33, 18, 22, 45, 66, + /* 100 */ 34, 46, 2, 5, 6, 3, 15, 66, 34, 46, + /* 110 */ 2, 67, 3, 69, 69, 34, 46, 2, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 25, 26, 27, 28, 4, 5, 28, 7, 8, 9, + /* 10 */ 10, 11, 12, 27, 28, 15, 16, 17, 18, 19, + /* 20 */ 28, 28, 4, 5, 24, 7, 8, 9, 10, 11, + /* 30 */ 12, 28, 28, 15, 16, 17, 18, 19, 28, 28, + /* 40 */ 4, 5, 24, 7, 8, 9, 10, 11, 12, 28, + /* 50 */ 28, 15, 16, 17, 18, 19, 20, 4, 5, 28, + /* 60 */ 7, 8, 9, 10, 11, 12, 28, 28, 15, 16, + /* 70 */ 17, 18, 19, 7, 8, 9, 10, 11, 12, 0, + /* 80 */ 1, 15, 16, 17, 18, 19, 11, 12, 1, 28, + /* 90 */ 15, 16, 17, 18, 19, 1, 28, 28, 13, 20, + /* 100 */ 21, 22, 23, 17, 18, 19, 23, 20, 21, 22, + /* 110 */ 23, 19, 19, 29, 29, 21, 22, 23, 29, 29, + /* 120 */ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + /* 130 */ 29, 29, 29, 29, 29, 29, 29, 29, +}; +#define YY_SHIFT_COUNT (34) +#define YY_SHIFT_MIN (0) +#define YY_SHIFT_MAX (94) +static const unsigned char yy_shift_ofst[] = { + /* 0 */ 87, 79, 94, 94, 94, 94, 94, 94, 94, 94, + /* 10 */ 94, 94, 94, 94, 94, 94, 94, 0, 18, 36, + /* 20 */ 53, 53, 66, 66, 66, 66, 75, 75, 86, 86, + /* 30 */ 92, 93, 93, 83, 85, +}; +#define YY_REDUCE_COUNT (16) +#define YY_REDUCE_MIN (-25) +#define YY_REDUCE_MAX (69) +static const signed char yy_reduce_ofst[] = { + /* 0 */ -25, -14, -22, -8, -7, 3, 4, 10, 11, 21, + /* 10 */ 22, 31, 38, 39, 61, 68, 69, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + /* 10 */ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + /* 20 */ 81, 80, 75, 78, 77, 76, 86, 79, 83, 82, + /* 30 */ 87, 85, 84, 74, 71, +}; +/********** End of lemon-generated parsing tables *****************************/ + +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + yyStackEntry *yytos; /* Pointer to top element of the stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyhwm; /* High-water mark of the stack */ +#endif +#ifndef YYNOERRORRECOVERY + int yyerrcnt; /* Shifts left before out of the error */ +#endif + ParseARG_SDECL /* A place to hold %extra_argument */ + ParseCTX_SDECL /* A place to hold %extra_context */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ + yyStackEntry yystk0; /* First stack entry */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ + yyStackEntry *yystackEnd; /* Last entry in the stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#if defined(YYCOVERAGE) || !defined(NDEBUG) +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + /* 0 */ "$", + /* 1 */ "G_IDENTIFIER", + /* 2 */ "G_ASSIGN", + /* 3 */ "G_SEMCOL", + /* 4 */ "G_BOOL_OR", + /* 5 */ "G_BOOL_AND", + /* 6 */ "G_BOOL_XOR", + /* 7 */ "G_LESS_THAN", + /* 8 */ "G_GREATER_THAN", + /* 9 */ "G_LESS_EQUAL_THAN", + /* 10 */ "G_GREATER_EQUAL_THAN", + /* 11 */ "G_EQUAL", + /* 12 */ "G_NOT_EQUAL", + /* 13 */ "G_UNIT", + /* 14 */ "G_BOOL_NOT", + /* 15 */ "G_PLUS", + /* 16 */ "G_MINUS", + /* 17 */ "G_DIVIDE", + /* 18 */ "G_MULT", + /* 19 */ "G_STRUCT_REF", + /* 20 */ "G_ENDS", + /* 21 */ "G_VALUE", + /* 22 */ "G_STRING", + /* 23 */ "G_PARENL", + /* 24 */ "G_PARENR", + /* 25 */ "main", + /* 26 */ "in", + /* 27 */ "stmt", + /* 28 */ "expr", +}; +#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "stmt ::= expr G_ENDS", + /* 1 */ "expr ::= G_VALUE", + /* 2 */ "expr ::= G_VALUE G_UNIT", + /* 3 */ "expr ::= G_STRING", + /* 4 */ "expr ::= G_IDENTIFIER", + /* 5 */ "expr ::= expr G_LESS_THAN expr", + /* 6 */ "expr ::= expr G_GREATER_THAN expr", + /* 7 */ "expr ::= expr G_LESS_EQUAL_THAN expr", + /* 8 */ "expr ::= expr G_GREATER_EQUAL_THAN expr", + /* 9 */ "expr ::= expr G_NOT_EQUAL expr", + /* 10 */ "expr ::= expr G_BOOL_AND expr", + /* 11 */ "expr ::= expr G_BOOL_OR expr", + /* 12 */ "expr ::= expr G_PLUS expr", + /* 13 */ "expr ::= expr G_MINUS expr", + /* 14 */ "expr ::= expr G_MULT expr", + /* 15 */ "expr ::= expr G_DIVIDE expr", + /* 16 */ "expr ::= expr G_EQUAL expr", + /* 17 */ "expr ::= expr G_STRUCT_REF expr", + /* 18 */ "expr ::= G_PARENL expr G_PARENR", + /* 19 */ "expr ::= G_IDENTIFIER G_PARENL expr G_PARENR", + /* 20 */ "main ::= in", + /* 21 */ "in ::= stmt", + /* 22 */ "in ::= in stmt", + /* 23 */ "stmt ::= G_ENDS", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. Return the number +** of errors. Return 0 on success. +*/ +static int yyGrowStack(yyParser *p){ + int newSize; + int idx; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; + if( p->yystack==&p->yystk0 ){ + pNew = malloc(newSize*sizeof(pNew[0])); + if( pNew ) pNew[0] = p->yystk0; + }else{ + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + } + if( pNew ){ + p->yystack = pNew; + p->yytos = &p->yystack[idx]; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, p->yystksz, newSize); + } +#endif + p->yystksz = newSize; + } + return pNew==0; +} +#endif + +/* Datatype of the argument to the memory allocated passed as the +** second argument to ParseAlloc() below. This can be changed by +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef YYMALLOCARGTYPE +# define YYMALLOCARGTYPE size_t +#endif + +/* Initialize a new parser that has already been allocated. +*/ +void ParseInit(void *yypRawParser ParseCTX_PDECL){ + yyParser *yypParser = (yyParser*)yypRawParser; + ParseCTX_STORE +#ifdef YYTRACKMAXSTACKDEPTH + yypParser->yyhwm = 0; +#endif +#if YYSTACKDEPTH<=0 + yypParser->yytos = NULL; + yypParser->yystack = NULL; + yypParser->yystksz = 0; + if( yyGrowStack(yypParser) ){ + yypParser->yystack = &yypParser->yystk0; + yypParser->yystksz = 1; + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yypParser->yytos = yypParser->yystack; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; +#if YYSTACKDEPTH>0 + yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; +#endif +} + +#ifndef Parse_ENGINEALWAYSONSTACK +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) ParseCTX_PDECL){ + yyParser *yypParser; + yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); + if( yypParser ){ + ParseCTX_STORE + ParseInit(yypParser ParseCTX_PARAM); + } + return (void*)yypParser; +} +#endif /* Parse_ENGINEALWAYSONSTACK */ + + +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "yymajor" is the symbol code, and "yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + ParseARG_FETCH + ParseCTX_FETCH + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are *not* used + ** inside the C code. + */ +/********* Begin destructor definitions ***************************************/ +/********* End destructor definitions *****************************************/ + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +*/ +static void yy_pop_parser_stack(yyParser *pParser){ + yyStackEntry *yytos; + assert( pParser->yytos!=0 ); + assert( pParser->yytos > pParser->yystack ); + yytos = pParser->yytos--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yy_destructor(pParser, yytos->major, &yytos->minor); +} + +/* +** Clear all secondary memory allocations from the parser +*/ +void ParseFinalize(void *p){ + yyParser *pParser = (yyParser*)p; + while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); +#endif +} + +#ifndef Parse_ENGINEALWAYSONSTACK +/* +** Deallocate and destroy a parser. Destructors are called for +** all stack elements before shutting the parser down. +** +** If the YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. +*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ +#ifndef YYPARSEFREENEVERNULL + if( p==0 ) return; +#endif + ParseFinalize(p); + (*freeProc)(p); +} +#endif /* Parse_ENGINEALWAYSONSTACK */ + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int ParseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyhwm; +} +#endif + +/* This array of booleans keeps track of the parser statement +** coverage. The element yycoverage[X][Y] is set when the parser +** is in state X and has a lookahead token Y. In a well-tested +** systems, every element of this matrix should end up being set. +*/ +#if defined(YYCOVERAGE) +static unsigned char yycoverage[YYNSTATE][YYNTOKEN]; +#endif + +/* +** Write into out a description of every state/lookahead combination that +** +** (1) has not been used by the parser, and +** (2) is not a syntax error. +** +** Return the number of missed state/lookahead combinations. +*/ +#if defined(YYCOVERAGE) +int ParseCoverage(FILE *out){ + int stateno, iLookAhead, i; + int nMissed = 0; + for(stateno=0; statenoYY_MAX_SHIFT ) return stateno; + assert( stateno <= YY_SHIFT_COUNT ); +#if defined(YYCOVERAGE) + yycoverage[stateno][iLookAhead] = 1; +#endif + do{ + i = yy_shift_ofst[stateno]; + assert( i>=0 ); + /* assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); */ + assert( iLookAhead!=YYNOCODE ); + assert( iLookAhead < YYNTOKEN ); + i += iLookAhead; + if( i>=YY_NLOOKAHEAD || yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j0 + ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + return yy_default[stateno]; + }else{ + return yy_action[i]; + } + }while(1); +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +*/ +static YYACTIONTYPE yy_find_reduce_action( + YYACTIONTYPE stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ +/******** End %stack_overflow code ********************************************/ + ParseARG_STORE /* Suppress warning about unused %extra_argument var */ + ParseCTX_STORE +} + +/* +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){ + if( yyTraceFILE ){ + if( yyNewStateyytos->major], + yyNewState); + }else{ + fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], + yyNewState - YY_MIN_REDUCE); + } + } +} +#else +# define yyTraceShift(X,Y,Z) +#endif + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + YYACTIONTYPE yyNewState, /* The new state to shift in */ + YYCODETYPE yyMajor, /* The major token to shift in */ + ParseTOKENTYPE yyMinor /* The minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yytos++; +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>yypParser->yystackEnd ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + if( yyGrowStack(yypParser) ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } + } +#endif + if( yyNewState > YY_MAX_SHIFT ){ + yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + } + yytos = yypParser->yytos; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor.yy0 = yyMinor; + yyTraceShift(yypParser, yyNewState, "Shift"); +} + +/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side +** of that rule */ +static const YYCODETYPE yyRuleInfoLhs[] = { + 27, /* (0) stmt ::= expr G_ENDS */ + 28, /* (1) expr ::= G_VALUE */ + 28, /* (2) expr ::= G_VALUE G_UNIT */ + 28, /* (3) expr ::= G_STRING */ + 28, /* (4) expr ::= G_IDENTIFIER */ + 28, /* (5) expr ::= expr G_LESS_THAN expr */ + 28, /* (6) expr ::= expr G_GREATER_THAN expr */ + 28, /* (7) expr ::= expr G_LESS_EQUAL_THAN expr */ + 28, /* (8) expr ::= expr G_GREATER_EQUAL_THAN expr */ + 28, /* (9) expr ::= expr G_NOT_EQUAL expr */ + 28, /* (10) expr ::= expr G_BOOL_AND expr */ + 28, /* (11) expr ::= expr G_BOOL_OR expr */ + 28, /* (12) expr ::= expr G_PLUS expr */ + 28, /* (13) expr ::= expr G_MINUS expr */ + 28, /* (14) expr ::= expr G_MULT expr */ + 28, /* (15) expr ::= expr G_DIVIDE expr */ + 28, /* (16) expr ::= expr G_EQUAL expr */ + 28, /* (17) expr ::= expr G_STRUCT_REF expr */ + 28, /* (18) expr ::= G_PARENL expr G_PARENR */ + 28, /* (19) expr ::= G_IDENTIFIER G_PARENL expr G_PARENR */ + 25, /* (20) main ::= in */ + 26, /* (21) in ::= stmt */ + 26, /* (22) in ::= in stmt */ + 27, /* (23) stmt ::= G_ENDS */ +}; + +/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number +** of symbols on the right-hand side of that rule. */ +static const signed char yyRuleInfoNRhs[] = { + -2, /* (0) stmt ::= expr G_ENDS */ + -1, /* (1) expr ::= G_VALUE */ + -2, /* (2) expr ::= G_VALUE G_UNIT */ + -1, /* (3) expr ::= G_STRING */ + -1, /* (4) expr ::= G_IDENTIFIER */ + -3, /* (5) expr ::= expr G_LESS_THAN expr */ + -3, /* (6) expr ::= expr G_GREATER_THAN expr */ + -3, /* (7) expr ::= expr G_LESS_EQUAL_THAN expr */ + -3, /* (8) expr ::= expr G_GREATER_EQUAL_THAN expr */ + -3, /* (9) expr ::= expr G_NOT_EQUAL expr */ + -3, /* (10) expr ::= expr G_BOOL_AND expr */ + -3, /* (11) expr ::= expr G_BOOL_OR expr */ + -3, /* (12) expr ::= expr G_PLUS expr */ + -3, /* (13) expr ::= expr G_MINUS expr */ + -3, /* (14) expr ::= expr G_MULT expr */ + -3, /* (15) expr ::= expr G_DIVIDE expr */ + -3, /* (16) expr ::= expr G_EQUAL expr */ + -3, /* (17) expr ::= expr G_STRUCT_REF expr */ + -3, /* (18) expr ::= G_PARENL expr G_PARENR */ + -4, /* (19) expr ::= G_IDENTIFIER G_PARENL expr G_PARENR */ + -1, /* (20) main ::= in */ + -1, /* (21) in ::= stmt */ + -2, /* (22) in ::= in stmt */ + -1, /* (23) stmt ::= G_ENDS */ +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +** +** The yyLookahead and yyLookaheadToken parameters provide reduce actions +** access to the lookahead token (if any). The yyLookahead will be YYNOCODE +** if the lookahead token has already been consumed. As this procedure is +** only called from one place, optimizing compilers will in-line it, which +** means that the extra parameters have no performance impact. +*/ +static YYACTIONTYPE yy_reduce( + yyParser *yypParser, /* The parser */ + unsigned int yyruleno, /* Number of the rule by which to reduce */ + int yyLookahead, /* Lookahead token, or YYNOCODE if none */ + ParseTOKENTYPE yyLookaheadToken /* Value of the lookahead token */ + ParseCTX_PDECL /* %extra_context */ +){ + int yygoto; /* The next state */ + YYACTIONTYPE yyact; /* The next action */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH + (void)yyLookahead; + (void)yyLookaheadToken; + yymsp = yypParser->yytos; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + yysize = yyRuleInfoNRhs[yyruleno]; + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s], go to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], yymsp[yysize].stateno); + }else{ + fprintf(yyTraceFILE, "%sReduce %d [%s].\n", + yyTracePrompt, yyruleno, yyRuleName[yyruleno]); + } + } +#endif /* NDEBUG */ + + /* Check that the stack is large enough to grow by a single entry + ** if the RHS of the rule is empty. This ensures that there is room + ** enough on the stack to push the LHS value */ + if( yyRuleInfoNRhs[yyruleno]==0 ){ +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=yypParser->yystackEnd ){ + yyStackOverflow(yypParser); + /* The call to yyStackOverflow() above pops the stack until it is + ** empty, causing the main parser loop to exit. So the return value + ** is never used and does not matter. */ + return 0; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + /* The call to yyStackOverflow() above pops the stack until it is + ** empty, causing the main parser loop to exit. So the return value + ** is never used and does not matter. */ + return 0; + } + yymsp = yypParser->yytos; + } +#endif + } + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ +/********** Begin reduce actions **********************************************/ + YYMINORTYPE yylhsminor; + case 0: /* stmt ::= expr G_ENDS */ +#line 54 "grammar.lemon" +{ pEval->setRoot(yymsp[-1].minor.yy0); } +#line 969 "grammar.c" + break; + case 1: /* expr ::= G_VALUE */ +#line 57 "grammar.lemon" +{ yylhsminor.yy0.op = TR_NUMBER; yylhsminor.yy0.value = yymsp[0].minor.yy0.value; yylhsminor.yy0.leaf[0] = yylhsminor.yy0.leaf[1] = NULL; yylhsminor.yy0.valid = true; } +#line 974 "grammar.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 2: /* expr ::= G_VALUE G_UNIT */ +#line 58 "grammar.lemon" +{ yylhsminor.yy0.op = TR_NUMBER; yylhsminor.yy0.value = yymsp[-1].minor.yy0.value; yylhsminor.yy0.leaf[0] = newNode( TR_UNIT, yymsp[0].minor.yy0.value.type, ""); yylhsminor.yy0.leaf[1] = NULL; yylhsminor.yy0.valid = true; } +#line 980 "grammar.c" + yymsp[-1].minor.yy0 = yylhsminor.yy0; + break; + case 3: /* expr ::= G_STRING */ +#line 59 "grammar.lemon" +{ yylhsminor.yy0.op = TR_STRING; yylhsminor.yy0.value = yymsp[0].minor.yy0.value; yylhsminor.yy0.leaf[0] = yylhsminor.yy0.leaf[1] = NULL; yylhsminor.yy0.valid = true; } +#line 986 "grammar.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 4: /* expr ::= G_IDENTIFIER */ +#line 60 "grammar.lemon" +{ yylhsminor.yy0.op = TR_IDENTIFIER; yylhsminor.yy0.value = yymsp[0].minor.yy0.value; yylhsminor.yy0.leaf[0] = yylhsminor.yy0.leaf[1] = NULL; yylhsminor.yy0.valid = true; } +#line 992 "grammar.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 5: /* expr ::= expr G_LESS_THAN expr */ +#line 61 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_LESS; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 998 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 6: /* expr ::= expr G_GREATER_THAN expr */ +#line 62 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_GREATER; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1004 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 7: /* expr ::= expr G_LESS_EQUAL_THAN expr */ +#line 63 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_LESS_EQUAL; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1010 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 8: /* expr ::= expr G_GREATER_EQUAL_THAN expr */ +#line 64 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_GREATER_EQUAL; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1016 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 9: /* expr ::= expr G_NOT_EQUAL expr */ +#line 65 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_NOT_EQUAL; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1022 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 10: /* expr ::= expr G_BOOL_AND expr */ +#line 66 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_BOOL_AND; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1028 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 11: /* expr ::= expr G_BOOL_OR expr */ +#line 67 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_BOOL_OR; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1034 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 12: /* expr ::= expr G_PLUS expr */ +#line 68 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_ADD; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1040 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 13: /* expr ::= expr G_MINUS expr */ +#line 69 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_SUB; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1046 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 14: /* expr ::= expr G_MULT expr */ +#line 70 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_MUL; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1052 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 15: /* expr ::= expr G_DIVIDE expr */ +#line 71 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_DIV; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1058 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 16: /* expr ::= expr G_EQUAL expr */ +#line 72 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_EQUAL; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1064 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 17: /* expr ::= expr G_STRUCT_REF expr */ +#line 74 "grammar.lemon" +{ yylhsminor.yy0.op = TR_STRUCT_REF; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-2].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[0].minor.yy0); yylhsminor.yy0.valid=yymsp[-2].minor.yy0.valid && yymsp[0].minor.yy0.valid; } +#line 1070 "grammar.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 18: /* expr ::= G_PARENL expr G_PARENR */ +#line 76 "grammar.lemon" +{ yymsp[-2].minor.yy0.op = yymsp[-1].minor.yy0.op; yymsp[-2].minor.yy0.value = yymsp[-1].minor.yy0.value; yymsp[-2].minor.yy0.valid=yymsp[-1].minor.yy0.valid; yymsp[-2].minor.yy0.leaf[0] = yymsp[-1].minor.yy0.leaf[0]; yymsp[-2].minor.yy0.leaf[1] = yymsp[-1].minor.yy0.leaf[1]; } +#line 1076 "grammar.c" + break; + case 19: /* expr ::= G_IDENTIFIER G_PARENL expr G_PARENR */ +#line 77 "grammar.lemon" +{ yylhsminor.yy0.op = TR_OP_FUNC_CALL; yylhsminor.yy0.leaf[0] = copyNode(yymsp[-3].minor.yy0); yylhsminor.yy0.leaf[1] = copyNode(yymsp[-1].minor.yy0); yylhsminor.yy0.valid=1; } +#line 1081 "grammar.c" + yymsp[-3].minor.yy0 = yylhsminor.yy0; + break; + default: + /* (20) main ::= in */ yytestcase(yyruleno==20); + /* (21) in ::= stmt (OPTIMIZED OUT) */ assert(yyruleno!=21); + /* (22) in ::= in stmt */ yytestcase(yyruleno==22); + /* (23) stmt ::= G_ENDS */ yytestcase(yyruleno==23); + break; +/********** End reduce actions ************************************************/ + }; + assert( yyrulenoYY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( yyact!=YY_ERROR_ACTION ); + + yymsp += yysize+1; + yypParser->yytos = yymsp; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact, "... then shift"); + return yyact; +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH + ParseCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ + ParseARG_STORE /* Suppress warning about unused %extra_argument variable */ + ParseCTX_STORE +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + ParseTOKENTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH + ParseCTX_FETCH +#define TOKEN yyminor +/************ Begin %syntax_error code ****************************************/ +#line 38 "grammar.lemon" + + pEval->parseError("Syntax error"); +#line 1151 "grammar.c" +/************ End %syntax_error code ******************************************/ + ParseARG_STORE /* Suppress warning about unused %extra_argument variable */ + ParseCTX_STORE +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH + ParseCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + assert( yypParser->yytos==yypParser->yystack ); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +#line 42 "grammar.lemon" + + pEval->parseOk(); +#line 1180 "grammar.c" +/*********** End %parse_accept code *******************************************/ + ParseARG_STORE /* Suppress warning about unused %extra_argument variable */ + ParseCTX_STORE +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + YYACTIONTYPE yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser = (yyParser*)yyp; /* The parser */ + ParseCTX_FETCH + ParseARG_STORE + + assert( yypParser->yytos!=0 ); +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + + yyact = yypParser->yytos->stateno; +#ifndef NDEBUG + if( yyTraceFILE ){ + if( yyact < YY_MIN_REDUCE ){ + fprintf(yyTraceFILE,"%sInput '%s' in state %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact); + }else{ + fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE); + } + } +#endif + + do{ + assert( yyact==yypParser->yytos->stateno ); + yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); + if( yyact >= YY_MIN_REDUCE ){ + yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor, + yyminor ParseCTX_PARAM); + }else if( yyact <= YY_MAX_SHIFTREDUCE ){ + yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt--; +#endif + break; + }else if( yyact==YY_ACCEPT_ACTION ){ + yypParser->yytos--; + yy_accept(yypParser); + return; + }else{ + assert( yyact == YY_ERROR_ACTION ); + yyminorunion.yy0 = yyminor; +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminor); + } + yymx = yypParser->yytos->major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); + yymajor = YYNOCODE; + }else{ + while( yypParser->yytos >= yypParser->yystack + && (yyact = yy_find_reduce_action( + yypParser->yytos->stateno, + YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; + if( yymajor==YYNOCODE ) break; + yyact = yypParser->yytos->stateno; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor, yyminor); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + break; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor, yyminor); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + } + break; +#endif + } + }while( yypParser->yytos>yypParser->yystack ); +#ifndef NDEBUG + if( yyTraceFILE ){ + yyStackEntry *i; + char cDiv = '['; + fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); + for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){ + fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]); + cDiv = ' '; + } + fprintf(yyTraceFILE,"]\n"); + } +#endif + return; +} + +/* +** Return the fallback token corresponding to canonical token iToken, or +** 0 if iToken has no fallback. +*/ +int ParseFallback(int iToken){ +#ifdef YYFALLBACK + if( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ){ + return yyFallback[iToken]; + } +#else + (void)iToken; +#endif + return 0; +} diff --git a/qa/libeval_compiler/grammar.h b/qa/libeval_compiler/grammar.h new file mode 100644 index 0000000000..30e62c7ba7 --- /dev/null +++ b/qa/libeval_compiler/grammar.h @@ -0,0 +1,24 @@ +#define G_IDENTIFIER 1 +#define G_ASSIGN 2 +#define G_SEMCOL 3 +#define G_BOOL_OR 4 +#define G_BOOL_AND 5 +#define G_BOOL_XOR 6 +#define G_LESS_THAN 7 +#define G_GREATER_THAN 8 +#define G_LESS_EQUAL_THAN 9 +#define G_GREATER_EQUAL_THAN 10 +#define G_EQUAL 11 +#define G_NOT_EQUAL 12 +#define G_UNIT 13 +#define G_BOOL_NOT 14 +#define G_PLUS 15 +#define G_MINUS 16 +#define G_DIVIDE 17 +#define G_MULT 18 +#define G_STRUCT_REF 19 +#define G_ENDS 20 +#define G_VALUE 21 +#define G_STRING 22 +#define G_PARENL 23 +#define G_PARENR 24 diff --git a/qa/libeval_compiler/grammar.lemon b/qa/libeval_compiler/grammar.lemon new file mode 100644 index 0000000000..2747bc462b --- /dev/null +++ b/qa/libeval_compiler/grammar.lemon @@ -0,0 +1,77 @@ +/* + This file is part of libeval, a simple math expression evaluator + + Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +%token_type { LIBEVAL::TREE_NODE } +%extra_argument { LIBEVAL::COMPILER* pEval } + +%nonassoc G_IDENTIFIER G_ASSIGN G_SEMCOL. +%nonassoc G_BOOL_OR G_BOOL_AND G_BOOL_XOR. +%nonassoc G_LESS_THAN G_GREATER_THAN G_LESS_EQUAL_THAN G_GREATER_EQUAL_THAN. +%nonassoc G_EQUAL G_NOT_EQUAL. +%right G_UNIT. +%left G_BOOL_NOT. +%left G_PLUS G_MINUS. +%left G_DIVIDE G_MULT. +%nonassoc G_STRUCT_REF. + +%include { +#include +#include "libeval_compiler.h" +} + +%syntax_error { + pEval->parseError("Syntax error"); +} + +%parse_accept { + pEval->parseOk(); +} + +main ::= in. + +/* Allow multiple statements in input string: x=1; y=2 */ +in ::= stmt. +in ::= in stmt. + +/* A statement can be empty, an expr or an expr followed by ';' */ +stmt ::= G_ENDS. +stmt ::= expr(A) G_ENDS. { pEval->setRoot(A); } +//stmt ::= expr G_SEMCOL. { pEval->setRoot(NULL) } + +expr(A) ::= G_VALUE(B). { A.op = TR_NUMBER; A.value = B.value; A.leaf[0] = A.leaf[1] = NULL; A.valid = true; } +expr(A) ::= G_VALUE(B) G_UNIT(C). { A.op = TR_NUMBER; A.value = B.value; A.leaf[0] = newNode( TR_UNIT, C.value.type, ""); A.leaf[1] = NULL; A.valid = true; } +expr(A) ::= G_STRING(B). { A.op = TR_STRING; A.value = B.value; A.leaf[0] = A.leaf[1] = NULL; A.valid = true; } +expr(A) ::= G_IDENTIFIER(B). { A.op = TR_IDENTIFIER; A.value = B.value; A.leaf[0] = A.leaf[1] = NULL; A.valid = true; } +expr(A) ::= expr(B) G_LESS_THAN expr(C). { A.op = TR_OP_LESS; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_GREATER_THAN expr(C). { A.op = TR_OP_GREATER; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_LESS_EQUAL_THAN expr(C). { A.op = TR_OP_LESS_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_GREATER_EQUAL_THAN expr(C). { A.op = TR_OP_GREATER_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_NOT_EQUAL expr(C). { A.op = TR_OP_NOT_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_BOOL_AND expr(C). { A.op = TR_OP_BOOL_AND; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_BOOL_OR expr(C). { A.op = TR_OP_BOOL_OR; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_PLUS expr(C). { A.op = TR_OP_ADD; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_MINUS expr(C). { A.op = TR_OP_SUB; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_MULT expr(C). { A.op = TR_OP_MUL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_DIVIDE expr(C). { A.op = TR_OP_DIV; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } +expr(A) ::= expr(B) G_EQUAL expr(C). { A.op = TR_OP_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } + +expr(A) ::= expr(B) G_STRUCT_REF expr(C). { A.op = TR_STRUCT_REF; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; } + +expr(A) ::= G_PARENL expr(B) G_PARENR. { A.op = B.op; A.value = B.value; A.valid=B.valid; A.leaf[0] = B.leaf[0]; A.leaf[1] = B.leaf[1]; } +expr(A) ::= G_IDENTIFIER(func_name) G_PARENL expr(B) G_PARENR. { A.op = TR_OP_FUNC_CALL; A.leaf[0] = copyNode(func_name); A.leaf[1] = copyNode(B); A.valid=1; } diff --git a/qa/libeval_compiler/libeval_compiler.cpp b/qa/libeval_compiler/libeval_compiler.cpp new file mode 100644 index 0000000000..ca8961d430 --- /dev/null +++ b/qa/libeval_compiler/libeval_compiler.cpp @@ -0,0 +1,735 @@ +/* + This file is part of libeval, a simple math expression evaluator + + Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#ifdef DEBUG +#include +#endif + +#include "libeval_compiler.h" + +/* The (generated) lemon parser is written in C. + * In order to keep its symbol from the global namespace include the parser code with + * a C++ namespace. + */ +namespace LIBEVAL +{ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wsign-compare" +#endif + +#include "grammar.c" +#include "grammar.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +static void libeval_dbg( const char* fmt, ... ) +{ +#ifdef DEBUG + va_list ap; + va_start( ap, fmt ); + fprintf( stderr, "libeval: " ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); +#endif +} + +static const std::string formatOpName( int op ) +{ + static const struct + { + int op; + std::string mnemonic; + } simpleOps[] = { { TR_OP_MUL, "MUL" }, { TR_OP_DIV, "DIV" }, { TR_OP_ADD, "ADD" }, + { TR_OP_SUB, "SUB" }, { TR_OP_LESS, "LESS" }, { TR_OP_GREATER, "GREATER" }, + { TR_OP_LESS_EQUAL, "LESS_EQUAL" }, { TR_OP_GREATER_EQUAL, "GREATER_EQUAL" }, + { TR_OP_EQUAL, "EQUAL" }, { TR_OP_NOT_EQUAL, "NEQUAL" }, { TR_OP_BOOL_AND, "AND" }, + { TR_OP_BOOL_OR, "OR" }, { TR_OP_BOOL_NOT, "NOT" }, { -1, "" } }; + + for( int i = 0; simpleOps[i].op >= 0; i++ ) + { + if( simpleOps[i].op == op ) + { + return simpleOps[i].mnemonic; + } + } + + return "???"; +} + + +std::string UCODE::UOP::Format() const +{ + char str[1024]; + + switch( m_op ) + { + case TR_UOP_PUSH_VAR: + sprintf( str, "PUSH VAR [%p]", m_arg ); + break; + case TR_UOP_PUSH_VALUE: + { + auto val = reinterpret_cast( m_arg ); + if( val->GetType() == VT_NUMERIC ) + sprintf( str, "PUSH NUM [%.10f]", val->AsDouble() ); + else + sprintf( str, "PUSH STR [%s]", val->AsString().c_str() ); + break; + } + default: + sprintf( str, "%s", formatOpName( m_op ).c_str() ); + break; + } + return str; +} + +std::string UCODE::Dump() const +{ + std::string rv; + for( auto op : m_ucode ) + { + rv += op->Format(); + rv += "\n"; + } + return rv; +}; + +std::string TOKENIZER::GetChars( std::function cond ) const +{ + std::string rv; + size_t p = m_pos; + // printf("p %d len %d\n", p, str.length() ); + while( p < m_str.length() && cond( m_str[p] ) ) + { + rv.append( 1, m_str[p] ); + p++; + } + return rv; +} + +bool TOKENIZER::MatchAhead( std::string match, std::function stopCond ) const +{ + int remaining = m_str.length() - m_pos; + if( remaining < (int) match.length() ) + return false; + + if( m_str.substr( m_pos, match.length() ) == match ) + { + return ( remaining == (int) match.length() || stopCond( m_str[m_pos + match.length()] ) ); + } + return false; +} + + +COMPILER::COMPILER() +{ + m_localeDecimalSeparator = '.'; + m_parseError = false; + m_parseFinished = false; + m_unitResolver.reset( new UNIT_RESOLVER ); + m_parser = LIBEVAL::ParseAlloc( malloc ); +} + + +COMPILER::~COMPILER() +{ + LIBEVAL::ParseFree( m_parser, free ); + + // Allow explicit call to destructor + m_parser = nullptr; + + Clear(); +} + + +void COMPILER::Clear() +{ + //free( current.token ); + m_tokenizer.Clear(); + m_parseError = true; +} + + +void COMPILER::parseError( const char* s ) +{ + m_parseError = true; +} + + +void COMPILER::parseOk() +{ + m_parseFinished = true; +} + + +bool COMPILER::Compile( const std::string& aString, UCODE* aCode ) +{ + // Feed parser token after token until end of input. + + newString( aString ); + m_tree = nullptr; + m_parseError = false; + m_parseFinished = false; + T_TOKEN tok; + + libeval_dbg( "str: '%s' empty: %d\n", aString.c_str(), !!aString.empty() ); + + if( aString.empty() ) + { + m_parseFinished = true; + return generateUCode( aCode ); + } + + do + { + tok = getToken(); + libeval_dbg( "parse: tok %d\n", tok.token ); + Parse( m_parser, tok.token, tok.value, this ); + //printf('error') + if( m_parseError ) + { + //printf( "PARSE ERR\n" ); + m_parseErrorToken = ""; + m_parseErrorPos = m_tokenizer.GetPos(); + return false; + } + + if( m_parseFinished || tok.token == G_ENDS ) + { + // Reset parser by passing zero as token ID, value is ignored. + Parse( m_parser, 0, tok.value, this ); + break; + } + } while( tok.token ); + + return generateUCode( aCode ); +} + + +void COMPILER::newString( const std::string& aString ) +{ + Clear(); + + m_lexerState = LS_DEFAULT; + m_tokenizer.Restart( aString ); + m_parseFinished = false; +} + +COMPILER::T_TOKEN COMPILER::getToken() +{ + T_TOKEN rv; + bool done = false; + do + { + + switch( m_lexerState ) + { + case LS_DEFAULT: + done = lexDefault( rv ); + break; + case LS_STRING: + done = lexString( rv ); + break; + } + //printf( "-> lstate %d done %d\n", m_lexerState, !!done ); + } while( !done ); + + return rv; +} + + +bool COMPILER::lexString( COMPILER::T_TOKEN& aToken ) +{ + auto str = m_tokenizer.GetChars( []( int c ) -> bool { return c != '"'; } ); + //printf("STR LIT '%s'\n", (const char *)str.c_str() ); + + aToken.token = G_STRING; + strcpy( aToken.value.value.str, str.c_str() ); + + m_tokenizer.NextChar( str.length() + 1 ); + m_lexerState = LS_DEFAULT; + return true; +} + + +int COMPILER::resolveUnits() +{ + int unitId = 0; + for( auto unitName : m_unitResolver->GetSupportedUnits() ) + { + if( m_tokenizer.MatchAhead( unitName, []( int c ) -> bool { return !isalnum( c ); } ) ) + { + libeval_dbg( "Match unit '%s'\n", unitName.c_str() ); + m_tokenizer.NextChar( unitName.length() ); + return unitId; + } + + unitId++; + } + + return -1; +} + +bool COMPILER::lexDefault( COMPILER::T_TOKEN& aToken ) +{ + T_TOKEN retval; + std::string current; + size_t idx; + int convertFrom; + + retval.token = G_ENDS; + + //printf( "tokdone %d\n", !!m_tokenizer.Done() ); + if( m_tokenizer.Done() ) + { + aToken = retval; + return true; + } + + auto isDecimalSeparator = [&]( char ch ) -> bool { + return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' ); + }; + + // Lambda: get value as string, store into clToken.token and update current index. + auto extractNumber = [&]() { + bool haveSeparator = false; + idx = 0; + auto ch = m_tokenizer.GetChar(); + + do + { + if( isDecimalSeparator( ch ) && haveSeparator ) + break; + + current.append( 1, ch ); + + if( isDecimalSeparator( ch ) ) + haveSeparator = true; + + m_tokenizer.NextChar(); + ch = m_tokenizer.GetChar(); + } while( isdigit( ch ) || isDecimalSeparator( ch ) ); + + // Ensure that the systems decimal separator is used + for( int i = current.length(); i; i-- ) + if( isDecimalSeparator( current[i - 1] ) ) + current[i - 1] = m_localeDecimalSeparator; + + + //printf("-> NUM: '%s'\n", (const char *) current.c_str() ); + }; + + + int ch; + + // Start processing of first/next token: Remove whitespace + for( ;; ) + { + ch = m_tokenizer.GetChar(); + + if( ch == ' ' ) + m_tokenizer.NextChar(); + else + break; + } + + libeval_dbg( "LEX ch '%c' pos %d\n", ch, m_tokenizer.GetPos() ); + + if( ch == 0 ) + { + /* End of input */ + } + else if( isdigit( ch ) ) + { + // VALUE + extractNumber(); + retval.token = G_VALUE; + strcpy( retval.value.value.str, current.c_str() ); + } + else if( ( convertFrom = resolveUnits() ) >= 0 ) + { + //printf("unit\n"); + // UNIT + // Units are appended to a VALUE. + // Determine factor to default unit if unit for value is given. + // Example: Default is mm, unit is inch: factor is 25.4 + // The factor is assigned to the terminal UNIT. The actual + // conversion is done within a parser action. + retval.token = G_UNIT; + retval.value.value.type = convertFrom; + } + else if( ch == '\"' ) // string literal + { + //printf( "MATCH STRING LITERAL\n" ); + m_lexerState = LS_STRING; + m_tokenizer.NextChar(); + return false; + } + else if( isalpha( ch ) ) + { + //printf("ALPHA\n"); + current = m_tokenizer.GetChars( []( int c ) -> bool { return isalnum( c ); } ); + //printf("Len: %d\n", current.length() ); + //printf("id '%s'\n", (const char *) current.c_str() ); + fflush( stdout ); + retval.token = G_IDENTIFIER; + strcpy( retval.value.value.str, current.c_str() ); + m_tokenizer.NextChar( current.length() ); + } + else if( m_tokenizer.MatchAhead( "==", []( int c ) -> bool { return c != '='; } ) ) + { + retval.token = G_EQUAL; + m_tokenizer.NextChar( 2 ); + //printf( "nc pos %d\n", m_tokenizer.GetPos() ); + } + else if( m_tokenizer.MatchAhead( "<=", []( int c ) -> bool { return c != '='; } ) ) + { + retval.token = G_LESS_EQUAL_THAN; + m_tokenizer.NextChar( 2 ); + } + else if( m_tokenizer.MatchAhead( ">=", []( int c ) -> bool { return c != '='; } ) ) + { + retval.token = G_GREATER_EQUAL_THAN; + m_tokenizer.NextChar( 2 ); + } + else if( m_tokenizer.MatchAhead( "&&", []( int c ) -> bool { return c != '&'; } ) ) + { + retval.token = G_BOOL_AND; + m_tokenizer.NextChar( 2 ); + } + else if( m_tokenizer.MatchAhead( "||", []( int c ) -> bool { return c != '|'; } ) ) + { + retval.token = G_BOOL_OR; + m_tokenizer.NextChar( 2 ); + } + else + { + //printf( "WTF: '%c'\n", ch ); + + // Single char tokens + switch( ch ) + { + case '+': + retval.token = G_PLUS; + break; + case '!': + retval.token = G_BOOL_NOT; + break; + case '-': + retval.token = G_MINUS; + break; + case '*': + retval.token = G_MULT; + break; + case '/': + retval.token = G_DIVIDE; + break; + case '<': + retval.token = G_LESS_THAN; + break; + case '>': + retval.token = G_GREATER_THAN; + break; + case '(': + retval.token = G_PARENL; + break; + case ')': + retval.token = G_PARENR; + break; + case ';': + retval.token = G_SEMCOL; + break; + case '.': + retval.token = G_STRUCT_REF; + break; + default: + m_parseError = true; + break; /* invalid character */ + } + + m_tokenizer.NextChar(); + } + + aToken = retval; + return true; +} + +const std::string formatNode( TREE_NODE* tok ) +{ + char str[1024]; + // printf("fmt tok %p v %p ", tok, tok->value.v ); + fflush( stdout ); + sprintf( str, "%s", (const char*) tok->value.str ); + return str; +} + + +void dumpNode( std::string& buf, TREE_NODE* tok, int depth = 0 ) +{ + char str[1024]; + sprintf( str, "\n[%p] ", tok ); //[tok %p] ", tok); + buf += str; + for( int i = 0; i < 2 * depth; i++ ) + buf += " "; + + if( tok->op & TR_OP_BINARY_MASK ) + { + sprintf( str, "%s", (const char*) formatOpName( tok->op ).c_str() ); + buf += str; + dumpNode( buf, tok->leaf[0], depth + 1 ); + dumpNode( buf, tok->leaf[1], depth + 1 ); + } + + switch( tok->op ) + { + case TR_NUMBER: + sprintf( str, "NUMERIC: " ); + buf += str; + sprintf( str, "%s", formatNode( tok ).c_str() ); + buf += str; + if( tok->leaf[0] ) + dumpNode( buf, tok->leaf[0], depth + 1 ); + break; + case TR_STRING: + sprintf( str, "STRING: " ); + buf += str; + sprintf( str, "%s", formatNode( tok ).c_str() ); + buf += str; + break; + case TR_IDENTIFIER: + sprintf( str, "ID: " ); + buf += str; + sprintf( str, "%s", formatNode( tok ).c_str() ); + buf += str; + break; + case TR_STRUCT_REF: + sprintf( str, "SREF: " ); + buf += str; + dumpNode( buf, tok->leaf[0], depth + 1 ); + dumpNode( buf, tok->leaf[1], depth + 1 ); + break; + case TR_UNIT: + sprintf( str, "UNIT: %d ", tok->value.type ); + buf += str; + break; + } +} + +void COMPILER::setRoot( TREE_NODE root ) +{ + m_tree = copyNode( root ); +} + + +bool COMPILER::generateUCode( UCODE* aCode ) +{ + std::vector stack; + std::set visitedNodes; + + auto visited = [&]( TREE_NODE* node ) -> bool { + return visitedNodes.find( node ) != visitedNodes.end(); + }; + + UCODE code; + + assert( m_tree ); + + stack.push_back( m_tree ); + + //printf("compile: tree %p\n", m_tree); + + while( !stack.empty() ) + { + auto node = stack.back(); + bool isTerminalNode = true; + + // printf( "process node %p [op %d] [stack %d]\n", node, node->op, stack.size() ); + + // process terminal nodes first + switch( node->op ) + { + case TR_STRUCT_REF: + { + assert( node->leaf[0]->op == TR_IDENTIFIER ); + assert( node->leaf[1]->op == TR_IDENTIFIER ); + + auto vref = aCode->createVarRef( node->leaf[0]->value.str, node->leaf[1]->value.str ); + aCode->AddOp( TR_UOP_PUSH_VAR, vref ); + break; + } + + case TR_NUMBER: + { + auto son = node->leaf[0]; + + double value = atof( node->value.str ); // fixme: locale + + if( son && son->op == TR_UNIT ) + { + //printf( "HandleUnit: %s unit %d\n", node->value.str, son->value.type ); + + value = m_unitResolver->Convert( node->value.str, son->value.type ); + visitedNodes.insert( son ); + } + + aCode->AddOp( TR_UOP_PUSH_VALUE, value ); + + break; + } + case TR_STRING: + { + aCode->AddOp( TR_UOP_PUSH_VALUE, node->value.str ); + break; + } + default: + isTerminalNode = false; + break; + } + + if( isTerminalNode ) + { + visitedNodes.insert( node ); + stack.pop_back(); + continue; + } + + if( node->leaf[0] && !visited( node->leaf[0] ) ) + { + stack.push_back( node->leaf[0] ); + } + else if( node->leaf[1] && !visited( node->leaf[1] ) ) + { + stack.push_back( node->leaf[1] ); + } + else + { + aCode->AddOp( node->op ); + visitedNodes.insert( node ); + stack.pop_back(); + } + } + + + return true; +} + + +void UCODE::UOP::Exec( CONTEXT* ctx, UCODE* ucode ) +{ + + switch( m_op ) + { + case TR_UOP_PUSH_VAR: + { + auto value = ctx->AllocValue(); + value->Set( reinterpret_cast( m_arg )->GetValue( ucode ) ); + ctx->Push( value ); + break; + } + case TR_UOP_PUSH_VALUE: + ctx->Push( reinterpret_cast( m_arg ) ); + return; + default: + break; + } + + if( m_op & TR_OP_BINARY_MASK ) + { + auto arg2 = ctx->Pop(); + auto arg1 = ctx->Pop(); + double result; + + switch( m_op ) + { + case TR_OP_ADD: + result = arg1->AsDouble() + arg2->AsDouble(); + break; + case TR_OP_SUB: + result = arg1->AsDouble() - arg2->AsDouble(); + break; + case TR_OP_MUL: + result = arg1->AsDouble() * arg2->AsDouble(); + break; + case TR_OP_DIV: + result = arg1->AsDouble() / arg2->AsDouble(); + break; + case TR_OP_LESS_EQUAL: + result = arg1->AsDouble() <= arg2->AsDouble() ? 1 : 0; + break; + case TR_OP_GREATER_EQUAL: + result = arg1->AsDouble() >= arg2->AsDouble() ? 1 : 0; + break; + case TR_OP_LESS: + result = arg1->AsDouble() < arg2->AsDouble() ? 1 : 0; + break; + case TR_OP_GREATER: + result = arg1->AsDouble() > arg2->AsDouble() ? 1 : 0; + break; + case TR_OP_EQUAL: + result = arg1->EqualTo( arg2 ) ? 1 : 0; + break; + case TR_OP_NOT_EQUAL: + result = arg1->EqualTo( arg2 ) ? 0 : 1; + break; + case TR_OP_BOOL_AND: + result = ( ( arg1->AsDouble() != 0.0 ? true : false ) + && ( arg2->AsDouble() != 0.0 ? true : false ) ) ? + 1 : + 0; + break; + case TR_OP_BOOL_OR: + result = ( ( arg1->AsDouble() != 0.0 ? true : false ) + || ( arg2->AsDouble() != 0.0 ? true : false ) ) ? + 1 : + 0; + break; + default: + result = 0.0; + break; + } + + auto rp = ctx->AllocValue(); + rp->Set( result ); + ctx->Push( rp ); + return; + } + else if( m_op & TR_OP_UNARY_MASK ) + { + // fixme : not operator + } +} + +VALUE* UCODE::Run() +{ + CONTEXT ctx; + for( const auto op : m_ucode ) + op->Exec( &ctx, this ); + + assert( ctx.SP() == 1 ); + return ctx.Pop(); +} + +} // namespace LIBEVAL diff --git a/qa/libeval_compiler/libeval_compiler.h b/qa/libeval_compiler/libeval_compiler.h new file mode 100644 index 0000000000..a7393e228d --- /dev/null +++ b/qa/libeval_compiler/libeval_compiler.h @@ -0,0 +1,471 @@ +/* + This file is part of libeval, a simple math expression evaluator + + Copyright (C) 2007 Michael Geselbracht, mgeselbracht3@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __LIBEVAL_COMPILER_H +#define __LIBEVAL_COMPILER_H + +#include +#include +#include +#include + +#include + +#define TR_OP_BINARY_MASK 0x200 +#define TR_OP_UNARY_MASK 0x100 + +#define TR_OP_MUL 0x201 +#define TR_OP_DIV 0x202 +#define TR_OP_ADD 0x203 +#define TR_OP_SUB 0x204 +#define TR_OP_LESS 0x25 +#define TR_OP_GREATER 0x206 +#define TR_OP_LESS_EQUAL 0x207 +#define TR_OP_GREATER_EQUAL 0x208 +#define TR_OP_EQUAL 0x209 +#define TR_OP_NOT_EQUAL 0x20a +#define TR_OP_BOOL_AND 0x20b +#define TR_OP_BOOL_OR 0x20c +#define TR_OP_BOOL_NOT 0x100 +#define TR_OP_FUNC_CALL 24 + +#define TR_UOP_PUSH_VAR 1 +#define TR_UOP_PUSH_VALUE 2 + +// This namespace is used for the lemon parser +namespace LIBEVAL +{ + +struct ERROR_STATUS +{ + bool pendingError; + + enum STAGE { + CST_PARSE = 0, + CST_CODEGEN, + CST_RUNTIME + }; + + std::string message; + std::string failingObject; + int failingPosition; +}; + + +enum VAR_TYPE_T +{ + VT_STRING = 1, + VT_NUMERIC, + VT_UNDEFINED +}; + +enum TOKEN_TYPE_T +{ + TR_NUMBER = 1, + TR_IDENTIFIER = 2, + TR_ASSIGN = 3, + TR_STRUCT_REF = 4, + TR_STRING = 5, + TR_UNIT = 6 +}; + +#define LIBEVAL_MAX_LITERAL_LENGTH 1024 + +struct TREE_NODE +{ + struct value_s + { + char str[LIBEVAL_MAX_LITERAL_LENGTH]; + int type; + } value; + int op; + TREE_NODE* leaf[2]; + bool valid; +}; + +static inline TREE_NODE* copyNode( TREE_NODE& t ) +{ + auto t2 = new TREE_NODE(); + t2->valid = t.valid; + strcpy(t2->value.str, t.value.str); + t2->op = t.op; + t2->value.type = t.value.type; + t2->leaf[0] = t.leaf[0]; + t2->leaf[1] = t.leaf[1]; + return t2; +} + + + +static inline TREE_NODE* newNode( int op, int type, std::string value ) +{ + auto t2 = new TREE_NODE(); + t2->valid = true; + strcpy(t2->value.str, value.c_str()); + t2->op = op; + t2->value.type = type; + t2->leaf[0] = nullptr; + t2->leaf[1] = nullptr; + return t2; +} + + +class UNIT_RESOLVER { + public: + UNIT_RESOLVER() + { + } + + virtual ~UNIT_RESOLVER() + { + } + + virtual const std::vector& GetSupportedUnits() const + { + static const std::vector nullUnits; + + return nullUnits; + } + + virtual double Convert( const std::string aString, int unitType ) const + { + return 0.0; + }; +}; + +class VALUE { +public: + VALUE(): + m_type(VT_UNDEFINED), + m_valueDbl( 0 ) + {}; + + VALUE( const std::string& aStr ) : + m_type( VT_STRING ), + m_valueDbl( 0 ), + m_valueStr( aStr ) + {}; + + VALUE( const double aVal ) : + m_type( VT_NUMERIC ), + m_valueDbl( aVal ) + {}; + + double AsDouble() const + { + return m_valueDbl; + } + + const std::string& AsString() const + { + return m_valueStr; + } + + bool operator==( const VALUE& b ) const + { + if( m_type != b.m_type ) + return false; + if( m_type == VT_NUMERIC && m_valueDbl != b.m_valueDbl ) + return false; + if( m_type == VT_STRING && m_valueStr != b.m_valueStr ) + return false; + + return true; + } + + + + VAR_TYPE_T GetType() const { return m_type; }; + + void Set( double aValue ) + { + m_type = VT_NUMERIC; + m_valueDbl = aValue; + } + + void Set( std::string aValue ) + { + m_type = VT_STRING; + m_valueStr = aValue; + } + + void Set( const VALUE &val ) + { + m_type = val.m_type; + m_valueDbl = val.m_valueDbl; + if(m_type == VT_STRING) + m_valueStr = val.m_valueStr; + + } + + + bool EqualTo( const VALUE* v2 ) const + { + assert ( m_type == v2->m_type ); + if(m_type == VT_STRING) + return m_valueStr == v2->m_valueStr; + else + return m_valueDbl == v2->m_valueDbl; + } + +private: + VAR_TYPE_T m_type; + double m_valueDbl; + std::string m_valueStr; + }; + +class UCODE +{ +public: + + class CONTEXT + { + public: + const int c_memSize = 128; + + CONTEXT() + { + m_sp = 0; + for (int i = 0; i < c_memSize; i++ ) + m_memory.push_back( VALUE() ); + } + + VALUE* AllocValue() + { + assert( m_memPos < c_memSize ); + auto rv = &m_memory[ m_memPos++ ]; + return rv; + } + + void Push( VALUE* v ) + { + m_stack[m_sp++] = v; + } + + VALUE* Pop() + { + m_sp--; + return m_stack[m_sp]; + } + + int SP() const + { + return m_sp; + } + private: + std::vector m_memory; + VALUE* m_stack[128]; + int m_sp = 0; + int m_memPos = 0; + }; + + class UOP + { + public: + UOP( int op, void* arg ) : m_op( op ), m_arg( arg ) + {}; + + void Exec( CONTEXT* ctx, UCODE *ucode ); + std::string Format() const; + + private: + int m_op; + void *m_arg; + }; + + class VAR_REF + { + public: + virtual VAR_TYPE_T GetType( const UCODE* aUcode ) const = 0; + virtual VALUE GetValue( const UCODE* aUcode ) const = 0; + }; + + + void AddOp( int op, double value ) + { + auto uop = new UOP( op, new VALUE( value ) ); + m_ucode.push_back( uop ); + } + + void AddOp( int op, std::string value ) + { + auto uop = new UOP( op, new VALUE( value ) ); + m_ucode.push_back( uop ); + } + + void AddOp( int op, VAR_REF* aRef = nullptr ) + { + auto uop = new UOP( op, aRef ); + m_ucode.push_back( uop ); + } + + VALUE* Run(); + std::string Dump() const; + + virtual VAR_REF* createVarRef( const std::string& var, const std::string& field ) + { + return NULL; + }; + +private: + std::vector m_ucode; +}; + +class TOKENIZER +{ +public: + void Restart( const std::string& aStr ) + { + m_str = aStr; + m_pos = 0; + } + + void Clear() + { + m_str = ""; + m_pos = 0; + } + + int GetChar() const + { + if( m_pos >= m_str.length() ) + return 0; + return m_str[m_pos]; + } + + bool Done() const + { + return m_pos >= m_str.length(); + } + + void NextChar( int aAdvance = 1 ) + { + m_pos += aAdvance; + } + + size_t GetPos() const + { + return m_pos; + } + + + std::string GetChars( std::function cond ) const; + bool MatchAhead( std::string match, std::function stopCond ) const; +private: + std::string m_str; + size_t m_pos; + +}; + + +class COMPILER +{ +public: + COMPILER(); + virtual ~COMPILER(); + + /* clear() should be invoked by the client if a new input string is to be processed. It + * will reset the parser. User defined variables are retained. + */ + void Clear(); + + /* Used by the lemon parser */ + void parseError( const char* s ); + void parseOk(); + + /* Check if previous invokation of process() was successful */ + inline bool IsValid() const + { + return !m_parseError; + } + + const std::string GetParseErrorToken() const + { + return m_parseErrorToken; + } + + const std::string GetParseErrorMessage() const + { + return m_parseErrorMessage; + } + + int GetParseErrorPosition() const + { + return m_parseErrorPos; + } + + void setRoot( LIBEVAL::TREE_NODE root ); + + bool Compile( const std::string& aString, UCODE* aCode ); + std::string DumpTree(); + +protected: + enum LEXER_STATE + { + LS_DEFAULT = 0, + LS_STRING = 1, + }; + + LEXER_STATE m_lexerState; + + bool generateUCode( UCODE* aCode ); + + + /* Token type used by the tokenizer */ + struct T_TOKEN + { + int token; + TREE_NODE value; + }; + + /* Begin processing of a new input string */ + void newString( const std::string& aString ); + + /* Tokenizer: Next token/value taken from input string. */ + T_TOKEN getToken(); + bool lexDefault( T_TOKEN& aToken ); + bool lexString( T_TOKEN& aToken ); + + /* Used by processing loop */ + void parse( int token, TREE_NODE value ); + + void* m_parser; // the current lemon parser state machine + int resolveUnits(); + + /* Token state for input string. */ + + TOKENIZER m_tokenizer; + char m_localeDecimalSeparator; + + /* Parse progress. Set by parser actions. */ + bool m_parseError; + bool m_parseFinished; + std::string m_parseErrorMessage; + std::string m_parseErrorToken; + int m_parseErrorPos; + + std::unique_ptr m_unitResolver; + + TREE_NODE* m_tree; +}; + + +} // namespace LIBEVAL + +#endif /* LIBEVAL_COMPILER_H_ */ diff --git a/qa/libeval_compiler/libeval_compiler_test.cpp b/qa/libeval_compiler/libeval_compiler_test.cpp new file mode 100644 index 0000000000..4a33e9341e --- /dev/null +++ b/qa/libeval_compiler/libeval_compiler_test.cpp @@ -0,0 +1,324 @@ +#include +#include + +#include "class_board.h" +#include "class_track.h" +#include "libeval_compiler.h" + + +#include +#include + +#include + +#include + +class PCB_EXPR_VAR_REF; + +class PCB_EXPR_UCODE : public LIBEVAL::UCODE +{ +public: + + virtual VAR_REF *createVarRef( const std::string &var, const std::string &field ) override; + + void SetItems( BOARD_ITEM *a, BOARD_ITEM* b ) + { + m_items[0] = a; + m_items[1] = b; + } + + BOARD_ITEM *GetItem( int index ) const + { + return m_items[index]; + } + +private: + BOARD_ITEM *m_items[2]; +}; + + +class PCB_EXPR_VAR_REF : public LIBEVAL::UCODE::VAR_REF +{ +public: + PCB_EXPR_VAR_REF ( int aItemIndex ) + : m_itemIndex(aItemIndex) + { + //printf("*** createVarRef %p %d\n", this, aItemIndex ); + } + + void SetType( LIBEVAL::VAR_TYPE_T type ) + { + m_type = type; + } + + void AddAllowedClass ( TYPE_ID type_hash, PROPERTY_BASE *prop ) + { + m_matchingTypes[type_hash] = prop; + } + + virtual LIBEVAL::VAR_TYPE_T GetType( const LIBEVAL::UCODE* aUcode ) const override + { + return m_type; + } + + virtual LIBEVAL::VALUE GetValue( const LIBEVAL::UCODE* aUcode ) const override + { + auto ucode = static_cast (aUcode); + auto item = ucode->GetItem( m_itemIndex ); + + auto it = m_matchingTypes.find( TYPE_HASH( *item ) ); + + if( it == m_matchingTypes.end() ) + { + printf("Null field!\n"); + return LIBEVAL::VALUE(0.0); + } + else + { + if( m_type == LIBEVAL::VT_NUMERIC ) + return LIBEVAL::VALUE( (double) item->Get( it->second ) ); + else + { + wxString str = item->Get( it->second ); + //printf("item %p GetStr '%s'\n", item, (const char*) str.c_str()); + return LIBEVAL::VALUE( (const char*) str.c_str() ); + } + } + } + +private: + + std::unordered_map m_matchingTypes; + int m_itemIndex; + LIBEVAL::VAR_TYPE_T m_type; +}; + +LIBEVAL::UCODE::VAR_REF *PCB_EXPR_UCODE::createVarRef( const std::string &var, const std::string &field ) +{ + PCB_EXPR_VAR_REF *rv; + + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + + auto classes = propMgr.GetAllClasses(); + auto vref = new PCB_EXPR_VAR_REF( var == "A" ? 0 : 1 ); + + for ( auto cls : classes ) + { + if( propMgr.IsOfType( cls.type, TYPE_HASH( BOARD_ITEM ) ) ) + { + PROPERTY_BASE* prop = propMgr.GetProperty( cls.type, field ); + if( prop ) + { + //printf("Field '%s' class %s ptr %p\n", field.c_str(), (const char *) cls.name.c_str(), prop ); + vref->AddAllowedClass( cls.type, prop ); + if ( prop->TypeHash() == TYPE_HASH(int) ) + vref->SetType( LIBEVAL::VT_NUMERIC ); + else if ( prop->TypeHash() == TYPE_HASH(wxString) ) + vref->SetType( LIBEVAL::VT_STRING ); + else { + printf("Unknown property type\n"); + } + } + } + } + + return vref; +} + + +BOARD* loadBoard( const std::string& filename ) +{ + PLUGIN::RELEASER pi( new PCB_IO ); + BOARD* brd = nullptr; + + try + { + brd = pi->Load( wxString( filename.c_str() ), NULL, NULL ); + } + catch( const IO_ERROR& ioe ) + { + wxString msg = wxString::Format( _( "Error loading board.\n%s" ), + ioe.Problem() ); + + printf( "%s\n", (const char*) msg.mb_str() ); + return nullptr; + } + + return brd; +} + + +class PCB_UNIT_RESOLVER : public LIBEVAL::UNIT_RESOLVER +{ + public: + virtual ~PCB_UNIT_RESOLVER() + { + } + + virtual const std::vector& GetSupportedUnits() const override + { + static const std::vector pcbUnits = {"mil", "mm", "in"}; + + return pcbUnits; + } + + virtual double Convert( const std::string aString, int unitId ) const override + { + double v = atof(aString.c_str()); + switch(unitId) + { + case 0 : + return Mils2iu( v ); + case 1: + return Millimeter2iu( v ); + case 2: + return Mils2iu( v * 1000.0 ); + default: + return v; + } + }; +}; + + +class PCB_EXPR_COMPILER : public LIBEVAL::COMPILER +{ + public: + PCB_EXPR_COMPILER() + { + m_unitResolver.reset( new PCB_UNIT_RESOLVER ); + } +}; + +bool testEvalExpr( const std::string expr, LIBEVAL::VALUE expectedResult, bool expectError = false, BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr ) +{ + PCB_EXPR_COMPILER compiler; + PCB_EXPR_UCODE ucode; + bool ok = true; + + ucode.SetItems( itemA, itemB ); + + bool error = !compiler.Compile( expr, &ucode ); + + if( error ) + { + if ( expectError ) + { + printf("result: OK (expected parse error)\n"); + ok = true; + return ok; + } else { + printf("result: FAIL (unexpected parse error)\n"); + ok = false; + } + } + + LIBEVAL::VALUE result; + + if( ok ) + { + result = *ucode.Run(); + ok = (result == expectedResult); + } + + if( expectedResult.GetType() == LIBEVAL::VT_NUMERIC ) + printf("result: %s (got %.10f expected: %.10f)\n", ok ? "OK" : "FAIL", result.AsDouble(), expectedResult.AsDouble() ); + else + printf("result: %s (got '%s' expected: '%s')\n", ok ? "OK" : "FAIL", result.AsString().c_str(), expectedResult.AsString().c_str() ); + + if (!ok ) + { + printf("Offending code dump: \n%s\n", ucode.Dump().c_str() ); + } + + return ok; +} + +bool EvaluatePCBExpression( const std::string& aExpr, int& aResult ) +{ + PCB_EXPR_COMPILER compiler; + PCB_EXPR_UCODE ucode; + if( !compiler.Compile( aExpr, &ucode ) ) + return false; + + auto result = ucode.Run(); + return true; +} + +class PCB_EXPR_EVALUATOR +{ +public: + PCB_EXPR_EVALUATOR(); + ~PCB_EXPR_EVALUATOR(); + + bool Evaluate( const wxString& aExpr ); + int Result() const { return m_result; } + wxString GetErrorString(); + +private: + bool m_error; + int m_result; + + PCB_EXPR_COMPILER m_compiler; + PCB_EXPR_UCODE m_ucode; +} + +int main( int argc, char *argv[] ) +{ + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + propMgr.Rebuild(); + + + using VAL = LIBEVAL::VALUE; + +/* testEvalExpr( "10mm + 20 mm", VAL(30e6) ); + testEvalExpr( "3*(7+8)", VAL(3*(7+8)) ); + testEvalExpr( "3*7+8", VAL(3*7+8) ); + testEvalExpr( "(3*7)+8", VAL(3*7+8) ); + testEvalExpr( "10mm + 20)", VAL(0), true ); + */ + + BOARD brd; + + NETINFO_LIST& netInfo = brd.GetNetInfo(); + + NETCLASSPTR netclass1( new NETCLASS("HV") ); + NETCLASSPTR netclass2( new NETCLASS("otherClass" ) ); + + printf("netcl1 classname '%s'\n", (const char *) netclass1->GetName().c_str() ); + + + auto net1info = new NETINFO_ITEM( &brd, "net1", 1); + auto net2info = new NETINFO_ITEM( &brd, "net2", 2); + + + net1info->SetClass( netclass1 ); + net2info->SetClass( netclass2 ); + + printf("netX classname '%s'\n", net1info->GetClassName().c_str() ); + + + printf("net1 class %p %p\n", net1info->GetNetClass(), netclass1.get() ); + + TRACK trackA(&brd); + TRACK trackB(&brd); + + printf("net1 classname '%s'\n", (const char*) net1info->GetClassName().c_str() ); + + trackA.SetNet( net1info ); + trackB.SetNet( net2info ); + + trackA.SetWidth( Mils2iu( 10 )); + trackB.SetWidth( Mils2iu( 20 )); + + printf("TrkA %p netclass '%s'\n", &trackA, (const char*) trackA.GetNetClassName().c_str() ); + printf("TrkB %p netclass '%s'\n", &trackB, (const char*) trackB.GetNetClassName().c_str() ); + + testEvalExpr( "A.Width > B.Width", VAL(0.0), false, &trackA, &trackB ); + testEvalExpr( "A.Width + B.Width", VAL(Mils2iu(10) + Mils2iu(20)), false, &trackA, &trackB ); + + testEvalExpr( "A.Netclass", VAL( (const char*) trackA.GetNetClassName().c_str() ), false, &trackA, &trackB ); + testEvalExpr( "A.Netclass == \"HV\" && B.netclass == \"otherClass\"", VAL( 1.0 ), false, &trackA, &trackB ); + testEvalExpr( "A.Netclass + 1.0", VAL( 1.0 ), false, &trackA, &trackB ); + + return 0; +} diff --git a/qa/qa_utils/mocks.cpp b/qa/qa_utils/mocks.cpp index 449b265643..0543570a1d 100644 --- a/qa/qa_utils/mocks.cpp +++ b/qa/qa_utils/mocks.cpp @@ -121,6 +121,7 @@ program; PGM_BASE& Pgm() { + printf("Pgm @ %p\n", &program ); return program; } @@ -129,7 +130,7 @@ PGM_BASE& Pgm() // is run from a python script, mot from a Kicad application PGM_BASE* PgmOrNull() { - return &program; + return nullptr; //&program; }