// Parser.cpp: implementation of the CParser class.
/*
 * Copyright (c) 1996-2000 Erwin Coumans <coockie@acm.org>
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Erwin Coumans makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 */

#include <stdlib.h>

#include "Value.h"
#include "InputParser.h"
#include "ErrorValue.h"
#include "IntValue.h"
#include "StringValue.h"
#include "FloatValue.h"
#include "BoolValue.h"
#include "EmptyValue.h"
#include "ConstExpr.h"
#include "Operator2Expr.h"
#include "Operator1Expr.h"
#include "IdentifierExpr.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// this is disable at the moment, I expected a memleak from it, but the error-cleanup was the reason
// well, looks we don't need it anyway, until maybe the Curved Surfaces are integrated into CSG 
// cool things like (IF(LOD==1,CCurvedValue,IF(LOD==2,CCurvedValue2)) etc...
#include "IfExpr.h" 


#define NUM_PRIORITY 6
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CParser::CParser() : m_identifierContext(NULL)
{
}



CParser::~CParser()
{
	if (m_identifierContext)
		m_identifierContext->Release();
}



void CParser::ScanError(STR_String str)
{
	// sets the global variable errmsg to an errormessage with
	// contents str, appending if it already exists
	//	AfxMessageBox("Parse Error:"+str,MB_ICONERROR);
	if (errmsg)
		errmsg = new COperator2Expr(VALUE_ADD_OPERATOR, errmsg,	Error(str));
	else
		errmsg = Error(str);

	sym = errorsym;
}



CExpression* CParser::Error(STR_String str)
{
	// makes and returns a new CConstExpr filled with an CErrorValue
	// with string str
	//	AfxMessageBox("Error:"+str,MB_ICONERROR);
	return new CConstExpr(new CErrorValue(str));
}



void CParser::NextCh()
{
	// sets the global variable ch to the next character, if it exists
	// and increases the global variable chcount
	chcount++;

	if (chcount < text.Length())
		ch = text[chcount];
	else
		ch = 0x00;
}



void CParser::TermChar(char c)
{
	// generates an error if the next char isn't the specified char c,
	// otherwise, skip the char
	if(ch == c)
	{
		NextCh();
	}
	else
	{
		STR_String str;
		str.Format("Warning: %c expected\ncontinuing without it", c);
		trace(str);
	}
}



void CParser::DigRep()
{
	// changes the current character to the first character that
	// isn't a decimal
	while ((ch >= '0') && (ch <= '9'))
		NextCh();
}



void CParser::CharRep()
{
	// changes the current character to the first character that
	// isn't an alphanumeric character
	while (((ch >= '0') && (ch <= '9'))
		|| ((ch >= 'a') && (ch <= 'z'))
		|| ((ch >= 'A') && (ch <= 'Z'))
		|| (ch == '.') || (ch == '_'))
		NextCh();
}



void CParser::GrabString(int start)
{
	// puts part of the input string into the global variable
	// const_as_string, from position start, to position chchount
	const_as_string = text.Mid(start, chcount-start);
}



