mirror of
https://github.com/samba-team/samba.git
synced 2025-12-13 16:23:50 +03:00
4515 lines
100 KiB
C
4515 lines
100 KiB
C
/*
|
|
* @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
|
|
*/
|