summaryrefslogtreecommitdiff
path: root/source4/lib/appweb/ejs-2.0/ejs/ejsParser.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/lib/appweb/ejs-2.0/ejs/ejsParser.c')
-rw-r--r--source4/lib/appweb/ejs-2.0/ejs/ejsParser.c4514
1 files changed, 4514 insertions, 0 deletions
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 = <xmlTag> .../</xmlTag>
+ */
+ 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 <name> [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 <name>(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
+ */