void CParser::NextSym()
{
	// sets the global variable sym to the next symbol, and
	// if it is an operator
	//   sets the global variable opkind to the kind of operator
	// if it is a constant
	//   sets the global variable constkind to the kind of operator
	// if it is a reference to a cell
	//   sets the global variable cellcoord to the kind of operator
	
	errmsg = NULL;
	while(ch == ' ' || ch == 0x9)
		NextCh();

	switch(ch)
	{
    case '(':
		sym = lbracksym; NextCh();
		break;
    case ')':
		sym = rbracksym; NextCh();
		break;
    case ',':
		sym = commasym; NextCh();
		break;
    case '+' :
		sym = opsym; opkind = OPplus; NextCh();
		break;
    case '-' :
		sym = opsym; opkind = OPminus; NextCh();
		break;
    case '*' :
		sym = opsym; opkind = OPtimes; NextCh();
		break;
    case '/' :
		sym = opsym; opkind = OPdivide; NextCh();
		break;
	case '&' :
		sym = opsym; opkind = OPand; NextCh(); TermChar('&');
		break;
	case '|' :
		sym = opsym; opkind = OPor; NextCh(); TermChar('|');
		break;
	case '=' :
		sym = opsym; opkind = OPequal; NextCh(); TermChar('=');
		break;
	case '!' :
		sym = opsym;
		NextCh();
		if (ch == '=')
		{
			opkind = OPunequal;
			NextCh();
		}
		else
		{
			opkind = OPnot;
		}
		break;
	case '>':
		sym = opsym;
		NextCh();
		if (ch == '=')
		{
			opkind = OPgreaterequal;
			NextCh();
		}
		else
		{
			opkind = OPgreater;
		}
		break;
	case '<':
		sym = opsym;
		NextCh();
		if (ch == '=') {
			opkind = OPlessequal;
			NextCh();
		} else {
			opkind = OPless;
		}
		break;
    case '\"' : {
		int start;
		sym = constsym;
		constkind = stringtype;
		NextCh();
		start = chcount;
		while ((ch != '\"') && (ch != 0x0))
			NextCh();
		GrabString(start);
		TermChar('\"');	// check for eol before '\"'
		break;
				}
    case 0x0: sym = eolsym; break;
    default: 
		{
			int start;
			start = chcount;
			DigRep();
			if ((start != chcount) || (ch == '.')) { // number
				sym = constsym;
				if (ch == '.') {
					constkind = floattype;
					NextCh();
					DigRep();
				}
				else constkind = inttype;
				if ((ch == 'e') || (ch == 'E')) {
					int mark;
					constkind = floattype;
					NextCh();
					if ((ch == '+') || (ch == '-')) NextCh();
					mark = chcount;
					DigRep();
					if (mark == chcount) {
						ScanError("Number expected after 'E'");
						return;
					}
				}
				GrabString(start);
			} else if (((ch >= 'a') && (ch <= 'z'))
				|| ((ch >= 'A') && (ch <= 'Z')))
			{ // reserved word?
				int start;
				STR_String funstr;
				start = chcount;
				CharRep();
				GrabString(start);
				funstr = const_as_string;
				funstr.Upper();
				if (funstr == STR_String("SUM")) {
					sym = sumsym;
				}
				else if (funstr == STR_String("NOT")) {
					sym = opsym;
					opkind = OPnot;
				}
				else if (funstr == STR_String("AND")) {
					sym = opsym; opkind = OPand;
				}
				else if (funstr == STR_String("OR")) {
					sym = opsym; opkind = OPor;
				}
				else if (funstr == STR_String("IF")) {
					sym = ifsym;
				} else if (funstr == STR_String("WHOMADE")) {
					sym = whocodedsym;
				} else if (funstr == STR_String("FALSE")) {
					sym = constsym; constkind = booltype; boolvalue = false;
				} else if (funstr == STR_String("TRUE")) {
					sym = constsym; constkind = booltype; boolvalue = true;
				} else {
					sym = idsym;
					//STR_String str;
					//str.Format("'%s' makes no sense here", (const char*)funstr);
					//ScanError(str);
				}
			} else { // unknown symbol
				STR_String str;
				str.Format("Unexpected character '%c'", ch);
				NextCh();
				ScanError(str);
				return;
			}
		}
	}
}

int CParser::MakeInt() {
	// returns the integer representation of the value in the global
	// variable const_as_string
	// pre: const_as_string contains only numercal chars
	return atoi(const_as_string);
}

STR_String CParser::Symbol2Str(int s) {
	// returns a string representation of of symbol s,
	// for use in Term when generating an error
	switch(s) {
    case errorsym: return "error";
    case lbracksym: return "(";
    case rbracksym: return ")";
    case commasym: return ",";
    case opsym: return "operator";
    case constsym: return "constant";
	case sumsym: return "SUM";
	case ifsym: return "IF";
	case whocodedsym: return "WHOMADE";
    case eolsym: return "end of line";
	case idsym: return "identifier";
    default: return "unknown";  // should not happen
	}
}

void CParser::Term(int s) {
	// generates an error if the next symbol isn't the specified symbol s
	// otherwise, skip the symbol
	if(s == sym) NextSym();
	else {
		STR_String msg;
		msg.Format("Warning: " + Symbol2Str(s) + " expected\ncontinuing without it");

//		AfxMessageBox(msg,MB_ICONERROR);

		trace(msg);
	}
}

