/*
* ejparse.c -- Ejscript(TM) Parser
*
* Copyright (c) GoAhead Software Inc., 1995-2000. All Rights Reserved.
*
* See the file "license.txt" for usage and redistribution license requirements
*/
/******************************** Description *********************************/
/*
* Ejscript parser. This implementes a subset of the JavaScript language.
* Multiple Ejscript parsers can be opened at a time.
*/
/********************************** Includes **********************************/
#include "ejIntrn.h"
#if CE
#include "CE/wincompat.h"
#endif
/********************************** Local Data ********************************/
ej_t **ejHandles; /* List of ej handles */
int ejMax = -1; /* Maximum size of */
/****************************** Forward Declarations **************************/
#ifndef B_STATS
#define setString(a,b,c) setstring(b,c)
#endif
static ej_t *ejPtr(int eid);
static void clearString(char_t **ptr);
static void setString(B_ARGS_DEC, char_t **ptr, char_t *s);
static void appendString(char_t **ptr, char_t *s);
static int parse(ej_t *ep, int state, int flags);
static int parseStmt(ej_t *ep, int state, int flags);
static int parseDeclaration(ej_t *ep, int state, int flags);
static int parseArgs(ej_t *ep, int state, int flags);
static int parseCond(ej_t *ep, int state, int flags);
static int parseExpr(ej_t *ep, int state, int flags);
static int evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs);
static int evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs);
static int evalFunction(ej_t *ep);
static void freeFunc(ejfunc_t *func);
static void ejRemoveNewlines(ej_t *ep, int state);
/************************************* Code ***********************************/
/*
* Initialize a Ejscript engine
*/
int ejOpenEngine(sym_fd_t variables, sym_fd_t functions)
{
ej_t *ep;
int eid, vid;
if ((eid = hAllocEntry((void***) &ejHandles, &ejMax, sizeof(ej_t))) < 0) {
return -1;
}
ep = ejHandles[eid];
ep->eid = eid;
/*
* Create a top level symbol table if one is not provided for variables and
* functions. Variables may create other symbol tables for block level
* declarations so we use hAlloc to manage a list of variable tables.
*/
if ((vid = hAlloc((void***) &ep->variables)) < 0) {
ejMax = hFree((void***) &ejHandles, ep->eid);
return -1;
}
if (vid >= ep->variableMax) {
ep->variableMax = vid + 1;
}
if (variables == -1) {
ep->variables[vid] = symOpen(64) + EJ_OFFSET;
ep->flags |= FLAGS_VARIABLES;
} else {
ep->variables[vid] = variables + EJ_OFFSET;
}
if (functions == -1) {
ep->functions = symOpen(64);
ep->flags |= FLAGS_FUNCTIONS;
} else {
ep->functions = functions;
}
ejLexOpen(ep);
/*
* Define standard constants
*/
ejSetGlobalVar(ep->eid, T("null"), NULL);
#if EMF
ejEmfOpen(ep->eid);
#endif
return ep->eid;
}
/******************************************************************************/
/*
* Close
*/
void ejCloseEngine(int eid)
{
ej_t *ep;
int i;
if ((ep = ejPtr(eid)) == NULL) {
return;
}
#if EMF
ejEmfClose(eid);
#endif
bfreeSafe(B_L, ep->error);
ep->error = NULL;
bfreeSafe(B_L, ep->result);
ep->result = NULL;
ejLexClose(ep);
for (i = ep->variableMax - 1; i >= 0; i--) {
if (ep->flags & FLAGS_VARIABLES) {
symClose(ep->variables[i] - EJ_OFFSET);
}
ep->variableMax = hFree((void***) &ep->variables, i);
}
if (ep->flags & FLAGS_FUNCTIONS) {
symClose(ep->functions);
}
ejMax = hFree((void***) &ejHandles, ep->eid);
bfree(B_L, ep);
}
#ifndef __NO_EJ_FILE
/******************************************************************************/
/*
* Evaluate a Ejscript file
*/
char_t *ejEvalFile(int eid, char_t *path, char_t **emsg)
{
gstat_t sbuf;
ej_t *ep;
char_t *script, *rs;
char *fileBuf;
int fd;
a_assert(path && *path);
if (emsg) {
*emsg = NULL;
}
if ((ep = ejPtr(eid)) == NULL) {
return NULL;
}
if ((fd = gopen(path, O_RDONLY | O_BINARY, 0666)) < 0) {
ejError(ep, T("Bad handle %d"), eid);
return NULL;
}
if (gstat(path, &sbuf) < 0) {
gclose(fd);
ejError(ep, T("Cant stat %s"), path);
return NULL;
}
if ((fileBuf = balloc(B_L, sbuf.st_size + 1)) == NULL) {
gclose(fd);
ejError(ep, T("Cant malloc %d"), sbuf.st_size);
return NULL;
}
if (gread(fd, fileBuf, sbuf.st_size) != (int)sbuf.st_size) {
gclose(fd);
bfree(B_L, fileBuf);
ejError(ep, T("Error reading %s"), path);
return NULL;
}
fileBuf[sbuf.st_size] = '\0';
gclose(fd);
if ((script = ballocAscToUni(fileBuf, sbuf.st_size)) == NULL) {
bfree(B_L, fileBuf);
ejError(ep, T("Cant malloc %d"), sbuf.st_size + 1);
return NULL;
}
bfree(B_L, fileBuf);
rs = ejEvalBlock(eid, script, emsg);
bfree(B_L, script);
return rs;
}
#endif /* __NO_EJ_FILE */
/******************************************************************************/
/*
* Create a new variable scope block so that consecutive ejEval calls may
* be made with the same varible scope. This space MUST be closed with
* ejCloseBlock when the evaluations are complete.
*/
int ejOpenBlock(int eid)
{
ej_t *ep;
int vid;
if((ep = ejPtr(eid)) == NULL) {
return -1;
}
if ((vid = hAlloc((void***) &ep->variables)) < 0) {
return -1;
}
if (vid >= ep->variableMax) {
ep->variableMax = vid + 1;
}
ep->variables[vid] = symOpen(64) + EJ_OFFSET;
return vid;
}
/******************************************************************************/
/*
* Close a variable scope block. The vid parameter is the return value from
* the call to ejOpenBlock
*/
int ejCloseBlock(int eid, int vid)
{
ej_t *ep;
if((ep = ejPtr(eid)) == NULL) {
return -1;
}
symClose(ep->variables[vid] - EJ_OFFSET);
ep->variableMax = hFree((void***) &ep->variables, vid);
return 0;
}
/******************************************************************************/
/*
* Create a new variable scope block and evaluate a script. All variables
* created during this context will be automatically deleted when complete.
*/
char_t *ejEvalBlock(int eid, char_t *script, char_t **emsg)
{
char_t* returnVal;
int vid;
a_assert(script);
vid = ejOpenBlock(eid);
returnVal = ejEval(eid, script, emsg);
ejCloseBlock(eid, vid);
return returnVal;
}
/******************************************************************************/
/*
* Parse and evaluate a Ejscript. The caller may provide a symbol table to
* use for variables and function definitions. Return char_t pointer on
* success otherwise NULL pointer is returned.
*/
char_t *ejEval(int eid, char_t *script, char_t **emsg)
{
ej_t *ep;
ejinput_t *oldBlock;
int state;
void *endlessLoopTest;
int loopCounter;
a_assert(script);
if (emsg) {
*emsg = NULL;
}
if ((ep = ejPtr(eid)) == NULL) {
return NULL;
}
setString(B_L, &ep->result, T(""));
/*
* Allocate a new evaluation block, and save the old one
*/
oldBlock = ep->input;
ejLexOpenScript(ep, script);
/*
* Do the actual parsing and evaluation
*/
loopCounter = 0;
endlessLoopTest = NULL;
do {
state = parse(ep, STATE_BEGIN, FLAGS_EXE);
if (state == STATE_RET) {
state = STATE_EOF;
}
/*
* prevent parser from going into infinite loop. If parsing the same
* line 10 times then fail and report Syntax error. Most normal error
* are caught in the parser itself.
*/
if (endlessLoopTest == ep->input->script.servp) {
if (loopCounter++ > 10) {
state = STATE_ERR;
ejError(ep, T("Syntax error"));
}
} else {
endlessLoopTest = ep->input->script.servp;
loopCounter = 0;
}
} while (state != STATE_EOF && state != STATE_ERR);
ejLexCloseScript(ep);
/*
* Return any error string to the user
*/
if (state == STATE_ERR && emsg) {
*emsg = bstrdup(B_L, ep->error);
}
/*
* Restore the old evaluation block
*/
ep->input = oldBlock;
if (state == STATE_EOF) {
return ep->result;
}
if (state == STATE_ERR) {
return NULL;
}
return ep->result;
}
/******************************************************************************/
/*
* Recursive descent parser for Ejscript
*/
static int parse(ej_t *ep, int state, int flags)
{
a_assert(ep);
switch (state) {
/*
* Any statement, function arguments or conditional expressions
*/
case STATE_STMT:
if ((state = parseStmt(ep, state, flags)) != STATE_STMT_DONE &&
state != STATE_EOF && state != STATE_STMT_BLOCK_DONE &&
state != STATE_RET) {
state = STATE_ERR;
}
break;
case STATE_DEC:
if ((state = parseStmt(ep, state, flags)) != STATE_DEC_DONE &&
state != STATE_EOF) {
state = STATE_ERR;
}
break;
case STATE_EXPR:
if ((state = parseStmt(ep, state, flags)) != STATE_EXPR_DONE &&
state != STATE_EOF) {
state = STATE_ERR;
}
break;
/*
* Variable declaration list
*/
case STATE_DEC_LIST:
state = parseDeclaration(ep, state, flags);
break;
/*
* Function argument string
*/
case STATE_ARG_LIST:
state = parseArgs(ep, state, flags);
break;
/*
* Logical condition list (relational operations separated by &&, ||)
*/
case STATE_COND:
state = parseCond(ep, state, flags);
break;
/*
* Expression list
*/
case STATE_RELEXP:
state = parseExpr(ep, state, flags);
break;
}
if (state == STATE_ERR && ep->error == NULL) {
ejError(ep, T("Syntax error"));
}
return state;
}
/******************************************************************************/
/*
* Parse any statement including functions and simple relational operations
*/
static int parseStmt(ej_t *ep, int state, int flags)
{
ejfunc_t func;
ejfunc_t *saveFunc;
ejinput_t condScript, endScript, bodyScript, incrScript;
char_t *value, *identifier;
int done, expectSemi, thenFlags, elseFlags, tid, cond, forFlags;
int ejVarType;
a_assert(ep);
/*
* Set these to NULL, else we try to free them if an error occurs.
*/
endScript.putBackToken = NULL;
bodyScript.putBackToken = NULL;
incrScript.putBackToken = NULL;
condScript.putBackToken = NULL;
expectSemi = 0;
saveFunc = NULL;
for (done = 0; !done; ) {
tid = ejLexGetToken(ep, state);
switch (tid) {
default:
ejLexPutbackToken(ep, TOK_EXPR, ep->token);
done++;
break;
case TOK_ERR:
state = STATE_ERR;
done++;
break;
case TOK_EOF:
state = STATE_EOF;
done++;
break;
case TOK_NEWLINE:
break;
case TOK_SEMI:
/*
* This case is when we discover no statement and just a lone ';'
*/
if (state != STATE_STMT) {
ejLexPutbackToken(ep, tid, ep->token);
}
done++;
break;
case TOK_ID:
/*
* This could either be a reference to a variable or an assignment
*/
identifier = NULL;
setString(B_L, &identifier, ep->token);
/*
* Peek ahead to see if this is an assignment
*/
tid = ejLexGetToken(ep, state);
if (tid == TOK_ASSIGNMENT) {
if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) {
clearString(&identifier);
goto error;
}
if (flags & FLAGS_EXE) {
if ( state == STATE_DEC ) {
ejSetLocalVar(ep->eid, identifier, ep->result);
} else {
ejVarType = ejGetVar(ep->eid, identifier, &value);
if (ejVarType > 0) {
ejSetLocalVar(ep->eid, identifier, ep->result);
} else {
ejSetGlobalVar(ep->eid, identifier, ep->result);
}
}
}
} else if (tid == TOK_INC_DEC ) {
value = NULL;
if (flags & FLAGS_EXE) {
ejVarType = ejGetVar(ep->eid, identifier, &value);
if (ejVarType < 0) {
ejError(ep, T("Undefined variable %s\n"), identifier);
goto error;
}
setString(B_L, &ep->result, value);
if (evalExpr(ep, value, (int) *ep->token, T("1")) < 0) {
state = STATE_ERR;
break;
}
if (ejVarType > 0) {
ejSetLocalVar(ep->eid, identifier, ep->result);
} else {
ejSetGlobalVar(ep->eid, identifier, ep->result);
}
}
} else {
/*
* If we are processing a declaration, allow undefined vars
*/
value = NULL;
if (state == STATE_DEC) {
if (ejGetVar(ep->eid, identifier, &value) > 0) {
ejError(ep, T("Variable already declared"),
identifier);
clearString(&identifier);
goto error;
}
ejSetLocalVar(ep->eid, identifier, NULL);
} else {
if ( flags & FLAGS_EXE ) {
if (ejGetVar(ep->eid, identifier, &value) < 0) {
ejError(ep, T("Undefined variable %s\n"),
identifier);
clearString(&identifier);
goto error;
}
}
}
setString(B_L, &ep->result, value);
ejLexPutbackToken(ep, tid, ep->token);
}
clearString(&identifier);
if (state == STATE_STMT) {
expectSemi++;
}
done++;
break;
case TOK_LITERAL:
/*
* Set the result to the literal (number or string constant)
*/
setString(B_L, &ep->result, ep->token);
if (state == STATE_STMT) {
expectSemi++;
}
done++;
break;
case TOK_FUNCTION:
/*
* We must save any current ep->func value for the current stack frame
*/
if (ep->func) {
saveFunc = ep->func;
}
memset(&func, 0, sizeof(ejfunc_t));
setString(B_L, &func.fname, ep->token);
ep->func = &func;
setString(B_L, &ep->result, T(""));
if (ejLexGetToken(ep, state) != TOK_LPAREN) {
freeFunc(&func);
goto error;
}
if (parse(ep, STATE_ARG_LIST, flags) != STATE_ARG_LIST_DONE) {
freeFunc(&func);
ep->func = saveFunc;
goto error;
}
/*
* Evaluate the function if required
*/
if (flags & FLAGS_EXE && evalFunction(ep) < 0) {
freeFunc(&func);
ep->func = saveFunc;
goto error;
}
freeFunc(&func);
ep->func = saveFunc;
if (ejLexGetToken(ep, state) != TOK_RPAREN) {
goto error;
}
if (state == STATE_STMT) {
expectSemi++;
}
done++;
break;
case TOK_IF:
if (state != STATE_STMT) {
goto error;
}
if (ejLexGetToken(ep, state) != TOK_LPAREN) {
goto error;
}
/*
* Evaluate the entire condition list "(condition)"
*/
if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) {
goto error;
}
if (ejLexGetToken(ep, state) != TOK_RPAREN) {
goto error;
}
/*
* This is the "then" case. We need to always parse both cases and
* execute only the relevant case.
*/
if (*ep->result == '1') {
thenFlags = flags;
elseFlags = flags & ~FLAGS_EXE;
} else {
thenFlags = flags & ~FLAGS_EXE;
elseFlags = flags;
}
/*
* Process the "then" case. Allow for RETURN statement
*/
switch (parse(ep, STATE_STMT, thenFlags)) {
case STATE_RET:
return STATE_RET;
case STATE_STMT_DONE:
break;
default:
goto error;
}
/*
* check to see if there is an "else" case
*/
ejRemoveNewlines(ep, state);
tid = ejLexGetToken(ep, state);
if (tid != TOK_ELSE) {
ejLexPutbackToken(ep, tid, ep->token);
done++;
break;
}
/*
* Process the "else" case. Allow for return.
*/
switch (parse(ep, STATE_STMT, elseFlags)) {
case STATE_RET:
return STATE_RET;
case STATE_STMT_DONE:
break;
default:
goto error;
}
done++;
break;
case TOK_FOR:
/*
* Format for the expression is:
*
* for (initial; condition; incr) {
* body;
* }
*/
if (state != STATE_STMT) {
goto error;
}
if (ejLexGetToken(ep, state) != TOK_LPAREN) {
goto error;
}
/*
* Evaluate the for loop initialization statement
*/
if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) {
goto error;
}
if (ejLexGetToken(ep, state) != TOK_SEMI) {
goto error;
}
/*
* The first time through, we save the current input context just
* to each step: prior to the conditional, the loop increment and the
* loop body.
*/
ejLexSaveInputState(ep, &condScript);
if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) {
goto error;
}
cond = (*ep->result != '0');
if (ejLexGetToken(ep, state) != TOK_SEMI) {
goto error;
}
/*
* Don't execute the loop increment statement or the body first time
*/
forFlags = flags & ~FLAGS_EXE;
ejLexSaveInputState(ep, &incrScript);
if (parse(ep, STATE_EXPR, forFlags) != STATE_EXPR_DONE) {
goto error;
}
if (ejLexGetToken(ep, state) != TOK_RPAREN) {
goto error;
}
/*
* Parse the body and remember the end of the body script
*/
ejLexSaveInputState(ep, &bodyScript);
if (parse(ep, STATE_STMT, forFlags) != STATE_STMT_DONE) {
goto error;
}
ejLexSaveInputState(ep, &endScript);
/*
* Now actually do the for loop. Note loop has been rotated
*/
while (cond && (flags & FLAGS_EXE) ) {
/*
* Evaluate the body
*/
ejLexRestoreInputState(ep, &bodyScript);
switch (parse(ep, STATE_STMT, flags)) {
case STATE_RET:
return STATE_RET;
case STATE_STMT_DONE:
break;
default:
goto error;
}
/*
* Evaluate the increment script
*/
ejLexRestoreInputState(ep, &incrScript);
if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) {
goto error;
}
/*
* Evaluate the condition
*/
ejLexRestoreInputState(ep, &condScript);
if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) {
goto error;
}
cond = (*ep->result != '0');
}
ejLexRestoreInputState(ep, &endScript);
done++;
break;
case TOK_VAR:
if (parse(ep, STATE_DEC_LIST, flags) != STATE_DEC_LIST_DONE) {
goto error;
}
done++;
break;
case TOK_COMMA:
ejLexPutbackToken(ep, TOK_EXPR, ep->token);
done++;
break;
case TOK_LPAREN:
if (state == STATE_EXPR) {
if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) {
goto error;
}
if (ejLexGetToken(ep, state) != TOK_RPAREN) {
goto error;
}
return STATE_EXPR_DONE;
}
done++;
break;
case TOK_RPAREN:
ejLexPutbackToken(ep, tid, ep->token);
return STATE_EXPR_DONE;
case TOK_LBRACE:
/*
* This handles any code in braces except "if () {} else {}"
*/
if (state != STATE_STMT) {
goto error;
}
/*
* Parse will return STATE_STMT_BLOCK_DONE when the RBRACE is seen
*/
do {
state = parse(ep, STATE_STMT, flags);
} while (state == STATE_STMT_DONE);
/*
* Allow return statement.
*/
if (state == STATE_RET) {
return state;
}
if (ejLexGetToken(ep, state) != TOK_RBRACE) {
goto error;
}
return STATE_STMT_DONE;
case TOK_RBRACE:
if (state == STATE_STMT) {
ejLexPutbackToken(ep, tid, ep->token);
return STATE_STMT_BLOCK_DONE;
}
goto error;
case TOK_RETURN:
if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) {
goto error;
}
if (flags & FLAGS_EXE) {
while ( ejLexGetToken(ep, state) != TOK_EOF );
done++;
return STATE_RET;
}
break;
}
}
if (expectSemi) {
tid = ejLexGetToken(ep, state);
if (tid != TOK_SEMI && tid != TOK_NEWLINE) {
goto error;
}
/*
* Skip newline after semi-colon
*/
ejRemoveNewlines(ep, state);
}
/*
* Free resources and return the correct status
*/
doneParse:
if (tid == TOK_FOR) {
ejLexFreeInputState(ep, &condScript);
ejLexFreeInputState(ep, &incrScript);
ejLexFreeInputState(ep, &endScript);
ejLexFreeInputState(ep, &bodyScript);
}
if (state == STATE_STMT) {
return STATE_STMT_DONE;
} else if (state == STATE_DEC) {
return STATE_DEC_DONE;
} else if (state == STATE_EXPR) {
return STATE_EXPR_DONE;
} else if (state == STATE_EOF) {
return state;
} else {
return STATE_ERR;
}
/*
* Common error exit
*/
error:
state = STATE_ERR;
goto doneParse;
}
/******************************************************************************/
/*
* Parse variable declaration list
*/
static int parseDeclaration(ej_t *ep, int state, int flags)
{
int tid;
a_assert(ep);
/*
* Declarations can be of the following forms:
* var x;
* var x, y, z;
* var x = 1 + 2 / 3, y = 2 + 4;
*
* We set the variable to NULL if there is no associated assignment.
*/
do {
if ((tid = ejLexGetToken(ep, state)) != TOK_ID) {
return STATE_ERR;
}
ejLexPutbackToken(ep, tid, ep->token);
/*
* Parse the entire assignment or simple identifier declaration
*/
if (parse(ep, STATE_DEC, flags) != STATE_DEC_DONE) {
return STATE_ERR;
}
/*
* Peek at the next token, continue if comma seen
*/
tid = ejLexGetToken(ep, state);
if (tid == TOK_SEMI) {
return STATE_DEC_LIST_DONE;
} else if (tid != TOK_COMMA) {
return STATE_ERR;
}
} while (tid == TOK_COMMA);
if (tid != TOK_SEMI) {
return STATE_ERR;
}
return STATE_DEC_LIST_DONE;
}
/******************************************************************************/
/*
* Parse function arguments
*/
static int parseArgs(ej_t *ep, int state, int flags)
{
int tid, aid;
a_assert(ep);
do {
state = parse(ep, STATE_RELEXP, flags);
if (state == STATE_EOF || state == STATE_ERR) {
return state;
}
if (state == STATE_RELEXP_DONE) {
aid = hAlloc((void***) &ep->func->args);
ep->func->args[aid] = bstrdup(B_L, ep->result);
ep->func->nArgs++;
}
/*
* Peek at the next token, continue if more args (ie. comma seen)
*/
tid = ejLexGetToken(ep, state);
if (tid != TOK_COMMA) {
ejLexPutbackToken(ep, tid, ep->token);
}
} while (tid == TOK_COMMA);
if (tid != TOK_RPAREN && state != STATE_RELEXP_DONE) {
return STATE_ERR;
}
return STATE_ARG_LIST_DONE;
}
/******************************************************************************/
/*
* Parse conditional expression (relational ops separated by ||, &&)
*/
static int parseCond(ej_t *ep, int state, int flags)
{
char_t *lhs, *rhs;
int tid, operator;
a_assert(ep);
setString(B_L, &ep->result, T(""));
rhs = lhs = NULL;
operator = 0;
do {
/*
* Recurse to handle one side of a conditional. Accumulate the
* left hand side and the final result in ep->result.
*/
state = parse(ep, STATE_RELEXP, flags);
if (state != STATE_RELEXP_DONE) {
state = STATE_ERR;
break;
}
if (operator > 0) {
setString(B_L, &rhs, ep->result);
if (evalCond(ep, lhs, operator, rhs) < 0) {
state = STATE_ERR;
break;
}
}
setString(B_L, &lhs, ep->result);
tid = ejLexGetToken(ep, state);
if (tid == TOK_LOGICAL) {
operator = (int) *ep->token;
} else if (tid == TOK_RPAREN || tid == TOK_SEMI) {
ejLexPutbackToken(ep, tid, ep->token);
state = STATE_COND_DONE;
break;
} else {
ejLexPutbackToken(ep, tid, ep->token);
}
} while (state == STATE_RELEXP_DONE);
if (lhs) {
bfree(B_L, lhs);
}
if (rhs) {
bfree(B_L, rhs);
}
return state;
}
/******************************************************************************/
/*
* Parse expression (leftHandSide operator rightHandSide)
*/
static int parseExpr(ej_t *ep, int state, int flags)
{
char_t *lhs, *rhs;
int rel, tid;
a_assert(ep);
setString(B_L, &ep->result, T(""));
rhs = lhs = NULL;
rel = 0;
tid = 0;
do {
/*
* This loop will handle an entire expression list. We call parse
* to evalutate each term which returns the result in ep->result.
*/
if (tid == TOK_LOGICAL) {
if ((state = parse(ep, STATE_RELEXP, flags)) != STATE_RELEXP_DONE) {
state = STATE_ERR;
break;
}
} else {
if ((state = parse(ep, STATE_EXPR, flags)) != STATE_EXPR_DONE) {
state = STATE_ERR;
break;
}
}
if (rel > 0) {
setString(B_L, &rhs, ep->result);
if (tid == TOK_LOGICAL) {
if (evalCond(ep, lhs, rel, rhs) < 0) {
state = STATE_ERR;
break;
}
} else {
if (evalExpr(ep, lhs, rel, rhs) < 0) {
state = STATE_ERR;
break;
}
}
}
setString(B_L, &lhs, ep->result);
if ((tid = ejLexGetToken(ep, state)) == TOK_EXPR ||
tid == TOK_INC_DEC || tid == TOK_LOGICAL) {
rel = (int) *ep->token;
} else {
ejLexPutbackToken(ep, tid, ep->token);
state = STATE_RELEXP_DONE;
}
} while (state == STATE_EXPR_DONE);
if (rhs) {
bfree(B_L, rhs);
}
if (lhs) {
bfree(B_L, lhs);
}
return state;
}
/******************************************************************************/
/*
* Evaluate a condition. Implements &&, ||, !
*/
static int evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs)
{
char_t buf[16];
int l, r, lval;
a_assert(lhs);
a_assert(rhs);
a_assert(rel > 0);
lval = 0;
if (gisdigit((int)*lhs) && gisdigit((int)*rhs)) {
l = gatoi(lhs);
r = gatoi(rhs);
switch (rel) {
case COND_AND:
lval = l && r;
break;
case COND_OR:
lval = l || r;
break;
default:
ejError(ep, T("Bad operator %d"), rel);
return -1;
}
} else {
if (!gisdigit((int)*lhs)) {
ejError(ep, T("Conditional must be numeric"), lhs);
} else {
ejError(ep, T("Conditional must be numeric"), rhs);
}
}
stritoa(lval, buf, sizeof(buf));
setString(B_L, &ep->result, buf);
return 0;
}
/******************************************************************************/
/*
* Evaluate an operation
*/
static int evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs)
{
char_t *cp, buf[16];
int numeric, l, r, lval;
a_assert(lhs);
a_assert(rhs);
a_assert(rel > 0);
/*
* All of the characters in the lhs and rhs must be numeric
*/
numeric = 1;
for (cp = lhs; *cp; cp++) {
if (!gisdigit((int)*cp)) {
numeric = 0;
break;
}
}
if (numeric) {
for (cp = rhs; *cp; cp++) {
if (!gisdigit((int)*cp)) {
numeric = 0;
break;
}
}
}
if (numeric) {
l = gatoi(lhs);
r = gatoi(rhs);
switch (rel) {
case EXPR_PLUS:
lval = l + r;
break;
case EXPR_INC:
lval = l + 1;
break;
case EXPR_MINUS:
lval = l - r;
break;
case EXPR_DEC:
lval = l - 1;
break;
case EXPR_MUL:
lval = l * r;
break;
case EXPR_DIV:
if (r != 0) {
lval = l / r;
} else {
lval = 0;
}
break;
case EXPR_MOD:
if (r != 0) {
lval = l % r;
} else {
lval = 0;
}
break;
case EXPR_LSHIFT:
lval = l << r;
break;
case EXPR_RSHIFT:
lval = l >> r;
break;
case EXPR_EQ:
lval = l == r;
break;
case EXPR_NOTEQ:
lval = l != r;
break;
case EXPR_LESS:
lval = (l < r) ? 1 : 0;
break;
case EXPR_LESSEQ:
lval = (l <= r) ? 1 : 0;
break;
case EXPR_GREATER:
lval = (l > r) ? 1 : 0;
break;
case EXPR_GREATEREQ:
lval = (l >= r) ? 1 : 0;
break;
case EXPR_BOOL_COMP:
lval = (r == 0) ? 1 : 0;
break;
default:
ejError(ep, T("Bad operator %d"), rel);
return -1;
}
} else {
switch (rel) {
case EXPR_PLUS:
clearString(&ep->result);
appendString(&ep->result, lhs);
appendString(&ep->result, rhs);
return 0;
case EXPR_LESS:
lval = gstrcmp(lhs, rhs) < 0;
break;
case EXPR_LESSEQ:
lval = gstrcmp(lhs, rhs) <= 0;
break;
case EXPR_GREATER:
lval = gstrcmp(lhs, rhs) > 0;
break;
case EXPR_GREATEREQ:
lval = gstrcmp(lhs, rhs) >= 0;
break;
case EXPR_EQ:
lval = gstrcmp(lhs, rhs) == 0;
break;
case EXPR_NOTEQ:
lval = gstrcmp(lhs, rhs) != 0;
break;
case EXPR_INC:
case EXPR_DEC:
case EXPR_MINUS:
case EXPR_DIV:
case EXPR_MOD:
case EXPR_LSHIFT:
case EXPR_RSHIFT:
default:
ejError(ep, T("Bad operator"));
return -1;
}
}
stritoa(lval, buf, sizeof(buf));
setString(B_L, &ep->result, buf);
return 0;
}
/******************************************************************************/
/*
* Evaluate a function
*/
static int evalFunction(ej_t *ep)
{
sym_t *sp;
int (*fn)(int eid, void *handle, int argc, char_t **argv);
if ((sp = symLookup(ep->functions, ep->func->fname)) == NULL) {
ejError(ep, T("Undefined procedure %s"), ep->func->fname);
return -1;
}
fn = (int (*)(int, void*, int, char_t**)) sp->content.value.integer;
if (fn == NULL) {
ejError(ep, T("Undefined procedure %s"), ep->func->fname);
return -1;
}
return (*fn)(ep->eid, (void*) ep->userHandle, ep->func->nArgs,
ep->func->args);
}
/******************************************************************************/
/*
* Output a parse ej_error message
*/
void ejError(ej_t* ep, char_t* fmt, ...)
{
va_list args;
ejinput_t *ip;
char_t *errbuf, *msgbuf;
a_assert(ep);
a_assert(fmt);
ip = ep->input;
va_start(args, fmt);
msgbuf = NULL;
fmtValloc(&msgbuf, E_MAX_ERROR, fmt, args);
va_end(args);
if (ep && ip) {
fmtAlloc(&errbuf, E_MAX_ERROR, T("%s\n At line %d, line => \n\n%s\n"),
msgbuf, ip->lineNumber, ip->line);
bfreeSafe(B_L, ep->error);
ep->error = errbuf;
}
bfreeSafe(B_L, msgbuf);
}
/******************************************************************************/
/*
* Clear a string value
*/
static void clearString(char_t **ptr)
{
a_assert(ptr);
if (*ptr) {
bfree(B_L, *ptr);
}
*ptr = NULL;
}
/******************************************************************************/
/*
* Set a string value
*/
static void setString(B_ARGS_DEC, char_t **ptr, char_t *s)
{
a_assert(ptr);
if (*ptr) {
bfree(B_ARGS, *ptr);
}
*ptr = bstrdup(B_ARGS, s);
}
/******************************************************************************/
/*
* Append to the pointer value
*/
static void appendString(char_t **ptr, char_t *s)
{
int len, oldlen;
a_assert(ptr);
if (*ptr) {
len = gstrlen(s);
oldlen = gstrlen(*ptr);
*ptr = brealloc(B_L, *ptr, (len + oldlen + 1) * sizeof(char_t));
gstrcpy(&(*ptr)[oldlen], s);
} else {
*ptr = bstrdup(B_L, s);
}
}
/******************************************************************************/
/*
* Define a function
*/
int ejSetGlobalFunction(int eid, char_t *name,
int (*fn)(int eid, void *handle, int argc, char_t **argv))
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
return ejSetGlobalFunctionDirect(ep->functions, name, fn);
}
/******************************************************************************/
/*
* Define a function directly into the function symbol table.
*/
int ejSetGlobalFunctionDirect(sym_fd_t functions, char_t *name,
int (*fn)(int eid, void *handle, int argc, char_t **argv))
{
if (symEnter(functions, name, valueInteger((long) fn), 0) == NULL) {
return -1;
}
return 0;
}
/******************************************************************************/
/*
* Remove ("undefine") a function
*/
int ejRemoveGlobalFunction(int eid, char_t *name)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
return symDelete(ep->functions, name);
}
/******************************************************************************/
/*
* Get a function definition
*/
void *ejGetGlobalFunction(int eid, char_t *name)
{
ej_t *ep;
sym_t *sp;
int (*fn)(int eid, void *handle, int argc, char_t **argv);
if ((ep = ejPtr(eid)) == NULL) {
return NULL;
}
if ((sp = symLookup(ep->functions, name)) != NULL) {
fn = (int (*)(int, void*, int, char_t**)) sp->content.value.integer;
return (void*) fn;
}
return NULL;
}
/******************************************************************************/
/*
* Utility routine to crack Ejscript arguments. Return the number of args
* seen. This routine only supports %s and %d type args.
*
* Typical usage:
*
* if (ejArgs(argc, argv, "%s %d", &name, &age) < 2) {
* error("Insufficient args\n");
* return -1;
* }
*/
int ejArgs(int argc, char_t **argv, char_t *fmt, ...)
{
va_list vargs;
char_t *cp, **sp;
int *ip;
int argn;
va_start(vargs, fmt);
if (argv == NULL) {
return 0;
}
for (argn = 0, cp = fmt; cp && *cp && argv[argn]; ) {
if (*cp++ != '%') {
continue;
}
switch (*cp) {
case 'd':
ip = va_arg(vargs, int*);
*ip = gatoi(argv[argn]);
break;
case 's':
sp = va_arg(vargs, char_t**);
*sp = argv[argn];
break;
default:
/*
* Unsupported
*/
a_assert(0);
}
argn++;
}
va_end(vargs);
return argn;
}
/******************************************************************************/
/*
* Define the user handle
*/
void ejSetUserHandle(int eid, int handle)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return;
}
ep->userHandle = handle;
}
/******************************************************************************/
/*
* Get the user handle
*/
int ejGetUserHandle(int eid)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
return ep->userHandle;
}
/******************************************************************************/
/*
* Get the current line number
*/
int ejGetLineNumber(int eid)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
return ep->input->lineNumber;
}
/******************************************************************************/
/*
* Set the result
*/
void ejSetResult(int eid, char_t *s)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return;
}
setString(B_L, &ep->result, s);
}
/******************************************************************************/
/*
* Get the result
*/
char_t *ejGetResult(int eid)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return NULL;
}
return ep->result;
}
/******************************************************************************/
/*
* Set a variable. Note: a variable with a value of NULL means declared but
* undefined. The value is defined in the top-most variable frame.
*/
void ejSetVar(int eid, char_t *var, char_t *value)
{
ej_t *ep;
value_t v;
a_assert(var && *var);
if ((ep = ejPtr(eid)) == NULL) {
return;
}
if (value == NULL) {
v = valueString(value, 0);
} else {
v = valueString(value, VALUE_ALLOCATE);
}
symEnter(ep->variables[ep->variableMax - 1] - EJ_OFFSET, var, v, 0);
}
/******************************************************************************/
/*
* Set a local variable. Note: a variable with a value of NULL means
* declared but undefined. The value is defined in the top-most variable frame.
*/
void ejSetLocalVar(int eid, char_t *var, char_t *value)
{
ej_t *ep;
value_t v;
a_assert(var && *var);
if ((ep = ejPtr(eid)) == NULL) {
return;
}
if (value == NULL) {
v = valueString(value, 0);
} else {
v = valueString(value, VALUE_ALLOCATE);
}
symEnter(ep->variables[ep->variableMax - 1] - EJ_OFFSET, var, v, 0);
}
/******************************************************************************/
/*
* Set a global variable. Note: a variable with a value of NULL means
* declared but undefined. The value is defined in the global variable frame.
*/
void ejSetGlobalVar(int eid, char_t *var, char_t *value)
{
ej_t *ep;
value_t v;
a_assert(var && *var);
if ((ep = ejPtr(eid)) == NULL) {
return;
}
if (value == NULL) {
v = valueString(value, 0);
} else {
v = valueString(value, VALUE_ALLOCATE);
}
symEnter(ep->variables[0] - EJ_OFFSET, var, v, 0);
}
/******************************************************************************/
/*
* Get a variable
*/
int ejGetVar(int eid, char_t *var, char_t **value)
{
ej_t *ep;
sym_t *sp;
int i;
a_assert(var && *var);
a_assert(value);
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
i = ep->variableMax - 1;
if ((sp = symLookup(ep->variables[i] - EJ_OFFSET, var)) == NULL) {
i = 0;
if ((sp = symLookup(ep->variables[0] - EJ_OFFSET, var)) == NULL) {
return -1;
}
}
a_assert(sp->content.type == string);
*value = sp->content.value.string;
return i;
}
/******************************************************************************/
/*
* Get the variable symbol table
*/
sym_fd_t ejGetVariableTable(int eid)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
return *ep->variables;
}
/******************************************************************************/
/*
* Get the functions symbol table
*/
sym_fd_t ejGetFunctionTable(int eid)
{
ej_t *ep;
if ((ep = ejPtr(eid)) == NULL) {
return -1;
}
return ep->functions;
}
/******************************************************************************/
/*
* Free an argument list
*/
static void freeFunc(ejfunc_t *func)
{
int i;
for (i = func->nArgs - 1; i >= 0; i--) {
bfree(B_L, func->args[i]);
func->nArgs = hFree((void***) &func->args, i);
}
if (func->fname) {
bfree(B_L, func->fname);
func->fname = NULL;
}
}
/******************************************************************************/
/*
* Get Ejscript pointer
*/
static ej_t *ejPtr(int eid)
{
a_assert(0 <= eid && eid < ejMax);
if (eid < 0 || eid >= ejMax || ejHandles[eid] == NULL) {
ejError(NULL, T("Bad handle %d"), eid);
return NULL;
}
return ejHandles[eid];
}
/******************************************************************************/
/*
* This function removes any new lines. Used for else cases, etc.
*/
static void ejRemoveNewlines(ej_t *ep, int state)
{
int tid;
do {
tid = ejLexGetToken(ep, state);
} while (tid == TOK_NEWLINE);
ejLexPutbackToken(ep, tid, ep->token);
}
/******************************************************************************/