Initial revision
This commit is contained in:
648
source/gameengine/Expressions/InputParser.cpp
Normal file
648
source/gameengine/Expressions/InputParser.cpp
Normal file
@@ -0,0 +1,648 @@
|
||||
// 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"
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user