From 77e8402dd68079c0e245fc8826daf2c6ad334766 Mon Sep 17 00:00:00 2001 From: Derrell Lipman Date: Tue, 26 Sep 2006 16:58:27 +0000 Subject: r18925: Add current snapshot of the ejs-2.0 code. Tridge, will you be incorporating this? (This used to be commit 917af234a8d517f82bd42256a940608a16b988f4) --- source4/lib/appweb/ejs-2.0/ejs/ejsParser.c | 4514 ++++++++++++++++++++++++++++ 1 file changed, 4514 insertions(+) create mode 100644 source4/lib/appweb/ejs-2.0/ejs/ejsParser.c (limited to 'source4/lib/appweb/ejs-2.0/ejs/ejsParser.c') diff --git a/source4/lib/appweb/ejs-2.0/ejs/ejsParser.c b/source4/lib/appweb/ejs-2.0/ejs/ejsParser.c new file mode 100644 index 0000000000..9fce6d27ee --- /dev/null +++ b/source4/lib/appweb/ejs-2.0/ejs/ejsParser.c @@ -0,0 +1,4514 @@ +/* + * @file ejsParser.c + * @brief EJS Parser and Execution + */ +/********************************* Copyright **********************************/ +/* + * @copy default.g + * + * Copyright (c) Mbedthis Software LLC, 2003-2006. 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 "ejs.h" + +#if BLD_FEATURE_EJS + +/****************************** Forward Declarations **************************/ + +static int createClass(Ejs *ep, EjsVar *parentClass, + const char *className, EjsVar *baseClass); +static int createProperty(Ejs *ep, EjsVar **obj, const char *id, + int state); +static int evalCond(Ejs *ep, EjsVar *lhs, int rel, EjsVar *rhs); +static int evalExpr(Ejs *ep, EjsVar *lhs, int rel, EjsVar *rhs); +#if BLD_FEATURE_FLOATING_POINT +static int evalFloatExpr(Ejs *ep, double l, int rel, double r); +#endif +static int evalBoolExpr(Ejs *ep, int l, int rel, int r); +static int evalNumericExpr(Ejs *ep, EjsNum l, int rel, EjsNum r); +static int evalObjExpr(Ejs *ep, EjsVar *lhs, int rel, EjsVar *rhs) ; +static int evalStringExpr(Ejs *ep, EjsVar *lhs, int rel, EjsVar *rhs); +static int evalMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, int flags); +static EjsProperty *findProperty(Ejs *ep, EjsVar *op, const char *property, + int flags); +static EjsVar *pickSpace(Ejs *ep, int state, const char *property, int flags); +static void freeProc(Ejs *ep, EjsProc *proc); +static int parseArgs(Ejs *ep, int state, int flags); +static int parseArrayLiteral(Ejs *ep, int state, int flags, char *id); +static int parseAssignment(Ejs *ep, int state, int flags, char *id); +static int parseClass(Ejs *ep, int state, int flags); +static int parseForInner(Ejs *ep, int state, int flags, + EjsInput *condScript, EjsInput *incrScript, + EjsInput *bodyScript, EjsInput *endScript); +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 parseRegFor(Ejs *ep, int state, int flags); +static int parseForIn(Ejs *ep, int state, int flags, int each); +static int parseId(Ejs *ep, int state, int flags, char **id, int *done); +static int parseInc(Ejs *ep, int state, int flags); +static int parseIf(Ejs *ep, int state, int flags, int *done); +static int parseFunction(Ejs *ep, int state, int flags); +static int parseMethod(Ejs *ep, int state, int flags, char *id); +static int parseObjectLiteral(Ejs *ep, int state, int flags, char *id); +static int parseStmt(Ejs *ep, int state, int flags); +static int parseThrow(Ejs *ep, int state, int flags); +static int parseTry(Ejs *ep, int state, int flags); +static void removeNewlines(Ejs *ep, int state); +static EjsProperty *searchSpacesForProperty(Ejs *ep, int state, EjsVar *obj, + char *property, int flags); +static int assignPropertyValue(Ejs *ep, char *id, int state, EjsVar *value, + int flags); +static int updateProperty(Ejs *ep, EjsVar *obj, const char *id, int state, + EjsVar *value); +static void updateResult(Ejs *ep, int state, int flags, EjsVar *vp); +static int getNextNonSpaceToken(Ejs *ep, int state); + +static int callConstructor(Ejs *ep, EjsVar *thisObj, EjsVar *baseClass, + MprArray *args); +static int callCMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, + EjsVar *prototype); +static int callStringCMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, + EjsVar *prototype); +static int callMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, + EjsVar *prototype); +static int runMethod(Ejs *ep, EjsVar *thisObj, EjsVar *method, + const char *methodName, MprArray *args); + +static EjsInput *getInputStruct(Ejs *ep); +static void freeInputStruct(Ejs *ep, EjsInput *input); + +static void *pushFrame(Ejs *ep, int size); +static void *popFrame(Ejs *ep, int size); + +/************************************* Code ***********************************/ +/* + * Recursive descent parser for EJS + */ + +int ejsParse(Ejs *ep, int state, int flags) +{ + mprAssert(ep); + +#if MOB + if (mprStackCheck(ep)) { + char *stack; + stack = ejsFormatStack(ep); + mprLog(ep, 0, "\nStack grew : MAX %d\n", mprStackSize(ep)); + mprLog(ep, 0, "Stack\n %s\n", stack); + mprFree(stack); + } +#endif + + if (ep->flags & EJS_FLAGS_EXIT) { + return EJS_STATE_RET; + } + + ep->inputMarker = ep->input->scriptServp; + + switch (state) { + /* + * Any statement, method arguments or conditional expressions + */ + case EJS_STATE_STMT: + state = parseStmt(ep, state, flags); + if (state != EJS_STATE_STMT_BLOCK_DONE && state != EJS_STATE_STMT_DONE){ + goto err; + } + break; + + case EJS_STATE_DEC: + state = parseStmt(ep, state, flags); + if (state != EJS_STATE_DEC_DONE) { + goto err; + } + break; + + case EJS_STATE_EXPR: + state = parseStmt(ep, state, flags); + if (state != EJS_STATE_EXPR_DONE) { + goto err; + } + break; + + /* + * Variable declaration list + */ + case EJS_STATE_DEC_LIST: + state = parseDeclaration(ep, state, flags); + if (state != EJS_STATE_DEC_LIST_DONE) { + goto err; + } + break; + + /* + * Method argument string + */ + case EJS_STATE_ARG_LIST: + state = parseArgs(ep, state, flags); + if (state != EJS_STATE_ARG_LIST_DONE) { + goto err; + } + break; + + /* + * Logical condition list (relational operations separated by &&, ||) + */ + case EJS_STATE_COND: + state = parseCond(ep, state, flags); + if (state != EJS_STATE_COND_DONE) { + goto err; + } + break; + + /* + * Expression list + */ + case EJS_STATE_RELEXP: + state = parseExpr(ep, state, flags); + if (state != EJS_STATE_RELEXP_DONE) { + goto err; + } + break; + } + + /* + * Recursion protection + */ + if (ep->input->scriptServp == ep->inputMarker) { + if (ep->recurseCount++ > 20) { + ejsSyntaxError(ep, "Input syntax error"); + state = EJS_STATE_ERR; + } + } else { + ep->recurseCount = 0; + } + + if (state == EJS_STATE_RET || state == EJS_STATE_EOF) { + return state; + } + +done: + return state; + +err: + if (state == EJS_STATE_RET || state == EJS_STATE_EOF) { + goto done; + } + if (state != EJS_STATE_ERR) { + ejsSyntaxError(ep, 0); + } + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseStmt { + EjsProc *saveProc; + EjsProperty *pp; + EjsVar *saveObj, *exception; + char *str, *id; + int done, tid, rs, saveObjPerm, expectEndOfStmt; +} ParseStmt; + +/* + * Parse expression (leftHandSide operator rightHandSide) + */ + + +static int parseStmt(Ejs *ep, int state, int flags) +{ + ParseStmt *sp; + + mprAssert(ep); + + if ((sp = pushFrame(ep, sizeof(ParseStmt))) == 0) { + return EJS_STATE_ERR; + } + + sp->id = 0; + sp->expectEndOfStmt = 0; + sp->saveProc = NULL; + + ep->currentObj = 0; + ep->currentProperty = 0; + + for (sp->done = 0; !sp->done && state != EJS_STATE_ERR; ) { + sp->tid = ejsLexGetToken(ep, state); + +#if (WIN || BREW_SIMULATOR) && BLD_DEBUG && DISABLED + /* MOB -- make cross platform */ + _CrtCheckMemory(); +#endif + + switch (sp->tid) { + default: + ejsLexPutbackToken(ep, sp->tid, ep->token); + goto done; + + case EJS_TOK_EXPR: + if (state == EJS_STATE_EXPR) { + ejsLexPutbackToken(ep, EJS_TOK_EXPR, ep->token); + } + goto done; + + case EJS_TOK_LOGICAL: + ejsLexPutbackToken(ep, sp->tid, ep->token); + goto done; + + case EJS_TOK_ERR: + if (state != EJS_STATE_ERR && !ep->gotException) { + ejsSyntaxError(ep, 0); + } + state = EJS_STATE_ERR; + goto done; + + case EJS_TOK_EOF: + state = EJS_STATE_EOF; + goto done; + + 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, sp->tid, ep->token); + } + goto done; + + case EJS_TOK_LBRACKET: + if (flags & EJS_FLAGS_EXE) { + ep->currentObj = &ep->currentProperty->var; + if (ep->currentObj != 0 && ep->currentObj->type != + EJS_TYPE_OBJECT) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Property reference to a non-object type \"%s\"\n", + sp->id); + goto err; + } + } + + sp->saveObj = ep->currentObj; + sp->saveObjPerm = ejsMakeObjPermanent(sp->saveObj, 1); + + sp->rs = ejsParse(ep, EJS_STATE_RELEXP, flags); + + ejsMakeObjPermanent(sp->saveObj, sp->saveObjPerm); + ep->currentObj = sp->saveObj; + + if (sp->rs < 0) { + state = sp->rs; + goto done; + } + + mprFree(sp->id); + /* MOB rc */ + sp->str = ejsVarToString(ep, ep->result); + sp->id = mprStrdup(ep, sp->str); + + if (sp->id[0] == '\0') { + if (flags & EJS_FLAGS_EXE) { + ejsError(ep, EJS_RANGE_ERROR, + "[] expression evaluates to the empty string\n"); + goto err; + } + } else { + sp->pp = searchSpacesForProperty(ep, state, ep->currentObj, + sp->id, flags); + ep->currentProperty = sp->pp; + updateResult(ep, state, flags, ejsGetVarPtr(sp->pp)); + } + + if ((sp->tid = ejsLexGetToken(ep, state)) != EJS_TOK_RBRACKET) { + ejsSyntaxError(ep, "Missing ']'"); + goto err; + } + break; + + case EJS_TOK_PERIOD: + if (flags & EJS_FLAGS_EXE) { + if (ep->currentProperty == 0) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Undefined object \"%s\"", sp->id); + goto err; + } + } + ep->currentObj = &ep->currentProperty->var; + if (flags & EJS_FLAGS_EXE) { + if (ep->currentObj != 0 && ep->currentObj->type != + EJS_TYPE_OBJECT) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Property reference to a non-object type \"%s\"\n", + sp->id); + goto err; + } + } + if ((sp->tid = ejsLexGetToken(ep, state)) != EJS_TOK_ID) { + ejsError(ep, EJS_REFERENCE_ERROR, "Bad property after '.': %s", + ep->token); + goto err; + } + /* Fall through */ + + case EJS_TOK_ID: + state = parseId(ep, state, flags, &sp->id, &sp->done); + if (sp->done && state == EJS_STATE_STMT) { + sp->expectEndOfStmt = 1; + } + break; + + case EJS_TOK_ASSIGNMENT: + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid == EJS_TOK_LBRACE) { + /* + * var = { name: value, name: value, ... } + */ + if (parseObjectLiteral(ep, state, flags, sp->id) < 0) { + ejsSyntaxError(ep, "Bad object literal"); + goto err; + } + + } else if (sp->tid == EJS_TOK_LBRACKET) { + /* + * var = [ array elements ] + */ + if (parseArrayLiteral(ep, state, flags, sp->id) < 0) { + ejsSyntaxError(ep, "Bad array literal"); + goto err; + } + + } else if (sp->tid == EJS_TOK_EXPR && + (int) *ep->token == EJS_EXPR_LESS) { + /* + * var = .../ + */ + ejsSyntaxError(ep, "XML literals are not yet supported"); + goto err; + + } else { + /* + * var = expression + */ + ejsLexPutbackToken(ep, sp->tid, ep->token); + state = parseAssignment(ep, state, flags, sp->id); + if (state == EJS_STATE_ERR) { + if (ep->flags & EJS_FLAGS_EXIT) { + state = EJS_STATE_RET; + goto done; + } + if (!ep->gotException) { + ejsSyntaxError(ep, 0); + } + goto err; + } + } + + if (flags & EJS_FLAGS_EXE) { + if (assignPropertyValue(ep, sp->id, state, ep->result, + flags) < 0) { + if (ep->gotException == 0) { + ejsError(ep, EJS_EVAL_ERROR, "Can't set property %s", + sp->id); + } + goto err; + } + } + + if (state == EJS_STATE_STMT) { + sp->expectEndOfStmt = 1; + goto done; + } + break; + + case EJS_TOK_INC_DEC: + state = parseInc(ep, state, flags); + if (state == EJS_STATE_STMT) { + sp->expectEndOfStmt = 1; + } + break; + + case EJS_TOK_NEW: + /* MOB -- could we remove rs and just use state */ + sp->rs = ejsParse(ep, EJS_STATE_EXPR, flags | EJS_FLAGS_NEW); + if (sp->rs < 0) { + state = sp->rs; + goto done; + } + break; + + case EJS_TOK_DELETE: + sp->rs = ejsParse(ep, EJS_STATE_EXPR, flags | EJS_FLAGS_DELETE); + if (sp->rs < 0) { + state = sp->rs; + goto done; + } + if (flags & EJS_FLAGS_EXE) { + /* Single place where properties are deleted */ + if (ep->currentObj == 0 || ep->currentProperty == 0) { + ejsError(ep, EJS_EVAL_ERROR, + "Can't find property to delete"); + goto err; + } + if (ep->currentObj->isArray) { + ejsSetArrayLength(ep, ep->currentObj, 0, + ep->currentProperty->name, 0); + } + ejsDeleteProperty(ep, ep->currentObj, + ep->currentProperty->name); + ep->currentProperty = 0; + } + goto done; + + case EJS_TOK_FUNCTION: + /* + * Parse a function declaration + */ + state = parseFunction(ep, state, flags); + goto done; + + case EJS_TOK_THROW: + state = parseThrow(ep, state, flags); + goto done; + + case EJS_TOK_TRY: + state = parseTry(ep, state, flags); + goto done; + + case EJS_TOK_CLASS: + case EJS_TOK_MODULE: + state = parseClass(ep, state, flags); + goto done; + + case EJS_TOK_LITERAL: + /* + * Set the result to the string literal + */ + if (flags & EJS_FLAGS_EXE) { + ejsWriteVarAsString(ep, ep->result, ep->token); + ejsSetVarName(ep, ep->result, ""); + } + if (state == EJS_STATE_STMT) { + sp->expectEndOfStmt = 1; + } + goto done; + + case EJS_TOK_NUMBER: + /* + * Set the result to the parsed number + */ + if (flags & EJS_FLAGS_EXE) { + ejsWriteVar(ep, ep->result, &ep->tokenNumber, 0); + } + if (state == EJS_STATE_STMT) { + sp->expectEndOfStmt = 1; + } + goto done; + + case EJS_TOK_METHOD_NAME: + /* + * parse a method() invocation + */ + mprAssert(ep->currentObj); + state = parseMethod(ep, state, flags, sp->id); + if (state == EJS_STATE_STMT) { + sp->expectEndOfStmt = 1; + } + if (ep->flags & EJS_FLAGS_EXIT) { + state = EJS_STATE_RET; + } + goto done; + + case EJS_TOK_IF: + state = parseIf(ep, state, flags, &sp->done); + if (state < 0) { + goto done; + } + break; + + case EJS_TOK_FOR: + state = parseFor(ep, state, flags); + goto done; + + case EJS_TOK_VAR: + if ((sp->rs = ejsParse(ep, EJS_STATE_DEC_LIST, flags)) < 0) { + state = sp->rs; + goto done; + } + goto done; + + case EJS_TOK_COMMA: + ejsLexPutbackToken(ep, sp->tid, ep->token); + goto done; + + case EJS_TOK_LPAREN: + if (state == EJS_STATE_EXPR) { + if ((sp->rs = ejsParse(ep, EJS_STATE_RELEXP, flags)) < 0) { + state = sp->rs; + goto done; + } + if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { + ejsSyntaxError(ep, 0); + goto err; + } + goto done; + + } else if (state == EJS_STATE_STMT) { + ejsLexPutbackToken(ep, EJS_TOK_METHOD_NAME, ep->token); + } + break; + + case EJS_TOK_RPAREN: + ejsLexPutbackToken(ep, sp->tid, ep->token); + goto done; + + case EJS_TOK_EXTENDS: + if (! (flags & EJS_FLAGS_CLASS_DEC)) { + ejsSyntaxError(ep, 0); + goto err; + } + sp->saveObj = ep->currentObj; + sp->saveObjPerm = ejsMakeObjPermanent(sp->saveObj, 1); + + sp->rs = ejsParse(ep, EJS_STATE_STMT, flags); + ejsMakeObjPermanent(sp->saveObj, sp->saveObjPerm); + + if (sp->rs < 0) { + state = sp->rs; + goto done; + } + + if (flags & EJS_FLAGS_EXE) { + if (createClass(ep, sp->saveObj, sp->id, + ejsGetVarPtr(ep->currentProperty)) < 0) { + goto err; + } + } + if (ejsLexGetToken(ep, state) != EJS_TOK_LBRACE) { + ejsSyntaxError(ep, 0); + goto err; + } + ejsLexPutbackToken(ep, ep->tid, ep->token); + goto done; + + case EJS_TOK_LBRACE: + if (flags & EJS_FLAGS_CLASS_DEC) { + if (state == EJS_STATE_DEC) { + if (flags & EJS_FLAGS_EXE) { + if (createClass(ep, ep->currentObj, sp->id, 0) < 0) { + goto err; + } + } + ejsLexPutbackToken(ep, sp->tid, ep->token); + + } else if (state == EJS_STATE_STMT) { + ejsLexPutbackToken(ep, sp->tid, ep->token); + } + goto done; + } + + /* + * This handles any code in braces except "if () {} else {}" + */ + if (state != EJS_STATE_STMT) { + ejsSyntaxError(ep, 0); + goto err; + } + + /* + * Parse will return EJS_STATE_STMT_BLOCK_DONE when the RBRACE + * is seen. + */ + sp->exception = 0; + do { + state = ejsParse(ep, EJS_STATE_STMT, flags); + if (state == EJS_STATE_ERR) { + /* + * We need to keep parsing to get to the end of the block + */ + if (sp->exception == 0) { + sp->exception = ejsDupVar(ep, ep->result, + EJS_SHALLOW_COPY); + if (sp->exception == 0) { + ejsMemoryError(ep); + goto err; + } + if (sp->exception->type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(sp->exception, 0); + mprAssert(sp->exception->objectState->alive == 0); + } + + /* + * If we're in a try block, we need to keep parsing + * so we can find the end of the block and the start + * of the catch block. Otherwise, we are done. + */ + if (!(flags & EJS_FLAGS_TRY)) { + break; + } + } + flags &= ~EJS_FLAGS_EXE; + if (ep->recurseCount > 20) { + break; + } + state = EJS_STATE_STMT_DONE; + ep->gotException = 0; + } + + } while (state == EJS_STATE_STMT_DONE); + + if (sp->exception) { + ep->gotException = 1; + ejsWriteVar(ep, ep->result, sp->exception, EJS_SHALLOW_COPY); + + /* Eat the closing brace */ + ejsLexGetToken(ep, state); + ejsFreeVar(ep, sp->exception); + + goto err; + } + ejsFreeVar(ep, sp->exception); + + if (state < 0) { + goto done; + } + + if (ejsLexGetToken(ep, state) != EJS_TOK_RBRACE) { + ejsSyntaxError(ep, 0); + goto err; + } + state = EJS_STATE_STMT_DONE; + goto done; + + case EJS_TOK_RBRACE: + if (state == EJS_STATE_STMT) { + ejsLexPutbackToken(ep, sp->tid, ep->token); + state = EJS_STATE_STMT_BLOCK_DONE; + + } else if (state == EJS_STATE_EXPR) { + ejsLexPutbackToken(ep, sp->tid, ep->token); + state = EJS_STATE_EXPR; + + } else { + ejsSyntaxError(ep, 0); + state = EJS_STATE_ERR; + } + goto done; + + case EJS_TOK_RETURN: + if ((sp->rs = ejsParse(ep, EJS_STATE_RELEXP, flags)) < 0) { + state = sp->rs; + goto done; + } + if (flags & EJS_FLAGS_EXE) { + state = EJS_STATE_RET; + goto done; + } + break; + } + } +done: + mprFree(sp->id); + + if (sp->expectEndOfStmt && state >= 0) { + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid == EJS_TOK_RBRACE) { + ejsLexPutbackToken(ep, EJS_TOK_RBRACE, ep->token); + + } else if (sp->tid != EJS_TOK_SEMI && sp->tid != EJS_TOK_NEWLINE && + sp->tid != EJS_TOK_EOF) { + ejsSyntaxError(ep, 0); + state = EJS_STATE_ERR; + + } else { + /* + * Skip newlines after semi-colon + */ + removeNewlines(ep, state); + } + } + + /* + * Advance the state + */ + switch (state) { + case EJS_STATE_STMT: + case EJS_STATE_STMT_DONE: + state = EJS_STATE_STMT_DONE; + break; + + case EJS_STATE_DEC: + case EJS_STATE_DEC_DONE: + state = EJS_STATE_DEC_DONE; + break; + + case EJS_STATE_EXPR: + case EJS_STATE_EXPR_DONE: + state = EJS_STATE_EXPR_DONE; + break; + + case EJS_STATE_STMT_BLOCK_DONE: + case EJS_STATE_EOF: + case EJS_STATE_RET: + break; + + default: + if (state != EJS_STATE_ERR) { + ejsSyntaxError(ep, 0); + } + state = EJS_STATE_ERR; + } + popFrame(ep, sizeof(ParseStmt)); + return state; + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseFor { + char *initToken; + int tid, foundVar, initId, each; +} ParseFor; + +/* + * Parse method arguments + */ + +static int parseFor(Ejs *ep, int state, int flags) +{ + ParseFor *sp; + + if ((sp = pushFrame(ep, sizeof(ParseFor))) == 0) { + return EJS_STATE_ERR; + } + + mprAssert(ep); + + if (state != EJS_STATE_STMT) { + ejsSyntaxError(ep, 0); + goto err; + } + + if ((sp->tid = ejsLexGetToken(ep, state)) == EJS_TOK_EACH) { + sp->each = 1; + sp->tid = ejsLexGetToken(ep, state); + + } else { + sp->each = 0; + } + + if (sp->tid != EJS_TOK_LPAREN) { + ejsSyntaxError(ep, 0); + goto err; + } + + /* + * Need to peek 2-3 tokens ahead and see if this is a + * for [each] ([var] x in set) + * or + * for (init ; whileCond; incr) + */ + sp->initId = ejsLexGetToken(ep, EJS_STATE_EXPR); + sp->foundVar = 0; + if (sp->initId == EJS_TOK_ID && strcmp(ep->token, "var") == 0) { + sp->foundVar = 1; + sp->initId = ejsLexGetToken(ep, EJS_STATE_EXPR); + } + sp->initToken = mprStrdup(ep, ep->token); + + sp->tid = ejsLexGetToken(ep, EJS_STATE_EXPR); + + ejsLexPutbackToken(ep, sp->tid, ep->token); + ejsLexPutbackToken(ep, sp->initId, sp->initToken); + mprFree(sp->initToken); + + if (sp->foundVar) { + ejsLexPutbackToken(ep, EJS_TOK_ID, "var"); + } + + if (sp->tid == EJS_TOK_IN) { + state = parseForIn(ep, state, flags, sp->each); + + } else { + state = parseRegFor(ep, state, flags); + } + +done: + popFrame(ep, sizeof(ParseFor)); + return state; + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Parse method arguments + */ + +static int parseArgs(Ejs *ep, int state, int flags) +{ + EjsVar *vp; + 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; + } + + /* + * If this is part of a constructor, must run methods in args normally + */ + flags &= ~EJS_FLAGS_NEW; + + state = ejsParse(ep, EJS_STATE_RELEXP, flags); + if (state < 0) { + return state; + } + if (flags & EJS_FLAGS_EXE) { + mprAssert(ep->proc->args); + vp = ejsDupVar(ep, ep->result, EJS_SHALLOW_COPY); + if (vp == 0) { + ejsMemoryError(ep); + return EJS_STATE_ERR; + } + /* MOB */ + if (vp->type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(vp, 0); + mprAssert(vp->objectState->alive == 0); + } + + /* + * Propagate the name + */ + ejsSetVarName(ep, vp, ep->result->propertyName); + + mprAddItem(ep->proc->args, vp); + + } + /* + * 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) { + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + return EJS_STATE_ARG_LIST_DONE; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseAssign { + EjsProperty *saveProperty; + EjsVar *saveObj; + int saveObjPerm, savePropPerm, rc; +} ParseAssign; + +/* + * Parse an assignment statement + */ + +static int parseAssignment(Ejs *ep, int state, int flags, char *id) +{ + ParseAssign *sp; + + + if (id == 0) { + if (!ep->gotException) { + ejsSyntaxError(ep, 0); + } + return EJS_STATE_ERR; + } + + if ((sp = pushFrame(ep, sizeof(ParseAssign))) == 0) { + return EJS_STATE_ERR; + } + + mprAssert(ep->currentObj); + + /* + * Parse the right hand side of the "=" + */ + sp->saveObj = ep->currentObj; + sp->saveProperty = ep->currentProperty; + + sp->saveObjPerm = ejsMakeObjPermanent(sp->saveObj, 1); + sp->savePropPerm = ejsMakeObjPermanent(ejsGetVarPtr(sp->saveProperty), 1); + + sp->rc = ejsParse(ep, EJS_STATE_RELEXP, flags | EJS_FLAGS_ASSIGNMENT); + + ejsMakeObjPermanent(sp->saveObj, sp->saveObjPerm); + ejsMakeObjPermanent(ejsGetVarPtr(sp->saveProperty), sp->savePropPerm); + + if (sp->rc < 0) { + state = EJS_STATE_ERR; + } + + ep->currentObj = sp->saveObj; + ep->currentProperty = sp->saveProperty; + + popFrame(ep, sizeof(ParseAssign)); + + if (! (flags & EJS_FLAGS_EXE)) { + return state; + } + + return state; +} + +/******************************************************************************/ + +static int assignPropertyValue(Ejs *ep, char *id, int state, EjsVar *value, + int flags) +{ + EjsProperty *saveProperty; + EjsVar *saveObj, *obj, *vp; + char *procName; + int saveObjPerm, savePropPerm, rc; + + mprAssert(flags & EJS_FLAGS_EXE); + + if (ep->currentProperty && + !ep->currentProperty->var.flags & EJS_GET_ACCESSOR) { + obj = ep->currentObj; + + } else { + /* + * Handle any set accessors. + * FUTURE OPT -- could be faster + * FUTURE OPT -- coming here even when doing just a set "x = value"; + */ + procName = 0; + if (mprAllocStrcat(MPR_LOC_ARGS(ep), &procName, EJS_MAX_ID + 5, 0, + "-set-", id, 0) > 0) { + + MprArray *args; + + ep->currentProperty = searchSpacesForProperty(ep, state, + ep->currentObj, procName, flags); + + if (ep->currentProperty) { + args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS); + + vp = ejsDupVar(ep, value, EJS_SHALLOW_COPY); + mprAddItem(args, vp); + mprAssert(! ejsObjIsCollectable(vp)); + + saveObj = ep->currentObj; + saveProperty = ep->currentProperty; + + saveObjPerm = ejsMakeObjPermanent(saveObj, 1); + savePropPerm = ejsMakeObjPermanent(ejsGetVarPtr(saveProperty), + 1); + + /* + * Invoke the set accessor + */ + rc = ejsRunMethod(ep, ep->currentObj, procName, args); + mprFree(procName); + ejsFreeMethodArgs(ep, args); + + ejsMakeObjPermanent(saveObj, saveObjPerm); + ejsMakeObjPermanent(ejsGetVarPtr(saveProperty), savePropPerm); + + ep->currentObj = saveObj; + ep->currentProperty = saveProperty; + + if (rc < 0) { + return EJS_STATE_ERR; + } + return state; + } + mprFree(procName); + } + + if (ep->currentProperty == 0) { + /* + * MOB -- can we omit this as updateProperty below will create + */ + if (createProperty(ep, &obj, id, state) < 0) { + return EJS_STATE_ERR; + } + } + } + + if (updateProperty(ep, obj, id, state, value) < 0) { + return EJS_STATE_ERR; + } + + vp = ejsGetVarPtr(ep->currentProperty); + if (vp->type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(vp, 1); + } + + return state; +} + +/******************************************************************************/ + +static int parseObjectLiteral(Ejs *ep, int state, int flags, char *id) +{ + EjsProperty *saveProperty; + EjsVar *saveObj; + EjsVar *obj; + char *name; + int saveObjPerm, savePropPerm, tid; + + name = 0; + + saveObj = ep->currentObj; + saveProperty = ep->currentProperty; + + saveObjPerm = ejsMakeObjPermanent(saveObj, 1); + savePropPerm = ejsMakeObjPermanent(ejsGetVarPtr(saveProperty), 1); + + if (flags & EJS_FLAGS_EXE) { + obj = ejsCreateSimpleObj(ep, "Object"); + if (obj == 0) { + ejsMemoryError(ep); + goto err; + } + mprAssert(! ejsObjIsCollectable(obj)); + + } else { + obj = 0; + } + + do { + tid = getNextNonSpaceToken(ep, state); + if (tid != EJS_TOK_ID) { + ejsSyntaxError(ep, 0); + goto err; + } + name = mprStrdup(ep, ep->token); + + tid = getNextNonSpaceToken(ep, state); + if (tid != EJS_TOK_COLON) { + ejsSyntaxError(ep, 0); + goto err; + } + + if (flags & EJS_FLAGS_EXE) { + /* FUTURE OPT -- can we optimize this. We are double accessing id + with the Put below. Should we be using this or ejsSetProperty + */ + if (ejsCreatePropertyMethod(ep, obj, name) == 0) { + ejsMemoryError(ep); + goto err; + } + } + + if (ejsParse(ep, EJS_STATE_RELEXP, flags) < 0) { + goto err; + } + if (flags & EJS_FLAGS_EXE) { + if (ejsSetPropertyMethod(ep, obj, name, ep->result) == 0) { + ejsMemoryError(ep); + goto err; + } + } + mprFree(name); + name = 0; + + tid = getNextNonSpaceToken(ep, state); + + } while (tid == EJS_TOK_COMMA); + + if (tid != EJS_TOK_RBRACE) { + ejsSyntaxError(ep, 0); + goto err; + } + + if (flags & EJS_FLAGS_EXE) { + ejsMakeObjLive(obj, 1); + ejsWriteVar(ep, ep->result, obj, EJS_SHALLOW_COPY); + } + +done: + ejsMakeObjPermanent(saveObj, saveObjPerm); + ejsMakeObjPermanent(ejsGetVarPtr(saveProperty), savePropPerm); + + ep->currentObj = saveObj; + ep->currentProperty = saveProperty; + + if (obj) { + ejsFreeVar(ep, obj); + } + return state; + +err: + mprFree(name); + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ + +static int parseArrayLiteral(Ejs *ep, int state, int flags, char *id) +{ + EjsProperty *saveProperty; + EjsVar *saveObj; + EjsVar *obj; + int saveObjPerm, savePropPerm, tid; + + saveObj = ep->currentObj; + saveProperty = ep->currentProperty; + + saveObjPerm = ejsMakeObjPermanent(saveObj, 1); + savePropPerm = ejsMakeObjPermanent(ejsGetVarPtr(saveProperty), 1); + + if (flags & EJS_FLAGS_EXE) { + obj = ejsCreateArray(ep, 0); + if (obj == 0) { + ejsMemoryError(ep); + goto err; + } + mprAssert(! ejsObjIsCollectable(obj)); + + } else { + obj = 0; + } + + do { + if (ejsParse(ep, EJS_STATE_RELEXP, flags) < 0) { + goto err; + } + if (flags & EJS_FLAGS_EXE) { + /* MOB _- should this be put[array.length] */ + if (ejsAddArrayElt(ep, obj, ep->result, EJS_SHALLOW_COPY) == 0) { + goto err; + } + } + + tid = getNextNonSpaceToken(ep, state); + + } while (tid == EJS_TOK_COMMA); + + if (tid != EJS_TOK_RBRACKET) { + ejsSyntaxError(ep, "Missing right bracket"); + goto err; + } + + if (flags & EJS_FLAGS_EXE) { + ejsMakeObjLive(obj, 1); + ejsWriteVar(ep, ep->result, obj, EJS_SHALLOW_COPY); + } + +done: + ejsMakeObjPermanent(saveObj, saveObjPerm); + ejsMakeObjPermanent(ejsGetVarPtr(saveProperty), savePropPerm); + + ep->currentObj = saveObj; + ep->currentProperty = saveProperty; + + ejsFreeVar(ep, obj); + return state; + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Create a property. + */ + +/* +MOB -- simplify this. Enforce ep->currentObj to be always set. +Then we can delete this and just call + + ejsCreatePropertyMethod(ep->currentObj, id); +*/ +//XX +static int createProperty(Ejs *ep, EjsVar **objp, const char *id, int state) +{ + EjsVar *obj, *vp; + + mprAssert(id && *id); + mprAssert(objp); + + /* + * Determine the variable scope to use for the property. + * Standard says: "var x" means declare locally. + * "x = 2" means declare globally if x is undefined. + */ + if (ep->currentObj) { + if (ep->currentObj->type != EJS_TYPE_OBJECT) { + ejsSyntaxError(ep, "Reference is not an object"); + return EJS_STATE_ERR; + } + obj = ep->currentObj; + + } else { + /* MOB -- we should never be doing this here. ep->currentObj should + always be set already */ + obj = (state == EJS_STATE_DEC) ? ep->local : ep->global; + } + mprAssert(obj); + + vp = ejsCreatePropertyMethod(ep, obj, id); + if (vp == 0) { + if (!ep->gotException) { + ejsMemoryError(ep); + } + return EJS_STATE_ERR; + } + + *objp = obj; + return state; +} + +/******************************************************************************/ +/* + * Update a property. + * + * Return with ep->currentProperty updated to point to the property. + */ + +static int updateProperty(Ejs *ep, EjsVar *obj, const char *id, int state, + EjsVar *value) +{ + EjsVar *vp; + + /* + * MOB -- do ready-only check here + */ + vp = ejsSetPropertyMethod(ep, obj, id, value); + if (vp == 0) { + ejsMemoryError(ep); + return EJS_STATE_ERR; + } + ep->currentProperty = ejsGetPropertyPtr(vp); + + obj->objectState->dirty = 1; + + return state; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseCond { + EjsVar lhs, rhs; + int tid, operator; +} ParseCond; + +/* + * Parse conditional expression (relational ops separated by ||, &&) + */ + +static int parseCond(Ejs *ep, int state, int flags) +{ + ParseCond *sp; + + if ((sp = pushFrame(ep, sizeof(ParseCond))) == 0) { + return EJS_STATE_ERR; + } + + mprAssert(ep); + + if (flags & EJS_FLAGS_EXE) { + ejsClearVar(ep, ep->result); + } + + sp->lhs.type = sp->rhs.type = EJS_TYPE_UNDEFINED; + sp->lhs.objectState = sp->rhs.objectState = 0; + sp->lhs.allocatedData = sp->rhs.allocatedData = 0; + + ejsSetVarName(ep, &sp->lhs, "lhs"); + ejsSetVarName(ep, &sp->rhs, "rhs"); + + sp->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 < 0) { + break; + } + + if (flags & EJS_FLAGS_EXE) { + if (sp->operator > 0) { + /* + * FUTURE -- does not do precedence + */ + ejsWriteVar(ep, &sp->rhs, ep->result, EJS_SHALLOW_COPY); + if (evalCond(ep, &sp->lhs, sp->operator, &sp->rhs) < 0) { + state = EJS_STATE_ERR; + break; + } + /* Result left in ep->result */ + /* MOB */ + if (sp->lhs.type == EJS_TYPE_OBJECT) { + mprAssert(sp->lhs.objectState->alive == 0); + } + if (sp->rhs.type == EJS_TYPE_OBJECT) { + mprAssert(sp->rhs.objectState->alive == 0); + } + } + } + + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid == EJS_TOK_LOGICAL) { + sp->operator = (int) *ep->token; + + } else if (sp->tid == EJS_TOK_RPAREN || sp->tid == EJS_TOK_SEMI) { + ejsLexPutbackToken(ep, sp->tid, ep->token); + state = EJS_STATE_COND_DONE; + break; + + } else { + ejsLexPutbackToken(ep, sp->tid, ep->token); + } + + if (flags & EJS_FLAGS_EXE) { + ejsWriteVar(ep, &sp->lhs, ep->result, EJS_SHALLOW_COPY); + } + + } while (state == EJS_STATE_RELEXP_DONE); + + ejsClearVar(ep, &sp->lhs); + ejsClearVar(ep, &sp->rhs); + + popFrame(ep, sizeof(ParseCond)); + + 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; + * var x = { property: value, property: value ... }; + * var x = [ property: value, property: value ... ]; + * + * 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) { + ejsSyntaxError(ep, 0); + 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 + * Stop on ";" or "in" which is used in a "for (var x in ..." + */ + tid = ejsLexGetToken(ep, state); + + if (tid == EJS_TOK_SEMI) { + return EJS_STATE_DEC_LIST_DONE; + + } else if (tid == EJS_TOK_IN) { + ejsLexPutbackToken(ep, tid, ep->token); + return EJS_STATE_DEC_LIST_DONE; + + } else if (flags & EJS_FLAGS_CLASS_DEC && + (tid == EJS_TOK_LBRACE || tid == EJS_TOK_EXTENDS)) { + ejsLexPutbackToken(ep, tid, ep->token); + return EJS_STATE_DEC_LIST_DONE; + + } else if (tid == EJS_TOK_RPAREN && flags & EJS_FLAGS_CATCH) { + ejsLexPutbackToken(ep, tid, ep->token); + return EJS_STATE_DEC_LIST_DONE; + + } else if (tid != EJS_TOK_COMMA) { + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + + } while (tid == EJS_TOK_COMMA); + + if (tid != EJS_TOK_SEMI) { + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + return EJS_STATE_DEC_LIST_DONE; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseExpr { + EjsVar lhs, rhs; + int rel, tid, unaryMinus; +} ParseExpr; + +/* + * Parse expression (leftHandSide operator rightHandSide) + */ + +static int parseExpr(Ejs *ep, int state, int flags) +{ + ParseExpr *sp; + + mprAssert(ep); + + if ((sp = pushFrame(ep, sizeof(ParseExpr))) == 0) { + return EJS_STATE_ERR; + } + + if (flags & EJS_FLAGS_EXE) { + ejsClearVar(ep, ep->result); + } + + sp->lhs.type = sp->rhs.type = EJS_TYPE_UNDEFINED; + sp->lhs.objectState = sp->rhs.objectState = 0; + sp->lhs.allocatedData = sp->rhs.allocatedData = 0; + + ejsSetVarName(ep, &sp->lhs, "lhs"); + ejsSetVarName(ep, &sp->rhs, "rhs"); + + sp->rel = 0; + sp->tid = 0; + sp->unaryMinus = 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 (sp->tid == EJS_TOK_LOGICAL) { + state = ejsParse(ep, EJS_STATE_RELEXP, flags); + if (state < 0) { + break; + } + } else { + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid == EJS_TOK_EXPR && (int) *ep->token == EJS_EXPR_MINUS) { + sp->unaryMinus = 1; + + } else { + ejsLexPutbackToken(ep, sp->tid, ep->token); + } + + state = ejsParse(ep, EJS_STATE_EXPR, flags); + if (state < 0) { + break; + } + } + + if (flags & EJS_FLAGS_EXE) { + if (sp->unaryMinus) { + switch (ep->result->type) { + default: + case EJS_TYPE_UNDEFINED: + case EJS_TYPE_NULL: + case EJS_TYPE_STRING_CMETHOD: + case EJS_TYPE_CMETHOD: + case EJS_TYPE_METHOD: + case EJS_TYPE_PTR: + case EJS_TYPE_OBJECT: + case EJS_TYPE_STRING: + case EJS_TYPE_BOOL: + ejsError(ep, EJS_SYNTAX_ERROR, "Invalid unary minus"); + state = EJS_STATE_ERR; + break; + +#if BLD_FEATURE_FLOATING_POINT + case EJS_TYPE_FLOAT: + ep->result->floating = - ep->result->floating; + break; +#endif + + case EJS_TYPE_INT: + ep->result->integer = - ep->result->integer; + break; + +#if BLD_FEATURE_INT64 + case EJS_TYPE_INT64: + ep->result->integer64 = - ep->result->integer64; + break; +#endif + } + } + sp->unaryMinus = 0; + + if (sp->rel > 0) { + ejsWriteVar(ep, &sp->rhs, ep->result, EJS_SHALLOW_COPY); + if (sp->tid == EJS_TOK_LOGICAL) { + if (evalCond(ep, &sp->lhs, sp->rel, &sp->rhs) < 0) { + state = EJS_STATE_ERR; + break; + } + } else { + if (evalExpr(ep, &sp->lhs, sp->rel, &sp->rhs) < 0) { + state = EJS_STATE_ERR; + break; + } + } + } + /* MOB */ + if (sp->lhs.type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(&sp->lhs, 0); + mprAssert(sp->lhs.objectState->alive == 0); + } + if (sp->rhs.type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(&sp->rhs, 0); + mprAssert(sp->rhs.objectState->alive == 0); + } + } + + if ((sp->tid = ejsLexGetToken(ep, state)) == EJS_TOK_EXPR || + sp->tid == EJS_TOK_INC_DEC || sp->tid == EJS_TOK_LOGICAL) { + sp->rel = (int) *ep->token; + ejsWriteVar(ep, &sp->lhs, ep->result, EJS_SHALLOW_COPY); + + } else { + ejsLexPutbackToken(ep, sp->tid, ep->token); + state = EJS_STATE_RELEXP_DONE; + } + + } while (state == EJS_STATE_EXPR_DONE); + + ejsClearVar(ep, &sp->lhs); + ejsClearVar(ep, &sp->rhs); + + popFrame(ep, sizeof(ParseExpr)); + + return state; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseForIn { + EjsInput *endScript, *bodyScript; + EjsProperty *pp, *nextp; + EjsVar *iteratorVar, *setVar, *vp; + int forFlags, tid; +} ParseForIn; + +/* + * Parse the "for ... in" statement. Format for the statement is: + * + * for [each] (var varName in expression) { + * body; + * } + */ + +static int parseForIn(Ejs *ep, int state, int flags, int each) +{ + ParseForIn *sp; + + mprAssert(ep); + + if ((sp = pushFrame(ep, sizeof(ParseForIn))) == 0) { + return EJS_STATE_ERR; + } + + sp->setVar = 0; + sp->iteratorVar = 0; + sp->bodyScript = 0; + sp->endScript = 0; + + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid != EJS_TOK_ID && sp->tid != EJS_TOK_VAR) { + ejsSyntaxError(ep, 0); + goto err; + } + ejsLexPutbackToken(ep, sp->tid, ep->token); + + state = ejsParse(ep, EJS_STATE_EXPR, EJS_FLAGS_FORIN | flags); + if (state < 0) { + goto done; + } + if (flags & EJS_FLAGS_EXE) { + if (ep->currentProperty == 0) { + ejsSyntaxError(ep, 0); + goto err; + } + sp->iteratorVar = &ep->currentProperty->var; + } else { + sp->iteratorVar = 0; + } + + if (ejsLexGetToken(ep, state) != EJS_TOK_IN) { + ejsSyntaxError(ep, 0); + goto err; + } + + /* + * Get the set + */ + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid != EJS_TOK_ID) { + ejsSyntaxError(ep, 0); + goto err; + } + ejsLexPutbackToken(ep, sp->tid, ep->token); + + state = ejsParse(ep, EJS_STATE_EXPR, flags); + if (state < 0) { + goto done; + } + + if ((flags & EJS_FLAGS_EXE) && + (ep->result == 0 || ep->result->type == EJS_TYPE_UNDEFINED)) { + ejsError(ep, EJS_REFERENCE_ERROR, "Can't access array or object"); + goto err; + } + + if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { + ejsSyntaxError(ep, 0); + goto err; + } + + sp->setVar = ejsDupVar(ep, ep->result, EJS_SHALLOW_COPY); + + sp->bodyScript = getInputStruct(ep); + + /* + * Parse the body and remember the end of the body script + */ + sp->forFlags = flags & ~EJS_FLAGS_EXE; + ejsLexSaveInputState(ep, sp->bodyScript); + + state = ejsParse(ep, EJS_STATE_STMT, sp->forFlags); + if (state < 0) { + goto done; + } + + sp->endScript = getInputStruct(ep); + ejsInitInputState(sp->endScript); + ejsLexSaveInputState(ep, sp->endScript); + + /* + * Enumerate the properties + */ + if (flags & EJS_FLAGS_EXE) { + if (sp->setVar->type == EJS_TYPE_OBJECT) { + + sp->setVar->objectState->preventDeleteProp = 1; + + sp->pp = ejsGetFirstProperty(sp->setVar, 0); + while (sp->pp) { + sp->nextp = ejsGetNextProperty(sp->pp, 0); + if (! sp->pp->dontEnumerate && !sp->pp->delayedDelete) { + if (each) { + sp->vp = ejsWriteVar(ep, sp->iteratorVar, + ejsGetVarPtr(sp->pp), EJS_SHALLOW_COPY); + } else { + sp->vp = ejsWriteVarAsString(ep, sp->iteratorVar, + sp->pp->name); + } + if (sp->vp == 0) { + ejsError(ep, EJS_MEMORY_ERROR, + "Can't write to variable"); + goto err; + } + + ejsLexRestoreInputState(ep, sp->bodyScript); + + state = ejsParse(ep, EJS_STATE_STMT, flags); + + if (state < 0) { + if (sp->setVar->objectState) { + sp->setVar->objectState->preventDeleteProp = 0; + } + goto done; + } + } + sp->pp = sp->nextp; + } + + /* + * Process delayed deletes + */ + if (sp->setVar->objectState) { + sp->setVar->objectState->preventDeleteProp = 0; + if (sp->setVar->objectState->delayedDeleteProp) { + sp->pp = ejsGetFirstProperty(sp->setVar, 0); + while (sp->pp) { + sp->nextp = ejsGetNextProperty(sp->pp, 0); + if (sp->pp->delayedDelete) { + ejsDeleteProperty(ep, sp->setVar, sp->pp->name); + } + sp->pp = sp->nextp; + } + sp->setVar->objectState->delayedDeleteProp = 0; + } + } + + } else { + ejsError(ep, EJS_REFERENCE_ERROR, + "Variable to iterate over is not an array or object"); + goto err; + } + } + + ejsLexRestoreInputState(ep, sp->endScript); + +done: + if (sp->endScript) { + ejsLexFreeInputState(ep, sp->endScript); + ejsLexFreeInputState(ep, sp->bodyScript); + } + + if (sp->bodyScript) { + freeInputStruct(ep, sp->bodyScript); + } + if (sp->endScript) { + freeInputStruct(ep, sp->endScript); + } + + if (sp->setVar) { + ejsFreeVar(ep, sp->setVar); + } + + popFrame(ep, sizeof(ParseForIn)); + + return state; + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Parse the for statement. Format for the expression is: + * + * for (initial; condition; incr) { + * body; + * } + */ + +static int parseRegFor(Ejs *ep, int state, int flags) +{ + EjsInput *condScript, *endScript, *bodyScript, *incrScript; + + endScript = getInputStruct(ep); + bodyScript = getInputStruct(ep); + incrScript = getInputStruct(ep); + condScript = getInputStruct(ep); + + ejsInitInputState(endScript); + ejsInitInputState(bodyScript); + ejsInitInputState(incrScript); + ejsInitInputState(condScript); + + state = parseForInner(ep, state, flags, + condScript, incrScript, bodyScript, endScript); + + ejsLexFreeInputState(ep, condScript); + ejsLexFreeInputState(ep, incrScript); + ejsLexFreeInputState(ep, endScript); + ejsLexFreeInputState(ep, bodyScript); + + freeInputStruct(ep, condScript); + freeInputStruct(ep, incrScript); + freeInputStruct(ep, endScript); + freeInputStruct(ep, bodyScript); + + return state; +} + +/******************************************************************************/ + +static int parseForInner(Ejs *ep, int state, int flags, EjsInput *condScript, + EjsInput *incrScript, EjsInput *bodyScript, EjsInput *endScript) +{ + int forFlags, cond, rs; + + mprAssert(ep); + + /* + * Evaluate the for loop initialization statement + */ + if ((state = ejsParse(ep, EJS_STATE_STMT, flags)) < 0) { + return state; + } + + /* + * 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 ((rs = ejsParse(ep, EJS_STATE_COND, flags)) < 0) { + return rs; + } + + cond = (ep->result->boolean != 0); + + if (ejsLexGetToken(ep, state) != EJS_TOK_SEMI) { + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + + /* + * Don't execute the loop increment statement or the body + * first time. + */ + forFlags = flags & ~EJS_FLAGS_EXE; + ejsLexSaveInputState(ep, incrScript); + if ((rs = ejsParse(ep, EJS_STATE_EXPR, forFlags)) < 0) { + return rs; + } + + if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + + /* + * Parse the body and remember the end of the body script + */ + ejsLexSaveInputState(ep, bodyScript); + if ((rs = ejsParse(ep, EJS_STATE_STMT, forFlags)) < 0) { + return rs; + } + 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); + + if ((rs = ejsParse(ep, EJS_STATE_STMT, flags)) < 0) { + return rs; + } + + /* + * Evaluate the increment script + */ + ejsLexRestoreInputState(ep, incrScript); + if ((rs = ejsParse(ep, EJS_STATE_EXPR, flags)) < 0) { + return rs; + } + /* + * Evaluate the condition + */ + ejsLexRestoreInputState(ep, condScript); + if ((rs = ejsParse(ep, EJS_STATE_COND, flags)) < 0) { + return 0; + } + mprAssert(ep->result->type == EJS_TYPE_BOOL); + cond = (ep->result->boolean != 0); + } + + ejsLexRestoreInputState(ep, endScript); + + return state; +} + +/******************************************************************************/ +/* + * Create the bare class object + */ + +static int createClass(Ejs *ep, EjsVar *obj, const char *className, + EjsVar *baseClass) +{ + EjsVar *classObj, *existingClass; + + existingClass = ejsGetClass(ep, obj, className); + if (existingClass) { + /* + * We allow partial clases and method redefinition + * FUTURE -- should prevent this if the class is sealed. + * DISABLED Error message and return OK. + */ + /* ejsError(ep, EJS_EVAL_ERROR, "Can't create class %s", className); */ + return 0; + } + + if (baseClass == 0) { + baseClass = ejsGetClass(ep, ep->service->globalClass, "Object"); + mprAssert(baseClass); + } + + classObj = ejsCreateSimpleClass(ep, baseClass, className); + if (classObj == 0) { + ejsMemoryError(ep); + return -1; + } + mprAssert(! ejsObjIsCollectable(classObj)); + + ep->currentProperty = ejsSetPropertyAndFree(ep, obj, className, classObj); + mprAssert(ep->currentProperty); + + if (ep->currentProperty == 0) { + return -1; + } + + return 0; +} + +/******************************************************************************/ +/* + * Local vars for parseTry + */ + +typedef struct ParseTry { + EjsVar *exception; + int tid, caught, rs, catchFlags; +} ParseTry; + +/* + * Parse try block + * + * try {} + */ + +static int parseTry(Ejs *ep, int state, int flags) +{ + ParseTry *sp; + + if ((sp = pushFrame(ep, sizeof(ParseTry))) == 0) { + return EJS_STATE_ERR; + } + + mprAssert(ep); + + sp->caught = 0; + sp->exception = 0; + sp->catchFlags = flags; + + /* + * Execute the code in the try block + */ + sp->rs = ejsParse(ep, EJS_STATE_STMT, flags | EJS_FLAGS_TRY); + if (sp->rs < 0) { + if (sp->rs == EJS_STATE_ERR) { + sp->exception = ejsDupVar(ep, ep->result, EJS_SHALLOW_COPY); + if (sp->exception == 0) { + ejsMemoryError(ep); + goto err; + } + } else { + state = sp->rs; + goto done; + } + + } else { + sp->catchFlags = flags & ~EJS_FLAGS_EXE; + } + + /* + * On success path or when an exception is caught, we must parse all + * catch and finally blocks. + */ + sp->tid = getNextNonSpaceToken(ep, state); + + if (sp->tid == EJS_TOK_CATCH) { + + ep->gotException = 0; + + sp->tid = getNextNonSpaceToken(ep, state); + + if (sp->tid == EJS_TOK_LBRACE) { + /* + * Unqualified "catch " + */ + ejsLexPutbackToken(ep, sp->tid, ep->token); + if (ejsParse(ep, EJS_STATE_STMT, sp->catchFlags) >= 0) { + sp->caught++; + } + + } else if (sp->tid == EJS_TOK_LPAREN) { + + /* + * Qualified "catch (variable) " + */ + if ((sp->rs = ejsParse(ep, EJS_STATE_DEC_LIST, + sp->catchFlags | EJS_FLAGS_CATCH)) < 0) { + ejsSyntaxError(ep, "Bad catch statement"); + state = sp->rs; + goto done; + } + + sp->tid = getNextNonSpaceToken(ep, state); + if (sp->tid != EJS_TOK_RPAREN) { + ejsSyntaxError(ep, 0); + goto err; + } + + if (sp->catchFlags & EJS_FLAGS_EXE) { + if (ep->currentProperty == 0) { + ejsError(ep, EJS_EVAL_ERROR, "Can't define catch variable"); + goto err; + } + + /* + * Set the catch variable + */ + if (ejsWriteVar(ep, + ejsGetVarPtr(ep->currentProperty), sp->exception, + EJS_SHALLOW_COPY) == 0) { + ejsError(ep, EJS_EVAL_ERROR, "Can't update catch variable"); + goto err; + } + } + + /* + * Parse the catch block + */ + if ((sp->rs = ejsParse(ep, EJS_STATE_STMT, sp->catchFlags)) < 0) { + state = sp->rs; + goto done; + } + sp->caught++; + ep->gotException = 0; + } + sp->tid = getNextNonSpaceToken(ep, state); + } + + /* + * Parse the finally block + */ + if (sp->tid == EJS_TOK_FINALLY) { + if (ejsParse(ep, EJS_STATE_STMT, flags) < 0) { + goto err; + } + } else { + ejsLexPutbackToken(ep, sp->tid, ep->token); + } + + /* + * Set the exception value + */ + if (sp->exception && !sp->caught) { + ejsWriteVar(ep, ep->result, sp->exception, EJS_SHALLOW_COPY); + goto err; + } + + state = EJS_STATE_STMT_DONE; + +done: + if (sp->exception) { + ejsFreeVar(ep, sp->exception); + } + + popFrame(ep, sizeof(ParseTry)); + return state; + + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Parse throw statement + * + * throw expression + */ + +static int parseThrow(Ejs *ep, int state, int flags) +{ + int rc; + + mprAssert(ep); + + if ((rc = ejsParse(ep, EJS_STATE_EXPR, flags)) < 0) { + return rc; + } + + + if (flags & EJS_FLAGS_EXE) { + /* + * We have thrown the exception so set the state to ERR + */ + ep->gotException = 1; + return EJS_STATE_ERR; + } + return state; +} + +/******************************************************************************/ +/* + * Parse a class and module declaration + * + * class [extends baseClass] { + * [public | private | ... ] var declarations ... + * [constructor] function declarations ... + * } + * + * Modules are identical except declared with a "module" instead of + * "class". Modules cannot be instantiated and are used for mixins. + * + */ + +static int parseClass(Ejs *ep, int state, int flags) +{ + int originalToken, tid, fid; + + mprAssert(ep); + + originalToken = ep->tid; + + /* + * Parse "class Name [extends BaseClass]" + */ + if (ejsParse(ep, EJS_STATE_DEC_LIST, flags | EJS_FLAGS_CLASS_DEC) < 0) { + return EJS_STATE_ERR; + } + + tid = getNextNonSpaceToken(ep, state); + + if (tid != EJS_TOK_LBRACE) { + return EJS_STATE_ERR; + } + + /* + * After parsing the class body, ep->local will contain the actual + * class/module object. So, we save ep->local by creating a new block. + */ + if (flags & EJS_FLAGS_EXE) { + fid = ejsSetBlock(ep, ejsGetVarPtr(ep->currentProperty)); + ejsSetVarName(ep, ep->local, ep->currentProperty->name); + + } else { + fid = -1; + } + + /* FUTURE -- should prevent modules from being instantiated */ + + /* + * Parse class body + */ + do { + state = ejsParse(ep, EJS_STATE_STMT, flags); + if (state < 0) { + if (fid >= 0) { + ejsCloseBlock(ep, fid); + } + return state; + } + tid = getNextNonSpaceToken(ep, state); + if (tid == EJS_TOK_RBRACE) { + break; + } + ejsLexPutbackToken(ep, tid, ep->token); + + } while (state >= 0); + + if (fid >= 0) { + ejsCloseBlock(ep, fid); + } + + if (tid != EJS_TOK_RBRACE) { + ejsSyntaxError(ep, 0); + state = EJS_STATE_ERR; + } + + return state; +} + +/******************************************************************************/ +/* + * Parse a function declaration + */ + +static int parseFunction(Ejs *ep, int state, int flags) +{ + EjsInput *endScript, *bodyScript; + EjsProperty *pp; + EjsVar *func, *funcProp, *currentObj, *vp, *baseClass; + char *procName; + int varFlags, len, tid, bodyFlags, innerState; + + mprAssert(ep); + + func = 0; + varFlags = 0; + + /* + * method (arg, arg, arg) { body }; + * method name(arg, arg, arg) { body }; + */ + + tid = ejsLexGetToken(ep, state); + + if (tid == EJS_TOK_GET) { + varFlags |= EJS_GET_ACCESSOR; + tid = ejsLexGetToken(ep, state); + + } else if (tid == EJS_TOK_SET) { + varFlags |= EJS_SET_ACCESSOR; + tid = ejsLexGetToken(ep, state); + } + + if (tid == EJS_TOK_ID) { + if (varFlags & EJS_SET_ACCESSOR) { + + if (mprAllocStrcat(MPR_LOC_ARGS(ep), &procName, EJS_MAX_ID + 5, + 0, "-set-", ep->token, 0) < 0) { + ejsError(ep, EJS_SYNTAX_ERROR, + "Name %s is too long", ep->token); + return EJS_STATE_ERR; + } + + } else { + procName = mprStrdup(ep, ep->token); + } + + tid = ejsLexGetToken(ep, state); + + } else { + procName = 0; + } + + if (tid != EJS_TOK_LPAREN) { + mprFree(procName); + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + + /* + * Hand craft the method value structure. + */ + if (flags & EJS_FLAGS_EXE) { + func = ejsCreateMethodVar(ep, 0, 0, 0); + if (func == 0) { + mprFree(procName); + ejsMemoryError(ep); + return EJS_STATE_ERR; + } + func->flags = varFlags; + } + + tid = ejsLexGetToken(ep, state); + while (tid == EJS_TOK_ID) { + if (flags & EJS_FLAGS_EXE) { + mprAddItem(func->method.args, + mprStrdup(func->method.args, 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); + ejsFreeVar(ep, func); + ejsSyntaxError(ep, 0); + 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); + ejsFreeVar(ep, func); + ejsSyntaxError(ep, 0); + return EJS_STATE_ERR; + } + + /* + * Register the method name early to allow for recursive + * method calls (see note in ECMA standard, page 71) + */ + funcProp = 0; + if (flags & EJS_FLAGS_EXE && procName) { + currentObj = pickSpace(ep, 0, procName, flags | EJS_FLAGS_LOCAL); + pp = ejsSetProperty(ep, currentObj, procName, func); + if (pp == 0) { + ejsFreeVar(ep, func); + ejsMemoryError(ep); + return EJS_STATE_ERR; + } + funcProp = ejsGetVarPtr(pp); + } + + + bodyScript = getInputStruct(ep); + + /* + * Parse the method body. Turn execute off. + */ + bodyFlags = flags & ~EJS_FLAGS_EXE; + ejsLexSaveInputState(ep, bodyScript); + + do { + innerState = ejsParse(ep, EJS_STATE_STMT, bodyFlags); + } while (innerState == EJS_STATE_STMT_DONE); + + tid = ejsLexGetToken(ep, state); + + if (innerState != EJS_STATE_STMT_BLOCK_DONE || tid != EJS_TOK_RBRACE) { + mprFree(procName); + ejsFreeVar(ep, func); + ejsLexFreeInputState(ep, bodyScript); + if (innerState != EJS_STATE_ERR) { + ejsSyntaxError(ep, 0); + } + freeInputStruct(ep, bodyScript); + return EJS_STATE_ERR; + } + + if (flags & EJS_FLAGS_EXE) { + endScript = getInputStruct(ep); + ejsLexSaveInputState(ep, endScript); + + /* + * Save the method body between the starting and ending parse + * positions. Overwrite the trailing '}' with a null. + */ + len = endScript->scriptServp - bodyScript->scriptServp; + func->method.body = mprAlloc(ep, len + 1); + memcpy(func->method.body, bodyScript->scriptServp, len); + + if (len <= 0) { + func->method.body[0] = '\0'; + } else { + func->method.body[len - 1] = '\0'; + } + ejsLexFreeInputState(ep, bodyScript); + ejsLexFreeInputState(ep, endScript); + freeInputStruct(ep, endScript); + + /* + * If we are in an assignment, don't register the method name, rather + * return the method structure in the parser result. + */ + if (procName) { + currentObj = pickSpace(ep, 0, procName, flags | EJS_FLAGS_LOCAL); + pp = ejsSetProperty(ep, currentObj, procName, func); + if (pp == 0) { + ejsFreeVar(ep, func); + ejsMemoryError(ep); + return EJS_STATE_ERR; + } + + if (currentObj->objectState->className && + strcmp(currentObj->objectState->className, procName) == 0) { + baseClass = currentObj->objectState->baseClass; + if (baseClass) { + if (strstr(func->method.body, "super(") != 0) { + funcProp->callsSuper = 1; + /* + * Define super() to point to the baseClass constructor + */ + vp = ejsGetPropertyAsVar(ep, baseClass, + baseClass->objectState->className); + if (vp) { + mprAssert(vp); + if (ejsSetProperty(ep, currentObj, "super", + vp) == 0) { + ejsFreeVar(ep, func); + ejsMemoryError(ep); + return EJS_STATE_ERR; + } + } + } + } + } + } + /* + * Always return the function. Try for all stmts to be expressions. + */ + /* MOB - rc */ + ejsWriteVar(ep, ep->result, func, EJS_SHALLOW_COPY); + } + freeInputStruct(ep, bodyScript); + + mprFree(procName); + ejsFreeVar(ep, func); + + return state; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseMethod { + EjsProc proc, *saveProc; + EjsVar *saveObj, *newObj; + int saveObjPerm, rc; + +} ParseMethod; + +/* + * Parse a method name and invoke the method. See parseFunction for + * function declarations. + */ + +static int parseMethod(Ejs *ep, int state, int flags, char *id) +{ + ParseMethod *sp; + + if ((sp = pushFrame(ep, sizeof(ParseMethod))) == 0) { + return EJS_STATE_ERR; + } + + /* + * Must save any current ep->proc value for the current stack frame + * to allow for recursive method calls. + */ + sp->saveProc = (ep->proc) ? ep->proc: 0; + + memset(&sp->proc, 0, sizeof(EjsProc)); + sp->proc.procName = mprStrdup(ep, id); + sp->proc.fn = &ep->currentProperty->var; + sp->proc.args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS); + ep->proc = &sp->proc; + +#if BLD_DEBUG + if (strcmp(sp->proc.procName, "printv") == 0) { + flags |= EJS_FLAGS_TRACE_ARGS; + } +#endif + + if (flags & EJS_FLAGS_EXE) { + ejsClearVar(ep, ep->result); + } + + if (! (flags & EJS_FLAGS_NO_ARGS)) { + sp->saveObj = ep->currentObj; + sp->saveObjPerm = ejsMakeObjPermanent(sp->saveObj, 1); + sp->rc = ejsParse(ep, EJS_STATE_ARG_LIST, flags); + ejsMakeObjPermanent(sp->saveObj, sp->saveObjPerm); + if (sp->rc < 0) { + goto err; + } + ep->currentObj = sp->saveObj; + } + +#if BLD_DEBUG + flags &= ~EJS_FLAGS_TRACE_ARGS; +#endif + + /* + * Evaluate the method if required + */ + if (flags & EJS_FLAGS_EXE) { + if (flags & EJS_FLAGS_NEW) { + sp->newObj = ejsCreateObjUsingArgv(ep, ep->currentObj, + sp->proc.procName, sp->proc.args); + + if (sp->newObj == 0) { + state = EJS_STATE_ERR; + + } else { + mprAssert(! ejsObjIsCollectable(sp->newObj)); + + /* + * Return the newly created object as the result of the + * command. NOTE: newObj may not be an object! + */ + /* MOB - rc */ + ejsWriteVar(ep, ep->result, sp->newObj, EJS_SHALLOW_COPY); + if (ejsVarIsObject(sp->newObj)) { + ejsMakeObjLive(sp->newObj, 1); + mprAssert(ejsObjIsCollectable(sp->newObj)); + mprAssert(ejsBlockInUse(sp->newObj)); + } + ejsFreeVar(ep, sp->newObj); + } + + } else { + + if (evalMethod(ep, ep->currentObj, &sp->proc, flags) < 0) { + /* Methods must call ejsError to set exceptions */ + state = EJS_STATE_ERR; + } + } + } + + if (! (flags & EJS_FLAGS_NO_ARGS)) { + if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { + if (state != EJS_STATE_ERR) { + ejsSyntaxError(ep, 0); + } + state = EJS_STATE_ERR; + } + } + +done: + freeProc(ep, &sp->proc); + ep->proc = sp->saveProc; + + popFrame(ep, sizeof(ParseMethod)); + return state; + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * 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, int *done) +{ + EjsVar *null; + int tid; + + mprFree(*id); + *id = mprStrdup(ep, ep->token); + + if (ep->currentObj == 0) { + /* First identifier segement */ + ep->currentObj = pickSpace(ep, state, *id, flags); + } + + tid = ejsLexGetToken(ep, state); + if (tid == EJS_TOK_ASSIGNMENT) { + flags |= EJS_FLAGS_LHS; + } + + /* + * Find the referenced variable and store it in currentProperty. + */ + if (flags & EJS_FLAGS_EXE) { + ep->currentProperty = searchSpacesForProperty(ep, state, + ep->currentObj, *id, flags); + + /* + * Handle properties that have been deleted inside an enumeration + */ + if (ep->currentProperty && ep->currentProperty->delayedDelete) { + ep->currentProperty = 0; + } + + if (ep->currentProperty && + ejsVarIsMethod(&ep->currentProperty->var) && + tid != EJS_TOK_LPAREN) { + if (ep->currentProperty->var.flags & EJS_GET_ACCESSOR) { + ejsLexPutbackToken(ep, tid, ep->token); + state = parseMethod(ep, state, flags | EJS_FLAGS_NO_ARGS, *id); + if (ep->flags & EJS_FLAGS_EXIT) { + state = EJS_STATE_RET; + } + if (state >= 0) { + ejsSetVarName(ep, ep->result, ep->currentProperty->name); + } + return state; + } + } + /* + * OPT. We should not have to do this always + */ + updateResult(ep, state, flags, ejsGetVarPtr(ep->currentProperty)); + } + + flags &= ~EJS_FLAGS_LHS; + + if (tid == EJS_TOK_LPAREN) { + if (ep->currentProperty == 0 && (flags & EJS_FLAGS_EXE)) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Method name not defined \"%s\"", *id); + return EJS_STATE_ERR; + } + ejsLexPutbackToken(ep, EJS_TOK_METHOD_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; + } + + if (flags & EJS_FLAGS_CLASS_DEC) { + if (tid == EJS_TOK_LBRACE || tid == EJS_TOK_EXTENDS) { + ejsLexPutbackToken(ep, tid, ep->token); + return state; + } + } + + if (flags & EJS_FLAGS_DELETE) { + if (tid == EJS_TOK_RBRACE) { + ejsLexPutbackToken(ep, tid, ep->token); + } + } + + /* + * 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, EJS_REFERENCE_ERROR, + "Variable already defined \"%s\"", *id); + return EJS_STATE_ERR; + } +#endif + /* + * Create or overwrite if it already exists + * Set newly declared variables to the null value. + */ + null = ejsCreateNullVar(ep); + ep->currentProperty = ejsSetPropertyAndFree(ep, ep->currentObj, + *id, null); + ejsClearVar(ep, ep->result); + + } else if (flags & EJS_FLAGS_FORIN) { + /* + * This allows "for (x" when x has not yet been defined + */ + if (ep->currentProperty == 0) { + /* MOB -- return code */ + ep->currentProperty = ejsCreateProperty(ep, + ep->currentObj, *id); + } + + } else if (ep->currentProperty == 0) { + + if (ep->currentObj && ((ep->currentObj == ep->global || + (ep->currentObj == ep->local)))) { + /* + * Test against currentObj and not currentObj->objectState + * as we must allow "i = global.x" and not allow + * "i = x" where x does not exist. + */ + ejsError(ep, EJS_REFERENCE_ERROR, + "Undefined variable \"%s\"", *id); + return EJS_STATE_ERR; + } + + if (flags & EJS_FLAGS_DELETE) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Undefined variable \"%s\"", *id); + return EJS_STATE_ERR; + } + } + } + ejsLexPutbackToken(ep, tid, ep->token); + if (tid == EJS_TOK_RBRACKET || tid == EJS_TOK_COMMA || + tid == EJS_TOK_IN) { + *done = 1; + } + return state; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct ParseIf { + int ifResult, thenFlags, elseFlags, tid, rs; +} ParseIf; + +/* + * Parse an "if" statement + */ + +static int parseIf(Ejs *ep, int state, int flags, int *done) +{ + ParseIf *sp; + + if ((sp = pushFrame(ep, sizeof(ParseIf))) == 0) { + return EJS_STATE_ERR; + } + + if (state != EJS_STATE_STMT) { + goto err; + } + if (ejsLexGetToken(ep, state) != EJS_TOK_LPAREN) { + goto err; + } + + /* + * Evaluate the entire condition list "(condition)" + */ + if (ejsParse(ep, EJS_STATE_COND, flags) < 0) { + goto err; + } + if (ejsLexGetToken(ep, state) != EJS_TOK_RPAREN) { + goto err; + } + + /* + * This is the "then" case. We need to always parse both cases and + * execute only the relevant case. + */ + sp->ifResult = ejsVarToBoolean(ep->result); + if (sp->ifResult) { + sp->thenFlags = flags; + sp->elseFlags = flags & ~EJS_FLAGS_EXE; + } else { + sp->thenFlags = flags & ~EJS_FLAGS_EXE; + sp->elseFlags = flags; + } + + /* + * Process the "then" case. + */ + if ((sp->rs = ejsParse(ep, EJS_STATE_STMT, sp->thenFlags)) < 0) { + if (! ep->gotException) { + state = sp->rs; + goto done; + } + } + + /* + * Check to see if there is an "else" case + */ + removeNewlines(ep, state); + sp->tid = ejsLexGetToken(ep, state); + if (sp->tid != EJS_TOK_ELSE) { + ejsLexPutbackToken(ep, sp->tid, ep->token); + *done = 1; + if (ep->gotException) { + goto err; + } + goto done; + } + + /* + * Process the "else" case. + */ + state = ejsParse(ep, EJS_STATE_STMT, sp->elseFlags); + +done: + *done = 1; + if (ep->gotException) { + state = EJS_STATE_ERR; + } + popFrame(ep, sizeof(ParseIf)); + return state; + + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Parse a postix "++" or "--" statement + */ + +static int parseInc(Ejs *ep, int state, int flags) +{ + EjsVar *one; + + if (! (flags & EJS_FLAGS_EXE)) { + return state; + } + + if (ep->currentProperty == 0) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Undefined variable \"%s\"", ep->token); + return EJS_STATE_ERR; + } + one = ejsCreateIntegerVar(ep, 1); + if (evalExpr(ep, &ep->currentProperty->var, (int) *ep->token, one) < 0) { + ejsFreeVar(ep, one); + return EJS_STATE_ERR; + } + if (ejsWriteVar(ep, &ep->currentProperty->var, ep->result, + EJS_SHALLOW_COPY) < 0) { + ejsError(ep, EJS_IO_ERROR, "Can't write to variable"); + ejsFreeVar(ep, one); + return EJS_STATE_ERR; + } + ejsFreeVar(ep, one); + return state; +} + +/******************************************************************************/ +/* + * Evaluate a condition. Implements &&, ||, !. Returns with a boolean result + * in ep->result. Returns EJS_STATE_ERR on errors, zero if successful. + */ + +static int evalCond(Ejs *ep, EjsVar *lhs, int rel, EjsVar *rhs) +{ + int l, r, lval; + + mprAssert(rel > 0); + + l = ejsVarToBoolean(lhs); + r = ejsVarToBoolean(rhs); + + switch (rel) { + case EJS_COND_AND: + lval = l && r; + break; + case EJS_COND_OR: + lval = l || r; + break; + default: + ejsError(ep, EJS_SYNTAX_ERROR, "Bad operator %d", rel); + return -1; + } + + /* MOB - rc */ + ejsWriteVarAsBoolean(ep, ep->result, lval); + return 0; +} + + +/******************************************************************************/ +/* + * return true if this string is a valid number + */ + +static int stringIsNumber(const char *s) +{ + char *endptr = NULL; + + if (s == NULL || *s == 0) { + return 0; + } + /* MOB -- not ideal */ +#if BREW + /* MOB this should check all digits and not just the first. */ + /* Does not support floating point - easy */ + + if (isdigit(*s) || (*s == '-' && isdigit(s[1]))) { + return 1; + } +#else + strtod(s, &endptr); +#endif + 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, EjsVar *lhs, int rel, EjsVar *rhs) +{ + EjsNum lval; + char *str; + 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. + * + * MOB -- should we do "valueOf" here also. + */ + if (lhs->type == EJS_TYPE_OBJECT && + (rhs->type != EJS_TYPE_OBJECT && + (rhs->type != EJS_TYPE_UNDEFINED && rhs->type != EJS_TYPE_NULL))) { + if (ejsVarIsNumber(rhs)) { + if (ejsRunMethod(ep, lhs, "toValue", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, lhs, ep->result, EJS_SHALLOW_COPY); + } else { + if (ejsRunMethod(ep, lhs, "toString", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, lhs, ep->result, EJS_SHALLOW_COPY); + } + } + + } else { + if (ejsRunMethod(ep, lhs, "toString", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, lhs, ep->result, EJS_SHALLOW_COPY); + } else { + if (ejsRunMethod(ep, lhs, "toValue", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, lhs, ep->result, EJS_SHALLOW_COPY); + } + } + } + /* Nothing more can be done */ + } + + if (rhs->type == EJS_TYPE_OBJECT && + (lhs->type != EJS_TYPE_OBJECT && + (lhs->type != EJS_TYPE_UNDEFINED && lhs->type != EJS_TYPE_NULL))) { + if (ejsVarIsNumber(lhs)) { + /* If LHS is number, then convert to a value first */ + if (ejsRunMethod(ep, rhs, "toValue", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, rhs, ep->result, EJS_SHALLOW_COPY); + } else { + if (ejsRunMethod(ep, rhs, "toString", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, rhs, ep->result, EJS_SHALLOW_COPY); + } + } + + } else { + /* If LHS is not a number, then convert to a string first */ + if (ejsRunMethod(ep, rhs, "toString", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, rhs, ep->result, EJS_SHALLOW_COPY); + + } else { + if (ejsRunMethod(ep, rhs, "toValue", 0) == 0) { + /* MOB - rc */ + ejsWriteVar(ep, rhs, ep->result, EJS_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 == EJS_TYPE_UNDEFINED || + rhs->type == EJS_TYPE_UNDEFINED) { + return evalBoolExpr(ep, + lhs->type == EJS_TYPE_UNDEFINED, + rel, + rhs->type == EJS_TYPE_UNDEFINED); + } + + if (lhs->type == EJS_TYPE_NULL || rhs->type == EJS_TYPE_NULL) { + return evalBoolExpr(ep, + lhs->type == EJS_TYPE_NULL, + rel, + rhs->type == EJS_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 == EJS_TYPE_BOOL && rhs->type == EJS_TYPE_BOOL && + (rel != EJS_EXPR_EQ && rel != EJS_EXPR_NOTEQ && + rel != EJS_EXPR_BOOL_COMP)) { + ejsWriteVarAsNumber(ep, lhs, ejsVarToNumber(lhs)); + } + + /* + * 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 == EJS_TYPE_STRING) { + if (stringIsNumber(lhs->string)) { + ejsWriteVarAsNumber(ep, lhs, ejsVarToNumber(lhs)); + + /* Examine further below */ + + } else { + /* + * Convert the RHS to a string + * MOB rc + */ + str = ejsVarToString(ep, rhs); + ejsWriteVarAsString(ep, rhs, str); + } + +#if BLD_FEATURE_FLOATING_POINT + } else if (lhs->type == EJS_TYPE_FLOAT) { + /* + * Convert rhs to floating + */ + ejsWriteVarAsFloat(ep, rhs, ejsVarToFloat(rhs)); + +#endif +#if BLD_FEATURE_INT64 + } else if (lhs->type == EJS_TYPE_INT64) { + /* + * Convert the rhs to 64 bit + */ + ejsWriteVarAsInteger64(ep, rhs, ejsVarToInteger64(rhs)); +#endif + } else if (lhs->type == EJS_TYPE_BOOL || lhs->type == EJS_TYPE_INT) { + + if (rhs->type == EJS_TYPE_STRING) { + if (stringIsNumber(rhs->string)) { + ejsWriteVarAsNumber(ep, rhs, ejsVarToNumber(rhs)); + } else { + /* + * Convert to lhs to a string + */ + str = ejsVarToString(ep, lhs); + /* MOB -- rc */ + if (str) { + ejsWriteVarAsString(ep, lhs, str); + } + } + +#if BLD_FEATURE_FLOATING_POINT + } else if (rhs->type == EJS_TYPE_FLOAT) { + /* + * Convert lhs to floating + */ + ejsWriteVarAsFloat(ep, lhs, ejsVarToFloat(lhs)); +#endif + + } else { + /* + * Forcibly convert both operands to numbers + */ + ejsWriteVarAsNumber(ep, lhs, ejsVarToNumber(lhs)); + ejsWriteVarAsNumber(ep, rhs, ejsVarToNumber(rhs)); + } + } + } + + /* + * 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 == EJS_TYPE_UNDEFINED || lhs->type == EJS_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 = ! ejsVarToBoolean(rhs); + break; + default: + ejsWriteVar(ep, ep->result, rhs, EJS_SHALLOW_COPY); + return 0; + } + ejsWriteVarAsBoolean(ep, ep->result, lval); + return 0; + } + + /* + * Types are the same here + */ + switch (lhs->type) { + default: + case EJS_TYPE_UNDEFINED: + case EJS_TYPE_NULL: + /* Should be handled above */ + mprAssert(0); + return 0; + + case EJS_TYPE_STRING_CMETHOD: + case EJS_TYPE_CMETHOD: + case EJS_TYPE_METHOD: + case EJS_TYPE_PTR: + ejsWriteVarAsBoolean(ep, ep->result, 0); + return 0; + + case EJS_TYPE_OBJECT: + rc = evalObjExpr(ep, lhs, rel, rhs); + break; + + case EJS_TYPE_BOOL: + rc = evalBoolExpr(ep, lhs->boolean, rel, rhs->boolean); + break; + +#if BLD_FEATURE_FLOATING_POINT + case EJS_TYPE_FLOAT: + rc = evalFloatExpr(ep, lhs->floating, rel, rhs->floating); + break; +#endif + + case EJS_TYPE_INT: + rc = evalNumericExpr(ep, (EjsNum) lhs->integer, rel, + (EjsNum) rhs->integer); + break; + +#if BLD_FEATURE_INT64 + case EJS_TYPE_INT64: + rc = evalNumericExpr(ep, (EjsNum) lhs->integer64, rel, + (EjsNum) rhs->integer64); + break; +#endif + + case EJS_TYPE_STRING: + rc = evalStringExpr(ep, lhs, rel, rhs); + } + + /* MOB */ + if (lhs->type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(lhs, 0); + mprAssert(lhs->objectState->alive == 0); + } + if (rhs->type == EJS_TYPE_OBJECT) { + ejsMakeObjLive(rhs, 0); + mprAssert(rhs->objectState->alive == 0); + } + + return rc; +} + +/******************************************************************************/ +#if BLD_FEATURE_FLOATING_POINT +/* + * Expressions with floating operands + */ + +static int evalFloatExpr(Ejs *ep, double l, int rel, double r) +{ + double lval; + int 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, EJS_SYNTAX_ERROR, "Bad operator %d", rel); + return -1; + } + ejsWriteVarAsBoolean(ep, ep->result, lval != 0); + + } else { + ejsWriteVarAsFloat(ep, ep->result, lval); + } + return 0; +} + +#endif /* BLD_FEATURE_FLOATING_POINT */ +/******************************************************************************/ +/* + * Expressions with object operands + */ + +static int evalObjExpr(Ejs *ep, EjsVar *lhs, int rel, EjsVar *rhs) +{ + int lval; + + switch (rel) { + case EJS_EXPR_EQ: + lval = lhs->objectState == rhs->objectState; + break; + case EJS_EXPR_NOTEQ: + lval = lhs->objectState != rhs->objectState; + break; + default: + ejsError(ep, EJS_SYNTAX_ERROR, "Bad operator %d", rel); + return -1; + } + ejsWriteVarAsBoolean(ep, ep->result, lval); + return 0; +} + +/******************************************************************************/ +/* + * Expressions with boolean operands + */ + +static int evalBoolExpr(Ejs *ep, int l, int rel, int r) +{ + int 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, EJS_SYNTAX_ERROR, "Bad operator %d", rel); + return -1; + } + ejsWriteVarAsBoolean(ep, ep->result, lval); + return 0; +} + +/******************************************************************************/ +/* + * Expressions with numeric operands + */ + +static int evalNumericExpr(Ejs *ep, EjsNum l, int rel, EjsNum r) +{ + EjsNum lval; + int 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, EJS_RANGE_ERROR, "Divide by zero"); + return -1; + } + break; + case EJS_EXPR_MOD: + if (r != 0) { + lval = l % r; + } else { + ejsError(ep, EJS_RANGE_ERROR, "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, EJS_SYNTAX_ERROR, "Bad operator %d", rel); + return -1; + } + ejsWriteVarAsBoolean(ep, ep->result, lval != 0); + + } else { + ejsWriteVarAsNumber(ep, ep->result, lval); + } + return 0; +} + +/******************************************************************************/ +/* + * Expressions with string operands + */ + +static int evalStringExpr(Ejs *ep, EjsVar *lhs, int rel, EjsVar *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. + */ + ejsClearVar(ep, ep->result); + ejsStrcat(ep, ep->result, lhs); + ejsStrcat(ep, 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: + ejsSyntaxError(ep, "Bad operator"); + return -1; + } + + ejsWriteVarAsBoolean(ep, ep->result, lval); + return 0; +} + +/******************************************************************************/ +/* + * Evaluate a method. obj is set to the current object if a method is being + * run. + */ + +static int evalMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, int flags) +{ + EjsProperty *pp; + EjsVar *saveThis, *prototype; + int saveThisPerm, rc, fid; + + mprAssert(ep); + + rc = 0; + fid = -1; + saveThis = 0; + saveThisPerm = 0; + prototype = proc->fn; + + if (prototype == 0) { + ejsError(ep, EJS_EVAL_ERROR, "Undefined method"); + return EJS_STATE_ERR; + } + + if (prototype->type == EJS_TYPE_OBJECT) { + prototype = ejsGetPropertyAsVar(ep, prototype, proc->procName); + } + + if (prototype) { + /* + * Create a new variable stack frame. ie. new local variables. + * Some C methods (eg. include) don't create a new local context. + */ + if (! (prototype->flags & EJS_NO_LOCAL)) { + fid = ejsOpenBlock(ep); + if (fid < 0) { + return EJS_STATE_ERR; + } + mprAssert(ejsBlockInUse(ep->local)); + + pp = ejsSetProperty(ep, ep->local, "this", obj); + ejsMakePropertyEnumerable(pp, 0); + + /* + * Optimization. Save "this" during this block. + */ + saveThis = ep->thisObject; + ep->thisObject = ejsGetVarPtr(pp); + saveThisPerm = ejsMakeObjPermanent(saveThis, 1); + } + + switch (prototype->type) { + default: + mprAssert(0); + break; + + case EJS_TYPE_STRING_CMETHOD: + rc = callStringCMethod(ep, obj, proc, prototype); + break; + + case EJS_TYPE_CMETHOD: + rc = callCMethod(ep, obj, proc, prototype); + break; + + case EJS_TYPE_METHOD: + rc = callMethod(ep, obj, proc, prototype); + break; + } + + if (fid >= 0) { + ejsMakeObjPermanent(saveThis, saveThisPerm); + ep->thisObject = saveThis; + mprAssert(ejsBlockInUse(ep->local)); + mprAssert(ejsBlockInUse(ep->thisObject)); + ejsCloseBlock(ep, fid); + } + } + + return rc; +} + +/******************************************************************************/ +/* + * Create a new object and call all required constructors. + * obj may be null in which case we look globally for className. + */ + +EjsVar *ejsCreateObjUsingArgvInternal(EJS_LOC_DEC(ep, loc), EjsVar *obj, + const char *className, MprArray *args) +{ + EjsVar *baseClass, *objectClass, *thisObj; + int rc; + + mprAssert(className && *className); + + /* + * Create a new object of the required class and pass it into the + * constructor as the "this" local variable. + */ + baseClass = ejsGetClass(ep, obj, className); + if (baseClass == 0) { + + if (obj && obj->objectState->className && + strcmp(obj->objectState->className, className) == 0) { + /* + * Handle case where we are calling the constructor inside + * the class. In this case, obj == baseClass. + */ + thisObj = ejsCreateSimpleObjUsingClassInt(EJS_LOC_PASS(ep, loc), + obj); + + } else { + + /* + * If the baseClass does not exist, try to create an Object + * We do this for compatibility with JS 1.5 style new Function. + * MOB -- but this masks an error if we really need className. + */ + objectClass = ejsGetClass(ep, 0, "Object"); + thisObj = ejsCreateSimpleObjUsingClassInt(EJS_LOC_PASS(ep, loc), + objectClass); + } + + } else { + thisObj = ejsCreateSimpleObjUsingClassInt(EJS_LOC_PASS(ep, loc), + baseClass); + } + + if (thisObj == 0) { + ejsMemoryError(ep); + return 0; + } + + /* + * Make the object permanent. While currently not alive, the constructor + * below may make the object alive. + */ + ejsMakeObjPermanent(thisObj, 1); + mprAssert(! ejsObjIsCollectable(thisObj)); + + rc = 0; + if (baseClass) { + if (! baseClass->objectState->noConstructor) { + rc = callConstructor(ep, thisObj, baseClass, args); + } + } else { + /* + * className is the function name when calling new on functions + */ + rc = ejsRunMethod(ep, thisObj, className, args); + } + + /* + * Constructor may change the type to a non-object. + * Function() does this. Ensure object is not collectable yet. + */ + if (ejsVarIsObject(thisObj)) { + ejsMakeObjPermanent(thisObj, 0); + ejsMakeObjLive(thisObj, 0); + } + + if (rc < 0) { + if (rc == MPR_ERR_NOT_FOUND) { + /* No constructor (default) */ + return thisObj; + } + if (! (ep->flags & EJS_FLAGS_EXIT)) { + if (! ep->gotException) { + ejsMemoryError(ep); + } + } + ejsFreeVar(ep, thisObj); + return 0; + } + + mprAssert(ejsBlockInUse(thisObj)); + + return thisObj; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct CallCons { + EjsVar *subClassConstructor, *subClass, *method; +} CallCons; + +/* + * Create a new object and call all required constructors. + */ + +static int callConstructor(Ejs *ep, EjsVar *thisObj, EjsVar *baseClass, + MprArray *args) +{ + CallCons *sp; + int state; + + if ((sp = pushFrame(ep, sizeof(CallCons))) == 0) { + return EJS_STATE_ERR; + } + + mprAssert(baseClass); + mprAssert(baseClass->objectState); + + state = 0; + + /* + * method will be null if there is no constructor for this class + */ + sp->method = ejsGetPropertyAsVar(ep, baseClass, + baseClass->objectState->className); + + if (sp->method == 0 || !ejsVarIsMethod(sp->method) || + !sp->method->callsSuper) { + /* + * Invoke base class constructors in reverse order (RECURSIVE) + */ + sp->subClass = baseClass->objectState->baseClass; + if (sp->subClass) { + + /* + * Note that the Object class does not have a constructor for + * speed. Construction for the base Object is done via + * ejsCreateObj above. The code below will invoke constructors + * in the right order (bottom up) via recursion. MOB -- need to + * scan for super() MOB -- Bug. Fails poorly if no constructor. + * Should allows this and invoke a default constructor. + */ + sp->subClassConstructor = ejsGetPropertyAsVar(ep, sp->subClass, + sp->subClass->objectState->className); + + if (sp->subClassConstructor) { + + if (callConstructor(ep, thisObj, sp->subClass, 0) < 0) { + if (! ep->gotException) { + ejsMemoryError(ep); + } + goto err; + } + } + } + } + + if (sp->method) { + /* + * Finally, invoke the constructor for this class itself. + */ + state = runMethod(ep, thisObj, sp->method, + baseClass->objectState->className, args); + } + +done: + popFrame(ep, sizeof(CallCons)); + return state; + +err: + state = EJS_STATE_ERR; + goto done; +} + +/******************************************************************************/ +/* + * Create a new object and call all required constructors using string args. + * MOB -- would be good to parse constructorArgs for "," and break into + * separate args. + * Returned object is not yet collectable. Will have alive bit cleared. + */ + +EjsVar *ejsCreateObj(Ejs *ep, EjsVar *obj, const char *className, + const char *constructorArgs) +{ + MprArray *args; + EjsVar *newp, *vp; + + args = mprCreateItemArray(ep, 0, 0); + if (args == 0) { + return 0; + } + + if (constructorArgs && *constructorArgs) { + vp = ejsCreateStringVarInternal(EJS_LOC_ARGS(ep), constructorArgs); + + if (mprAddItem(args, vp) < 0) { + mprFree(args); + return 0; + } + } + + newp = ejsCreateObjUsingArgv(ep, obj, className, args); + + ejsFreeMethodArgs(ep, args); + + mprAssert(! ejsObjIsCollectable(newp)); + mprAssert(ejsBlockInUse(newp)); + + return newp; +} + +/******************************************************************************/ + +static int callStringCMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, + EjsVar *prototype) +{ + EjsVar **argValues; + MprArray *actualArgs; + char **argBuf, *str; + int i, rc; + + actualArgs = proc->args; + argValues = (EjsVar**) actualArgs->items; + + if (actualArgs->length > 0) { + argBuf = mprAlloc(ep, actualArgs->length * sizeof(char*)); + for (i = 0; i < actualArgs->length; i++) { + str = ejsVarToString(ep, argValues[i]); + /* MOB rc */ + argBuf[i] = mprStrdup(ep, str); + } + } else { + argBuf = 0; + } + + /* + * Call the method depending on the various handle flags + */ + ep->userData = prototype->cMethodWithStrings.userData; + if (prototype->flags & EJS_ALT_HANDLE) { + /* + * Used by the AppWeb GaCompat module. The alt handle is set to the + * web server request struct + */ + rc = ((EjsAltStringCMethod) + prototype->cMethodWithStrings.fn) + (ep, ep->altHandle, obj, actualArgs->length, argBuf); + + } else if (prototype->flags & EJS_PRIMARY_HANDLE) { + /* + * Used by ESP. The primary handle is set to the esp struct + */ + rc = (prototype->cMethodWithStrings.fn)(ep->primaryHandle, + obj, actualArgs->length, argBuf); + + } else { + /* + * Used EJS for the standard procs + */ + rc = (prototype->cMethodWithStrings.fn)(ep, obj, actualArgs->length, + argBuf); + } + + if (actualArgs->length > 0) { + for (i = 0; i < actualArgs->length; i++) { + mprFree(argBuf[i]); + } + mprFree(argBuf); + } + ep->userData = 0; + + return rc; +} + +/******************************************************************************/ + +static int callCMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, EjsVar *prototype) +{ + EjsVar **argValues; + MprArray *actualArgs; + int rc; + + actualArgs = proc->args; + argValues = (EjsVar**) actualArgs->items; + + ep->userData = prototype->cMethod.userData; + + /* + * Call the method depending on the various handle flags + * Sometimes cMethod.fn is NULL if there is no constructor for + * an object. + */ + if (prototype->flags & EJS_ALT_HANDLE) { + /* + * Use by the GaCompat module. The alt handle is set to the + * web server request struct + */ + rc = ((EjsAltCMethod) prototype->cMethod.fn) + (ep, ep->altHandle, obj, actualArgs->length, argValues); + + } else if (prototype->flags & EJS_PRIMARY_HANDLE) { + /* + * Used by ESP. The primary handle is set to the esp struct + */ + rc = (prototype->cMethod.fn) + (ep->primaryHandle, obj, actualArgs->length, argValues); + + } else { + /* + * Used EJS for the standard procs + */ + rc = (prototype->cMethod.fn)(ep, obj, actualArgs->length, argValues); + } + + ep->userData = 0; + + return rc; +} + +/******************************************************************************/ +/* + * Local vars + */ + +typedef struct CallMethod { + MprArray *formalArgs, *actualArgs; + EjsVar *arguments, *callee, **argValues; + char **argNames, buf[16]; + int i, argumentsObj; +} CallMethod; + + +static int callMethod(Ejs *ep, EjsVar *obj, EjsProc *proc, EjsVar *prototype) +{ + CallMethod *sp; + int i; + + if ((sp = pushFrame(ep, sizeof(CallMethod))) == 0) { + return EJS_STATE_ERR; + } + + sp->arguments = 0; + sp->callee = 0; + + sp->actualArgs = proc->args; + sp->argValues = (EjsVar**) sp->actualArgs->items; + sp->formalArgs = prototype->method.args; + sp->argNames = (char**) sp->formalArgs->items; + + /* + * Only create arguments and callee if the function actually uses them + */ + sp->argumentsObj = 0; + if (strstr(prototype->method.body, "arguments") != 0) { + sp->argumentsObj++; + + /* + * Create the arguments and callee variables + * MOB -- should we make real arrays here ? YES + */ + sp->arguments = ejsCreateSimpleObj(ep, "Object"); + ejsSetVarName(ep, sp->arguments, "arguments"); + mprAssert(! ejsObjIsCollectable(sp->arguments)); + + sp->callee = ejsCreateSimpleObj(ep, "Object"); + ejsSetVarName(ep, sp->callee, "callee"); + mprAssert(! ejsObjIsCollectable(sp->callee)); + + /* + * Overwrite the length property + */ + ejsSetPropertyToInteger(ep, sp->arguments, "length", + sp->actualArgs->length); + ejsSetPropertyToInteger(ep, sp->callee, "length", + sp->formalArgs->length); + } + + /* + * Define all the agruments to be set to the actual parameters + */ + for (i = 0; i < sp->formalArgs->length; i++) { + if (i >= sp->actualArgs->length) { + /* MOB -- return code */ + ejsCreateProperty(ep, ep->local, sp->argNames[i]); + + } else { + /* MOB -- return code */ + ejsSetProperty(ep, ep->local, sp->argNames[i], sp->argValues[i]); + } + } + + if (sp->argumentsObj) { + for (i = 0; i < sp->actualArgs->length; i++) { + mprItoa(sp->buf, sizeof(sp->buf), i); + ejsSetProperty(ep, sp->arguments, sp->buf, sp->argValues[i]); + } + + ejsSetPropertyAndFree(ep, sp->arguments, "callee", sp->callee); + ejsSetPropertyAndFree(ep, ep->local, "arguments", sp->arguments); + } + + /* + * Actually run the method + */ + + i = ejsEvalScript(ep, prototype->method.body, 0); + + popFrame(ep, sizeof(CallMethod)); + return i; +} + +/******************************************************************************/ +/* + * Run a method. Obj is set to "this" object. MethodName must exist in it + * or in a sub class. + */ + +int ejsRunMethod(Ejs *ep, EjsVar *obj, const char *methodName, MprArray *args) +{ + EjsProperty *pp; + EjsProc proc, *saveProc; + int rc; + + mprAssert(obj); + mprAssert(methodName && *methodName); + + pp = ejsGetProperty(ep, obj, methodName); + if (pp == 0) { + /* MOB -- this should be all in some common accessor routine */ + pp = ejsGetProperty(ep, ep->local, methodName); + if (pp == 0) { + pp = ejsGetProperty(ep, ep->global, methodName); + if (pp == 0) { + ejsError(ep, EJS_REFERENCE_ERROR, + "Undefined method \"%s\"", methodName); + return MPR_ERR_NOT_FOUND; + } + } + } + + saveProc = ep->proc; + ep->proc = &proc; + + memset(&proc, 0, sizeof(EjsProc)); + + ejsClearVar(ep, ep->result); + + /* MOB -- if closures are going to work, we need to have proc be an + Object and let the GC look after it */ + + proc.fn = &pp->var; + if (proc.fn == 0 || proc.fn->type == EJS_TYPE_UNDEFINED) { + ep->proc = saveProc; + return MPR_ERR_NOT_FOUND; + } + + proc.procName = mprStrdup(ep, methodName); + if (args == 0) { + proc.args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS); + } else { + proc.args = args; + } + + rc = evalMethod(ep, obj, &proc, 0); + + if (args) { + proc.args = 0; + } + freeProc(ep, &proc); + + ep->proc = saveProc; + + return rc; +} + +/******************************************************************************/ +/* + * Run a method. Obj is set to "this" object. MethodName must exist in it + * or in a sub class. + */ + +int ejsRunMethodCmd(Ejs *ep, EjsVar *obj, const char *methodName, + const char *cmdFmt, ...) +{ + MprArray *args; + va_list cmdArgs; + char *buf, *arg, *cp; + int rc; + + mprAssert(methodName && *methodName); + mprAssert(cmdFmt && *cmdFmt); + + va_start(cmdArgs, cmdFmt); + mprAllocVsprintf(MPR_LOC_ARGS(ep), &buf, 0, cmdFmt, cmdArgs); + va_end(cmdArgs); + + args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS); + + for (arg = cp = buf; cp && *cp; cp++) { + if (*cp == ',') { + *cp = 0; + mprAddItem(args, ejsParseVar(ep, arg, 0)); + arg = cp + 1; + } + } + if (cp > arg) { + mprAddItem(args, ejsParseVar(ep, arg, 0)); + } + + rc = ejsRunMethod(ep, obj, methodName, args); + + ejsFreeMethodArgs(ep, args); + mprFree(buf); + + return rc; +} + +/******************************************************************************/ +/* + * Run a method. Obj is set to "this" object. + */ + +static int runMethod(Ejs *ep, EjsVar *thisObj, EjsVar *method, + const char *methodName, MprArray *args) +{ + EjsProc proc, *saveProc; + int rc; + + mprAssert(thisObj); + mprAssert(method); + + saveProc = ep->proc; + ep->proc = &proc; + + memset(&proc, 0, sizeof(EjsProc)); + + ejsClearVar(ep, ep->result); + + /* MOB -- if closures are going to work, we need to have proc be an + Object and let the GC look after it */ + + proc.fn = method; + if (proc.fn == 0 || proc.fn->type == EJS_TYPE_UNDEFINED) { + ep->proc = saveProc; + return MPR_ERR_NOT_FOUND; + } + + proc.procName = mprStrdup(ep, methodName); + if (args == 0) { + proc.args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS); + } else { + proc.args = args; + } + + rc = evalMethod(ep, thisObj, &proc, 0); + + if (args) { + proc.args = 0; + } + freeProc(ep, &proc); + + ep->proc = saveProc; + + return rc; +} + +/******************************************************************************/ +/* + * Find which object contains the property given the current context. + * We call this when there is no explicit object and the object must be + * determined by the context. + */ + +static EjsVar *pickSpace(Ejs *ep, int state, const char *property, int flags) +{ + EjsVar *obj; + + mprAssert(ep); + mprAssert(property && *property); + + /* MOB - this is ugly and the logic is confused */ + + 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 this and finally global */ + + if (ejsGetSimpleProperty(ep, ep->local, property)) { + obj = ep->local; + + } else if (ep->thisObject && + findProperty(ep, ep->thisObject, property, flags)) { + obj = ep->thisObject; + + } else { +#if EJS_ECMA_STND + obj = ep->global; +#else + if (flags & EJS_FLAGS_EXE && + !findProperty(ep, ep->global, property, flags)) { + obj = ep->local; + } else { + obj = ep->global; + } +#endif + } + } + 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 method + * names only. Returns the property or NULL. + * MOB -- need to rework this API. + */ + +static EjsProperty *searchSpacesForProperty(Ejs *ep, int state, EjsVar *obj, + char *property, int flags) +{ + EjsProperty *pp; + + if (obj) { + return findProperty(ep, obj, property, flags); + } + + /* MOB -- really should have a search stack */ + + pp = findProperty(ep, ep->local, property, flags); + if (pp == 0 && state != EJS_STATE_DEC) { + + if (ep->thisObject) { + pp = findProperty(ep, ep->thisObject, property, flags); + } + if (pp == 0) { + pp = findProperty(ep, ep->global, property, flags); + } + } + return pp; +} + +/******************************************************************************/ +/* + * Search an object and its base classes to find an object given an object + * an a property name. If not an assignment (LHS), then follow base classes. + * Otherwise, just look in the specified object. + */ + +static EjsProperty *findProperty(Ejs *ep, EjsVar *op, const char *property, + int flags) +{ + /* MOB -- NEW. Remove when EXE fixes are in. */ + if (! (flags & EJS_FLAGS_EXE) && op->type == EJS_TYPE_UNDEFINED) { + return 0; + } + + if (flags & EJS_FLAGS_LHS) { + return ejsGetPropertyPtr(ejsGetSimpleProperty(ep, op, property)); + + } else { + /* + * Follow base classes + */ + return ejsGetPropertyPtr(ejsGetProperty(ep, op, property)); + } +} + +/******************************************************************************/ +/* + * Update result + */ + +static void updateResult(Ejs *ep, int state, int flags, EjsVar *vp) +{ + if (flags & EJS_FLAGS_EXE && state != EJS_STATE_DEC) { + ejsClearVar(ep, ep->result); + if (vp) { + ejsWriteVar(ep, ep->result, vp, EJS_SHALLOW_COPY); + ejsSetVarName(ep, ep->result, vp->propertyName); + } + } +} + +/******************************************************************************/ +/* + * Append to the pointer value + */ + +int ejsStrcat(Ejs *ep, EjsVar *dest, EjsVar *src) +{ + char *oldBuf, *buf, *str; + int oldLen, newLen, len; + + mprAssert(dest); + mprAssert(ejsVarIsString(src)); + + if (ejsVarIsValid(dest)) { + + if (! ejsVarIsString(dest)) { + /* Bad type for dest */ + return -1; + } + + if (! ejsVarIsString(src)) { + str = ejsVarToString(ep, src); + if (str == 0) { + return -1; + } + len = strlen(str); + + } else { + str = src->string; + len = src->length; + } + + oldBuf = dest->string; + oldLen = dest->length; + newLen = oldLen + len + 1; + + if (newLen < MPR_SLAB_STR_MAX) { + buf = oldBuf; + } else { + buf = mprRealloc(ep, oldBuf, newLen); + if (buf == 0) { + return -1; + } + dest->string = buf; + } + memcpy(&buf[oldLen], str, len); + dest->length += len; + + } else { + ejsWriteVarAsString(ep, dest, src->string); + } + return 0; +} + +/******************************************************************************/ +/* + * Exit the script + */ + +void ejsExit(Ejs *ep, int status) +{ + ep->scriptStatus = status; + ep->flags |= EJS_FLAGS_EXIT; +} + +/******************************************************************************/ +/* + * Free an argument list + */ + +static void freeProc(Ejs *ep, EjsProc *proc) +{ + if (proc->args) { + ejsFreeMethodArgs(ep, proc->args); + } + + if (proc->procName) { + mprFree(proc->procName); + proc->procName = NULL; + } +} + +/******************************************************************************/ + +void ejsFreeMethodArgs(Ejs *ep, MprArray *args) +{ + int i; + + for (i = args->length - 1; i >= 0; i--) { + ejsFreeVar(ep, args->items[i]); + mprRemoveItemByIndex(args, i); + } + mprFree(args); +} + +/******************************************************************************/ +/* + * This method 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); +} + +/******************************************************************************/ + +static int getNextNonSpaceToken(Ejs *ep, int state) +{ + int tid; + + do { + tid = ejsLexGetToken(ep, state); + } while (tid == EJS_TOK_NEWLINE); + return tid; +} + +/******************************************************************************/ + +int ejsGetFlags(Ejs *ep) +{ + return ep->flags; +} + +/******************************************************************************/ + +bool ejsIsExiting(Ejs *ep) +{ + return (ep->flags & EJS_FLAGS_EXIT) ? 1: 0; +} + +/******************************************************************************/ + +void ejsClearExiting(Ejs *ep) +{ + ep->flags &= ~EJS_FLAGS_EXIT; +} + +/******************************************************************************/ + +static EjsInput *getInputStruct(Ejs *ep) +{ + EjsInput *input; + + if (ep->inputList) { + input = ep->inputList; + ep->inputList = input->nextInput; + + } else { + input = mprAlloc(ep, sizeof(EjsInput)); + } + return input; +} + +/******************************************************************************/ + +static void freeInputStruct(Ejs *ep, EjsInput *input) +{ + input->nextInput = ep->inputList; + ep->inputList = input; +} + +/******************************************************************************/ + +static void *pushFrame(Ejs *ep, int size) +{ + /* + * Grow down stack + */ + ep->stkPtr -= size; + if (ep->stkPtr < ep->stack) { + mprError(ep, MPR_LOC, "Exceeded parse stack"); + return 0; + } + return ep->stkPtr; +} + +/******************************************************************************/ + +static void *popFrame(Ejs *ep, int size) +{ + ep->stkPtr += size; + if (ep->stkPtr > &ep->stack[EJS_MAX_STACK]) { + mprError(ep, MPR_LOC, "Over poped parse stack"); + return 0; + } + return ep->stkPtr; +} + +/******************************************************************************/ +#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 + */ -- cgit