//***********************************************************************************************************
//**
//**	hp21asm - Parsing Routines
//**
//**	2014 Nov: split from single file / bh
//**
//***********************************************************************************************************
#include <string.h>
#include <ctype.h>
#include "sym.h"
#include "util.h"

extern double			strtod();

extern void			List_err();

extern int			Sym_find();
extern int			Sym_validChr();

extern int			CurAddr;				// needed for address calcs in expressions
extern int			doModern;				// needed for number-base syntax


//* END Refs
//***********************************************************************************************************
//* Internals

extern char			*parsePtr;				// forward refs
extern int			parse_escChr();
extern void			parse_sym();
extern void			parse_space();


//----------------------------------------------------
//  Convert floating point number to HP 21xx format
//  Accepts:
//    num		Number to convert
//  Returns:
//    ret_words		Number in 2 words in HP format
//    ret		0=Success, -2=Underflow, -3=Overflow

static int
parse__double_to_hp( num, ret_word1, ret_word2 ) 
	double			num;
	int			*ret_word1, *ret_word2;
 {
	int			neg;
	long			frac;
	int			exp;

	*ret_word1 = 0;
	*ret_word2 = 0;

	if( num == 0.0 )
		return( 0 );

	neg = num < 0.0;

	exp = 0;

	if( neg ) num = -num;

	while( num >= 1.0 ) { num = num / 2; ++exp; }
	while( num <  0.5 ) { num = num * 2; --exp; }

	if( neg ) num = -num;

	if( neg ) num += (0177 / 65536.0 / 65536.0);
	else	  num += (0200 / 65536.0 / 65536.0);

	if(  neg && num >= -0.5 ) { num = num * 2; --exp; }
	if( !neg && num >= 1.0 )  { num = num / 2; ++exp; }

	if( exp < -128 )
		return( -2 );

	if( exp > 127)
	 {	if( neg ) { *ret_word1 = 0x8000; *ret_word2 = 0x01FE; }
		else	  { *ret_word1 = 0x7FFF; *ret_word2 = 0xFFFE; }
		return( -3 );
	 }

	frac = num * 65536.0 * 128.0  * 256.0;
	exp = exp << 1;
	if( exp < 0 ) ++exp;

	*ret_word1 = (int)(frac >> 16) & 0xFFFF;
	*ret_word2 = (int)(frac & 0xFF00) | (exp & 0x00FF);
	return( 0 );
 }


//----------------------------------------------------
// Parse off a sequence of digits valid for the base.

static int
parse__digits( base )
	int			base;
 {
	int			dig, val;

	val = 0;

	repeat
	 {	dig = *parsePtr - '0';
		if( dig<0 || dig>=base )
			break;
		val = val*base + dig;
		++parsePtr;
	 }

	return( val );
 }


//----------------------------------------------------
// Parse off a decimal or octal number.

static int
parse__const()
 {
	char			*savePtr = parsePtr;
	int			val;

	if( doModern )
	 {	if( *parsePtr == '0' )						// check for octal
			val = parse__digits( 8 );
		else if( *parsePtr == '\'' )					// check for character constant
		 {	if( *++parsePtr == '\\' )
			 {	++parsePtr;
				val = parse_escChr();
			 }
			else if( *parsePtr )
				val = *parsePtr++;
			else
			 {	val = 0;
				List_err( "Unexpected EOL" );
			 }
			if( *parsePtr == '\'' ) ++parsePtr;			// closing quote is optional !!
		 }
		else								// otherwise decimal
			val = parse__digits( 10 );

		if( isalnum(*parsePtr) )					// half-hearted syntax check !!
			List_err( "Malformed constant" );
	 }
	else									// do original hp syntax
	 {	val = parse__digits( 10 );					// parse as decimal
		if( toupper(*parsePtr) == 'B' )					// check for octal B suffix
		 {	parsePtr = savePtr;					// reparse as octal
			val = parse__digits( 8 );
			if( toupper(*parsePtr) == 'B' )
				++parsePtr;
			else
				List_err( "Illegal octal digit" );
		 }
	 }

	return( val );
 }


//----------------------------------------------------
// Parse off an expression terminal.

static int
parse__term()
 {
	char			sym[SYM_MAXLEN*2+1];
	char			c;

	c = *parsePtr;

	if( isdigit(c) || c=='\'' )
		return( parse__const() );

	if( Sym_validChr(c) )
	 {	parse_sym( sym );
		return( Sym_find(sym) );
	 }

	if( c == '*' )
	 {	++parsePtr;
		return( CurAddr );
	 }

	List_err( "Syntax error in expression" );
	return( 0 );
 }


//----------------------------------------------------
// Parse off a signed expression terminal.

static int
parse__neg()
 {
	if( *parsePtr == '-' )
	 {	++parsePtr;
		return( (-parse__term()) & 0xFFFF );
	 }

	if( *parsePtr == '+' ) ++parsePtr;

	return( parse__term() );
 }