int CParser::Priority(int optorkind) {
	// returns the priority of an operator
	// higher number means higher priority
	switch(optorkind) {
	case OPor: return 1;
	case OPand: return 2;
	case OPgreater:
	case OPless:
	case OPgreaterequal:
	case OPlessequal:
	case OPequal:
	case OPunequal: return 3;
    case OPplus:
    case OPminus: return 4;
    case OPtimes:
    case OPdivide: return 5;
	}
	assert(false);
	return 0;      // should not happen
}

CExpression *CParser::Ex(int i) {
	// parses an expression in the imput, starting at priority i, and
	// returns an CExpression, containing the parsed input
	CExpression *e1 = NULL, *e2 = NULL;
	int opkind2;
	
	if (i < NUM_PRIORITY) {
		e1 = Ex(i + 1);
		while ((sym == opsym) && (Priority(opkind) == i)) {
			opkind2 = opkind;
			NextSym();
			e2 = Ex(i + 1);
			switch(opkind2) {
			case OPplus: e1 = new COperator2Expr(VALUE_ADD_OPERATOR,e1, e2); break;
			case OPminus: e1 = new COperator2Expr(VALUE_SUB_OPERATOR,e1, e2); break;
			case OPtimes:	e1 = new COperator2Expr(VALUE_MUL_OPERATOR,e1, e2); break;
			case OPdivide: e1 = new COperator2Expr(VALUE_DIV_OPERATOR,e1, e2); break;
			case OPand: e1 = new COperator2Expr(VALUE_AND_OPERATOR,e1, e2); break;
			case OPor: e1 = new COperator2Expr(VALUE_OR_OPERATOR,e1, e2); break;
			case OPequal: e1 = new COperator2Expr(VALUE_EQL_OPERATOR,e1, e2); break;
			case OPunequal: e1 = new COperator2Expr(VALUE_NEQ_OPERATOR,e1, e2); break;
			case OPgreater: e1 = new COperator2Expr(VALUE_GRE_OPERATOR,e1, e2); break;
			case OPless: e1 = new COperator2Expr(VALUE_LES_OPERATOR,e1, e2); break;
			case OPgreaterequal: e1 = new COperator2Expr(VALUE_GEQ_OPERATOR,e1, e2); break;
			case OPlessequal: e1 = new COperator2Expr(VALUE_LEQ_OPERATOR,e1, e2); break;
			default: assert(false);	break; // should not happen
			}
		}
	} else if (i == NUM_PRIORITY) {
		if ((sym == opsym) 
			&& ( (opkind == OPminus) || (opkind == OPnot) || (opkind == OPplus) ) 
			)
		{
			NextSym();
			switch(opkind) {
			/* +1 is also a valid number! */
			case OPplus: e1 = new COperator1Expr(VALUE_POS_OPERATOR, Ex(NUM_PRIORITY)); break;
			case OPminus: e1 = new COperator1Expr(VALUE_NEG_OPERATOR, Ex(NUM_PRIORITY)); break;
			case OPnot: e1 = new COperator1Expr(VALUE_NOT_OPERATOR, Ex(NUM_PRIORITY)); break;
			default: {
						// should not happen
						e1 = Error("operator +, - or ! expected");
					 }
			}
		}
		else {
			switch(sym) {
			case constsym: {
				switch(constkind) {
				case booltype:
					e1 = new CConstExpr(new CBoolValue(boolvalue));
					break;
				case inttype:
					{
						int temp;
						temp = atoi(const_as_string);
						e1 = new CConstExpr(new CIntValue(temp));
						break;
					}
				case floattype:
					{
						double temp;
						temp = atof(const_as_string);
						e1 = new CConstExpr(new CFloatValue(temp));
						break;
					}
				case stringtype:
					e1 = new CConstExpr(new CStringValue(const_as_string,""));
					break;
				default :
					assert(false);
					break;
				}
				NextSym();
				break;
						   }
			case lbracksym:
				NextSym();
				e1 = Ex(1);
				Term(rbracksym);
				break;
			case ifsym:
			{
				CExpression *e3;
				NextSym();
				Term(lbracksym);
				e1 = Ex(1);
				Term(commasym);
				e2 = Ex(1);
				if (sym == commasym) {
					NextSym();
					e3 = Ex(1);
				} else {
					e3 = new CConstExpr(new CEmptyValue());
				}
				Term(rbracksym);
				e1 = new CIfExpr(e1, e2, e3);
				break;
			}
			case idsym:
				{
					e1 = new CIdentifierExpr(const_as_string,m_identifierContext);
					NextSym();
					
					break;
				}
			case errorsym:
				{
					assert(!e1);
					STR_String errtext="[no info]";
					if (errmsg)
					{
						CValue* errmsgval = errmsg->Calculate();
						errtext=errmsgval->GetText();
						errmsgval->Release();
					
						//e1 = Error(errmsg->Calculate()->GetText());//new CConstExpr(errmsg->Calculate());
						
						if ( !(errmsg->Release()) )
						{
							errmsg=NULL;
						} else {
							// does this happen ?
							assert ("does this happen");
						}
					}
					e1 = Error(errtext);

					break;				
				}
			default:
				NextSym();
				//return Error("Expression expected");
				assert(!e1);
				e1 = Error("Expression expected");
			}
		}
	}
	return e1;
}

