Numeric expression evaluator
This commit is contained in:
parent
579d3f478d
commit
b5fc6e45cf
|
@ -348,6 +348,8 @@ set( COMMON_SRCS
|
||||||
geometry/shape_collisions.cpp
|
geometry/shape_collisions.cpp
|
||||||
geometry/shape_file_io.cpp
|
geometry/shape_file_io.cpp
|
||||||
geometry/convex_hull.cpp
|
geometry/convex_hull.cpp
|
||||||
|
|
||||||
|
libeval/numeric_evaluator.cpp
|
||||||
)
|
)
|
||||||
add_library( common STATIC ${COMMON_SRCS} )
|
add_library( common STATIC ${COMMON_SRCS} )
|
||||||
add_dependencies( common lib-dependencies )
|
add_dependencies( common lib-dependencies )
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include <class_title_block.h>
|
#include <class_title_block.h>
|
||||||
#include <common.h>
|
#include <common.h>
|
||||||
#include <base_units.h>
|
#include <base_units.h>
|
||||||
|
#include "libeval/numeric_evaluator.h"
|
||||||
|
|
||||||
|
|
||||||
#if defined( PCBNEW ) || defined( CVPCB ) || defined( EESCHEMA ) || defined( GERBVIEW ) || defined( PL_EDITOR )
|
#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 ValueFromTextCtrl( const wxTextCtrl& aTextCtr )
|
||||||
{
|
{
|
||||||
int value;
|
int value;
|
||||||
wxString msg = aTextCtr.GetValue();
|
wxString msg = aTextCtr.GetValue();
|
||||||
|
NumericEvaluator eval;
|
||||||
|
|
||||||
|
if( eval.process( msg.mb_str() ) )
|
||||||
|
msg = wxString::FromUTF8( eval.result() );
|
||||||
|
|
||||||
value = ValueFromString( g_UserUnit, msg );
|
value = ValueFromString( g_UserUnit, msg );
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
%token_type { numEval::TokenType }
|
||||||
|
%extra_argument { NumericEvaluator* pEval }
|
||||||
|
|
||||||
|
%nonassoc VAR ASSIGN UNIT SEMCOL.
|
||||||
|
%left PLUS MINUS.
|
||||||
|
%left DIVIDE MULT.
|
||||||
|
|
||||||
|
%include {
|
||||||
|
#include <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <libeval/numeric_evaluator.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 ::= 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; }
|
||||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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());
|
||||||
|
}
|
||||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TESTMODE 0
|
||||||
|
|
||||||
|
#include <libeval/numeric_evaluator.h>
|
||||||
|
|
||||||
|
#if !TESTMODE
|
||||||
|
#include <common.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.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 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<decltype(clToken.token)>(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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
// 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<const void*, std::string> clObjMap; // Map pointer to text entry -> (original) input string
|
||||||
|
std::map<std::string, double> clVarMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* NUMERIC_EVALUATOR_H_ */
|
Loading…
Reference in New Issue