//* END Internals
//***********************************************************************************************************
//* Interface

char				*parsePtr;				// current parse position in current source line


//----------------------------------------------------
// Parse off and convert an escape character

int
parse_escChr()
 {
	int			v;

	if( !*parsePtr )
	 {	List_err( "unexpected EOL" );
		return( 0 );
	 }

	switch( *parsePtr++ )
	 {	case 'r':	v = '\r';	break;
		case 'n':	v = '\n';	break;
		case 'l':	v = '\n';	break;
		case 't':	v = '\t';	break;
		case 'b':	v = '\b';	break;
		case '"':	v = '"';	break;
		case '\\':	v = '\\';	break;
		case '0':	v = 0;		break;
		default:
			List_err( "unknown escape character" );
			return( 0 );
	 }

	return( v );
 }


//----------------------------------------------------
// Parse off an expression.
// The expression may include add and subtract operators.
// Terminals may be:
//	- * for the current address
//	- symbols
//	- decimal numbers
//	- octal numbers, indicated by a suffix of B

int
parse_arg()
 {
	int			v1, v2, op;

	parse_space();

	v1 = parse__neg();
	while( *parsePtr=='+' || *parsePtr=='-' )
	 {	op = *parsePtr++;
		v2 = parse__neg();
		if( op == '+' ) v1 = v1 + v2;
		if( op == '-' ) v1 = v1 - v2;
	 }

	return( v1 & 0xFFFF );
 }


//----------------------------------------------------
// Parse off a number in octal base.

int
parse_oct()
{
	int			sign, val;

	parse_space();

	sign = *parsePtr;
	if( sign=='+' || sign=='-' ) ++parsePtr;
	val = parse__digits( 8 );
	if( sign == '-' ) val = (-val) & 0xFFFF;

	return( val );
 }


//----------------------------------------------------
// Parse off a number in decimal base.
// If the digit sequence ends with a period or E, parsing will continue so as
// to interpret the number as a floating point number, and the value will be
// returned in two words.

int
parse_dec( ret_word1, ret_word2 )
	int			*ret_word1, *ret_word2;
 {
	char			*savePtr = parsePtr;
	int			sign, val;
	double			num;

	parse_space();

	sign = *parsePtr;
	if( sign=='+' || sign=='-' ) ++parsePtr;
	val = parse__digits( 10 );
	if( sign=='-' ) val = (-val) & 0xFFFF;

	if( *parsePtr=='.' || *parsePtr=='E' )
	 {	num = strtod( savePtr, &parsePtr );
		parse__double_to_hp( num, ret_word1, ret_word2 );
		return( 2 );
	 }

	*ret_word1 = val;
	return( 1 );
 }


//----------------------------------------------------
// Parse off a symbol and return it in the string <ret_sym>.
// <ret_sym> must be a string buffer at last 2*SYM_MAXLEN bytes, to allow referencing fully scoped names

void
parse_sym( ret_sym )
	char			ret_sym[];
 {
	int			i;

	i = 0;

	while( Sym_validChr(*parsePtr) )
	 {	if( i < SYM_MAXLEN*2 ) ret_sym[i] = *parsePtr;
		++i;
		++parsePtr;
	 }

	if( i > SYM_MAXLEN*2 )
	 {	List_err( "Symbol too long" );
		ret_sym[SYM_MAXLEN*2] = 0;
		return;
	 }

	ret_sym[i] = 0;
 }


//----------------------------------------------------
// See if the string <str> is present at the parse position in the source line.
// If a match is found, the parse position is adjusted to after the match, and TRUE is returned.
// If no match, FALSE is returned and the parse position is unaltered.

int
parse_try( str )
	char			*str;
 {
	int			i;

	for( i=0; str[i]; ++i )
		if( toupper(parsePtr[i]) != str[i] )
			return( FALSE );

	if( parsePtr[i]!=0 && !isblank(parsePtr[i]) && parsePtr[i]!=',' )
		return( FALSE );

	parsePtr += i;
	return( TRUE );
 }


//----------------------------------------------------
// Parse off an instruction modifier if present.

int
parse_mod( mod )
	char			mod;
 {
	if( *parsePtr==',' && *(parsePtr+1)==mod )
	 {	parsePtr += 2;
		return( TRUE );
	 }

	return( FALSE );
 }


//----------------------------------------------------
// Check for a comma (list), skip over it if present.

int
parse_comma()
 {
	if( *parsePtr != ',' )
		return( FALSE );

	++parsePtr;
	return( TRUE );
 }


//----------------------------------------------------
// Skip over blank space.

void
parse_space()
 {
	while( *parsePtr==' ' || *parsePtr=='\t' ) ++parsePtr;
 }


//----------------------------------------------------
// Check for end of token.

int
parse_atEOT()
 {
	return( *parsePtr==0 || isblank(*parsePtr)  );
 }


//* EOF
//***********************************************************************************************************
