// rpinterpreter.cpp // Copyright (c) Menno Rubingh 2016. Web: [http://rubinghsoftware.de] // // MR Mar 2016. // // Simple general-purpose interpreter for Reverse Polish expression for // floating-point arithmetic. // Scans RP expression from input string to an internal representation, // and evaluates this later on variables that are set (prior to evaluation) // and read (after evaluation) by the client code. // // //--- // Uses: // // llgp_between.h -- Macros BETWEEN_IE(), BETWEEN_II(). // // rpimpl_ex.h -- SContext, Ex (internal scancontext and exception). // rpimpl_stack.h -- Class StackDbl. // rpimpl_var.h -- Class VarTable. // rpimpl_oper.h, .cpp -- Classes OperationXxx. // rpimpl_oplist.h, .cpp -- ParsedOperationList. // rpimpl_scan.h, .cpp -- Function scanRPExpression(). // rpimpl_eval.h, .cpp -- Function evaluateRPExpression(). // // rpinterpreter.h, .cpp -- Exported class RPInterpreter. // // //--- // Dependency structure: // // +---------------------------------------------+ // | Client code | // +---------------------------------------------+ // // +=============================================+ // | rpinterpreter.h, .cpp | // | | // |--------------------+------------------------+ // | rpimpl_scan | rpimpl_eval | // | | | // |--------------------+----+-------------------+ // | rpimpl_oplist | | // |-------------------------+ | // | rpimpl_oper | | // |-------------------------+ | // | | // | ,-----------------+----------------. | // | | rpimpl_stack | rpimpl_var | | // | `-----------------+----------------' | // | | // +---------------------------------------------+ // | rpimpl_ex.h | // +---------------------------------------------+ // // +---------------------------------------------+ // | llgp_between.h | // +---------------------------------------------+ // // A block uses only the blocks strictly vertically below it. // // Thick lines isolate: // A Block A uses only block B, but not any blocks below B. // === // B // // //--- // Build to static library 'rpinterpreter.a': // // g++ -ggdb -Wall -c rpimpl_oper.cpp // g++ -ggdb -Wall -c rpimpl_oplist.cpp // g++ -std=c++0x -ggdb -Wall -c rpimpl_scan.cpp // g++ -ggdb -Wall -c rpimpl_eval.cpp // g++ -ggdb -Wall -c rpinterpreter.cpp // // ar rcs rpinterpreter.a \. // rpimpl_oplist.o rpimpl_scan.o rpimpl_eval.o rpinterpreter.o // // #include "rpimpl_ex.h" //Ex. #include "rpimpl_var.h" //VarTable. #include "rpimpl_oplist.h" //ParsedOperationList. #include "rpimpl_scan.h" //scanRPExpression(). #include "rpimpl_eval.h" //evaluateRPExpression(). #include "rpinterpreter.h" //RPInterpreter. #include <stdio.h> #include <math.h> //isnan(). //-------------------------------------------------------------------------------------- // Definition of the RPInterpreter member functions. //-------------------------------------------------------------------------------------- // Print contents of parsed expression to FILE. void RPInterpreter:: dump( FILE * oF ) const { ParsedOperationList const * pOplist = (ParsedOperationList const *)m_exprdata; pOplist->dump( oF ); } // Constructor initializes parsed expression to empty, and variable values to 0.0. RPInterpreter:: RPInterpreter( void ) { m_exprdata = (void *)( new ParsedOperationList() ); m_vardata = (void *)( new VarTable() ); } RPInterpreter:: ~RPInterpreter( void ) { ParsedOperationList * pOplist = (ParsedOperationList *)m_exprdata; VarTable * pVars = (VarTable *)m_vardata; delete pOplist; delete pVars; } // Clear parsed expression to empty and clear all variables to 0.0. void RPInterpreter:: reinit( void ) { ParsedOperationList * pOplist = (ParsedOperationList *)m_exprdata; VarTable * pVars = (VarTable *)m_vardata; pOplist->reinit(); pVars ->clear(); } // Clear the values of all variables to 0.0. void RPInterpreter:: clearVars( void ) { VarTable * pVars = (VarTable *)m_vardata; pVars->clear(); } // // Set and read the value of a variable. // Variable is identified by parameter iC which is one lower-case ASCII char 'a'-'z'. // (Assertion fails if iC is out of range.) // // The function 'getVar()' returns false when the value of the variable is a NaN, // which means that a DOMAIN ERROR occurred during the evaluate() call. It returns // true if the variable is not a NaN (in this case the variable can still be // + or - HUGE_VAL). // For more info see the text file 'rpinterpreter_doc_floatingPointErrors.txt' // which should be contained among the sources. // bool RPInterpreter:: getVar( char iC , double * oValue ) const { VarTable const * pVars = (VarTable const *)m_vardata; *oValue = pVars->getByName( iC ); return !isnan(*oValue); } void RPInterpreter:: putVar( char iC, double iValue ) { VarTable * pVars = (VarTable *)m_vardata; pVars->putByName( iC, iValue ); } // // Scan RP expression from string. // And then, test evaluation of the expression on dummy variable values (to // test for expression well-formedness, i.e. no stack under/overflows, and // stacksize is 0 at the end and at any ';' character). // // The input string as a whole must consist of the RP expression only, // with no other characters. // // Converts the string into an executable representation of the expression, // stored internally in the RPInterpreter object, which can be executed later // by calling 'evaluate()'. // // Assumes that at the time of calling, the internal parsed-expression data // is empty (assertion fails if this assumption is violated). // // On error, prints to stderr and returns false. // bool RPInterpreter:: scanFromString( char const * iStr ) { ParsedOperationList * pOplist = (ParsedOperationList *)m_exprdata; // Scan ParsedOperationList from input string. try { scanRPExpression( iStr, pOplist ); } catch ( Ex e ) { fprintf( stderr, "Expression syntax error (col %d): %s\n", 1 + e.getCol(), e.getMsg() ); return false; } // Execute test evaluation, on variables with arbitrary values. // This checks for stack under/overflow, stacksize zero at end and at ';'. VarTable testvars; //Leave at initial values all 0.0. try { evaluateRPExpression( pOplist, &testvars ); } catch ( Ex e ) { fprintf( stderr, "Expression not well-formed (col %d): %s\n", 1 + e.getCol(), e.getMsg() ); return false; } // Check whether a DOMAIN ERROR occurred during expression evaluation for // CONSTANT operand values (but not for variable operand values which is left // as a run-time error). // Check this by checking whether any variable is a NaN. (This gives more // information than the fetestexcept(FE_INVALID) method, and running time is // not important during expression scanning.) for ( int k = 0; k < VarTable::getNVars(); k++ ) { double val = testvars.getByIndex(k); if ( isnan(val) ) { fprintf( stderr, "Domain error (variable '%c') during test evaluation of expression\n", VarTable::indexToName(k) ); return false; } } return true; } // // Evaluate the internally stored parsed expression for the current values of // the variables, overwriting variables as specified in the expresssion. // The operations (+ - * ...) in the expression do not clip. // // Assumes that the parsed expression is well-formed (as checked by scanFromString(), // i.e. no stack under/overflows, and stacksize is 0 at the end and at any ';' // character). Assertion fails if this assumption is violated. // // The evaluate() function does NOT check internally for floating-point errors; // see the text file 'rpinterpreter_doc_floatingPointErrors.txt' which should be // contained among the sources. // The client code can detect DOMAIN ERRORS by checking the return value of the // getVal() function, or alternatively by bracketing a (series of) evaluate() // call(s) between feclearexcept() and fetestexcept(FE_INVALID). // void RPInterpreter:: evaluate( void ) { ParsedOperationList const * pOplist = (ParsedOperationList const *)m_exprdata; VarTable * pVars = (VarTable *)m_vardata; try { evaluateRPExpression( pOplist, pVars ); } catch ( Ex e ) //Internal exception 'Ex' should not happen because of the checks { // executed in scanFromString(). assert( false ); } }