/* * 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); } /******************************************************************************/