/* * @file ejsParser.c * @brief EJS Parser and Execution */ /********************************* Copyright **********************************/ /* * @copy default.g * * Copyright (c) Mbedthis Software LLC, 2003-2005. All Rights Reserved. * Portions Copyright (c) GoAhead Software, 1995-2000. All Rights Reserved. * * This software is distributed under commercial and open source licenses. * You may use the GPL open source license described below or you may acquire * a commercial license from Mbedthis Software. You agree to be fully bound * by the terms of either license. Consult the LICENSE.TXT distributed with * this software for full details. * * This software is open source; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See the GNU General Public License for more * details at: http://www.mbedthis.com/downloads/gplLicense.html * * This program is distributed WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * This GPL license does NOT permit incorporating this software into * proprietary programs. If you are unable to comply with the GPL, you must * acquire a commercial license to use this software. Commercial licenses * for this software and support services are available from Mbedthis * Software at http://www.mbedthis.com * * @end */ /********************************** Includes **********************************/ #include "ejsInternal.h" #if BLD_FEATURE_EJS /****************************** Forward Declarations **************************/ static void appendValue(MprVar *v1, MprVar *v2); static int evalCond(Ejs *ep, MprVar *lhs, int rel, MprVar *rhs); static int evalExpr(Ejs *ep, MprVar *lhs, int rel, MprVar *rhs); #if BLD_FEATURE_FLOATING_POINT static int evalFloatExpr(Ejs *ep, double l, int rel, double r); #endif static int evalBoolExpr(Ejs *ep, bool l, int rel, bool r); static int evalPtrExpr(Ejs *ep, void *l, int rel, void *r); static int evalNumericExpr(Ejs *ep, MprNum l, int rel, MprNum r); static int evalStringExpr(Ejs *ep, MprVar *lhs, int rel, MprVar *rhs); static int evalFunction(Ejs *ep, MprVar *obj, int flags); static void freeProc(EjsProc *proc); static int parseArgs(Ejs *ep, int state, int flags); static int parseAssignment(Ejs *ep, int state, int flags, char *id, char *fullName); static int parseCond(Ejs *ep, int state, int flags); static int parseDeclaration(Ejs *ep, int state, int flags); static int parseExpr(Ejs *ep, int state, int flags); static int parseFor(Ejs *ep, int state, int flags); static int parseForIn(Ejs *ep, int state, int flags); static int parseFunctionDec(Ejs *ep, int state, int flags); static int parseFunction(Ejs *ep, int state, int flags, char *id); static int parseId(Ejs *ep, int state, int flags, char **id, char **fullName, int *fullNameLen, int *done); static int parseInc(Ejs *ep, int state, int flags); static int parseIf(Ejs *ep, int state, int flags, int *done); static int parseStmt(Ejs *ep, int state, int flags); static void removeNewlines(Ejs *ep, int state); static void updateResult(Ejs *ep, int state, int flags, MprVar *vp); /************************************* Code ***********************************/ /* * Recursive descent parser for EJS */ int ejsParse(Ejs *ep, int state, int flags) { mprAssert(ep); switch (state) { /* * Any statement, function arguments or conditional expressions */ case EJS_STATE_STMT: if ((state = parseStmt(ep, state, flags)) != EJS_STATE_STMT_DONE && state != EJS_STATE_EOF && state != EJS_STATE_STMT_BLOCK_DONE && state != EJS_STATE_RET) { state = EJS_STATE_ERR; } break; case EJS_STATE_DEC: if ((state = parseStmt(ep, state, flags)) != EJS_STATE_DEC_DONE && state != EJS_STATE_EOF) { state = EJS_STATE_ERR; } break; case EJS_STATE_EXPR: if ((state = parseStmt(ep, state, flags)) != EJS_STATE_EXPR_DONE && state != EJS_STATE_EOF) { state = EJS_STATE_ERR; } break; /* * Variable declaration list */ case EJS_STATE_DEC_LIST: state = parseDeclaration(ep, state, flags); break; /* * Function argument string */ case EJS_STATE_ARG_LIST: state = parseArgs(ep, state, flags); break; /* * Logical condition list (relational operations separated by &&, ||) */ case EJS_STATE_COND: state = parseCond(ep, state, flags); break; /* * Expression list */ case EJS_STATE_RELEXP: state = parseExpr(ep, state, flags); break; } if (state == EJS_STATE_ERR && ep->error == NULL) { ejsError(ep, "Syntax error"); } return state; } /******************************************************************************/ /* * Parse any statement including functions and simple relational operations */ static int parseStmt(Ejs *ep, int state, int flags) { EjsProc *saveProc; MprVar *vp, *saveObj; char *id, *fullName, *initToken; int done, expectSemi, tid, fullNameLen, rel; int initId; mprAssert(ep); expectSemi = 0; saveProc = NULL; id = 0; fullName = 0; fullNameLen = 0; ep->currentObj = 0; ep->currentProperty = 0; for (done = 0; !done && state != EJS_STATE_ERR; ) { tid = ejsLexGetToken(ep, state); switch (tid) { default: ejsLexPutbackToken(ep, EJS_TOK_EXPR, ep->token); done++; break; case EJS_TOK_EXPR: rel = (int) *ep->token; if (state == EJS_STATE_EXPR) { ejsLexPutbackToken(ep, EJS_TOK_EXPR, ep->token); } done++; break; case EJS_TOK_LOGICAL: ejsLexPutbackToken(ep, tid, ep->token); done++; break; case EJS_TOK_ERR: state = EJS_STATE_ERR; done++; break; case EJS_TOK_EOF: state = EJS_STATE_EOF; done++; break; case EJS_TOK_NEWLINE: break; case EJS_TOK_SEMI: /* * This case is when we discover no statement and just a lone ';' */ if (state != EJS_STATE_STMT) { ejsLexPutbackToken(ep, tid, ep->token); } done++; break; case EJS_TOK_PERIOD: if (flags & EJS_FLAGS_EXE) { if (ep->currentProperty == 0) { ejsError(ep, "Undefined object \"%s\"\n", id); goto error; } } ep->currentObj = ep->currentProperty; if ((tid = ejsLexGetToken(ep, state)) != EJS_TOK_ID) { ejsError(ep, "Bad property after '.': %s\n", ep->token); goto error; } mprFree(id); id = mprStrdup(ep->token); vp = ejsFindProperty(ep, state, ep->currentObj, id, flags); updateResult(ep, state, flags, vp); #if BLD_DEBUG fullNameLen = mprReallocStrcat(&fullName, MPR_MAX_VAR, fullNameLen, 0, ".", NULL); #endif ep->currentProperty = vp; ejsLexPutbackToken(ep, tid, ep->token); break; case EJS_TOK_LBRACKET: ep->currentObj = ep->currentProperty; saveObj = ep->currentObj; if (ejsParse(ep, EJS_STATE_RELEXP, flags) != EJS_STATE_RELEXP_DONE){ goto error; } ep->currentObj = saveObj; mprFree(id); mprVarToString(&id, MPR_MAX_STRING, 0, &ep->result); if (id[0] == '\0') { if (flags & EJS_FLAGS_EXE) { ejsError(ep, "[] expression evaluates to the empty string\n"); goto error; } } else { vp = ejsFindProperty(ep, state, ep->currentObj, id, flags); ep->currentProperty = vp; updateResult(ep, state, flags, vp); } #if BLD_DEBUG if (id[0] && strlen(id) < (MPR_MAX_VAR / 2)) { /* * If not executing yet, id may not be known */ fullNameLen = mprReallocStrcat(&fullName, MPR_MAX_VAR, fullNameLen, 0, "[", id, "]", NULL); } #endif if ((tid = ejsLexGetToken(ep, state)) != EJS_TOK_RBRACKET) { ejsError(ep, "Missing ']'\n"); goto error; } break; case EJS_TOK_ID: state = parseId(ep, state, flags, &id, &fullName, &fullNameLen, &done); if (done && state == EJS_STATE_STMT) { expectSemi++; } break; case EJS_TOK_ASSIGNMENT: state = parseAssignment(ep, state, flags, id, fullName); if (state == EJS_STATE_STMT) { expectSemi++; done++; } break; case EJS_TOK_INC_DEC: state = parseInc(ep, state, flags); if (state == EJS_STATE_STMT) { expectSemi++; } break; case EJS_TOK_NEW: if (ejsParse(ep, EJS_STATE_EXPR, flags | EJS_FLAGS_NEW) != EJS_STATE_EXPR_DONE) { goto error; } break; case EJS_TOK_DELETE: if (ejsParse(ep, EJS_STATE_EXPR, flags | EJS_FLAGS_DELETE) != EJS_STATE_EXPR_DONE) { goto error; } if (flags & EJS_FLAGS_EXE) { mprDeleteProperty(ep->currentObj, ep->currentProperty->name); } done++; break; case EJS_TOK_FUNCTION: state = parseFunctionDec(ep, state, flags); done++; break; case EJS_TOK_LITERAL: /* * Set the result to the string literal */ mprCopyVarValue(&ep->result, mprCreateStringVar(ep->token, 0), MPR_SHALLOW_COPY); if (state == EJS_STATE_STMT) { expectSemi++; } done++; break; case EJS_TOK_NUMBER: /* * Set the result to the parsed number */ mprCopyVar(&ep->result, &ep->tokenNumber, 0); if (state == EJS_STATE_STMT) { expectSemi++; } done++; break; case EJS_TOK_FUNCTION_NAME: state = parseFunction(ep, state, flags, id); if (state == EJS_STATE_STMT) { expectSemi++; } if (ep->flags & EJS_FLAGS_EXIT) { state = EJS_STATE_RET; } done++; break; case EJS_TOK_IF: state = parseIf(ep, state, flags, &done); if (state == EJS_STATE_RET) { goto doneParse; } break; case EJS_TOK_FOR: if (state != EJS_STATE_STMT) { goto error; } if (ejsLexGetToken(ep, state) != EJS_TOK_LPAREN) { goto error; } /* * Need to peek 2-3 tokens ahead and see if this is a * for ([var] x in set) * or * for (init ; whileCond; incr) */ initId = ejsLexGetToken(ep, EJS_STATE_EXPR); if (initId == EJS_TOK_ID && strcmp(ep->token, "var") == 0) { /* Simply eat var tokens */ initId = ejsLexGetToken(ep, EJS_STATE_EXPR); } initToken = mprStrdup(ep->token); tid = ejsLexGetToken(ep, EJS_STATE_EXPR); ejsLexPutbackToken(ep, tid, ep->token); ejsLexPutbackToken(ep, initId, initToken); mprFree(initToken); if (tid == EJS_TOK_IN) { if ((state = parseForIn(ep, state, flags)) < 0) { goto error; } } else { if ((state = parseFor(ep, state, flags)) < 0) { goto error; } } done++; break; case EJS_TOK_VAR: if (ejsParse(ep, EJS_STATE_DEC_LIST, flags) != EJS_STATE_DEC_LIST_DONE) { goto error; } done++; break; case EJS_TOK_COMMA: ejsLexPutbackToken(ep, tid, ep->token); done++; break; case EJS_TOK_LPAREN: if (state == EJS_STATE_EXPR) { if (ejsParse(ep, EJS_STATE_RELEXP, flags) != EJS_STATE_RELEXP_DONE) { goto error; } if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { goto error; } } done++; break; case EJS_TOK_RPAREN: ejsLexPutbackToken(ep, tid, ep->token); done++; break; case EJS_TOK_LBRACE: /* * This handles any code in braces except "if () {} else {}" */ if (state != EJS_STATE_STMT) { goto error; } /* * Parse will return EJS_STATE_STMT_BLOCK_DONE when the RBRACE * is seen. */ do { state = ejsParse(ep, EJS_STATE_STMT, flags); } while (state == EJS_STATE_STMT_DONE); if (state != EJS_STATE_RET) { if (ejsLexGetToken(ep, state) != EJS_TOK_RBRACE) { goto error; } state = EJS_STATE_STMT_DONE; } done++; break; case EJS_TOK_RBRACE: if (state == EJS_STATE_STMT) { ejsLexPutbackToken(ep, tid, ep->token); state = EJS_STATE_STMT_BLOCK_DONE; done++; break; } goto error; case EJS_TOK_RETURN: if (ejsParse(ep, EJS_STATE_RELEXP, flags) != EJS_STATE_RELEXP_DONE) { goto error; } if (flags & EJS_FLAGS_EXE) { while (ejsLexGetToken(ep, state) != EJS_TOK_EOF) { ; } state = EJS_STATE_RET; done++; } break; } } if (expectSemi) { tid = ejsLexGetToken(ep, state); if (tid != EJS_TOK_SEMI && tid != EJS_TOK_NEWLINE && tid != EJS_TOK_EOF) { goto error; } /* * Skip newline after semi-colon */ removeNewlines(ep, state); } /* * Free resources and return the correct status */ doneParse: mprFree(id); mprFree(fullName); /* * Advance the state */ switch (state) { case EJS_STATE_STMT: return EJS_STATE_STMT_DONE; case EJS_STATE_DEC: return EJS_STATE_DEC_DONE; case EJS_STATE_EXPR: return EJS_STATE_EXPR_DONE; case EJS_STATE_STMT_DONE: case EJS_STATE_STMT_BLOCK_DONE: case EJS_STATE_EOF: case EJS_STATE_RET: return state; default: return EJS_STATE_ERR; } /* * Common error exit */ error: state = EJS_STATE_ERR; goto doneParse; } /******************************************************************************/ /* * Parse function arguments */ static int parseArgs(Ejs *ep, int state, int flags) { int tid; mprAssert(ep); do { /* * Peek and see if there are no args */ tid = ejsLexGetToken(ep, state); ejsLexPutbackToken(ep, tid, ep->token); if (tid == EJS_TOK_RPAREN) { break; } state = ejsParse(ep, EJS_STATE_RELEXP, flags); if (state == EJS_STATE_EOF || state == EJS_STATE_ERR) { return state; } if (state == EJS_STATE_RELEXP_DONE) { if (flags & EJS_FLAGS_EXE) { mprAssert(ep->proc->args); mprAddToArray(ep->proc->args, mprDupVar(&ep->result, MPR_SHALLOW_COPY)); } } /* * Peek at the next token, continue if more args (ie. comma seen) */ tid = ejsLexGetToken(ep, state); if (tid != EJS_TOK_COMMA) { ejsLexPutbackToken(ep, tid, ep->token); } } while (tid == EJS_TOK_COMMA); if (tid != EJS_TOK_RPAREN && state != EJS_STATE_RELEXP_DONE) { return EJS_STATE_ERR; } return EJS_STATE_ARG_LIST_DONE; } /******************************************************************************/ /* * Parse an assignment statement */ static int parseAssignment(Ejs *ep, int state, int flags, char *id, char *fullName) { MprVar *vp, *saveProperty, *saveObj; if (id == 0) { return -1; } saveObj = ep->currentObj; saveProperty = ep->currentProperty; if (ejsParse(ep, EJS_STATE_RELEXP, flags | EJS_FLAGS_ASSIGNMENT) != EJS_STATE_RELEXP_DONE) { return -1; } ep->currentObj = saveObj; ep->currentProperty = saveProperty; if (! (flags & EJS_FLAGS_EXE)) { return state; } if (ep->currentProperty) { /* * Update the variable. Update the property name if not * yet defined. */ if (ep->currentProperty->name == 0 || ep->currentProperty->name[0] == '\0') { mprSetVarName(ep->currentProperty, id); } if (mprWriteProperty(ep->currentProperty, &ep->result) < 0){ ejsError(ep, "Can't write to variable\n"); return -1; } } else { /* * Create the variable */ if (ep->currentObj) { if (ep->currentObj->type != MPR_TYPE_OBJECT) { if (strcmp(ep->currentObj->name, "session") == 0) { ejsError(ep, "Variable \"%s\" is not an array or object." "If using ESP, you need useSession(); in your page.", ep->currentObj->name); } else { ejsError(ep, "Variable \"%s\" is not an array or object", ep->currentObj->name); } return -1; } vp = mprCreateProperty(ep->currentObj, id, &ep->result); } else { /* * Standard says: "var x" means declare locally. * "x = 2" means declare globally if x is undefined. */ if (state == EJS_STATE_DEC) { vp = mprCreateProperty(ep->local, id, &ep->result); } else { vp = mprCreateProperty(ep->global, id, &ep->result); } } #if BLD_DEBUG mprSetVarFullName(vp, fullName); #endif } return state; } /******************************************************************************/ /* * Parse conditional expression (relational ops separated by ||, &&) */ static int parseCond(Ejs *ep, int state, int flags) { MprVar lhs, rhs; int tid, operator; mprAssert(ep); mprDestroyVar(&ep->result); rhs = lhs = mprCreateUndefinedVar(); operator = 0; do { /* * Recurse to handle one side of a conditional. Accumulate the * left hand side and the final result in ep->result. */ state = ejsParse(ep, EJS_STATE_RELEXP, flags); if (state != EJS_STATE_RELEXP_DONE) { state = EJS_STATE_ERR; break; } if (operator > 0) { mprCopyVar(&rhs, &ep->result, MPR_SHALLOW_COPY); if (evalCond(ep, &lhs, operator, &rhs) < 0) { state = EJS_STATE_ERR; break; } } mprCopyVar(&lhs, &ep->result, MPR_SHALLOW_COPY); tid = ejsLexGetToken(ep, state); if (tid == EJS_TOK_LOGICAL) { operator = (int) *ep->token; } else if (tid == EJS_TOK_RPAREN || tid == EJS_TOK_SEMI) { ejsLexPutbackToken(ep, tid, ep->token); state = EJS_STATE_COND_DONE; break; } else { ejsLexPutbackToken(ep, tid, ep->token); } tid = (state == EJS_STATE_RELEXP_DONE); } while (state == EJS_STATE_RELEXP_DONE); mprDestroyVar(&lhs); mprDestroyVar(&rhs); return state; } /******************************************************************************/ /* * Parse variable declaration list. 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. */ static int parseDeclaration(Ejs *ep, int state, int flags) { int tid; mprAssert(ep); do { if ((tid = ejsLexGetToken(ep, state)) != EJS_TOK_ID) { return EJS_STATE_ERR; } ejsLexPutbackToken(ep, tid, ep->token); /* * Parse the entire assignment or simple identifier declaration */ if (ejsParse(ep, EJS_STATE_DEC, flags) != EJS_STATE_DEC_DONE) { return EJS_STATE_ERR; } /* * Peek at the next token, continue if comma seen */ tid = ejsLexGetToken(ep, state); if (tid == EJS_TOK_SEMI) { return EJS_STATE_DEC_LIST_DONE; } else if (tid != EJS_TOK_COMMA) { return EJS_STATE_ERR; } } while (tid == EJS_TOK_COMMA); if (tid != EJS_TOK_SEMI) { return EJS_STATE_ERR; } return EJS_STATE_DEC_LIST_DONE; } /******************************************************************************/ /* * Parse expression (leftHandSide operator rightHandSide) */ static int parseExpr(Ejs *ep, int state, int flags) { MprVar lhs, rhs; int rel, tid; mprAssert(ep); mprDestroyVar(&ep->result); rhs = lhs = mprCreateUndefinedVar(); 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 == EJS_TOK_LOGICAL) { state = ejsParse(ep, EJS_STATE_RELEXP, flags); if (state != EJS_STATE_RELEXP_DONE) { state = EJS_STATE_ERR; break; } } else { tid = ejsLexGetToken(ep, state); if (tid == EJS_TOK_EXPR && (int) *ep->token == EJS_EXPR_MINUS) { lhs = mprCreateIntegerVar(0); rel = (int) *ep->token; } else { ejsLexPutbackToken(ep, tid, ep->token); } state = ejsParse(ep, EJS_STATE_EXPR, flags); if (state != EJS_STATE_EXPR_DONE) { state = EJS_STATE_ERR; break; } } if (rel > 0) { mprCopyVar(&rhs, &ep->result, MPR_SHALLOW_COPY); if (tid == EJS_TOK_LOGICAL) { if (evalCond(ep, &lhs, rel, &rhs) < 0) { state = EJS_STATE_ERR; break; } } else { if (evalExpr(ep, &lhs, rel, &rhs) < 0) { state = EJS_STATE_ERR; break; } } } mprCopyVar(&lhs, &ep->result, MPR_SHALLOW_COPY); if ((tid = ejsLexGetToken(ep, state)) == EJS_TOK_EXPR || tid == EJS_TOK_INC_DEC || tid == EJS_TOK_LOGICAL) { rel = (int) *ep->token; } else { ejsLexPutbackToken(ep, tid, ep->token); state = EJS_STATE_RELEXP_DONE; } } while (state == EJS_STATE_EXPR_DONE); mprDestroyVar(&lhs); mprDestroyVar(&rhs); return state; } /******************************************************************************/ /* * Parse the "for ... in" statement. Format for the statement is: * * for (var in expr) { * body; * } */ static int parseForIn(Ejs *ep, int state, int flags) { EjsInput endScript, bodyScript; MprVar *iteratorVar, *setVar, *vp, v; int forFlags, tid; mprAssert(ep); tid = ejsLexGetToken(ep, state); if (tid != EJS_TOK_ID) { return -1; } ejsLexPutbackToken(ep, tid, ep->token); if (ejsParse(ep, EJS_STATE_EXPR, EJS_FLAGS_FOREACH | EJS_FLAGS_EXE) != EJS_STATE_EXPR_DONE) { return -1; } if (ep->currentProperty == 0) { return -1; } iteratorVar = ep->currentProperty; if (ejsLexGetToken(ep, state) != EJS_TOK_IN) { return -1; } /* * Get the set */ tid = ejsLexGetToken(ep, state); if (tid != EJS_TOK_ID) { return -1; } ejsLexPutbackToken(ep, tid, ep->token); if (ejsParse(ep, EJS_STATE_EXPR, flags) != EJS_STATE_EXPR_DONE) { return -1; } if (ep->currentProperty == 0 && flags & EJS_FLAGS_EXE) { return -1; } setVar = ep->currentProperty; if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { return -1; } /* * Parse the body and remember the end of the body script */ forFlags = flags & ~EJS_FLAGS_EXE; ejsLexSaveInputState(ep, &bodyScript); if (ejsParse(ep, EJS_STATE_STMT, forFlags) != EJS_STATE_STMT_DONE) { ejsLexFreeInputState(ep, &bodyScript); return -1; } ejsInitInputState(&endScript); ejsLexSaveInputState(ep, &endScript); /* * Now actually do the for loop. */ if (flags & EJS_FLAGS_EXE) { if (setVar->type == MPR_TYPE_OBJECT) { vp = mprGetFirstProperty(setVar, MPR_ENUM_DATA); while (vp) { if (strcmp(vp->name, "length") != 0) { v = mprCreateStringVar(vp->name, 0); if (mprWriteProperty(iteratorVar, &v) < 0) { ejsError(ep, "Can't write to variable\n"); ejsLexFreeInputState(ep, &bodyScript); ejsLexFreeInputState(ep, &endScript); return -1; } ejsLexRestoreInputState(ep, &bodyScript); switch (ejsParse(ep, EJS_STATE_STMT, flags)) { case EJS_STATE_RET: return EJS_STATE_RET; case EJS_STATE_STMT_DONE: break; default: ejsLexFreeInputState(ep, &endScript); ejsLexFreeInputState(ep, &bodyScript); return -1; } } vp = mprGetNextProperty(setVar, vp, MPR_ENUM_DATA); } } else { ejsError(ep, "Variable \"%s\" is not an array or object", setVar->name); ejsLexFreeInputState(ep, &endScript); ejsLexFreeInputState(ep, &bodyScript); return -1; } } ejsLexRestoreInputState(ep, &endScript); ejsLexFreeInputState(ep, &endScript); ejsLexFreeInputState(ep, &bodyScript); return state; } /******************************************************************************/ /* * Parse the for statement. Format for the expression is: * * for (initial; condition; incr) { * body; * } */ static int parseFor(Ejs *ep, int state, int flags) { EjsInput condScript, endScript, bodyScript, incrScript; int forFlags, cond; ejsInitInputState(&endScript); ejsInitInputState(&bodyScript); ejsInitInputState(&incrScript); ejsInitInputState(&condScript); mprAssert(ep); /* * Evaluate the for loop initialization statement */ if (ejsParse(ep, EJS_STATE_EXPR, flags) != EJS_STATE_EXPR_DONE) { return -1; } if (ejsLexGetToken(ep, state) != EJS_TOK_SEMI) { return -1; } /* * The first time through, we save the current input context just prior * to each step: prior to the conditional, the loop increment and * the loop body. */ ejsLexSaveInputState(ep, &condScript); if (ejsParse(ep, EJS_STATE_COND, flags) != EJS_STATE_COND_DONE) { goto error; } cond = (ep->result.boolean != 0); if (ejsLexGetToken(ep, state) != EJS_TOK_SEMI) { goto error; } /* * Don't execute the loop increment statement or the body * first time. */ forFlags = flags & ~EJS_FLAGS_EXE; ejsLexSaveInputState(ep, &incrScript); if (ejsParse(ep, EJS_STATE_EXPR, forFlags) != EJS_STATE_EXPR_DONE) { goto error; } if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { goto error; } /* * Parse the body and remember the end of the body script */ ejsLexSaveInputState(ep, &bodyScript); if (ejsParse(ep, EJS_STATE_STMT, forFlags) != EJS_STATE_STMT_DONE) { goto error; } ejsLexSaveInputState(ep, &endScript); /* * Now actually do the for loop. Note loop has been rotated */ while (cond && (flags & EJS_FLAGS_EXE)) { /* * Evaluate the body */ ejsLexRestoreInputState(ep, &bodyScript); switch (ejsParse(ep, EJS_STATE_STMT, flags)) { case EJS_STATE_RET: return EJS_STATE_RET; case EJS_STATE_STMT_DONE: break; default: goto error; } /* * Evaluate the increment script */ ejsLexRestoreInputState(ep, &incrScript); if (ejsParse(ep, EJS_STATE_EXPR, flags) != EJS_STATE_EXPR_DONE){ goto error; } /* * Evaluate the condition */ ejsLexRestoreInputState(ep, &condScript); if (ejsParse(ep, EJS_STATE_COND, flags) != EJS_STATE_COND_DONE) { goto error; } mprAssert(ep->result.type == MPR_TYPE_BOOL); cond = (ep->result.boolean != 0); } ejsLexRestoreInputState(ep, &endScript); done: ejsLexFreeInputState(ep, &condScript); ejsLexFreeInputState(ep, &incrScript); ejsLexFreeInputState(ep, &endScript); ejsLexFreeInputState(ep, &bodyScript); return state; error: state = EJS_STATE_ERR; goto done; } /******************************************************************************/ /* * Parse a function declaration */ static int parseFunctionDec(Ejs *ep, int state, int flags) { EjsInput endScript, bodyScript; MprVar v, *currentObj, *vp; char *procName; int len, tid, bodyFlags; mprAssert(ep); mprAssert(ejsPtr(ep->eid)); /* * function <name>(arg, arg, arg) { body }; * function name(arg, arg, arg) { body }; */ tid = ejsLexGetToken(ep, state); if (tid == EJS_TOK_ID) { procName = mprStrdup(ep->token); tid = ejsLexGetToken(ep, state); } else { procName = 0; } if (tid != EJS_TOK_LPAREN) { mprFree(procName); return EJS_STATE_ERR; } /* * Hand craft the function value structure. */ v = mprCreateFunctionVar(0, 0, 0); tid = ejsLexGetToken(ep, state); while (tid == EJS_TOK_ID) { mprAddToArray(v.function.args, mprStrdup(ep->token)); tid = ejsLexGetToken(ep, state); if (tid == EJS_TOK_RPAREN || tid != EJS_TOK_COMMA) { break; } tid = ejsLexGetToken(ep, state); } if (tid != EJS_TOK_RPAREN) { mprFree(procName); mprDestroyVar(&v); return EJS_STATE_ERR; } /* Allow new lines before opening brace */ do { tid = ejsLexGetToken(ep, state); } while (tid == EJS_TOK_NEWLINE); if (tid != EJS_TOK_LBRACE) { mprFree(procName); mprDestroyVar(&v); return EJS_STATE_ERR; } /* * Register the function name early to allow for recursive * function calls (see note in ECMA standard, page 71) */ if (!(flags & EJS_FLAGS_ASSIGNMENT)) { currentObj = ejsFindObj(ep, 0, procName, flags); vp = mprSetProperty(currentObj, procName, &v); } /* * Parse the function body. Turn execute off. */ bodyFlags = flags & ~EJS_FLAGS_EXE; ejsLexSaveInputState(ep, &bodyScript); do { state = ejsParse(ep, EJS_STATE_STMT, bodyFlags); } while (state == EJS_STATE_STMT_DONE); tid = ejsLexGetToken(ep, state); if (state != EJS_STATE_STMT_BLOCK_DONE || tid != EJS_TOK_RBRACE) { mprFree(procName); mprDestroyVar(&v); ejsLexFreeInputState(ep, &bodyScript); return EJS_STATE_ERR; } ejsLexSaveInputState(ep, &endScript); /* * Save the function body between the starting and ending parse positions. * Overwrite the trailing '}' with a null. */ len = endScript.scriptServp - bodyScript.scriptServp; v.function.body = mprMalloc(len + 1); memcpy(v.function.body, bodyScript.scriptServp, len); if (len <= 0) { v.function.body[0] = '\0'; } else { v.function.body[len - 1] = '\0'; } ejsLexFreeInputState(ep, &bodyScript); ejsLexFreeInputState(ep, &endScript); /* * If we are in an assignment, don't register the function name, rather * return the function structure in the parser result. */ if (flags & EJS_FLAGS_ASSIGNMENT) { mprCopyVar(&ep->result, &v, MPR_SHALLOW_COPY); } else { currentObj = ejsFindObj(ep, 0, procName, flags); vp = mprSetProperty(currentObj, procName, &v); } mprFree(procName); mprDestroyVar(&v); return EJS_STATE_STMT; } /******************************************************************************/ /* * Parse a function name and invoke the function */ static int parseFunction(Ejs *ep, int state, int flags, char *id) { EjsProc proc, *saveProc; MprVar *saveObj; /* * Must save any current ep->proc value for the current stack frame * to allow for recursive function calls. */ saveProc = (ep->proc) ? ep->proc: 0; memset(&proc, 0, sizeof(EjsProc)); proc.procName = mprStrdup(id); proc.fn = ep->currentProperty; proc.args = mprCreateArray(); ep->proc = &proc; mprDestroyVar(&ep->result); saveObj = ep->currentObj; if (ejsParse(ep, EJS_STATE_ARG_LIST, flags) != EJS_STATE_ARG_LIST_DONE) { freeProc(&proc); ep->proc = saveProc; return -1; } ep->currentObj = saveObj; /* * Evaluate the function if required */ if (flags & EJS_FLAGS_EXE) { if (evalFunction(ep, ep->currentObj, flags) < 0) { freeProc(&proc); ep->proc = saveProc; return -1; } } freeProc(&proc); ep->proc = saveProc; if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { return -1; } return state; } /******************************************************************************/ /* * Parse an identifier. This is a segment of a fully qualified variable. * May come here for an initial identifier or for property names * after a "." or "[...]". */ static int parseId(Ejs *ep, int state, int flags, char **id, char **fullName, int *fullNameLen, int *done) { int tid; mprFree(*id); *id = mprStrdup(ep->token); #if BLD_DEBUG *fullNameLen = mprReallocStrcat(fullName, MPR_MAX_VAR, *fullNameLen, 0, *id, NULL); #endif if (ep->currentObj == 0) { ep->currentObj = ejsFindObj(ep, state, *id, flags); } /* * Find the referenced variable and store it in currentProperty. */ ep->currentProperty = ejsFindProperty(ep, state, ep->currentObj, *id, flags); updateResult(ep, state, flags, ep->currentProperty); #if BLD_DEBUG if (ep->currentProperty && (ep->currentProperty->name == 0 || ep->currentProperty->name[0] == '\0')) { mprSetVarName(ep->currentProperty, *id); } #endif tid = ejsLexGetToken(ep, state); if (tid == EJS_TOK_LPAREN) { if (ep->currentProperty == 0 && (flags & EJS_FLAGS_EXE)) { ejsError(ep, "Function name not defined \"%s\"\n", *id); return -1; } ejsLexPutbackToken(ep, EJS_TOK_FUNCTION_NAME, ep->token); return state; } if (tid == EJS_TOK_PERIOD || tid == EJS_TOK_LBRACKET || tid == EJS_TOK_ASSIGNMENT || tid == EJS_TOK_INC_DEC) { ejsLexPutbackToken(ep, tid, ep->token); return state; } /* * Only come here for variable access and declarations. * Assignment handled elsewhere. */ if (flags & EJS_FLAGS_EXE) { if (state == EJS_STATE_DEC) { /* * Declare a variable. Standard allows: var x ; var x ; */ #if DISABLED if (ep->currentProperty != 0) { ejsError(ep, "Variable already defined \"%s\"\n", *id); return -1; } #endif /* * Create or overwrite if it already exists */ mprSetPropertyValue(ep->currentObj, *id, mprCreateUndefinedVar()); ep->currentProperty = 0; mprDestroyVar(&ep->result); } else if (flags & EJS_FLAGS_FOREACH) { if (ep->currentProperty == 0) { ep->currentProperty = mprCreatePropertyValue(ep->currentObj, *id, mprCreateUndefinedVar()); } } else { if (ep->currentProperty == 0) { if (ep->currentObj == ep->global || ep->currentObj == ep->local) { ejsError(ep, "Undefined variable \"%s\"\n", *id); return -1; } ep->currentProperty = mprCreatePropertyValue(ep->currentObj, *id, mprCreateUndefinedVar()); } } } ejsLexPutbackToken(ep, tid, ep->token); if (tid == EJS_TOK_RBRACKET || tid == EJS_TOK_COMMA || tid == EJS_TOK_IN) { *done = 1; } return state; } /******************************************************************************/ /* * Parse an "if" statement */ static int parseIf(Ejs *ep, int state, int flags, int *done) { bool ifResult; int thenFlags, elseFlags, tid; if (state != EJS_STATE_STMT) { return -1; } if (ejsLexGetToken(ep, state) != EJS_TOK_LPAREN) { return -1; } /* * Evaluate the entire condition list "(condition)" */ if (ejsParse(ep, EJS_STATE_COND, flags) != EJS_STATE_COND_DONE) { return -1; } if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { return -1; } /* * This is the "then" case. We need to always parse both cases and * execute only the relevant case. */ ifResult = mprVarToBool(&ep->result); if (ifResult) { thenFlags = flags; elseFlags = flags & ~EJS_FLAGS_EXE; } else { thenFlags = flags & ~EJS_FLAGS_EXE; elseFlags = flags; } /* * Process the "then" case. */ switch (ejsParse(ep, EJS_STATE_STMT, thenFlags)) { case EJS_STATE_RET: state = EJS_STATE_RET; return state; case EJS_STATE_STMT_DONE: break; default: return -1; } /* * Check to see if there is an "else" case */ removeNewlines(ep, state); tid = ejsLexGetToken(ep, state); if (tid != EJS_TOK_ELSE) { ejsLexPutbackToken(ep, tid, ep->token); *done = 1; return state; } /* * Process the "else" case. */ switch (ejsParse(ep, EJS_STATE_STMT, elseFlags)) { case EJS_STATE_RET: state = EJS_STATE_RET; return state; case EJS_STATE_STMT_DONE: break; default: return -1; } *done = 1; return state; } /******************************************************************************/ /* * Parse an "++" or "--" statement */ static int parseInc(Ejs *ep, int state, int flags) { MprVar one; if (! (flags & EJS_FLAGS_EXE)) { return state; } if (ep->currentProperty == 0) { ejsError(ep, "Undefined variable \"%s\"\n", ep->token); return -1; } one = mprCreateIntegerVar(1); if (evalExpr(ep, ep->currentProperty, (int) *ep->token, &one) < 0) { return -1; } if (mprWriteProperty(ep->currentProperty, &ep->result) < 0) { ejsError(ep, "Can't write to variable\n"); return -1; } return state; } /******************************************************************************/ /* * Evaluate a condition. Implements &&, ||, !. Returns with a boolean result * in ep->result. Returns -1 on errors, zero if successful. */ static int evalCond(Ejs *ep, MprVar *lhs, int rel, MprVar *rhs) { bool l, r, lval; mprAssert(rel > 0); l = mprVarToBool(lhs); r = mprVarToBool(rhs); switch (rel) { case EJS_COND_AND: lval = l && r; break; case EJS_COND_OR: lval = l || r; break; default: ejsError(ep, "Bad operator %d", rel); return -1; } mprCopyVarValue(&ep->result, mprCreateBoolVar(lval), 0); return 0; } /* return true if this string is a valid number */ static int string_is_number(const char *s) { char *endptr = NULL; if (s == NULL || *s == 0) { return 0; } strtod(s, &endptr); if (endptr != NULL && *endptr == 0) { return 1; } return 0; } /******************************************************************************/ /* * Evaluate an operation. Returns with the result in ep->result. Returns -1 * on errors, otherwise zero is returned. */ static int evalExpr(Ejs *ep, MprVar *lhs, int rel, MprVar *rhs) { char *str; MprNum lval, num; int rc; mprAssert(rel > 0); str = 0; lval = 0; /* * Type conversion. This is tricky and must be according to the standard. * Only numbers (including floats) and strings can be compared. All other * types are first converted to numbers by preference and if that fails, * to strings. * * First convert objects to comparable types. The "===" operator will * test the sameness of object references. Here, we coerce to comparable * types first. */ if (lhs->type == MPR_TYPE_OBJECT) { if (ejsRunFunction(ep->eid, lhs, "toValue", 0) == 0) { mprCopyVar(lhs, &ep->result, MPR_SHALLOW_COPY); } else { if (ejsRunFunction(ep->eid, lhs, "toString", 0) == 0) { mprCopyVar(lhs, &ep->result, MPR_SHALLOW_COPY); } } /* Nothing more can be done */ } if (rhs->type == MPR_TYPE_OBJECT) { if (ejsRunFunction(ep->eid, rhs, "toValue", 0) == 0) { mprCopyVar(rhs, &ep->result, MPR_SHALLOW_COPY); } else { if (ejsRunFunction(ep->eid, rhs, "toString", 0) == 0) { mprCopyVar(rhs, &ep->result, MPR_SHALLOW_COPY); } } /* Nothing more can be done */ } /* undefined and null are special, in that they don't get promoted when comparing */ if (rel == EJS_EXPR_EQ || rel == EJS_EXPR_NOTEQ) { if (lhs->type == MPR_TYPE_UNDEFINED || rhs->type == MPR_TYPE_UNDEFINED) { return evalBoolExpr(ep, lhs->type == MPR_TYPE_UNDEFINED, rel, rhs->type == MPR_TYPE_UNDEFINED); } if (lhs->type == MPR_TYPE_NULL || rhs->type == MPR_TYPE_NULL) { return evalBoolExpr(ep, lhs->type == MPR_TYPE_NULL, rel, rhs->type == MPR_TYPE_NULL); } } /* * From here on, lhs and rhs may contain allocated data (strings), so * we must always destroy before overwriting. */ /* * Only allow a few bool operations. Otherwise convert to number. */ if (lhs->type == MPR_TYPE_BOOL && rhs->type == MPR_TYPE_BOOL && (rel != EJS_EXPR_EQ && rel != EJS_EXPR_NOTEQ && rel != EJS_EXPR_BOOL_COMP)) { num = mprVarToNumber(lhs); mprDestroyVar(lhs); *lhs = mprCreateNumberVar(num); } /* * Types do not match, so try to coerce the right operand to match the left * But first, try to convert a left operand that is a numeric stored as a * string, into a numeric. */ if (lhs->type != rhs->type) { if (lhs->type == MPR_TYPE_STRING) { if (string_is_number(lhs->string)) { num = mprVarToNumber(lhs); lhs->allocatedVar = 0; mprDestroyVar(lhs); *lhs = mprCreateNumberVar(num); /* Examine further below */ } else { /* * Convert the RHS to a string */ mprVarToString(&str, MPR_MAX_STRING, 0, rhs); rhs->allocatedVar = 0; mprDestroyVar(rhs); *rhs = mprCreateStringVar(str, 1); mprFree(str); } #if BLD_FEATURE_FLOATING_POINT } else if (lhs->type == MPR_TYPE_FLOAT) { /* * Convert rhs to floating */ double f = mprVarToFloat(rhs); mprDestroyVar(rhs); *rhs = mprCreateFloatVar(f); #endif #if BLD_FEATURE_INT64 } else if (lhs->type == MPR_TYPE_INT64) { /* * Convert the rhs to 64 bit */ int64 n = mprVarToInteger64(rhs); mprDestroyVar(rhs); *rhs = mprCreateInteger64Var(n); #endif } else if (lhs->type == MPR_TYPE_BOOL || lhs->type == MPR_TYPE_INT) { if (rhs->type == MPR_TYPE_STRING) { /* * Convert to lhs to a string */ mprVarToString(&str, MPR_MAX_STRING, 0, lhs); mprDestroyVar(lhs); *lhs = mprCreateStringVar(str, 1); mprFree(str); #if BLD_FEATURE_FLOATING_POINT } else if (rhs->type == MPR_TYPE_FLOAT) { /* * Convert lhs to floating */ double f = mprVarToFloat(lhs); mprDestroyVar(lhs); *lhs = mprCreateFloatVar(f); #endif } else { /* * Convert both operands to numbers */ num = mprVarToNumber(lhs); mprDestroyVar(lhs); *lhs = mprCreateNumberVar(num); num = mprVarToNumber(rhs); mprDestroyVar(rhs); *rhs = mprCreateNumberVar(num); } } } /* * We have failed to coerce the types to be the same. Special case here * for undefined and null. We need to allow comparisions against these * special values. */ if (lhs->type == MPR_TYPE_UNDEFINED || lhs->type == MPR_TYPE_NULL) { switch (rel) { case EJS_EXPR_EQ: lval = lhs->type == rhs->type; break; case EJS_EXPR_NOTEQ: lval = lhs->type != rhs->type; break; case EJS_EXPR_BOOL_COMP: lval = ! mprVarToBool(rhs); break; default: lval = 0; } mprCopyVarValue(&ep->result, mprCreateBoolVar((bool) lval), 0); return 0; } /* * Types are the same here */ switch (lhs->type) { default: case MPR_TYPE_UNDEFINED: case MPR_TYPE_NULL: /* Should be handled above */ mprAssert(0); return 0; case MPR_TYPE_STRING_CFUNCTION: case MPR_TYPE_CFUNCTION: case MPR_TYPE_FUNCTION: case MPR_TYPE_OBJECT: mprCopyVarValue(&ep->result, mprCreateBoolVar(0), 0); return 0; case MPR_TYPE_PTR: rc = evalPtrExpr(ep, lhs->ptr, rel, rhs->ptr); break; case MPR_TYPE_BOOL: rc = evalBoolExpr(ep, lhs->boolean, rel, rhs->boolean); break; #if BLD_FEATURE_FLOATING_POINT case MPR_TYPE_FLOAT: rc = evalFloatExpr(ep, lhs->floating, rel, rhs->floating); break; #endif case MPR_TYPE_INT: rc = evalNumericExpr(ep, (MprNum) lhs->integer, rel, (MprNum) rhs->integer); break; #if BLD_FEATURE_INT64 case MPR_TYPE_INT64: rc = evalNumericExpr(ep, (MprNum) lhs->integer64, rel, (MprNum) rhs->integer64); break; #endif case MPR_TYPE_STRING: rc = evalStringExpr(ep, lhs, rel, rhs); } return rc; } /******************************************************************************/ #if BLD_FEATURE_FLOATING_POINT /* * Expressions with floating operands */ static int evalFloatExpr(Ejs *ep, double l, int rel, double r) { double lval; bool logical; lval = 0; logical = 0; switch (rel) { case EJS_EXPR_PLUS: lval = l + r; break; case EJS_EXPR_INC: lval = l + 1; break; case EJS_EXPR_MINUS: lval = l - r; break; case EJS_EXPR_DEC: lval = l - 1; break; case EJS_EXPR_MUL: lval = l * r; break; case EJS_EXPR_DIV: lval = l / r; break; default: logical++; break; } /* * Logical operators */ if (logical) { switch (rel) { case EJS_EXPR_EQ: lval = l == r; break; case EJS_EXPR_NOTEQ: lval = l != r; break; case EJS_EXPR_LESS: lval = (l < r) ? 1 : 0; break; case EJS_EXPR_LESSEQ: lval = (l <= r) ? 1 : 0; break; case EJS_EXPR_GREATER: lval = (l > r) ? 1 : 0; break; case EJS_EXPR_GREATEREQ: lval = (l >= r) ? 1 : 0; break; case EJS_EXPR_BOOL_COMP: lval = (r == 0) ? 1 : 0; break; default: ejsError(ep, "Bad operator %d", rel); return -1; } mprCopyVarValue(&ep->result, mprCreateBoolVar(lval != 0), 0); } else { mprCopyVarValue(&ep->result, mprCreateFloatVar(lval), 0); } return 0; } #endif /* BLD_FEATURE_FLOATING_POINT */ /******************************************************************************/ /* * Expressions with boolean operands */ static int evalBoolExpr(Ejs *ep, bool l, int rel, bool r) { bool lval; switch (rel) { case EJS_EXPR_EQ: lval = l == r; break; case EJS_EXPR_NOTEQ: lval = l != r; break; case EJS_EXPR_BOOL_COMP: lval = (r == 0) ? 1 : 0; break; default: ejsError(ep, "Bad operator %d", rel); return -1; } mprCopyVarValue(&ep->result, mprCreateBoolVar(lval), 0); return 0; } static int evalPtrExpr(Ejs *ep, void *l, int rel, void *r) { bool lval; switch (rel) { case EJS_EXPR_EQ: lval = l == r; break; case EJS_EXPR_NOTEQ: lval = l != r; break; case EJS_EXPR_BOOL_COMP: lval = (r == NULL) ? 1 : 0; break; default: ejsError(ep, "Bad operator %d", rel); return -1; } mprCopyVarValue(&ep->result, mprCreateBoolVar(lval), 0); return 0; } /******************************************************************************/ /* * Expressions with numeric operands */ static int evalNumericExpr(Ejs *ep, MprNum l, int rel, MprNum r) { MprNum lval; bool logical; lval = 0; logical = 0; switch (rel) { case EJS_EXPR_PLUS: lval = l + r; break; case EJS_EXPR_INC: lval = l + 1; break; case EJS_EXPR_MINUS: lval = l - r; break; case EJS_EXPR_DEC: lval = l - 1; break; case EJS_EXPR_MUL: lval = l * r; break; case EJS_EXPR_DIV: if (r != 0) { lval = l / r; } else { ejsError(ep, "Divide by zero"); return -1; } break; case EJS_EXPR_MOD: if (r != 0) { lval = l % r; } else { ejsError(ep, "Modulo zero"); return -1; } break; case EJS_EXPR_LSHIFT: lval = l << r; break; case EJS_EXPR_RSHIFT: lval = l >> r; break; default: logical++; break; } /* * Logical operators */ if (logical) { switch (rel) { case EJS_EXPR_EQ: lval = l == r; break; case EJS_EXPR_NOTEQ: lval = l != r; break; case EJS_EXPR_LESS: lval = (l < r) ? 1 : 0; break; case EJS_EXPR_LESSEQ: lval = (l <= r) ? 1 : 0; break; case EJS_EXPR_GREATER: lval = (l > r) ? 1 : 0; break; case EJS_EXPR_GREATEREQ: lval = (l >= r) ? 1 : 0; break; case EJS_EXPR_BOOL_COMP: lval = (r == 0) ? 1 : 0; break; default: ejsError(ep, "Bad operator %d", rel); return -1; } mprCopyVarValue(&ep->result, mprCreateBoolVar(lval != 0), 0); } else { mprCopyVarValue(&ep->result, mprCreateNumberVar(lval), 0); } return 0; } /******************************************************************************/ /* * Expressions with string operands */ static int evalStringExpr(Ejs *ep, MprVar *lhs, int rel, MprVar *rhs) { int lval; mprAssert(ep); mprAssert(lhs); mprAssert(rhs); switch (rel) { case EJS_EXPR_LESS: lval = strcmp(lhs->string, rhs->string) < 0; break; case EJS_EXPR_LESSEQ: lval = strcmp(lhs->string, rhs->string) <= 0; break; case EJS_EXPR_GREATER: lval = strcmp(lhs->string, rhs->string) > 0; break; case EJS_EXPR_GREATEREQ: lval = strcmp(lhs->string, rhs->string) >= 0; break; case EJS_EXPR_EQ: lval = strcmp(lhs->string, rhs->string) == 0; break; case EJS_EXPR_NOTEQ: lval = strcmp(lhs->string, rhs->string) != 0; break; case EJS_EXPR_PLUS: /* * This differs from all the above operations. We append rhs to lhs. */ mprDestroyVar(&ep->result); appendValue(&ep->result, lhs); appendValue(&ep->result, rhs); return 0; case EJS_EXPR_INC: case EJS_EXPR_DEC: case EJS_EXPR_MINUS: case EJS_EXPR_DIV: case EJS_EXPR_MOD: case EJS_EXPR_LSHIFT: case EJS_EXPR_RSHIFT: default: ejsError(ep, "Bad operator"); return -1; } mprCopyVarValue(&ep->result, mprCreateBoolVar(lval), 0); return 0; } /******************************************************************************/ /* * Evaluate a function. obj is set to the current object if a function is being * run. */ static int evalFunction(Ejs *ep, MprVar *obj, int flags) { EjsProc *proc; MprVar arguments, callee, thisObject, *prototype, **argValues; MprArray *formalArgs, *actualArgs; char buf[16], **argNames, **argBuf; int i, rc, fid; mprAssert(ep); mprAssert(ejsPtr(ep->eid)); rc = -1; proc = ep->proc; prototype = proc->fn; actualArgs = proc->args; argValues = (MprVar**) actualArgs->handles; /* * Create a new variable stack frame. ie. new local variables. */ fid = ejsOpenBlock(ep->eid); if (flags & EJS_FLAGS_NEW) { /* * Create a new bare object and pass it into the constructor as the * "this" local variable. */ thisObject = ejsCreateObj("this", EJS_OBJ_HASH_SIZE); mprCreatePropertyValue(ep->local, "this", thisObject); } else if (obj) { mprCreateProperty(ep->local, "this", obj); } switch (prototype->type) { default: mprAssert(0); break; case MPR_TYPE_STRING_CFUNCTION: if (actualArgs->used > 0) { argBuf = mprMalloc((1+actualArgs->used) * sizeof(char*)); for (i = 0; i < actualArgs->used; i++) { mprVarToString(&argBuf[i], MPR_MAX_STRING, 0, argValues[i]); } argBuf[i] = NULL; } else { argBuf = 0; } /* * Call the function depending on the various handle flags */ ep->thisPtr = prototype->cFunctionWithStrings.thisPtr; if (prototype->flags & MPR_VAR_ALT_HANDLE) { rc = ((EjsAltStringCFunction) prototype->cFunctionWithStrings.fn) (ep->eid, ep->altHandle, actualArgs->used, argBuf); } else if (prototype->flags & MPR_VAR_SCRIPT_HANDLE) { rc = (prototype->cFunctionWithStrings.fn)(ep->eid, actualArgs->used, argBuf); } else { rc = (prototype->cFunctionWithStrings.fn)(ep->primaryHandle, actualArgs->used, argBuf); } if (actualArgs->used > 0) { for (i = 0; i < actualArgs->used; i++) { mprFree(argBuf[i]); } mprFree(argBuf); } ep->thisPtr = 0; break; case MPR_TYPE_CFUNCTION: /* * Call the function depending on the various handle flags */ ep->thisPtr = prototype->cFunction.thisPtr; if (prototype->flags & MPR_VAR_ALT_HANDLE) { rc = ((EjsAltCFunction) prototype->cFunction.fn) (ep->eid, ep->altHandle, actualArgs->used, argValues); } else if (prototype->flags & MPR_VAR_SCRIPT_HANDLE) { rc = (prototype->cFunction.fn)(ep->eid, actualArgs->used, argValues); } else { rc = (prototype->cFunction.fn)(ep->primaryHandle, actualArgs->used, argValues); } ep->thisPtr = 0; break; case MPR_TYPE_FUNCTION: formalArgs = prototype->function.args; argNames = (char**) formalArgs->handles; if (formalArgs->used > actualArgs->used) { ejsError(ep, "Bad number of args. Should be %d", formalArgs->used); return -1; } /* * Create the arguments and callee variables */ arguments = ejsCreateObj("arguments", EJS_SMALL_OBJ_HASH_SIZE); callee = ejsCreateObj("callee", EJS_SMALL_OBJ_HASH_SIZE); /* * Overwrite the length property */ mprCreatePropertyValue(&arguments, "length", mprCreateIntegerVar(actualArgs->used)); mprCreatePropertyValue(&callee, "length", mprCreateIntegerVar(formalArgs->used)); /* * Define all the agruments to be set to the actual parameters */ for (i = 0; i < formalArgs->used; i++) { mprCreateProperty(ep->local, argNames[i], argValues[i]); } for (i = 0; i < actualArgs->used; i++) { mprItoa(i, buf, sizeof(buf)); mprCreateProperty(&arguments, buf, argValues[i]); } mprCreateProperty(&arguments, "callee", &callee); mprCreateProperty(ep->local, "arguments", &arguments); /* * Can destroy our variables here as they are now referenced via * "local" */ mprDestroyVar(&callee); mprDestroyVar(&arguments); /* * Actually run the function */ rc = ejsEvalScript(ep->eid, prototype->function.body, 0, 0); break; } ejsCloseBlock(ep->eid, fid); /* * New statements return the newly created object as the result of the * command */ if (flags & EJS_FLAGS_NEW) { mprDestroyVar(&ep->result); /* * Don't copy, we want to assign the actual object into result. * (mprCopyVar would inc the refCount to 2). */ ep->result = thisObject; } return rc; } /******************************************************************************/ /* * Run a function */ int ejsRunFunction(int eid, MprVar *obj, const char *functionName, MprArray *args) { EjsProc proc, *saveProc; Ejs *ep; int rc; mprAssert(obj); mprAssert(functionName && *functionName); if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return MPR_ERR_NOT_FOUND; } saveProc = ep->proc; ep->proc = &proc; memset(&proc, 0, sizeof(EjsProc)); mprDestroyVar(&ep->result); proc.fn = mprGetProperty(obj, functionName, 0); if (proc.fn == 0 || proc.fn->type == MPR_TYPE_UNDEFINED) { ep->proc = saveProc; return MPR_ERR_NOT_FOUND; } proc.procName = mprStrdup(functionName); if (args == 0) { proc.args = mprCreateArray(); rc = evalFunction(ep, obj, 0); } else { proc.args = args; rc = evalFunction(ep, obj, 0); proc.args = 0; } freeProc(&proc); ep->proc = saveProc; return rc; } /******************************************************************************/ /* * Find which object contains the property given the current context. * Only used for top level properties. */ MprVar *ejsFindObj(Ejs *ep, int state, const char *property, int flags) { MprVar *obj; mprAssert(ep); mprAssert(property && *property); if (flags & EJS_FLAGS_GLOBAL) { obj = ep->global; } else if (state == EJS_STATE_DEC || flags & EJS_FLAGS_LOCAL) { obj = ep->local; } else { /* First look local, then look global */ if (mprGetProperty(ep->local, property, 0)) { obj = ep->local; } else { obj = ep->global; } } return obj; } /******************************************************************************/ /* * Find an object property given a object and a property name. We * intelligently look in the local and global namespaces depending on * our state. If not found in local or global, try base classes for function * names only. Returns the property or NULL. */ MprVar *ejsFindProperty(Ejs *ep, int state, MprVar *obj, char *property, int flags) { MprVar *vp; mprAssert(ep); if (flags & EJS_FLAGS_EXE) { mprAssert(property && *property); } if (obj != 0) { #if FUTURE && MB op = obj; do { vp = mprGetProperty(op, property, 0); if (vp != 0) { if (op != obj && mprVarIsFunction(vp->type)) { } break; } op = op->baseObj; } while (op); #endif vp = mprGetProperty(obj, property, 0); } else { if (state == EJS_STATE_DEC) { vp = mprGetProperty(ep->local, property, 0); } else { /* Look local first, then global */ vp = mprGetProperty(ep->local, property, 0); if (vp == NULL) { vp = mprGetProperty(ep->global, property, 0); } } } return vp; } /******************************************************************************/ /* * Update result */ static void updateResult(Ejs *ep, int state, int flags, MprVar *vp) { if (flags & EJS_FLAGS_EXE && state != EJS_STATE_DEC) { mprDestroyVar(&ep->result); if (vp) { mprCopyProperty(&ep->result, vp, MPR_SHALLOW_COPY); } } } /******************************************************************************/ /* * Append to the pointer value */ static void appendValue(MprVar *dest, MprVar *src) { char *value, *oldBuf, *buf; int len, oldLen; mprAssert(dest); mprVarToString(&value, MPR_MAX_STRING, 0, src); if (mprVarIsValid(dest)) { len = strlen(value); oldBuf = dest->string; oldLen = strlen(oldBuf); buf = mprRealloc(oldBuf, (len + oldLen + 1) * sizeof(char)); dest->string = buf; strcpy(&buf[oldLen], value); } else { *dest = mprCreateStringVar(value, 1); } mprFree(value); } /******************************************************************************/ /* * Exit with status */ void ejsSetExitStatus(int eid, int status) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return; } ep->exitStatus = status; ep->flags |= EJS_FLAGS_EXIT; } /******************************************************************************/ /* * Free an argument list */ static void freeProc(EjsProc *proc) { MprVar **argValues; int i; if (proc->args) { argValues = (MprVar**) proc->args->handles; for (i = 0; i < proc->args->max; i++) { if (argValues[i]) { mprDestroyVar(argValues[i]); mprFree(argValues[i]); mprRemoveFromArray(proc->args, i); } } mprDestroyArray(proc->args); } if (proc->procName) { mprFree(proc->procName); proc->procName = NULL; } } /******************************************************************************/ /* * This function removes any new lines. Used for else cases, etc. */ static void removeNewlines(Ejs *ep, int state) { int tid; do { tid = ejsLexGetToken(ep, state); } while (tid == EJS_TOK_NEWLINE); ejsLexPutbackToken(ep, tid, ep->token); } /******************************************************************************/ #else void ejsParserDummy() {} /******************************************************************************/ #endif /* BLD_FEATURE_EJS */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim:tw=78 * vim600: sw=4 ts=4 fdm=marker * vim<600: sw=4 ts=4 */