From b5fc6e45cfb0ff0c101074e782e61edf4f88bf7c Mon Sep 17 00:00:00 2001 From: Michael Geselbracht Date: Thu, 23 Nov 2017 11:36:24 +0100 Subject: [PATCH] Numeric expression evaluator --- common/CMakeLists.txt | 2 + common/base_units.cpp | 7 +- common/libeval/grammar.c | 1011 ++++++++++++++++++++++++++ common/libeval/grammar.lemon | 68 ++ common/libeval/main.cpp | 52 ++ common/libeval/numeric_evaluator.cpp | 334 +++++++++ include/libeval/grammar.h | 12 + include/libeval/numeric_evaluator.h | 193 +++++ 8 files changed, 1678 insertions(+), 1 deletion(-) create mode 100644 common/libeval/grammar.c create mode 100644 common/libeval/grammar.lemon create mode 100644 common/libeval/main.cpp create mode 100644 common/libeval/numeric_evaluator.cpp create mode 100644 include/libeval/grammar.h create mode 100644 include/libeval/numeric_evaluator.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e1deda0707..d5b2bee81d 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -348,6 +348,8 @@ set( COMMON_SRCS geometry/shape_collisions.cpp geometry/shape_file_io.cpp geometry/convex_hull.cpp + + libeval/numeric_evaluator.cpp ) add_library( common STATIC ${COMMON_SRCS} ) add_dependencies( common lib-dependencies ) diff --git a/common/base_units.cpp b/common/base_units.cpp index 4611b182df..323824badd 100644 --- a/common/base_units.cpp +++ b/common/base_units.cpp @@ -39,6 +39,7 @@ #include #include #include +#include "libeval/numeric_evaluator.h" #if defined( PCBNEW ) || defined( CVPCB ) || defined( EESCHEMA ) || defined( GERBVIEW ) || defined( PL_EDITOR ) @@ -383,8 +384,12 @@ int ValueFromString( const wxString& aTextValue ) int ValueFromTextCtrl( const wxTextCtrl& aTextCtr ) { - int value; + int value; wxString msg = aTextCtr.GetValue(); + NumericEvaluator eval; + + if( eval.process( msg.mb_str() ) ) + msg = wxString::FromUTF8( eval.result() ); value = ValueFromString( g_UserUnit, msg ); diff --git a/common/libeval/grammar.c b/common/libeval/grammar.c new file mode 100644 index 0000000000..5708d02320 --- /dev/null +++ b/common/libeval/grammar.c @@ -0,0 +1,1011 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include +#line 27 "grammar.lemon" + +#include +#include +#include +#line 13 "grammar.c" +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** ParseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for base tokens 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_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +#define YYCODETYPE unsigned char +#define YYNOCODE 19 +#define YYACTIONTYPE unsigned char +#define ParseTOKENTYPE numEval::TokenType +typedef union { + int yyinit; + ParseTOKENTYPE yy0; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define ParseARG_SDECL NumericEvaluator* pEval ; +#define ParseARG_PDECL , NumericEvaluator* pEval +#define ParseARG_FETCH NumericEvaluator* pEval = yypParser->pEval +#define ParseARG_STORE yypParser->pEval = pEval +#define YYNSTATE 26 +#define YYNRULE 16 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 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 < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is 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 and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** 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. +*/ +#define YY_ACTTAB_COUNT (47) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 22, 8, 6, 3, 4, 23, 26, 15, 8, 6, + /* 10 */ 3, 4, 7, 25, 9, 24, 16, 2, 8, 6, + /* 20 */ 3, 4, 15, 21, 15, 18, 5, 7, 12, 7, + /* 30 */ 24, 16, 2, 16, 2, 14, 43, 1, 17, 9, + /* 40 */ 3, 4, 13, 11, 20, 19, 10, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 4, 5, 6, 7, 8, 9, 0, 1, 5, 6, + /* 10 */ 7, 8, 6, 16, 17, 9, 10, 11, 5, 6, + /* 20 */ 7, 8, 1, 3, 1, 12, 2, 6, 17, 6, + /* 30 */ 9, 10, 11, 10, 11, 17, 14, 15, 16, 17, + /* 40 */ 7, 8, 17, 17, 17, 17, 17, +}; +#define YY_SHIFT_USE_DFLT (-5) +#define YY_SHIFT_COUNT (16) +#define YY_SHIFT_MIN (-4) +#define YY_SHIFT_MAX (33) +static const signed char yy_shift_ofst[] = { + /* 0 */ 21, 6, 23, 23, 23, 23, 23, 23, 23, -4, + /* 10 */ 13, 3, 33, 33, 33, 24, 20, +}; +#define YY_REDUCE_USE_DFLT (-4) +#define YY_REDUCE_COUNT (8) +#define YY_REDUCE_MIN (-3) +#define YY_REDUCE_MAX (29) +static const signed char yy_reduce_ofst[] = { + /* 0 */ 22, -3, 29, 28, 27, 26, 25, 18, 11, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + /* 10 */ 42, 36, 37, 38, 34, 35, 32, 27, 41, 40, + /* 20 */ 39, 33, 31, 30, 29, 28, +}; + +/* The next table maps tokens 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. +*/ +#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. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + 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 { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + ParseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's 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 */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + "$", "VAR", "ASSIGN", "UNIT", + "SEMCOL", "PLUS", "MINUS", "DIVIDE", + "MULT", "ENDS", "VALUE", "PARENL", + "PARENR", "error", "main", "in", + "stmt", "expr", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "main ::= in", + /* 1 */ "in ::= stmt", + /* 2 */ "in ::= in stmt", + /* 3 */ "stmt ::= ENDS", + /* 4 */ "stmt ::= expr ENDS", + /* 5 */ "stmt ::= expr SEMCOL", + /* 6 */ "expr ::= VALUE", + /* 7 */ "expr ::= VALUE UNIT", + /* 8 */ "expr ::= MINUS expr", + /* 9 */ "expr ::= VAR", + /* 10 */ "expr ::= VAR ASSIGN expr", + /* 11 */ "expr ::= expr PLUS expr", + /* 12 */ "expr ::= expr MINUS expr", + /* 13 */ "expr ::= expr MULT expr", + /* 14 */ "expr ::= expr DIVIDE expr", + /* 15 */ "expr ::= PARENL expr PARENR", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** 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)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the 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. +*/ +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; + 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. + */ + 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. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
    +**
  • A pointer to the parser. This should be a pointer +** obtained from ParseAlloc. +**
  • A pointer to a function used to reclaim memory obtained +** from malloc. +**