CExpression *CParser::Expr() {
	// parses an expression in the imput, and
	// returns an CExpression, containing the parsed input
	return Ex(1);
}

CExpression* CParser::ProcessText
(STR_String intext) {
	
	// and parses the string in intext and returns it.
	
	
	CExpression* expr;
	text = intext;
	
	
	chcount = 0;	
	if (text.Length() == 0) {
		return NULL;
	}
	
	ch = text[0];
	/*if (ch != '=') {
	expr = new CConstExpr(new CStringValue(text));
	*dependant = deplist;
	return expr;
	} else 
	*/
	//	NextCh();
	NextSym();
	expr = Expr();
	if (sym != eolsym) {
		CExpression* oldexpr = expr;
		expr = new COperator2Expr(VALUE_ADD_OPERATOR,
			oldexpr, Error(STR_String("Extra characters after expression")));//new CConstExpr(new CErrorValue("Extra characters after expression")));
	}
	if (errmsg)
		errmsg->Release();
	
	return expr;
}



float CParser::GetFloat(STR_String txt)
{
	// returns parsed text into a float
	// empty string returns -1
	
//	AfxMessageBox("parsed string="+txt);
	CValue* val=NULL;
	float result=-1;
//	String tmpstr;

	CExpression* expr = ProcessText(txt);
	if (expr) {
		val = expr->Calculate();
		result=val->GetNumber();
		
		
	
		val->Release();
		expr->Release();
	}
//	tmpstr.Format("parseresult=%g",result);
//		AfxMessageBox(tmpstr);
	return result;
}

CValue* CParser::GetValue(STR_String txt, bool bFallbackToText)
{
	// returns parsed text into a value, 
	// empty string returns NULL value !
	// if bFallbackToText then unparsed stuff is put into text
	
	CValue* result=NULL;
	CExpression* expr = ProcessText(txt);
	if (expr) {
		result = expr->Calculate();
		expr->Release();
	}
	if (result)
	{
		// if the parsed stuff lead to an errorvalue, don't return errors, just NULL
		if (result->IsError()) {
			result->Release();
			result=NULL;
			if (bFallbackToText) {
				if (txt.Length()>0)
				{
					result = new CStringValue(txt,"");
				}
			}
		}
	}
	return result;
}

void CParser::SetContext(CValue* context)
{
	if (m_identifierContext)
	{
		m_identifierContext->Release();
	}
	m_identifierContext = context;
}




PyObject*	CParserPyMake(PyObject* ignored,PyObject* args)
{
	char* txt;
	Py_Try(PyArg_ParseTuple(args,"s",&txt));
	CParser parser;
	CExpression* expr = parser.ProcessText(txt);
	CValue* val = expr->Calculate();
	expr->Release();
	return val;
}

static PyMethodDef	CParserMethods[] = 
{
	{ "calc", CParserPyMake , Py_NEWARGS},
	{ NULL,NULL}	// Sentinel
};

extern "C" {
	void initExpressionModule(void)
	{
		Py_InitModule("Expression",CParserMethods);
	}
}

