// rpimpl_scan.cpp
// Copyright (c) Menno Rubingh 2016. Web: [http://rubinghsoftware.de]
//
// MR Mar 2016.
//
// Definition of the function 'scanRPExpression()':
// Scan a RP expression string and convert it into a list of OperationXxx instances.
// Used internally in the RPInterpreter implementation.
//
//---
// Build with g++ option "-std=c++0x" (for static_assert).
//
#include "rpimpl_oper.h" //OperastionXxx classes.
#include "rpimpl_oplist.h" //ParsedOperationList.
#include "rpimpl_ex.h" //SContext, Ex.
#include <stdio.h>
//-------------------------------------------------------------------------------------
// Scan a single element of a RP expression.
//
// If unsuccessful
// ==> Leave *oNScan and *oOp unchanged, and return false.
// If successful
// ==> Set *oNScan to the nr of chars scanned,
// set *oOp to a ptr to a newly instantuated OperationXxx instance,
// and return true.
//-------------------------------------------------------------------------------------
// Scan the semicolon operator.
static
bool sscanSemicolon(
char const * iStr,
SContext const * iCxt,
int * oNScan,
OperationBase ** oOp )
{
assert( *oNScan == 0 );
assert( *oOp == NULL );
if ( *iStr != ';' ) { return false; }
*oNScan = 1;
*oOp = new OperationSemicolon( iCxt );
return true;
}
// Scan one of the binary operator characters '+', '-', '*'. '/', '^'.
static
bool sscanBinOp(
char const * iStr,
SContext const * iCxt,
int * oNScan,
OperationBase ** oOp )
{
assert( *oNScan == 0 );
assert( *oOp == NULL );
switch ( *iStr )
{
case '+': *oNScan = 1; *oOp = new OperationBinAdd( iCxt ); return true; break;
case '-': *oNScan = 1; *oOp = new OperationBinSub( iCxt ); return true; break;
case '*': *oNScan = 1; *oOp = new OperationBinMul( iCxt ); return true; break;
case '/': *oNScan = 1; *oOp = new OperationBinDiv( iCxt ); return true; break;
case '^': *oNScan = 1; *oOp = new OperationBinPow( iCxt ); return true; break;
default : return false; break;
}
}
// Scan one of the variable letters 'a' ... 'z' NOT preceded by '='.
static
bool sscanPushVar(
char const * iStr,
SContext const * iCxt,
int * oNScan,
OperationBase ** oOp )
{
assert( *oNScan == 0 );
assert( *oOp == NULL );
int kIndex;
if ( VarTable::nameToIndex_scan( *iStr, &kIndex ) )
{ *oNScan = 1; *oOp = new OperationPushVar( iCxt, kIndex ); return true; }
else
{ return false; }
}
// Scan '=' followed by one of the variable letters 'a' ... 'z'.
static
bool sscanPopVar(
char const * iStr,
SContext const * iCxt,
int * oNScan,
OperationBase ** oOp )
{
assert( *oNScan == 0 );
assert( *oOp == NULL );
if ( iStr[0] != '=' ) { return false; }
int kIndex;
if ( VarTable::nameToIndex_scan( iStr[1], &kIndex ) )
{ *oNScan = 2; *oOp = new OperationPopVar( iCxt, kIndex ); return true; }
else
{ return false; }
}
// Scan a nonnegative decimal floating-point literal constant expresed using the
// characters '.' and '0'-'9'.
// Algo:
// - Stop scanning at the first char unequal to '.' or '0'-'9'.
// - Accumulate the value in an UINT (not double).
// - Accumulate the divisor in an UINT (not double), by counting the number
// of decimals after the '.' (of which there can be at most one).
// - At the end, compute value/divisor.
typedef unsigned int uint;
static
bool char_isDec( char iC, uint * oValue )
{
if ( BETWEEN_II( '0', iC, '9' ) ) { *oValue = iC - '0'; return true; }
return false;
}
static
bool sscanConst(
char const * iStr,
SContext const * iCxt,
int * oNScan,
OperationBase ** oOp )
{
static int const DIGMAX_TOTAL = 9; //Max nr of digits in total.
static int const DIGMAX_AFTERDOT = 9; //Max nr of digits after dot.
static_assert( DIGMAX_TOTAL < sizeof(uint) * 2.4, "DIGMAX_TOTAL too large" );
static_assert( DIGMAX_AFTERDOT < sizeof(uint) * 2.4, "DIGMAX_AFTERDOT too large" );
// Note: 2.4 = a value slightly smaller than than 10log(256) = the
// number of decimal digits in an UNSIGNED 8-bit number.
assert( *oNScan == 0 );
assert( *oOp == NULL );
// Check the first char.
char c0 = *iStr;
uint dummy;
if ( ! ( (c0=='.') || char_isDec(c0,&dummy) ) ) { return false; }
// Scan the characters, accumulate total digit string value and divisor.
bool seenDot = false;
int nDigTotal = 0;
int nDigAfterDot = 0;
uint accAll = 0;
uint accDivisor = 1;
int nScan = 0;
for(;;)
{
char c = iStr[nScan];
uint digvalue;
if ( char_isDec( c, &digvalue ) )
{
nDigTotal++;
if ( nDigTotal > DIGMAX_TOTAL )
{ throw Ex( iCxt, "constant too large" ); }
accAll *= 10;
accAll += digvalue;
if ( seenDot )
{
nDigAfterDot++;
if ( nDigAfterDot > DIGMAX_AFTERDOT )
{ throw Ex( iCxt, "too many decimals after dot" ); }
accDivisor *= 10;
}
}
else if ( c == '.' )
{
if ( seenDot ) { throw Ex( iCxt, "more than one dot in constant" ); }
seenDot = true;
}
else
{
break; //END OF LOOP.
}
nScan++;
}//for
// Compute double value.
double rv = (double)accAll / (double)accDivisor;
// Create output.
*oNScan = nScan;
*oOp = new OperationPushConst( iCxt, rv );
return true;
}
//-------------------------------------------------------------------------------------
// Scan ANY of the elements of a RP expression.
//-------------------------------------------------------------------------------------
static
bool sscanAny(
char const * iStr,
SContext const * iCxt,
int * oNScan,
OperationBase ** oOp )
{
if ( sscanSemicolon( iStr, iCxt, oNScan, oOp ) ) { return true; }
else if ( sscanPopVar ( iStr, iCxt, oNScan, oOp ) ) { return true; }
else if ( sscanPushVar ( iStr, iCxt, oNScan, oOp ) ) { return true; }
else if ( sscanBinOp ( iStr, iCxt, oNScan, oOp ) ) { return true; }
else if ( sscanConst ( iStr, iCxt, oNScan, oOp ) ) { return true; }
else { return false; }
}
//-------------------------------------------------------------------------------------
// Convert string to ParsedOperationList.
//
// The string as a whole must consist of the RP expression only, with no
// other characters (but space characters are allowed between lexical elements).
//
// Assumes that the ParsedOperationList is empty on entering the function
// (an assertion fails if this assumption is violated).
//-------------------------------------------------------------------------------------
void scanRPExpression(
char const * iStr,
ParsedOperationList * oOpList )
{
assert( oOpList->getN() == 0 );
SContext cxt; //Inits to character position zero.
while ( *iStr != '\0' )
{
int nscan = 0;
OperationBase * pOp = NULL;
if ( *iStr == ' ' ) //Space character: ignore.
{
nscan = 1;
}
else if ( sscanAny( iStr, &cxt, &nscan, &pOp ) )
{
oOpList->appendElem( &cxt, pOp );
}
else
{
throw Ex( &cxt, "illegal character in RP expression" );
}
iStr += nscan;
cxt.advance( nscan );
}
}