/* 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 */ // JEY TODO: remove this version; NumericEvaluator :: NumericEvaluator() : pClParser( 0 ) { struct lconv* lc = localeconv(); cClDecSep = *lc->decimal_point; bClTextInputStorage = true; bClError = false; bClParseFinished = false; if( pClParser == nullptr ) pClParser = numEval::ParseAlloc(malloc); switch( g_UserUnit ) { case INCHES: eClUnitDefault = Unit::Inch; break; case MILLIMETRES: eClUnitDefault = Unit::Metric; break; default: eClUnitDefault = Unit::Metric; break; } } NumericEvaluator::NumericEvaluator( EDA_UNITS_T aUnits, bool aUseMils ) : pClParser( 0 ) { struct lconv* lc = localeconv(); cClDecSep = *lc->decimal_point; bClTextInputStorage = true; pClParser = numEval::ParseAlloc(malloc); switch( aUnits ) { case INCHES: if( aUseMils ) eClUnitDefault = Unit::Mil; else eClUnitDefault = Unit::Inch; break; case MILLIMETRES: eClUnitDefault = Unit::Metric; break; default: eClUnitDefault = Unit::Metric; break; } } NumericEvaluator :: ~NumericEvaluator() { numEval::ParseFree(pClParser, free); // Allow explicit call to destructor pClParser = nullptr; clear(); } void NumericEvaluator :: clear(const void* pObj) { free(clToken.token); clToken.token = nullptr; clToken.input = nullptr; bClError = true; if (bClTextInputStorage) clObjMap.clear(); } 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() { 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) pClParser = numEval::ParseAlloc(malloc); bClError = false; 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. // Valid units are ", in, mm, mil and thou. Returns Unit::Invalid otherwise. auto checkUnit = [this]() -> Unit { char ch = clToken.input[clToken.pos]; if (ch == '"') { clToken.pos++; return Unit::Inch; } // 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 && ch == 'm' && tolower(cptr[1]) == 'm' && !isalnum(cptr[2])) { clToken.pos += 2; return Unit::Metric; } if (sizeLeft >= 2 && ch == 'i' && tolower(cptr[1]) == 'n' && !isalnum(cptr[2])) { clToken.pos += 2; return Unit::Inch; } if (sizeLeft >= 3 && ch == 'm' && tolower(cptr[1]) == 'i' && tolower(cptr[2]) == 'l' && !isalnum(cptr[3])) { clToken.pos += 3; return Unit::Mil; } if (sizeLeft >= 4 && ch == 't' && tolower(cptr[1]) == 'h' && tolower(cptr[2]) == 'o' && tolower(cptr[2]) == 'u' && !isalnum(cptr[3])) { clToken.pos += 4; return Unit::Mil; } 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) || 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 (eClUnitDefault == Unit::Mil) { switch (convertFrom) { case Unit::Inch : retval.value.dValue = 1.0*1000.0; break; case Unit::Mil : retval.value.dValue = 1.0; break; case Unit::Metric : retval.value.dValue = 1000.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; default: bClError = true; break; /* invalid character */ } 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; }