/*
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
#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)
{
struct lconv* lc = localeconv();
cClDecSep = *lc->decimal_point;
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(const void* pObj)
{
free(clToken.token);
clToken.token = nullptr;
clToken.input = nullptr;
bClError = true;
if (bClTextInputStorage && pObj) clObjMap.erase(pObj);
}
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, "%.10g", 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;
do {
tok = getToken();
parse(tok.token, tok.value);
if (bClParseFinished || tok.token == ENDS) {
// Reset parser by passing zero as token ID, value is ignored.
numEval::Parse(pClParser, 0, tok.value, 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));
strcpy(clToken.token, "0");
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;
auto isDecSep = [&](char ch) -> bool {
if (ch == cClDecSep) return true;
if (cClDecSep == ',' && ch == '.') return true;
return false;
};
// Lambda: get value as string, store into clToken.token and update current index.
auto extractNumber = [&]() {
short sepCount = 0;
idx = 0;
auto ch = clToken.input[clToken.pos];
do {
if (ch == isDecSep(ch) && sepCount) break;
clToken.token[idx++] = ch;
if (isDecSep(ch)) sepCount++;
ch = clToken.input[++clToken.pos];
} while (isdigit(ch) || isDecSep(ch));
clToken.token[idx] = 0;
// Ensure that the systems decimal separator is used
for (int i = strlen(clToken.token); i; i--) if (isDecSep(clToken.token[i-1])) clToken.token[i-1] = cClDecSep;
};
/* Lamda: Get unit for current token. Returns Unit::Invalid if token is not a unit.
* '"', "in", "th", "mi", "mil" or "mm"
*/
auto checkUnit = [this]() -> Unit {
const int sizeLeft = clToken.inputLen - clToken.pos;
Unit convertFrom = Unit::Invalid;
char unit[2] = { 0, 0 };
for (int i = 0; i < sizeLeft && i < int(sizeof(unit)/sizeof(unit[0])); i++) unit[i] = tolower(clToken.input[clToken.pos+i]);
auto tokcmp = [sizeLeft, unit](const char* s, int len) -> int {
if (len > sizeLeft) return 0;
if (!strncmp(unit, s, len)) return len;
return 0;
};
int size = 0;
if ((size = tokcmp("\"", 1))) convertFrom = Unit::Inch;
else if ((size = tokcmp("in", 2))) convertFrom = Unit::Inch;
else if ((size = tokcmp("mi", 2))) convertFrom = Unit::Mil;
else if ((size = tokcmp("th", 2))) convertFrom = Unit::Mil;
else if ((size = tokcmp("mm", 2))) convertFrom = Unit::Metric;
clToken.pos += size;
if (size) {
while (clToken.pos < clToken.inputLen && isalnum(clToken.input[clToken.pos])) clToken.pos++;
}
return convertFrom;
};
// 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) || isDecSep(ch)) { // 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;
}