+*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int ParseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#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 + j %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int 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( i!=YY_REDUCE_USE_DFLT ); + 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 && iyyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 14, 1 }, + { 15, 1 }, + { 15, 2 }, + { 16, 1 }, + { 16, 2 }, + { 16, 2 }, + { 17, 1 }, + { 17, 2 }, + { 17, 2 }, + { 17, 1 }, + { 17, 3 }, + { 17, 3 }, + { 17, 3 }, + { 17, 3 }, + { 17, 3 }, + { 17, 3 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ + case 4: /* stmt ::= expr ENDS */ +#line 49 "grammar.lemon" +{ pEval->parseSetResult(yymsp[-1].minor.yy0.valid ? yymsp[-1].minor.yy0.dValue : NAN); } +#line 688 "grammar.c" + break; + case 5: /* stmt ::= expr SEMCOL */ +#line 50 "grammar.lemon" +{ pEval->parseSetResult(NAN); } +#line 693 "grammar.c" + break; + case 6: /* expr ::= VALUE */ +#line 52 "grammar.lemon" +{ yygotominor.yy0.dValue = yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=true; } +#line 698 "grammar.c" + break; + case 7: /* expr ::= VALUE UNIT */ +#line 53 "grammar.lemon" +{ yygotominor.yy0.dValue = yymsp[-1].minor.yy0.dValue * yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=true; } +#line 703 "grammar.c" + break; + case 8: /* expr ::= MINUS expr */ +#line 54 "grammar.lemon" +{ yygotominor.yy0.dValue = -yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=yymsp[0].minor.yy0.valid; } +#line 708 "grammar.c" + break; + case 9: /* expr ::= VAR */ +#line 55 "grammar.lemon" +{ yygotominor.yy0.dValue = pEval->getVar(yymsp[0].minor.yy0.text); yygotominor.yy0.valid=true; } +#line 713 "grammar.c" + break; + case 10: /* expr ::= VAR ASSIGN expr */ +#line 56 "grammar.lemon" +{ pEval->setVar(yymsp[-2].minor.yy0.text, yymsp[0].minor.yy0.dValue); yygotominor.yy0.dValue = yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=false; } +#line 718 "grammar.c" + break; + case 11: /* expr ::= expr PLUS expr */ +#line 57 "grammar.lemon" +{ yygotominor.yy0.dValue = yymsp[-2].minor.yy0.dValue + yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=yymsp[0].minor.yy0.valid; } +#line 723 "grammar.c" + break; + case 12: /* expr ::= expr MINUS expr */ +#line 58 "grammar.lemon" +{ yygotominor.yy0.dValue = yymsp[-2].minor.yy0.dValue - yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=yymsp[0].minor.yy0.valid; } +#line 728 "grammar.c" + break; + case 13: /* expr ::= expr MULT expr */ +#line 59 "grammar.lemon" +{ yygotominor.yy0.dValue = yymsp[-2].minor.yy0.dValue * yymsp[0].minor.yy0.dValue; yygotominor.yy0.valid=yymsp[0].minor.yy0.valid; } +#line 733 "grammar.c" + break; + case 14: /* expr ::= expr DIVIDE expr */ +#line 60 "grammar.lemon" +{ + if (yymsp[0].minor.yy0.dValue != 0.0) { + yygotominor.yy0.dValue = yymsp[-2].minor.yy0.dValue / yymsp[0].minor.yy0.dValue; + } + else pEval->parseError("Div by zero"); + yygotominor.yy0.valid=yymsp[0].minor.yy0.valid; +} +#line 744 "grammar.c" + break; + case 15: /* expr ::= PARENL expr PARENR */ +#line 67 "grammar.lemon" +{ yygotominor.yy0.dValue = yymsp[-1].minor.yy0.dValue; yygotominor.yy0.valid=yymsp[-1].minor.yy0.valid; } +#line 749 "grammar.c" + break; + default: + /* (0) main ::= in */ yytestcase(yyruleno==0); + /* (1) in ::= stmt */ yytestcase(yyruleno==1); + /* (2) in ::= in stmt */ yytestcase(yyruleno==2); + /* (3) stmt ::= ENDS */ yytestcase(yyruleno==3); + break; + }; + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#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 */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN (yyminor.yy0) +#line 33 "grammar.lemon" + + pEval->parseError("Syntax error"); +#line 818 "grammar.c" + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +#line 37 "grammar.lemon" + + pEval->parseOk(); +#line 840 "grammar.c" + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* 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; + int yyact; /* The parser action. */ + int yyendofinput; /* True if we are at the end of input */ +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + } + yyminorunion.yy0 = yyminor; + yyendofinput = (yymajor==0); + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyactyyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#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,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].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->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#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,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#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,yyminorunion); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/common/libeval/grammar.lemon b/common/libeval/grammar.lemon new file mode 100644 index 0000000000..d103eab66d --- /dev/null +++ b/common/libeval/grammar.lemon @@ -0,0 +1,68 @@ +/* + 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 { numEval::TokenType } +%extra_argument { NumericEvaluator* pEval } + +%nonassoc VAR ASSIGN UNIT SEMCOL. +%left PLUS MINUS. +%left DIVIDE MULT. + +%include { +#include +#include +#include +} + +%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 ::= ENDS. +stmt ::= expr(A) ENDS. { pEval->parseSetResult(A.valid ? A.dValue : NAN); } +stmt ::= expr SEMCOL. { pEval->parseSetResult(NAN); } + +expr(A) ::= VALUE(B). { A.dValue = B.dValue; A.valid=true; } +expr(A) ::= VALUE(B) UNIT(C). { A.dValue = B.dValue * C.dValue; A.valid=true; } +expr(A) ::= MINUS expr(B). { A.dValue = -B.dValue; A.valid=B.valid; } +expr(A) ::= VAR(B). { A.dValue = pEval->getVar(B.text); A.valid=true; } +expr(A) ::= VAR(B) ASSIGN expr(C). { pEval->setVar(B.text, C.dValue); A.dValue = C.dValue; A.valid=false; } +expr(A) ::= expr(B) PLUS expr(C). { A.dValue = B.dValue + C.dValue; A.valid=C.valid; } +expr(A) ::= expr(B) MINUS expr(C). { A.dValue = B.dValue - C.dValue; A.valid=C.valid; } +expr(A) ::= expr(B) MULT expr(C). { A.dValue = B.dValue * C.dValue; A.valid=C.valid; } +expr(A) ::= expr(B) DIVIDE expr(C). { + if (C.dValue != 0.0) { + A.dValue = B.dValue / C.dValue; + } + else pEval->parseError("Div by zero"); + A.valid=C.valid; +} +expr(A) ::= PARENL expr(B) PARENR. { A.dValue = B.dValue; A.valid=B.valid; } + diff --git a/common/libeval/main.cpp b/common/libeval/main.cpp new file mode 100644 index 0000000000..7e174971dd --- /dev/null +++ b/common/libeval/main.cpp @@ -0,0 +1,52 @@ +/* + 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 + +#include "numeric_evaluator.h" + +int main() +{ + NumericEvaluator eval; + + eval.process("2.54mm+50mil"); + if (eval.isValid()) printf("%s\n", eval.result()); + + eval.process("x=1; y=5;"); + if (eval.isValid()) printf("%s\n", eval.result()); + eval.process("x+y"); + if (eval.isValid()) printf("%s\n", eval.result()); + + eval.setVar("posx", -3.14152); + bool retval = eval.process("posx"); + assert(retval == eval.isValid()); + if (eval.isValid()) printf("%s\n", eval.result()); + + eval.process("x=1; y=2"); + eval.setVar("z", 3); + eval.process("x+y+z"); + printf("x+y+z=%s\n", eval.result()); + + eval.process("1\""); + printf("1\" = %s\n", eval.result()); + eval.process("12.7 - 0.1\" - 50mil"); + printf("12.7 - 0.1\" - 50mil = %s\n", eval.result()); +} + diff --git a/common/libeval/numeric_evaluator.cpp b/common/libeval/numeric_evaluator.cpp new file mode 100644 index 0000000000..2c4cf0d17b --- /dev/null +++ b/common/libeval/numeric_evaluator.cpp @@ -0,0 +1,334 @@ +/* + 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 . +*/ + +#define TESTMODE 0 + +#include + +#if !TESTMODE +#include +#else +#include +#endif + + +#include +#include +#include +#include + + +/* 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 numEval +{ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wsign-compare" +#endif + +#include "grammar.c" +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +} /* namespace numEval */ + +NumericEvaluator :: NumericEvaluator() : pClParser(0) +{ + cClDecSep = '.'; + + bClTextInputStorage = true; + + init(); +} + +NumericEvaluator :: ~NumericEvaluator() +{ + numEval::ParseFree(pClParser, free); + + // Allow explicit call to destructor + pClParser = nullptr; + + clear(); +} + +void +NumericEvaluator :: init() +{ + if (pClParser == nullptr) + pClParser = numEval::ParseAlloc(malloc); + + //numEval::ParseTrace(stdout, "lib"); + +#if TESTMODE + eClUnitDefault = Unit::Metric; +#else + switch (g_UserUnit) + { + case INCHES : eClUnitDefault = Unit::Inch; break; + case MILLIMETRES : eClUnitDefault = Unit::Metric; break; + default: eClUnitDefault = Unit::Metric; break; + } +#endif +} + +void +NumericEvaluator :: clear() +{ + free(clToken.token); + clToken.token = nullptr; + clToken.input = nullptr; + bClError = true; +} + +void +NumericEvaluator :: parse(int token, numEval::TokenType value) +{ + numEval::Parse(pClParser, token, value, this); +} + +void +NumericEvaluator :: parseError(const char* s) +{ + bClError = true; +} + +void +NumericEvaluator :: parseOk() +{ + bClError = false; + bClParseFinished = true; +} + +void +NumericEvaluator :: parseSetResult(double val) +{ + snprintf(clToken.token, clToken.OutLen, "%g", val); +} + +const char* +NumericEvaluator :: textInput(const void* pObj) const +{ + auto it = clObjMap.find(pObj); + if (it != clObjMap.end()) return it->second.c_str(); + + return nullptr; +} + +bool +NumericEvaluator :: process(const char* s) +{ + /* Process new string. + * Feed parser token after token until end of input. + */ + + newString(s); + + if (pClParser == nullptr) init(); + bClParseFinished = false; + + Token tok; + numEval::TokenType parseTok; + do { + tok = getToken(); + parse(tok.token, tok.value); + if (bClParseFinished || tok.token == ENDS) { + numEval::Parse(pClParser, 0, parseTok, this); + break; + } + //usleep(200000); + } while (tok.token); + + return !bClError; +} + +bool +NumericEvaluator :: process(const char* s, const void* pObj) +{ + if (bClTextInputStorage) // Store input string for (text entry) pObj. + clObjMap[pObj] = s; + return process(s); +} + +void +NumericEvaluator :: newString(const char* s) +{ + clear(); + auto len = strlen(s); + clToken.token = reinterpret_cast(malloc(TokenStat::OutLen+1)); + clToken.inputLen = len; + clToken.pos = 0; + clToken.input = s; + bClParseFinished = false; +} + +NumericEvaluator::Token +NumericEvaluator :: getToken() +{ + Token retval; + size_t idx; + + retval.token = ENDS; + retval.value.dValue = 0; + + if (clToken.token == nullptr) return retval; + if (clToken.input == nullptr) return retval; + if (clToken.pos >= clToken.inputLen) return retval; + + // Lambda: get value as string, store into clToken.token and update current index. + auto extractNumber = [&idx, this]() { + short sepCount = 0; + idx = 0; + auto ch = clToken.input[clToken.pos]; + do { + if (ch == cClDecSep && sepCount) break; + clToken.token[idx++] = ch; + if (ch == cClDecSep) sepCount++; + ch = clToken.input[++clToken.pos]; + } while (isdigit(ch) || ch == cClDecSep); + clToken.token[idx] = 0; + }; + + // Lamda: Get unit for current token. Returns Unit::Invalid if token is not a unit. + auto checkUnit = [this]() -> Unit { + // '"' or "mm" or "mil" + char ch = clToken.input[clToken.pos]; + if (ch == '"' || ch == 'm') { + Unit convertFrom = Unit::Invalid; + if (ch == '"') { + convertFrom = Unit::Inch; + clToken.pos++; + } + else { + // Do not use strcasecmp() as it is not available on all platforms + const char* cptr = &clToken.input[clToken.pos]; + const auto sizeLeft = clToken.inputLen - clToken.pos; + if (sizeLeft >= 2) { + if (tolower(cptr[1]) == 'm' && !isalnum(cptr[2])) { + convertFrom = Unit::Metric; + clToken.pos += 2; + } + else if (sizeLeft >= 3) { + if (tolower(cptr[1]) == 'i' && tolower(cptr[2]) == 'l' && !isalnum(cptr[3])) { + convertFrom = Unit::Mil; + clToken.pos += 3; + } + } + } + } + return convertFrom; + } + return Unit::Invalid; + }; + + // Start processing of first/next token: Remove whitespace + char ch; + for (;;) { + ch = clToken.input[clToken.pos]; + if (ch == ' ') { + clToken.pos++; + } + else break; + } + + Unit convertFrom; + + if (ch == 0) { + /* End of input */ + } + else if (isdigit(ch) || ch == cClDecSep) { // VALUE + extractNumber(); + retval.token = VALUE; + retval.value.dValue = atof(clToken.token); + } + else if ((convertFrom = checkUnit()) != Unit::Invalid) { // 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 = UNIT; + if (eClUnitDefault == Unit::Metric) + { + switch (convertFrom) + { + case Unit::Inch : retval.value.dValue = 25.4; break; + case Unit::Mil : retval.value.dValue = 25.4/1000.0; break; + case Unit::Metric : retval.value.dValue = 1.0; break; + case Unit::Invalid : break; + } + } + else if (eClUnitDefault == Unit::Inch) + { + switch (convertFrom) + { + case Unit::Inch : retval.value.dValue = 1.0; break; + case Unit::Mil : retval.value.dValue = 1.0/1000.0; break; + case Unit::Metric : retval.value.dValue = 1.0/25.4; break; + case Unit::Invalid : break; + } + } + } + else if (isalpha(ch)) { // VAR + const char* cptr = &clToken.input[clToken.pos]; + cptr++; + while (isalnum(*cptr)) cptr++; + retval.token = VAR; + size_t bytesToCopy = cptr - &clToken.input[clToken.pos]; + if (bytesToCopy >= sizeof(retval.value.text)) bytesToCopy = sizeof(retval.value.text)-1; + strncpy(retval.value.text, &clToken.input[clToken.pos], bytesToCopy); + retval.value.text[bytesToCopy] = 0; + clToken.pos += cptr - &clToken.input[clToken.pos]; + } + else { // Single char tokens + switch (ch) { + case '+' : retval.token = PLUS; break; + case '-' : retval.token = MINUS; break; + case '*' : retval.token = MULT; break; + case '/' : retval.token = DIVIDE; break; + case '(' : retval.token = PARENL; break; + case ')' : retval.token = PARENR; break; + case '=' : retval.token = ASSIGN; break; + case ';' : retval.token = SEMCOL; break; + } + clToken.pos++; + } + + return retval; +} + +void +NumericEvaluator :: setVar(const std::string& s, double value) +{ + clVarMap[s] = value; +} + +double +NumericEvaluator :: getVar(const std::string& s) +{ + auto result = clVarMap.find(s); + if (result != clVarMap.end()) return result->second; + return 0.0; +} diff --git a/include/libeval/grammar.h b/include/libeval/grammar.h new file mode 100644 index 0000000000..27dddd2848 --- /dev/null +++ b/include/libeval/grammar.h @@ -0,0 +1,12 @@ +#define VAR 1 +#define ASSIGN 2 +#define UNIT 3 +#define SEMCOL 4 +#define PLUS 5 +#define MINUS 6 +#define DIVIDE 7 +#define MULT 8 +#define ENDS 9 +#define VALUE 10 +#define PARENL 11 +#define PARENR 12 diff --git a/include/libeval/numeric_evaluator.h b/include/libeval/numeric_evaluator.h new file mode 100644 index 0000000000..f2f91d097c --- /dev/null +++ b/include/libeval/numeric_evaluator.h @@ -0,0 +1,193 @@ +/* + 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 . +*/ + +/* +An evaluator object is used to replace an input string that represents +a mathematical expression by its result. + +Example: Consider the input "3+4". The result of this expression is "7". +The NumericEvaluator can be used like this: + + NumericEvaluator eval; + eval.process("3+4"); + printf("3+4", eval.result()); + +The same example with error checking. Please note that even a valid input string may result +in an empty output string or "NaN". + + NumericEvaluator eval; + bool ret = eval.process("3+4"); + assert(ret == eval.isValid()); // isValid() reflects return value of process(). + if (eval.isValid()) printf("3+4=%s\n", eval.result()); + +Using variables +Expressions can refer to variables if they were defined by previous expressions. +A variable can be defined by an expression or by the setVar() method. +Expressions that define/set variables do not have a result. + + eval.process("x=1; y=2"); // Result is NaN + eval.setVar("z", 3); + eval.process("x+y+z"); + printf("x+y+z=%s\n", eval.result()); + +Input string storage +An evaluator object can store and retrieve the original input string using a pointer +as key. This can be used to restore the input string of a text entry field. + + eval.process("25.4-0.7", &eval); + printf("%s = %s\n", eval.textInput(&eval), eval.result()); + +Unit conversion +The evaluator uses a default unit and constants can be specified with a unit. +As long as no units are used the default unit is not relevant. The default +unit is taken from the global (Kicad) variable g_UserUnit. +Supported units are millimeters (mm), Mil (mil) and inch (") + + eval.process("1\""); + printf("1\" = %s\n", eval.result()); + eval.process("12.7 - 0.1\" - 50mil"); + printf("12.7 - 0.1\" - 50mil = %s\n", eval.result()); +*/ + +#ifndef NUMERIC_EVALUATOR_H_ +#define NUMERIC_EVALUATOR_H_ + +#include "grammar.h" +#include +#include +#include +#include + +// This namespace is used for the lemon parser +namespace numEval +{ + +struct TokenType +{ + union { + double dValue; + int iValue; + }; + bool valid; + char text[32]; +}; + +} // namespace numEval + +class NumericEvaluator { + enum class Unit { Invalid, Metric, Inch, Mil }; + +public: + NumericEvaluator(); + ~NumericEvaluator(); + + /* Initialization and destruction. init() is invoked be the constructor and should not be needed + * by the user. + * clear() should be invoked by the user if a new input string is to be processed. It will reset + * the parser. User defined variables are retained. + */ + void init(); + void clear(); + + /* Set the decimal separator for the input string. Defaults to '.' */ + void setDecimalSeparator(char sep); + + /* Enable or disable support for input string storage. + * If enabled the input string is saved if process(const char*, const void*) is used. + */ + void enableTextInputStorage(bool w) { bClTextInputStorage = w; } + + /* Used by the lemon parser */ + void parse(int token, numEval::TokenType value); + void parseError(const char* s); + void parseOk(); + void parseSetResult(double); + + /* Check if previous invokation of process() was successful */ + inline bool isValid() const { return !bClError; } + + /* Result of string processing. Undefined if !isValid() */ + inline const char* result() const { return clToken.token; } + + /* Evaluate input string. + * Result can be retrieved by result(). + * Returns true if input string could be evaluated, otherwise false. + */ + bool process(const char* s); + + /* Like process(const char*) but also stores input string in a std:map with key pObj. */ + bool process(const char* s, const void* pObj); + + /* Retrieve old input string with key pObj. */ + const char* textInput(const void* pObj) const; + + /* Add/set variable with value */ + void setVar(const std::string&, double value); + + /* Get value of variable. Returns 0.0 if not defined. */ + double getVar(const std::string&); + + /* Remove single variable */ + void removeVar(const std::string& s) { clVarMap.erase(s); } + + /* Remove all variables */ + void clearVar() { clVarMap.clear(); } + +protected: + /* Token type used by the tokenizer */ + struct Token { + int token; + numEval::TokenType value; + }; + + /* Begin processing of a new input string */ + void newString(const char* s); + + /* Tokenizer: Next token/value taken from input string. */ + Token getToken(); + +private: + void* pClParser; // the current lemon parser state machine + + /* Token state for input string. */ + struct TokenStat { + enum { OutLen=32 }; + TokenStat() : input(0), token(0), inputLen(0) { /* empty */ } + const char* input; // current input string ("var=4") + char* token; // output token ("var", type:VAR; "4", type:VALUE) + size_t inputLen; // strlen(input) + size_t pos; // current index + } clToken; + + char cClDecSep; // decimal separator ('.') + + /* Parse progress. Set by parser actions. */ + bool bClError; + bool bClParseFinished; + + bool bClTextInputStorage; // Enable input string storage used by process(const char*, const void*) + + Unit eClUnitDefault; // Default unit for values + + std::map clObjMap; // Map pointer to text entry -> (original) input string + std::map clVarMap; +}; + + +#endif /* NUMERIC_EVALUATOR_H_ */