summaryrefslogtreecommitdiffstats
path: root/c/src/libnetworking/rtems_webserver/ejparse.c
diff options
context:
space:
mode:
authorJoel Sherrill <joel.sherrill@OARcorp.com>1999-10-27 12:50:33 +0000
committerJoel Sherrill <joel.sherrill@OARcorp.com>1999-10-27 12:50:33 +0000
commitc1cdaa0ce8017b075487e6670f89eb4e715258ea (patch)
treed4571a02595d6cf6a24d40d6968d83ece3b7a574 /c/src/libnetworking/rtems_webserver/ejparse.c
parentNew files created by split of old imfs_handlers.c. (diff)
downloadrtems-c1cdaa0ce8017b075487e6670f89eb4e715258ea.tar.bz2
Patch from Emmanuel Raguet <raguet@crf.canon.fr> and Eric Valette
<valette@crf.canon.fr> to add a port of the GoAhead web server (httpd) to the RTEMS build tree. They have successfully used this BSP on i386/pc386 and PowerPC/mcp750. Mark and Joel spoke with Nick Berliner <nickb@goahead.com> on 26 Oct 1999 about this port and got verbal approval to include it in RTEMS distributions.
Diffstat (limited to 'c/src/libnetworking/rtems_webserver/ejparse.c')
-rw-r--r--c/src/libnetworking/rtems_webserver/ejparse.c1665
1 files changed, 1665 insertions, 0 deletions
diff --git a/c/src/libnetworking/rtems_webserver/ejparse.c b/c/src/libnetworking/rtems_webserver/ejparse.c
new file mode 100644
index 0000000000..1734f6f719
--- /dev/null
+++ b/c/src/libnetworking/rtems_webserver/ejparse.c
@@ -0,0 +1,1665 @@
+/*
+ * ejparse.c -- Ejscript(TM) Parser
+ *
+ * Copyright (c) Go Ahead Software, Inc., 1995-1999
+ *
+ * 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 "ej.h"
+
+/********************************** Local Data ********************************/
+
+ej_t **ejHandles; /* List of ej handles */
+int ejMax = -1; /* Maximum size of */
+
+/****************************** Forward Declarations **************************/
+
+static ej_t *ejPtr(int eid);
+static void clearString(char_t **ptr);
+static void setString(char_t **ptr, char_t *s);
+static void appendString(char_t **ptr, char_t *s);
+static void freeVar(sym_t* sp);
+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);
+
+/************************************* 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);
+
+ if (ep->flags & FLAGS_VARIABLES) {
+ for (i = ep->variableMax - 1; i >= 0; i--) {
+ symClose(ep->variables[i] - EJ_OFFSET, freeVar);
+ ep->variableMax = hFree((void***) &ep->variables, i);
+ }
+ }
+ if (ep->flags & FLAGS_FUNCTIONS) {
+ symClose(ep->functions, freeVar);
+ }
+
+ ejMax = hFree((void***) &ejHandles, ep->eid);
+ bfree(B_L, ep);
+}
+
+/******************************************************************************/
+/*
+ * Callback from symClose. Free the variable.
+ */
+
+static void freeVar(sym_t* sp)
+{
+ valueFree(&sp->content);
+}
+
+/******************************************************************************/
+/*
+ * Evaluate a Ejscript file
+ */
+
+#if DEV
+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) {
+ close(fd);
+ ejError(ep, T("Cant stat %s"), path);
+ return NULL;
+ }
+ if ((fileBuf = balloc(B_L, sbuf.st_size + 1)) == NULL) {
+ close(fd);
+ ejError(ep, T("Cant malloc %d"), sbuf.st_size);
+ return NULL;
+ }
+ if (read(fd, fileBuf, sbuf.st_size) != (int)sbuf.st_size) {
+ close(fd);
+ bfree(B_L, fileBuf);
+ ejError(ep, T("Error reading %s"), path);
+ return NULL;
+ }
+ fileBuf[sbuf.st_size] = '\0';
+ close(fd);
+
+ if ((script = ballocAscToUni(fileBuf)) == 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
+
+/******************************************************************************/
+/*
+ * 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, freeVar);
+ 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;
+
+ a_assert(script);
+
+ if (emsg) {
+ *emsg = NULL;
+ }
+ if ((ep = ejPtr(eid)) == NULL) {
+ return NULL;
+ }
+
+ setString(&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
+ */
+ do {
+ state = parse(ep, STATE_BEGIN, FLAGS_EXE);
+ } 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:
+ case STATE_DEC:
+ state = parseStmt(ep, state, flags);
+ break;
+
+ case STATE_EXPR:
+ state = parseStmt(ep, state, flags);
+ 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;
+ char_t *identifier;
+ int done, expectSemi, thenFlags, elseFlags, tid, cond, forFlags;
+
+ 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(&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) {
+ goto error;
+ }
+ if (flags & FLAGS_EXE) {
+ if ( state == STATE_DEC ) {
+ ejSetLocalVar(ep->eid, identifier, ep->result);
+ }
+ else {
+ if (ejGetVar(ep->eid, identifier, &value) > 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 ) {
+ if (ejGetVar(ep->eid, identifier, &value) < 0) {
+ ejError(ep, T("Undefined variable %s\n"), identifier);
+ goto error;
+ }
+ setString(&ep->result, value);
+ if (evalExpr(ep, value, (int) *ep->token, T("1")) < 0) {
+ state = STATE_ERR;
+ break;
+ }
+ 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(&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(&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(&func.fname, ep->token);
+ ep->func = &func;
+
+ setString(&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
+ */
+ if (parse(ep, STATE_STMT, thenFlags) != STATE_STMT_DONE) {
+ goto error;
+ }
+ tid = ejLexGetToken(ep, state);
+ if (tid != TOK_ELSE) {
+ ejLexPutbackToken(ep, tid, ep->token);
+ done++;
+ break;
+ }
+/*
+ * Process the "else" case
+ */
+ if (parse(ep, STATE_STMT, elseFlags) != STATE_STMT_DONE) {
+ 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);
+ if (parse(ep, STATE_STMT, flags) != STATE_STMT_DONE) {
+ 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);
+
+ 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_EOF;
+ }
+ break;
+ }
+ }
+
+ if (expectSemi) {
+ tid = ejLexGetToken(ep, state);
+ if (tid != TOK_SEMI && tid != TOK_NEWLINE) {
+ goto error;
+ }
+
+/*
+ * Skip newline after semi-colon
+ */
+ tid = ejLexGetToken(ep, state);
+ if (tid != TOK_NEWLINE) {
+ ejLexPutbackToken(ep, tid, ep->token);
+ }
+ }
+
+/*
+ * 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(&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(&rhs, ep->result);
+ if (evalCond(ep, lhs, operator, rhs) < 0) {
+ state = STATE_ERR;
+ break;
+ }
+ }
+ setString(&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(&ep->result, T(""));
+ rhs = lhs = NULL;
+ rel = 0;
+
+ do {
+/*
+ * This loop will handle an entire expression list. We call parse
+ * to evalutate each term which returns the result in ep->result.
+ */
+ state = parse(ep, STATE_EXPR, flags);
+ if (state != STATE_EXPR_DONE) {
+ state = STATE_ERR;
+ break;
+ }
+ if (rel > 0) {
+ setString(&rhs, ep->result);
+ if (evalExpr(ep, lhs, rel, rhs) < 0) {
+ state = STATE_ERR;
+ break;
+ }
+ }
+ setString(&lhs, ep->result);
+
+ if ((tid = ejLexGetToken(ep, state)) == TOK_EXPR) {
+ rel = (int) *ep->token;
+
+ } else if (tid == TOK_INC_DEC) {
+ 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(*lhs) && gisdigit(*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(*lhs)) {
+ ejError(ep, T("Conditional must be numeric"), lhs);
+ } else {
+ ejError(ep, T("Conditional must be numeric"), rhs);
+ }
+ }
+
+ stritoa(lval, buf, sizeof(buf));
+ setString(&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(*cp)) {
+ numeric = 0;
+ break;
+ }
+ }
+ if (numeric) {
+ for (cp = rhs; *cp; cp++) {
+ if (!gisdigit(*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;
+ 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(&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;
+ gvsnprintf(&msgbuf, E_MAX_ERROR, fmt, args);
+ va_end(args);
+
+ if (ep && ip) {
+ errbuf = NULL;
+ gsnprintf(&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(char_t **ptr, char_t *s)
+{
+ a_assert(ptr);
+
+ if (*ptr) {
+ bfree(B_L, *ptr);
+ }
+ *ptr = bstrdup(B_L, 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;
+}
+
+/******************************************************************************/
+/*
+ * 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(&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;
+ }
+
+ for (i = ep->variableMax - 1; i >= 0; i--) {
+ if ((sp = symLookup(ep->variables[i] - EJ_OFFSET, var)) == NULL) {
+ continue;
+ }
+ a_assert(sp->content.type == string);
+ *value = sp->content.value.string;
+ return i;
+ }
+ return -1;
+}
+
+/******************************************************************************/
+#if UNUSED
+/*
+ * Get the variable symbol table
+ */
+
+sym_fd_t ejGetVariableTable(int eid)
+{
+ ej_t *ep;
+
+ if ((ep = ejPtr(eid)) == NULL) {
+ return -1;
+ }
+ return ep->variables;
+}
+#endif
+/******************************************************************************/
+/*
+ * 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];
+}
+
+/******************************************************************************/