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