// 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 );
}
}