From 121bb5fcdfb3bc5f3586a0937e1b24885236b53d Mon Sep 17 00:00:00 2001 From: geniusgogo Date: Tue, 25 Feb 2014 01:47:49 +0800 Subject: add JavaScript Interpreter --- components/external/espruino/src/jsparse.c | 2213 ++++++++++++++++++++++++++++ 1 file changed, 2213 insertions(+) create mode 100644 components/external/espruino/src/jsparse.c (limited to 'components/external/espruino/src/jsparse.c') diff --git a/components/external/espruino/src/jsparse.c b/components/external/espruino/src/jsparse.c new file mode 100644 index 0000000000..fb7d42a068 --- /dev/null +++ b/components/external/espruino/src/jsparse.c @@ -0,0 +1,2213 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Recursive descent parser for code execution + * ---------------------------------------------------------------------------- + */ +#include "jsparse.h" +#include "jsinteractive.h" +#include "jswrapper.h" + +/* Info about execution when Parsing - this saves passing it on the stack + * for each call */ +JsExecInfo execInfo; + +// ----------------------------------------------- Forward decls +JsVar *jspeBase(); +JsVar *jspeBaseWithComma(); +JsVar *jspeBlock(); +JsVar *jspeStatement(); +// ----------------------------------------------- Utils +#define JSP_MATCH_WITH_CLEANUP_AND_RETURN(TOKEN, CLEANUP_CODE, RETURN_VAL) { if (!jslMatch(execInfo.lex,(TOKEN))) { jspSetError(); CLEANUP_CODE; return RETURN_VAL; } } +#define JSP_MATCH_WITH_RETURN(TOKEN, RETURN_VAL) JSP_MATCH_WITH_CLEANUP_AND_RETURN(TOKEN, , RETURN_VAL) +#define JSP_MATCH(TOKEN) JSP_MATCH_WITH_CLEANUP_AND_RETURN(TOKEN, , 0) +#define JSP_SHOULD_EXECUTE (((execInfo.execute)&EXEC_RUN_MASK)==EXEC_YES) +#define JSP_SAVE_EXECUTE() JsExecFlags oldExecute = execInfo.execute +#define JSP_RESTORE_EXECUTE() execInfo.execute = (execInfo.execute&(JsExecFlags)(~EXEC_SAVE_RESTORE_MASK)) | (oldExecute&EXEC_SAVE_RESTORE_MASK); +#define JSP_HAS_ERROR (((execInfo.execute)&EXEC_ERROR_MASK)!=0) + +/// if interrupting execution, this is set +bool jspIsInterrupted() { + return (execInfo.execute & EXEC_INTERRUPTED)!=0; +} + +/// if interrupting execution, this is set +void jspSetInterrupted(bool interrupt) { + if (interrupt) + execInfo.execute = execInfo.execute | EXEC_INTERRUPTED; + else + execInfo.execute = execInfo.execute & (JsExecFlags)~EXEC_INTERRUPTED; +} + +static inline void jspSetError() { + execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_YES) | EXEC_ERROR; +} + +bool jspHasError() { + return JSP_HAS_ERROR; +} + +///< Same as jsvSetValueOfName, but nice error message +void jspReplaceWith(JsVar *dst, JsVar *src) { + // If this is an index in an array buffer, write directly into the array buffer + if (jsvIsArrayBufferName(dst)) { + JsVarInt idx = jsvGetInteger(dst); + JsVar *arrayBuffer = jsvLock(dst->firstChild); + jsvArrayBufferSet(arrayBuffer, idx, src); + jsvUnLock(arrayBuffer); + return; + } + // if destination isn't there, isn't a 'name', or is used, give an error + if (!jsvIsName(dst)) { + jsErrorAt("Unable to assign value to non-reference", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + return; + } + jsvSetValueOfName(dst, src); +} + +void jspeiInit(JsParse *parse, JsLex *lex) { + execInfo.parse = parse; + execInfo.lex = lex; + execInfo.scopeCount = 0; + execInfo.execute = EXEC_YES; +} + +void jspeiKill() { + execInfo.parse = 0; + execInfo.lex = 0; + assert(execInfo.scopeCount==0); +} + +bool jspeiAddScope(JsVarRef scope) { + if (execInfo.scopeCount >= JSPARSE_MAX_SCOPES) { + jsError("Maximum number of scopes exceeded"); + jspSetError(); + return false; + } + execInfo.scopes[execInfo.scopeCount++] = jsvRefRef(scope); + return true; +} + +void jspeiRemoveScope() { + if (execInfo.scopeCount <= 0) { + jsErrorInternal("Too many scopes removed"); + jspSetError(); + return; + } + jsvUnRefRef(execInfo.scopes[--execInfo.scopeCount]); +} + +JsVar *jspeiFindInScopes(const char *name) { + int i; + for (i=execInfo.scopeCount-1;i>=0;i--) { + JsVar *ref = jsvFindChildFromStringRef(execInfo.scopes[i], name, false); + if (ref) return ref; + } + return jsvFindChildFromString(execInfo.parse->root, name, false); +} + +JsVar *jspeiFindOnTop(const char *name, bool createIfNotFound) { + if (execInfo.scopeCount>0) + return jsvFindChildFromStringRef(execInfo.scopes[execInfo.scopeCount-1], name, createIfNotFound); + return jsvFindChildFromString(execInfo.parse->root, name, createIfNotFound); +} +JsVar *jspeiFindNameOnTop(JsVar *childName, bool createIfNotFound) { + if (execInfo.scopeCount>0) + return jsvFindChildFromVarRef(execInfo.scopes[execInfo.scopeCount-1], childName, createIfNotFound); + return jsvFindChildFromVar(execInfo.parse->root, childName, createIfNotFound); +} + +/** Here we assume that we have already looked in the parent itself - + * and are now going down looking at the stuff it inherited */ +JsVar *jspeiFindChildFromStringInParents(JsVar *parent, const char *name) { + if (jsvIsObject(parent)) { + // If an object, look for an 'inherits' var + JsVar *inheritsFrom = jsvObjectGetChild(parent, JSPARSE_INHERITS_VAR, 0); + + // if there's no inheritsFrom, just default to 'Object.prototype' + if (!inheritsFrom) { + JsVar *obj = jsvObjectGetChild(execInfo.parse->root, "Object", 0); + if (obj) { + inheritsFrom = jsvObjectGetChild(obj, JSPARSE_PROTOTYPE_VAR, 0); + jsvUnLock(obj); + } + } + + if (inheritsFrom && inheritsFrom!=parent) { + // we have what it inherits from (this is ACTUALLY the prototype var) + // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/proto + JsVar *child = jsvFindChildFromString(inheritsFrom, name, false); + if (!child) + child = jspeiFindChildFromStringInParents(inheritsFrom, name); + jsvUnLock(inheritsFrom); + if (child) return child; + } + } else { // Not actually an object - but might be an array/string/etc + const char *objectName = jswGetBasicObjectName(parent); + while (objectName) { + JsVar *objName = jsvFindChildFromString(execInfo.parse->root, objectName, false); + if (objName) { + JsVar *result = 0; + JsVar *obj = jsvSkipNameAndUnLock(objName); + if (obj) { + // We have found an object with this name - search for the prototype var + JsVar *proto = jsvObjectGetChild(obj, JSPARSE_PROTOTYPE_VAR, 0); + if (proto) { + result = jsvFindChildFromString(proto, name, false); + jsvUnLock(proto); + } + jsvUnLock(obj); + } + if (result) return result; + } + /* We haven't found anything in the actual object, we should check the 'Object' itself + eg, we tried 'String', so now we should try 'Object'. Built-in types don't have room for + a prototype field, so we hard-code it */ + objectName = jswGetBasicObjectPrototypeName(objectName); + } + } + + // no luck! + return 0; +} + +JsVar *jspeiGetScopesAsVar() { + if (execInfo.scopeCount==0) return 0; + JsVar *arr = jsvNewWithFlags(JSV_ARRAY); + int i; + for (i=0;ifirstChild); + return arr; +} + +void jspeiLoadScopesFromVar(JsVar *arr) { + execInfo.scopeCount = 0; + //printf("%d\n",arr->firstChild); + JsVarRef childref = arr->firstChild; + while (childref) { + JsVar *child = jsvLock(childref); + //printf("%d %d %d %d\n",execInfo.scopeCount,childref,child, child->firstChild); + execInfo.scopes[execInfo.scopeCount] = jsvRefRef(child->firstChild); + execInfo.scopeCount++; + childref = child->nextSibling; + jsvUnLock(child); + } +} +// ----------------------------------------------- +#ifdef ARM +extern int _end; +#endif +bool jspCheckStackPosition() { +#ifdef ARM + void *frame = __builtin_frame_address(0); + if ((char*)frame < ((char*)&_end)+1024/*so many bytes leeway*/) { +// jsiConsolePrintf("frame: %d,end:%d\n",(int)frame,(int)&_end); + jsErrorAt("Too much recursion - the stack is about to overflow", execInfo.lex, execInfo.lex->tokenStart ); + jspSetInterrupted(true); + return false; + } +#endif + return true; +} + + +// Set execFlags such that we are not executing +void jspSetNoExecute() { + execInfo.execute = (execInfo.execute & (JsExecFlags)(int)~EXEC_RUN_MASK) | EXEC_NO; +} + +// parse single variable name +bool jspParseVariableName() { + JSP_MATCH(LEX_ID); + return true; +} + +// parse function with no arguments +bool jspParseEmptyFunction() { + JSP_MATCH(LEX_ID); + JSP_MATCH('('); + if (execInfo.lex->tk != ')') + jsvUnLock(jspeBase()); + // throw away extra params + while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') { + JSP_MATCH(','); + jsvUnLock(jspeBase()); + } + JSP_MATCH(')'); + return true; +} + +// parse function with a single argument, return its value (no names!) +JsVar *jspParseSingleFunction() { + JsVar *v = 0; + JSP_MATCH(LEX_ID); + JSP_MATCH('('); + if (execInfo.lex->tk != ')') + v = jsvSkipNameAndUnLock(jspeBase()); + // throw away extra params + while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') { + JSP_MATCH_WITH_RETURN(',', v); + jsvUnLock(jspeBase()); + } + JSP_MATCH_WITH_RETURN(')', v); + return v; +} + +/// parse function with max 4 arguments (can set arg to 0 to avoid parse). Usually first arg will be 0, but if we DON'T want to skip names on an arg stuff, we can say +bool jspParseFunction(JspSkipFlags skipName, JsVar **a, JsVar **b, JsVar **c, JsVar **d) { + if (a) *a = 0; + if (b) *b = 0; + if (c) *c = 0; + if (d) *d = 0; + JSP_MATCH(LEX_ID); + JSP_MATCH('('); + if (a && execInfo.lex->tk != ')') { + *a = jspeBase(); + if (!(skipName&JSP_NOSKIP_A)) *a = jsvSkipNameAndUnLock(*a); + } + if (b && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *b = jspeBase(); + if (!(skipName&JSP_NOSKIP_B)) *b = jsvSkipNameAndUnLock(*b); + } + if (c && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *c = jspeBase(); + if (!(skipName&JSP_NOSKIP_C)) *c = jsvSkipNameAndUnLock(*c); + } + if (d && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *d = jspeBase(); + if (!(skipName&JSP_NOSKIP_D)) *d = jsvSkipNameAndUnLock(*d); + } + // throw away extra params + while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') { + JSP_MATCH(','); + jsvUnLock(jspeBase()); + } + JSP_MATCH(')'); + return true; +} + +/// parse function with max 8 arguments (can set arg to 0 to avoid parse). Usually first arg will be 0, but if we DON'T want to skip names on an arg stuff, we can say +bool jspParseFunction8(JspSkipFlags skipName, JsVar **a, JsVar **b, JsVar **c, JsVar **d, JsVar **e, JsVar **f, JsVar **g, JsVar **h) { + if (a) *a = 0; + if (b) *b = 0; + if (c) *c = 0; + if (d) *d = 0; + if (e) *e = 0; + if (f) *f = 0; + if (g) *g = 0; + if (h) *h = 0; + JSP_MATCH(LEX_ID); + JSP_MATCH('('); + if (a && execInfo.lex->tk != ')') { + *a = jspeBase(); + if (!(skipName&JSP_NOSKIP_A)) *a = jsvSkipNameAndUnLock(*a); + } + if (b && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *b = jspeBase(); + if (!(skipName&JSP_NOSKIP_B)) *b = jsvSkipNameAndUnLock(*b); + } + if (c && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *c = jspeBase(); + if (!(skipName&JSP_NOSKIP_C)) *c = jsvSkipNameAndUnLock(*c); + } + if (d && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *d = jspeBase(); + if (!(skipName&JSP_NOSKIP_D)) *d = jsvSkipNameAndUnLock(*d); + } + if (e && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *e = jspeBase(); + if (!(skipName&JSP_NOSKIP_E)) *e = jsvSkipNameAndUnLock(*e); + } + if (f && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *f = jspeBase(); + if (!(skipName&JSP_NOSKIP_F)) *f = jsvSkipNameAndUnLock(*f); + } + if (g && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *g = jspeBase(); + if (!(skipName&JSP_NOSKIP_G)) *g = jsvSkipNameAndUnLock(*g); + } + if (h && execInfo.lex->tk != ')') { + JSP_MATCH(','); + *h = jspeBase(); + if (!(skipName&JSP_NOSKIP_H)) *h = jsvSkipNameAndUnLock(*h); + } + // throw away extra params + while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') { + JSP_MATCH(','); + jsvUnLock(jspeBase()); + } + JSP_MATCH(')'); + return true; +} + +/// parse a function with any number of argument, and return an array of de-named aruments +JsVar *jspParseFunctionAsArray() { + JSP_MATCH(LEX_ID); + JsVar *arr = jsvNewWithFlags(JSV_ARRAY); + if (!arr) return 0; // out of memory + JSP_MATCH_WITH_CLEANUP_AND_RETURN('(', jsvUnLock(arr), 0); + while (!JSP_HAS_ERROR && execInfo.lex->tk!=')' && execInfo.lex->tk!=LEX_EOF) { + JsVar *arg = jsvSkipNameAndUnLock(jspeBase()); + jsvArrayPushAndUnLock(arr, arg); // even if undefined + if (execInfo.lex->tk!=')') JSP_MATCH_WITH_CLEANUP_AND_RETURN(',', jsvUnLock(arr), 0); + } + JSP_MATCH_WITH_CLEANUP_AND_RETURN(')', jsvUnLock(arr), 0); + return arr; +} + +// ---------------------------------------------- + +// we return a value so that JSP_MATCH can return 0 if it fails (if we pass 0, we just parse all args) +bool jspeFunctionArguments(JsVar *funcVar) { + JSP_MATCH('('); + while (execInfo.lex->tk!=')') { + if (funcVar) { + JsVar *param = jsvAddNamedChild(funcVar, 0, jslGetTokenValueAsString(execInfo.lex)); + if (!param) { // out of memory + jspSetError(); + return false; + } + param->flags |= JSV_FUNCTION_PARAMETER; // force this to be called a function parameter + jsvUnLock(param); + } + JSP_MATCH(LEX_ID); + if (execInfo.lex->tk!=')') JSP_MATCH(','); + } + JSP_MATCH(')'); + return true; +} + +bool jspeParseNativeFunction(JsCallback callbackPtr) { + char funcName[JSLEX_MAX_TOKEN_LENGTH]; + JsVar *funcVar; + JsVar *base = jsvLockAgain(execInfo.parse->root); + JSP_MATCH(LEX_R_FUNCTION); + // not too bothered about speed/memory here as only called on init :) + strncpy(funcName, jslGetTokenValueAsString(execInfo.lex), JSLEX_MAX_TOKEN_LENGTH); + JSP_MATCH(LEX_ID); + /* Check for dots, we might want to do something like function 'String.substring' ... */ + while (execInfo.lex->tk == '.') { + JsVar *link; + JSP_MATCH('.'); + link = jsvFindChildFromString(base, funcName, false); + // if it doesn't exist, make a new object class + if (!link) { + JsVar *obj = jsvNewWithFlags(JSV_OBJECT); + link = jsvAddNamedChild(base, obj, funcName); + jsvUnLock(obj); + } + // set base to the object (not the name) + jsvUnLock(base); + base = jsvSkipNameAndUnLock(link); + // Look for another name + strncpy(funcName, jslGetTokenValueAsString(execInfo.lex), JSLEX_MAX_TOKEN_LENGTH); + JSP_MATCH(LEX_ID); + } + // So now, base points to an object where we want our function + funcVar = jsvNewWithFlags(JSV_FUNCTION | JSV_NATIVE); + if (!funcVar) { + jsvUnLock(base); + jspSetError(); + return false; // Out of memory + } + funcVar->varData.callback = callbackPtr; + jspeFunctionArguments(funcVar); + + if (JSP_HAS_ERROR) { // probably out of memory while parsing + jsvUnLock(base); + jsvUnLock(funcVar); + return false; + } + // Add the function with its name + JsVar *funcNameVar = jsvFindChildFromString(base, funcName, true); + if (funcNameVar) // could be out of memory + jsvUnLock(jsvSetValueOfName(funcNameVar, funcVar)); // unlocks funcNameVar + jsvUnLock(base); + jsvUnLock(funcVar); + return true; +} + +bool jspAddNativeFunction(JsParse *parse, const char *funcDesc, JsCallback callbackPtr) { + JsVar *fncode = jsvNewFromString(funcDesc); + if (!fncode) return false; // out of memory! + + JSP_SAVE_EXECUTE(); + JsExecInfo oldExecInfo = execInfo; + + // Set up Lexer + + JsLex lex; + jslInit(&lex, fncode); + jsvUnLock(fncode); + + + jspeiInit(parse, &lex); + + // Parse + bool success = jspeParseNativeFunction(callbackPtr); + if (!success) { + jsError("Parsing Native Function failed!"); + jspSetError(); + } + + + // cleanup + jspeiKill(); + jslKill(&lex); + JSP_RESTORE_EXECUTE(); + oldExecInfo.execute = execInfo.execute; // JSP_RESTORE_EXECUTE has made this ok. + execInfo = oldExecInfo; + + return success; +} + +JsVar *jspeFunctionDefinition() { + // actually parse a function... We assume that the LEX_FUNCTION and name + // have already been parsed + JsVar *funcVar = 0; + if (JSP_SHOULD_EXECUTE) + funcVar = jsvNewWithFlags(JSV_FUNCTION); + // Get arguments save them to the structure + if (!jspeFunctionArguments(funcVar)) { + jsvUnLock(funcVar); + // parse failed + return 0; + } + // Get the code - first parse it so we know where it stops + JslCharPos funcBegin = execInfo.lex->tokenStart; + JSP_SAVE_EXECUTE(); + jspSetNoExecute(); + jsvUnLock(jspeBlock()); + JSP_RESTORE_EXECUTE(); + // Then create var and set + if (JSP_SHOULD_EXECUTE) { + // code var + JsVar *funcCodeVar = jsvNewFromLexer(execInfo.lex, funcBegin, (JslCharPos)(execInfo.lex->tokenLastEnd+1)); + jsvUnLock(jsvAddNamedChild(funcVar, funcCodeVar, JSPARSE_FUNCTION_CODE_NAME)); + jsvUnLock(funcCodeVar); + // scope var + JsVar *funcScopeVar = jspeiGetScopesAsVar(); + if (funcScopeVar) { + jsvUnLock(jsvAddNamedChild(funcVar, funcScopeVar, JSPARSE_FUNCTION_SCOPE_NAME)); + jsvUnLock(funcScopeVar); + } + } + return funcVar; +} + +/* Parse just the brackets of a function - and throw + * everything away */ +bool jspeParseFunctionCallBrackets() { + JSP_MATCH('('); + while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') { + jsvUnLock(jspeBase()); + if (execInfo.lex->tk!=')') JSP_MATCH(','); + } + if (!JSP_HAS_ERROR) JSP_MATCH(')'); + return 0; +} + +/** Handle a function call (assumes we've parsed the function name and we're + * on the start bracket). 'thisArg' is the value of the 'this' variable when the + * function is executed (it's usually the parent object) + * + * If !isParsing and arg0!=0, argument 0 is set to what is supplied (same with arg1) + * + * functionName is used only for error reporting - and can be 0 + */ +JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *thisArg, bool isParsing, int argCount, JsVar **argPtr) { + if (JSP_SHOULD_EXECUTE && !function) { + jsErrorAt("Function not found! Skipping.", execInfo.lex, execInfo.lex->tokenLastStart ); + jspSetError(); + } + + if (JSP_SHOULD_EXECUTE) jspCheckStackPosition(); // try and ensure that we won't overflow our stack + + if (JSP_SHOULD_EXECUTE && function) { + JsVar *functionRoot; + JsVar *functionCode = 0; + JsVar *returnVarName; + JsVar *returnVar; + JsVarRef v; + if (!jsvIsFunction(function)) { + char buf[JS_ERROR_BUF_SIZE]; + strncpy(buf, "Expecting a function to call", JS_ERROR_BUF_SIZE); + const char *name = jswGetBasicObjectName(function); + if (name) { + strncat(buf, ", got a ", JS_ERROR_BUF_SIZE); + strncat(buf, name, JS_ERROR_BUF_SIZE); + } + jsErrorAt(buf, execInfo.lex, execInfo.lex->tokenLastStart ); + jspSetError(); + return 0; + } + + /** Special case - we're parsing and we hit an already-defined function + * that has no 'code'. This means that we should use jswHandleFunctionCall + * to try and parse it */ + if (!jsvIsNative(function)) { + functionCode = jsvFindChildFromString(function, JSPARSE_FUNCTION_CODE_NAME, false); + if (isParsing && !functionCode) { + char buf[32]; + jsvGetString(functionName, buf, sizeof(buf)); + JslCharPos pos = execInfo.lex->tokenStart; + jslSeekTo(execInfo.lex, execInfo.lex->tokenLastStart); // NASTY! because jswHandleFunctionCall expects to parse IDs + JsVar *res = jswHandleFunctionCall(0, 0, buf); + // but we didn't find anything - so just carry on... + if (res!=JSW_HANDLEFUNCTIONCALL_UNHANDLED) + return res; + jslSeekTo(execInfo.lex, pos); // NASTY! + } + } + + if (isParsing) JSP_MATCH('('); + // create a new symbol table entry for execution of this function + // OPT: can we cache this function execution environment + param variables? + // OPT: Probably when calling a function ONCE, use it, otherwise when recursing, make new? + functionRoot = jsvNewWithFlags(JSV_FUNCTION); + if (!functionRoot) { // out of memory + jspSetError(); + return 0; + } + JsVar *oldThisVar = execInfo.thisVar; + execInfo.thisVar = thisArg; + if (isParsing) { + int hadParams = 0; + // grab in all parameters. We go around this loop until we've run out + // of named parameters AND we've parsed all the supplied arguments + v = function->firstChild; + while (!JSP_HAS_ERROR && (v || execInfo.lex->tk!=')')) { + JsVar *param = 0; + if (v) param = jsvLock(v); + bool paramDefined = jsvIsFunctionParameter(param); + if (execInfo.lex->tk!=')' || paramDefined) { + hadParams++; + JsVar *value = 0; + // ONLY parse this if it was supplied, otherwise leave 0 (undefined) + if (execInfo.lex->tk!=')') + value = jspeBase(); + // and if execute, copy it over + if (JSP_SHOULD_EXECUTE) { + value = jsvSkipNameAndUnLock(value); + JsVar *paramName = paramDefined ? jsvCopy(param) : jsvNewFromEmptyString(); + paramName->flags |= JSV_FUNCTION_PARAMETER; // force this to be called a function parameter + JsVar *newValueName = jsvMakeIntoVariableName(paramName, value); + if (newValueName) { // could be out of memory + jsvAddName(functionRoot, newValueName); + } else + jspSetError(); + jsvUnLock(newValueName); + } + jsvUnLock(value); + if (execInfo.lex->tk!=')') JSP_MATCH(','); + } + if (param) { + v = param->nextSibling; + jsvUnLock(param); + } + } + JSP_MATCH(')'); + } else if (JSP_SHOULD_EXECUTE && argCount>0) { // and NOT isParsing + int args = 0; + v = function->firstChild; + while (argsflags |= JSV_FUNCTION_PARAMETER; // force this to be called a function parameter + JsVar *newValueName = jsvMakeIntoVariableName(paramName, argPtr[args]); + if (newValueName) // could be out of memory - or maybe just not supplied! + jsvAddName(functionRoot, newValueName); + jsvUnLock(newValueName); + args++; + if (param) { + v = param->nextSibling; + jsvUnLock(param); + } + } + } + // setup a return variable + returnVarName = jsvAddNamedChild(functionRoot, 0, JSPARSE_RETURN_VAR); + if (!returnVarName) // out of memory + jspSetError(); + //jsvTrace(jsvGetRef(functionRoot), 5); // debugging + + if (!JSP_HAS_ERROR) { + if (jsvIsNative(function)) { + assert(function->varData.callback); + if (function->varData.callback) + function->varData.callback(jsvGetRef(functionRoot)); + } else { + // save old scopes + JsVarRef oldScopes[JSPARSE_MAX_SCOPES]; + int oldScopeCount; + int i; + oldScopeCount = execInfo.scopeCount; + for (i=0;ifirstChild); + //jsvTrace(jsvGetRef(functionScopeVar),5); + jspeiLoadScopesFromVar(functionScopeVar); + jsvUnLock(functionScopeVar); + jsvUnLock(functionScope); + } else { + // no scope var defined? We have no scopes at all! + execInfo.scopeCount = 0; + } + // add the function's execute space to the symbol table so we can recurse + if (jspeiAddScope(jsvGetRef(functionRoot))) { + /* Adding scope may have failed - we may have descended too deep - so be sure + * not to pull somebody else's scope off + */ + + /* we just want to execute the block, but something could + * have messed up and left us with the wrong ScriptLex, so + * we want to be careful here... */ + if (functionCode) { + JsLex *oldLex; + JsVar* functionCodeVar = jsvSkipNameAndUnLock(functionCode); + JsLex newLex; + jslInit(&newLex, functionCodeVar); + jsvUnLock(functionCodeVar); + + oldLex = execInfo.lex; + execInfo.lex = &newLex; + JSP_SAVE_EXECUTE(); + jspeBlock(); + bool hasError = JSP_HAS_ERROR; + JSP_RESTORE_EXECUTE(); // because return will probably have set execute to false + jslKill(&newLex); + execInfo.lex = oldLex; + if (hasError) { + jsiConsolePrint("in function "); + if (jsvIsString(functionName)) { + jsiConsolePrint("\""); + jsiConsolePrintStringVar(functionName); + jsiConsolePrint("\" "); + } + jsiConsolePrint("called from "); + if (execInfo.lex) + jsiConsolePrintPosition(execInfo.lex, execInfo.lex->tokenLastEnd); + else + jsiConsolePrint("system\n"); + jspSetError(); + } + } + + jspeiRemoveScope(); + } + + // Unref old scopes + for (i=0;iroot, obj, tokenName); + jsvUnLock(obj); + } + } else { + a = jswHandleFunctionCall(0, 0, tokenName); + if (a != JSW_HANDLEFUNCTIONCALL_UNHANDLED) + return a; + /* Variable doesn't exist! JavaScript says we should create it + * (we won't add it here. This is done in the assignment operator)*/ + a = jsvMakeIntoVariableName(jslGetTokenValueAsVar(execInfo.lex), 0); + } + } + JSP_MATCH_WITH_RETURN(LEX_ID, a); + + return a; +} + +JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) { + /* The parent if we're executing a method call */ + JsVar *parent = 0; + + while (execInfo.lex->tk=='.' || execInfo.lex->tk=='[') { + if (execInfo.lex->tk == '.') { // ------------------------------------- Record Access + JSP_MATCH('.'); + if (JSP_SHOULD_EXECUTE) { + // Note: name will go away when we oarse something else! + const char *name = jslGetTokenValueAsString(execInfo.lex); + + JsVar *aVar = jsvSkipName(a); + JsVar *child = 0; + if (aVar && jswGetBasicObjectName(aVar)) { + // if we're an object (or pretending to be one) + if (jsvHasChildren(aVar)) + child = jsvFindChildFromString(aVar, name, false); + + if (!child) + child = jspeiFindChildFromStringInParents(aVar, name); + + + if (child) { + // it was found - no need for name ptr now, so match! + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(parent);jsvUnLock(a);, child); + } else { // NOT FOUND... + /* Check for builtins via separate function + * This way we save on RAM for built-ins because all comes out of program code. + * + * We don't check for prototype vars, so people can overload the built + * in functions (eg. Person.prototype.toString). HOWEVER if we did + * this for 'this' then we couldn't say 'this.toString()' + * */ + if (!jsvIsString(a) || (!jsvIsStringEqual(a, JSPARSE_PROTOTYPE_VAR))) // don't try and use builtins on the prototype var! + child = jswHandleFunctionCall(aVar, a/*name*/, name); + else + child = JSW_HANDLEFUNCTIONCALL_UNHANDLED; + if (child == JSW_HANDLEFUNCTIONCALL_UNHANDLED) { + child = 0; + // It wasn't handled... We already know this is an object so just add a new child + if (jsvIsObject(aVar) || jsvIsFunction(aVar) || jsvIsArray(aVar)) { + JsVar *value = 0; + if (jsvIsFunction(aVar) && strcmp(name, JSPARSE_PROTOTYPE_VAR)==0) + value = jsvNewWithFlags(JSV_OBJECT); // prototype is supposed to be an object + child = jsvAddNamedChild(aVar, value, name); + jsvUnLock(value); + } else { + // could have been a string... + jsErrorAt("Field or method does not already exist, and can't create it on a non-object", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + } + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(parent);jsvUnLock(a);, child); + } + } + } else { + jsErrorAt("Using '.' operator on non-object", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(parent);jsvUnLock(a);, child); + } + jsvUnLock(parent); + parent = aVar; + jsvUnLock(a); + a = child; + } else { + // Not executing, just match + JSP_MATCH_WITH_RETURN(LEX_ID, a); + } + } else if (execInfo.lex->tk == '[') { // ------------------------------------- Array Access + JsVar *index; + JSP_MATCH('['); + index = jspeBase(); + JSP_MATCH_WITH_CLEANUP_AND_RETURN(']', jsvUnLock(parent);jsvUnLock(index);, a); + if (JSP_SHOULD_EXECUTE) { + /* Index filtering (bug #19) - if we have an array index A that is: + is_string(A) && int_to_string(string_to_int(A)) = =A + then convert it to an integer. Should be too nasty for performance + as we only do this when accessing an array with a string */ + if (jsvIsString(index) && jsvIsStringNumericStrict(index)) { + JsVar *v = jsvNewFromInteger(jsvGetInteger(index)); + jsvUnLock(index); + index = v; + } + + JsVar *aVar = jsvSkipName(a); + if (aVar && (jsvIsArrayBuffer(aVar))) { + // for array buffers, we actually create a NAME, and hand that back - then when we assign (or use SkipName) we pull out the correct data + JsVar *indexValue = jsvSkipName(index); + jsvUnLock(a); + a = jsvMakeIntoVariableName(jsvNewFromInteger(jsvGetInteger(indexValue)), aVar); + jsvUnLock(indexValue); + if (a) // turn into an 'array buffer name' + a->flags = (a->flags & ~(JSV_NAME|JSV_VARTYPEMASK)) | JSV_ARRAYBUFFERNAME; + } else if (aVar && (jsvIsArray(aVar) || jsvIsObject(aVar) || jsvIsFunction(aVar))) { + // TODO: If we set to undefined, maybe we should remove the name? + JsVar *indexValue = jsvSkipName(index); + if (!jsvIsString(indexValue) && !jsvIsNumeric(indexValue)) + indexValue = jsvAsString(indexValue, true); + JsVar *child = jsvFindChildFromVar(aVar, indexValue, true); + jsvUnLock(indexValue); + + jsvUnLock(parent); + parent = jsvLockAgain(aVar); + jsvUnLock(a); + a = child; + } else if (aVar && (jsvIsString(aVar))) { + JsVarInt idx = jsvGetIntegerAndUnLock(jsvSkipName(index)); + JsVar *child = 0; + if (idx>=0 && idx<(JsVarInt)jsvGetStringLength(aVar)) { + char ch = jsvGetCharInString(aVar, (int)idx); + child = jsvNewFromEmptyString(); + if (child) jsvAppendStringBuf(child, &ch, 1); + } + jsvUnLock(parent); + parent = jsvLockAgain(aVar); + jsvUnLock(a); + a = child; + } else { + jsWarnAt("Variable is not an Array or Object", execInfo.lex, execInfo.lex->tokenLastEnd); + jsvUnLock(parent); + parent = 0; + jsvUnLock(a); + a = 0; + } + jsvUnLock(aVar); + } + jsvUnLock(index); + } else { + assert(0); + } + } + + if (parentResult) *parentResult = parent; + else jsvUnLock(parent); + return a; +} + +JsVar *jspeFactor(); +void jspEnsureIsPrototype(JsVar *prototypeName); + +JsVar *jspeConstruct(JsVar *func, JsVar *funcName, bool hasArgs) { + assert(JSP_SHOULD_EXECUTE); + if (!jsvIsFunction(func)) { + jsErrorAt("Constructor should be a function", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + return 0; + } + + JsVar *thisObj = jsvNewWithFlags(JSV_OBJECT); + // Make sure the function has a 'prototype' var + JsVar *prototypeName = jsvFindChildFromString(func, JSPARSE_PROTOTYPE_VAR, true); + jspEnsureIsPrototype(prototypeName); // make sure it's an object + jsvUnLock(jsvAddNamedChild(thisObj, prototypeName, JSPARSE_INHERITS_VAR)); + jsvUnLock(prototypeName); + + JsVar *a = jspeFunctionCall(func, funcName, thisObj, hasArgs, 0, 0); + + if (a) { + jsvUnLock(thisObj); + thisObj = a; + } else { + jsvUnLock(a); + JsVar *constructor = jsvFindChildFromString(thisObj, JSPARSE_CONSTRUCTOR_VAR, true); + if (constructor) { + jsvSetValueOfName(constructor, funcName); + jsvUnLock(constructor); + } + } + return thisObj; +} + +JsVar *jspeFactorFunctionCall() { + /* The parent if we're executing a method call */ + bool isConstructor = false; + if (execInfo.lex->tk==LEX_R_NEW) { + JSP_MATCH(LEX_R_NEW); + isConstructor = true; + + if (execInfo.lex->tk==LEX_R_NEW) { + jsError("Nesting 'new' operators is unsupported"); + jspSetError(); + return 0; + } + } + + JsVar *parent = 0; + JsVar *a = jspeFactorMember(jspeFactor(), &parent); + + while (execInfo.lex->tk=='(' || (isConstructor && JSP_SHOULD_EXECUTE)) { + JsVar *funcName = a; + JsVar *func = jsvSkipName(funcName); + + /* The constructor function doesn't change parsing, so if we're + * not executing, just short-cut it. */ + if (isConstructor && JSP_SHOULD_EXECUTE) { + // If we have '(' parse an argument list, otherwise don't look for any args + bool parseArgs = execInfo.lex->tk=='('; + a = jspeConstruct(func, funcName, parseArgs); + isConstructor = false; // don't treat subsequent brackets as constructors + } else + a = jspeFunctionCall(func, funcName, parent, true, 0, 0); + + jsvUnLock(funcName); + jsvUnLock(func); + + jsvUnLock(parent); parent=0; + a = jspeFactorMember(a, &parent); + } + + jsvUnLock(parent); + return a; +} + +JsVar *jspeFactorId() { + return jspeFactorSingleId(); +} + + +JsVar *jspeFactorObject() { + if (JSP_SHOULD_EXECUTE) { + JsVar *contents = jsvNewWithFlags(JSV_OBJECT); + if (!contents) { // out of memory + jspSetError(); + return 0; + } + /* JSON-style object definition */ + JSP_MATCH_WITH_RETURN('{', contents); + while (!JSP_HAS_ERROR && execInfo.lex->tk != '}') { + JsVar *varName = 0; + if (JSP_SHOULD_EXECUTE) { + varName = jslGetTokenValueAsVar(execInfo.lex); + if (!varName) { // out of memory + return contents; + } + } + // we only allow strings or IDs on the left hand side of an initialisation + if (execInfo.lex->tk==LEX_STR) { + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_STR, jsvUnLock(varName), contents); + } else { + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(varName), contents); + } + JSP_MATCH_WITH_CLEANUP_AND_RETURN(':', jsvUnLock(varName), contents); + if (JSP_SHOULD_EXECUTE) { + JsVar *valueVar; + JsVar *value = jspeBase(); // value can be 0 (could be undefined!) + valueVar = jsvSkipNameAndUnLock(value); + varName = jsvMakeIntoVariableName(varName, valueVar); + jsvAddName(contents, varName); + jsvUnLock(valueVar); + } + jsvUnLock(varName); + // no need to clean here, as it will definitely be used + if (execInfo.lex->tk != '}') JSP_MATCH_WITH_RETURN(',', contents); + } + JSP_MATCH_WITH_RETURN('}', contents); + return contents; + } else { + // Not executing so do fast skip + return jspeBlock(); + } +} + +JsVar *jspeFactorArray() { + int idx = 0; + JsVar *contents = 0; + if (JSP_SHOULD_EXECUTE) { + contents = jsvNewWithFlags(JSV_ARRAY); + if (!contents) { // out of memory + jspSetError(); + return 0; + } + } + /* JSON-style array */ + JSP_MATCH_WITH_RETURN('[', contents); + while (!JSP_HAS_ERROR && execInfo.lex->tk != ']') { + if (JSP_SHOULD_EXECUTE) { + // OPT: Store array indices as actual ints + JsVar *a; + JsVar *aVar; + JsVar *indexName; + a = jspeBase(); + aVar = jsvSkipNameAndUnLock(a); + indexName = jsvMakeIntoVariableName(jsvNewFromInteger(idx), aVar); + if (indexName) { // could be out of memory + jsvAddName(contents, indexName); + jsvUnLock(indexName); + } + jsvUnLock(aVar); + } else { + jsvUnLock(jspeBase()); + } + // no need to clean here, as it will definitely be used + if (execInfo.lex->tk != ']') JSP_MATCH_WITH_RETURN(',', contents); + idx++; + } + JSP_MATCH_WITH_RETURN(']', contents); + return contents; +} + +void jspEnsureIsPrototype(JsVar *prototypeName) { + if (!prototypeName) return; + JsVar *prototypeVar = jsvSkipName(prototypeName); + if (!jsvIsObject(prototypeVar)) { + if (!jsvIsUndefined(prototypeVar)) + jsWarn("Prototype is not an Object, so setting it to {}"); + jsvUnLock(prototypeVar); + prototypeVar = jsvNewWithFlags(JSV_OBJECT); // prototype is supposed to be an object + JsVar *lastName = jsvSkipToLastName(prototypeName); + jsvSetValueOfName(lastName, prototypeVar); + jsvUnLock(lastName); + } + jsvUnLock(prototypeVar); +} + +JsVar *jspeFactorTypeOf() { + JSP_MATCH(LEX_R_TYPEOF); + JsVar *a = jspeBase(); + JsVar *result = 0; + if (JSP_SHOULD_EXECUTE) { + a = jsvSkipNameAndUnLock(a); + result=jsvNewFromString(jsvGetTypeOf(a)); + } + jsvUnLock(a); + return result; +} + +JsVar *jspeFactor() { + if (execInfo.lex->tk=='(') { + JsVar *a = 0; + JSP_MATCH('('); + if (jspCheckStackPosition()) + a = jspeBaseWithComma(); + if (!JSP_HAS_ERROR) JSP_MATCH_WITH_RETURN(')',a); + return a; + } else if (execInfo.lex->tk==LEX_R_TRUE) { + JSP_MATCH(LEX_R_TRUE); + return JSP_SHOULD_EXECUTE ? jsvNewFromBool(true) : 0; + } else if (execInfo.lex->tk==LEX_R_FALSE) { + JSP_MATCH(LEX_R_FALSE); + return JSP_SHOULD_EXECUTE ? jsvNewFromBool(false) : 0; + } else if (execInfo.lex->tk==LEX_R_NULL) { + JSP_MATCH(LEX_R_NULL); + return JSP_SHOULD_EXECUTE ? jsvNewWithFlags(JSV_NULL) : 0; + } else if (execInfo.lex->tk==LEX_R_UNDEFINED) { + JSP_MATCH(LEX_R_UNDEFINED); + return 0; + } else if (execInfo.lex->tk==LEX_ID) { + return jspeFactorId(); + } else if (execInfo.lex->tk==LEX_INT) { + // atol works only on decimals + // strtol handles 0x12345 as well + //JsVarInt v = (JsVarInt)atol(jslGetTokenValueAsString(execInfo.lex)); + //JsVarInt v = (JsVarInt)strtol(jslGetTokenValueAsString(execInfo.lex),0,0); // broken on PIC + if (JSP_SHOULD_EXECUTE) { + JsVarInt v = stringToInt(jslGetTokenValueAsString(execInfo.lex)); + JSP_MATCH(LEX_INT); + return jsvNewFromInteger(v); + } else { + JSP_MATCH(LEX_INT); + return 0; + } + } else if (execInfo.lex->tk==LEX_FLOAT) { + if (JSP_SHOULD_EXECUTE) { + JsVarFloat v = stringToFloat(jslGetTokenValueAsString(execInfo.lex)); + JSP_MATCH(LEX_FLOAT); + return jsvNewFromFloat(v); + } else { + JSP_MATCH(LEX_FLOAT); + return 0; + } + } else if (execInfo.lex->tk==LEX_STR) { + if (JSP_SHOULD_EXECUTE) { + JsVar *a = jslGetTokenValueAsVar(execInfo.lex); + JSP_MATCH_WITH_RETURN(LEX_STR, a); + return a; + } else { + JSP_MATCH(LEX_STR); + return 0; + } + } else if (execInfo.lex->tk=='{') { + return jspeFactorObject(); + } else if (execInfo.lex->tk=='[') { + return jspeFactorArray(); + } else if (execInfo.lex->tk==LEX_R_FUNCTION) { + JSP_MATCH(LEX_R_FUNCTION); + return jspeFunctionDefinition(); + } else if (execInfo.lex->tk==LEX_R_THIS) { + JSP_MATCH(LEX_R_THIS); + return jsvLockAgain( execInfo.thisVar ? execInfo.thisVar : execInfo.parse->root ); + } else if (execInfo.lex->tk==LEX_R_TYPEOF) { + return jspeFactorTypeOf(); + } else if (execInfo.lex->tk==LEX_R_VOID) { + JSP_MATCH(LEX_R_VOID); + jsvUnLock(jspeFactor()); + return 0; + } + // Nothing we can do here... just hope it's the end... + JSP_MATCH(LEX_EOF); + return 0; +} + +__attribute((noinline)) JsVar *__jspePostfix(JsVar *a) { + while (execInfo.lex->tk==LEX_PLUSPLUS || execInfo.lex->tk==LEX_MINUSMINUS) { + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + if (JSP_SHOULD_EXECUTE) { + JsVar *one = jsvNewFromInteger(1); + JsVar *res = jsvMathsOpSkipNames(a, one, op==LEX_PLUSPLUS ? '+' : '-'); + JsVar *oldValue; + jsvUnLock(one); + oldValue = jsvSkipName(a); // keep the old value + // in-place add/subtract + jspReplaceWith(a, res); + jsvUnLock(res); + // but then use the old value + jsvUnLock(a); + a = oldValue; + } + } + return a; +} + +JsVar *jspePostfix() { + JsVar *a; + if (execInfo.lex->tk==LEX_PLUSPLUS || execInfo.lex->tk==LEX_MINUSMINUS) { + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + a = jspePostfix(); + if (JSP_SHOULD_EXECUTE) { + JsVar *one = jsvNewFromInteger(1); + JsVar *res = jsvMathsOpSkipNames(a, one, op==LEX_PLUSPLUS ? '+' : '-'); + jsvUnLock(one); + // in-place add/subtract + jspReplaceWith(a, res); + jsvUnLock(res); + } + } else + a = jspeFactorFunctionCall(); + return __jspePostfix(a); +} + +JsVar *jspeUnary() { + if (execInfo.lex->tk=='!' || execInfo.lex->tk=='~' || execInfo.lex->tk=='-' || execInfo.lex->tk=='+') { + short tk = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + if (!JSP_SHOULD_EXECUTE) { + return jspePostfix(); + } + if (tk=='!') { // logical not + return jsvNewFromBool(!jsvGetBoolAndUnLock(jsvSkipNameAndUnLock(jspeUnary()))); + } else if (tk=='~') { // bitwise not + return jsvNewFromInteger(~jsvGetIntegerAndUnLock(jsvSkipNameAndUnLock(jspeUnary()))); + } else if (tk=='-') { // unary minus + return jsvNegateAndUnLock(jspeUnary()); // names already skipped + } else if (tk=='+') { // unary plus (convert to number) + return jsvAsNumber(jspeUnary()); // names already skipped + } + assert(0); + return 0; + } else + return jspePostfix(); +} + +__attribute((noinline)) JsVar *__jspeTerm(JsVar *a) { + while (execInfo.lex->tk=='*' || execInfo.lex->tk=='/' || execInfo.lex->tk=='%') { + JsVar *b; + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + b = jspeUnary(); + if (JSP_SHOULD_EXECUTE) { + JsVar *res = jsvMathsOpSkipNames(a, b, op); + jsvUnLock(a); a = res; + } + jsvUnLock(b); + } + return a; +} + +JsVar *jspeTerm() { + return __jspeTerm(jspeUnary()); +} + +__attribute((noinline)) JsVar *__jspeExpression(JsVar *a) { + while (execInfo.lex->tk=='+' || execInfo.lex->tk=='-') { + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + JsVar *b = jspeTerm(); + if (JSP_SHOULD_EXECUTE) { + // not in-place, so just replace + JsVar *res = jsvMathsOpSkipNames(a, b, op); + jsvUnLock(a); a = res; + } + jsvUnLock(b); + } + return a; +} + + +JsVar *jspeExpression() { + return __jspeExpression(jspeTerm()); +} + +__attribute((noinline)) JsVar *__jspeShift(JsVar *a) { + if (execInfo.lex->tk==LEX_LSHIFT || execInfo.lex->tk==LEX_RSHIFT || execInfo.lex->tk==LEX_RSHIFTUNSIGNED) { + JsVar *b; + int op = execInfo.lex->tk; + JSP_MATCH(op); + b = jspeExpression(); + if (JSP_SHOULD_EXECUTE) { + JsVar *res = jsvMathsOpSkipNames(a, b, op); + jsvUnLock(a); a = res; + } + jsvUnLock(b); + } + return a; +} + +JsVar *jspeShift() { + return __jspeShift(jspeExpression()); +} + +__attribute((noinline)) JsVar *__jspeCondition(JsVar *a) { + JsVar *b; + while (execInfo.lex->tk==LEX_EQUAL || execInfo.lex->tk==LEX_NEQUAL || + execInfo.lex->tk==LEX_TYPEEQUAL || execInfo.lex->tk==LEX_NTYPEEQUAL || + execInfo.lex->tk==LEX_LEQUAL || execInfo.lex->tk==LEX_GEQUAL || + execInfo.lex->tk=='<' || execInfo.lex->tk=='>' || + execInfo.lex->tk==LEX_R_INSTANCEOF || + (execInfo.lex->tk==LEX_R_IN && !(execInfo.execute&EXEC_FOR_INIT))) { + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + b = jspeShift(); + if (JSP_SHOULD_EXECUTE) { + JsVar *res = 0; + if (op==LEX_R_IN) { + JsVar *av = jsvSkipName(a); + JsVar *bv = jsvSkipName(b); + if (jsvIsArray(bv) || jsvIsObject(bv)) { + JsVar *varFound = jsvGetArrayIndexOf(bv, av, false/*not exact*/); // ArrayIndexOf will return 0 if not found + res = jsvNewFromBool(varFound!=0); + jsvUnLock(varFound); + } // else it will be undefined + jsvUnLock(av); + jsvUnLock(bv); + } else if (op==LEX_R_INSTANCEOF) { + bool inst = false; + JsVar *av = jsvSkipName(a); + JsVar *bv = jsvSkipName(b); + if (!jsvIsFunction(bv)) { + jsErrorAt("Expecting a function on RHS in instanceof check", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + } else { + if (jsvIsObject(av)) { + JsVar *constructor = jsvObjectGetChild(av, JSPARSE_CONSTRUCTOR_VAR, 0); + if (constructor==bv) inst=true; + else inst = jspIsConstructor(bv,"Object"); + jsvUnLock(constructor); + } else { + const char *name = jswGetBasicObjectName(av); + if (name) { + inst = jspIsConstructor(bv, name); + } + } + } + jsvUnLock(av); + jsvUnLock(bv); + res = jsvNewFromBool(inst); + } else { + res = jsvMathsOpSkipNames(a, b, op); + + } + jsvUnLock(a); a = res; + } + jsvUnLock(b); + } + return a; +} + +JsVar *jspeCondition() { + return __jspeCondition(jspeShift()); +} + +__attribute((noinline)) JsVar *__jspeLogic(JsVar *a) { + JsVar *b = 0; + while (execInfo.lex->tk=='&' || execInfo.lex->tk=='|' || execInfo.lex->tk=='^' || execInfo.lex->tk==LEX_ANDAND || execInfo.lex->tk==LEX_OROR) { + bool shortCircuit = false; + bool boolean = false; + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + + // if we have short-circuit ops, then if we know the outcome + // we don't bother to execute the other op. Even if not + // we need to tell mathsOp it's an & or | + if (op==LEX_ANDAND) { + op = '&'; + shortCircuit = !jsvGetBoolAndUnLock(jsvSkipName(a)); + boolean = true; + } else if (op==LEX_OROR) { + op = '|'; + shortCircuit = jsvGetBoolAndUnLock(jsvSkipName(a)); + boolean = true; + } + + JSP_SAVE_EXECUTE(); + if (shortCircuit) jspSetNoExecute(); + b = jspeCondition(); + if (shortCircuit) JSP_RESTORE_EXECUTE(); + if (JSP_SHOULD_EXECUTE && !shortCircuit) { + JsVar *res; + if (boolean) { + JsVar *newa = jsvNewFromBool(jsvGetBoolAndUnLock(jsvSkipName(a))); + JsVar *newb = jsvNewFromBool(jsvGetBoolAndUnLock(jsvSkipName(b))); + jsvUnLock(a); a = newa; + jsvUnLock(b); b = newb; + } + res = jsvMathsOpSkipNames(a, b, op); + jsvUnLock(a); a = res; + } + jsvUnLock(b); + } + return a; +} + +JsVar *jspeLogic() { + return __jspeLogic(jspeCondition()); +} + +__attribute((noinline)) JsVar *__jspeTernary(JsVar *lhs) { + if (execInfo.lex->tk=='?') { + JSP_MATCH('?'); + if (!JSP_SHOULD_EXECUTE) { + // just let lhs pass through + jsvUnLock(jspeBase()); + JSP_MATCH(':'); + jsvUnLock(jspeBase()); + } else { + bool first = jsvGetBoolAndUnLock(jsvSkipName(lhs)); + jsvUnLock(lhs); + if (first) { + lhs = jspeBase(); + JSP_MATCH(':'); + JSP_SAVE_EXECUTE(); + jspSetNoExecute(); + jsvUnLock(jspeBase()); + JSP_RESTORE_EXECUTE(); + } else { + JSP_SAVE_EXECUTE(); + jspSetNoExecute(); + jsvUnLock(jspeBase()); + JSP_RESTORE_EXECUTE(); + JSP_MATCH(':'); + lhs = jspeBase(); + } + } + } + + return lhs; +} + +JsVar *jspeTernary() { + return __jspeTernary(jspeLogic()); +} + +__attribute((noinline)) JsVar *__jspeBase(JsVar *lhs) { + if (execInfo.lex->tk=='=' || execInfo.lex->tk==LEX_PLUSEQUAL || execInfo.lex->tk==LEX_MINUSEQUAL || + execInfo.lex->tk==LEX_MULEQUAL || execInfo.lex->tk==LEX_DIVEQUAL || execInfo.lex->tk==LEX_MODEQUAL || + execInfo.lex->tk==LEX_ANDEQUAL || execInfo.lex->tk==LEX_OREQUAL || + execInfo.lex->tk==LEX_XOREQUAL || execInfo.lex->tk==LEX_RSHIFTEQUAL || + execInfo.lex->tk==LEX_LSHIFTEQUAL || execInfo.lex->tk==LEX_RSHIFTUNSIGNEDEQUAL) { + JsVar *rhs; + /* If we're assigning to this and we don't have a parent, + * add it to the symbol table root as per JavaScript. */ + if (JSP_SHOULD_EXECUTE && lhs && !lhs->refs) { + if (jsvIsName(lhs)/* && jsvGetStringLength(lhs)>0*/) { + if (!jsvIsArrayBufferName(lhs)) + jsvAddName(execInfo.parse->root, lhs); + } else // TODO: Why was this here? can it happen? + jsWarnAt("Trying to assign to an un-named type\n", execInfo.lex, execInfo.lex->tokenLastEnd); + } + + int op = execInfo.lex->tk; + JSP_MATCH(execInfo.lex->tk); + rhs = jspeBase(); + rhs = jsvSkipNameAndUnLock(rhs); // ensure we get rid of any references on the RHS + if (JSP_SHOULD_EXECUTE && lhs) { + if (op=='=') { + jspReplaceWith(lhs, rhs); + } else { + if (op==LEX_PLUSEQUAL) op='+'; + else if (op==LEX_MINUSEQUAL) op='-'; + else if (op==LEX_MULEQUAL) op='*'; + else if (op==LEX_DIVEQUAL) op='/'; + else if (op==LEX_MODEQUAL) op='%'; + else if (op==LEX_ANDEQUAL) op='&'; + else if (op==LEX_OREQUAL) op='|'; + else if (op==LEX_XOREQUAL) op='^'; + else if (op==LEX_RSHIFTEQUAL) op=LEX_RSHIFT; + else if (op==LEX_LSHIFTEQUAL) op=LEX_LSHIFT; + else if (op==LEX_RSHIFTUNSIGNEDEQUAL) op=LEX_RSHIFTUNSIGNED; + if (op=='+' && jsvIsName(lhs)) { + JsVar *currentValue = jsvSkipName(lhs); + if (jsvIsString(currentValue) && currentValue->refs==1) { + /* A special case for string += where this is the only use of the string, + * as we may be able to do a simple append (rather than clone + append)*/ + JsVar *str = jsvAsString(rhs, false); + jsvAppendStringVarComplete(currentValue, str); + jsvUnLock(str); + op = 0; + } + jsvUnLock(currentValue); + } + if (op) { + /* Fallback which does a proper add */ + JsVar *res = jsvMathsOpSkipNames(lhs,rhs,op); + jspReplaceWith(lhs, res); + jsvUnLock(res); + } + } + } + jsvUnLock(rhs); + } + return lhs; +} + + +JsVar *jspeBase() { + return __jspeBase(jspeTernary()); +} + +// jspeBase where ',' is allowed to add multiple expressions +JsVar *jspeBaseWithComma() { + while (!JSP_HAS_ERROR) { + JsVar *a = jspeBase(); + if (execInfo.lex->tk!=',') return a; + // if we get a comma, we just forget this data and parse the next bit... + jsvUnLock(a); + JSP_MATCH(','); + } + return 0; +} + +JsVar *jspeBlock() { + JSP_MATCH('{'); + if (JSP_SHOULD_EXECUTE) { + while (execInfo.lex->tk && execInfo.lex->tk!='}') { + jsvUnLock(jspeStatement()); + if (JSP_HAS_ERROR) { + if (execInfo.lex && !(execInfo.execute&EXEC_ERROR_LINE_REPORTED)) { + execInfo.execute = (JsExecFlags)(execInfo.execute | EXEC_ERROR_LINE_REPORTED); + jsiConsolePrint("at "); + jsiConsolePrintPosition(execInfo.lex, execInfo.lex->tokenLastEnd); + jsiConsolePrintTokenLineMarker(execInfo.lex, execInfo.lex->tokenLastEnd); + } + return 0; + } + } + JSP_MATCH('}'); + } else { + // fast skip of blocks + int brackets = 1; + while (execInfo.lex->tk && brackets) { + if (execInfo.lex->tk == '{') brackets++; + if (execInfo.lex->tk == '}') brackets--; + JSP_MATCH(execInfo.lex->tk); + } + } + return 0; +} + +JsVar *jspeBlockOrStatement() { + if (execInfo.lex->tk=='{') + return jspeBlock(); + else { + JsVar *v = jspeStatement(); + if (execInfo.lex->tk==';') JSP_MATCH(';'); + return v; + } +} + +JsVar *jspeStatementVar() { + JsVar *lastDefined = 0; + /* variable creation. TODO - we need a better way of parsing the left + * hand side. Maybe just have a flag called can_create_var that we + * set and then we parse as if we're doing a normal equals.*/ + JSP_MATCH(LEX_R_VAR); + bool hasComma = true; // for first time in loop + while (hasComma && execInfo.lex->tk == LEX_ID) { + JsVar *a = 0; + if (JSP_SHOULD_EXECUTE) { + a = jspeiFindOnTop(jslGetTokenValueAsString(execInfo.lex), true); + if (!a) { // out of memory + jspSetError(); + return lastDefined; + } + } + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(a), lastDefined); + // now do stuff defined with dots + while (execInfo.lex->tk == '.') { + JSP_MATCH_WITH_CLEANUP_AND_RETURN('.', jsvUnLock(a), lastDefined); + if (JSP_SHOULD_EXECUTE) { + JsVar *lastA = a; + a = jsvFindChildFromString(lastA, jslGetTokenValueAsString(execInfo.lex), true); + jsvUnLock(lastA); + } + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(a), lastDefined); + } + // sort out initialiser + if (execInfo.lex->tk == '=') { + JsVar *var; + JSP_MATCH_WITH_CLEANUP_AND_RETURN('=', jsvUnLock(a), lastDefined); + var = jsvSkipNameAndUnLock(jspeBase()); + if (JSP_SHOULD_EXECUTE) + jspReplaceWith(a, var); + jsvUnLock(var); + } + jsvUnLock(lastDefined); + lastDefined = a; + hasComma = execInfo.lex->tk == ','; + if (hasComma) JSP_MATCH_WITH_RETURN(',', lastDefined); + } + return lastDefined; +} + +JsVar *jspeStatementIf() { + bool cond; + JsVar *var; + JSP_MATCH(LEX_R_IF); + JSP_MATCH('('); + var = jspeBase(); + JSP_MATCH(')'); + cond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(var)); + jsvUnLock(var); + + JSP_SAVE_EXECUTE(); + if (!cond) jspSetNoExecute(); + jsvUnLock(jspeBlockOrStatement()); + if (!cond) JSP_RESTORE_EXECUTE(); + if (execInfo.lex->tk==LEX_R_ELSE) { + //JSP_MATCH(';'); ??? + JSP_MATCH(LEX_R_ELSE); + JSP_SAVE_EXECUTE(); + if (cond) jspSetNoExecute(); + jsvUnLock(jspeBlockOrStatement()); + if (cond) JSP_RESTORE_EXECUTE(); + } + return 0; +} + +JsVar *jspeStatementSwitch() { + JSP_MATCH(LEX_R_SWITCH); + JSP_MATCH('('); + JsVar *switchOn = jspeBase(); + JSP_MATCH_WITH_CLEANUP_AND_RETURN(')', jsvUnLock(switchOn), 0); + JSP_MATCH_WITH_CLEANUP_AND_RETURN('{', jsvUnLock(switchOn), 0); + JSP_SAVE_EXECUTE(); + bool execute = JSP_SHOULD_EXECUTE; + bool hasExecuted = false; + if (execute) execInfo.execute=EXEC_NO|EXEC_IN_SWITCH; + while (execInfo.lex->tk==LEX_R_CASE) { + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_R_CASE, jsvUnLock(switchOn), 0); + JsExecFlags oldFlags = execInfo.execute; + if (execute) execInfo.execute=EXEC_YES|EXEC_IN_SWITCH; + JsVar *test = jspeBase(); + execInfo.execute = oldFlags|EXEC_IN_SWITCH;; + JSP_MATCH_WITH_CLEANUP_AND_RETURN(':', jsvUnLock(switchOn);jsvUnLock(test), 0); + bool cond = false; + if (execute) + cond = jsvGetBoolAndUnLock(jsvMathsOpSkipNames(switchOn, test, LEX_EQUAL)); + if (cond) hasExecuted = true; + jsvUnLock(test); + if (cond && (execInfo.execute&EXEC_RUN_MASK)==EXEC_NO) + execInfo.execute=EXEC_YES|EXEC_IN_SWITCH; + while (!JSP_HAS_ERROR && execInfo.lex->tk!=LEX_EOF && execInfo.lex->tk!=LEX_R_CASE && execInfo.lex->tk!=LEX_R_DEFAULT && execInfo.lex->tk!='}') + jsvUnLock(jspeBlockOrStatement()); + } + jsvUnLock(switchOn); + if (execute && (execInfo.execute&EXEC_RUN_MASK)==EXEC_BREAK) + execInfo.execute=EXEC_YES|EXEC_IN_SWITCH; + JSP_RESTORE_EXECUTE(); + + if (execInfo.lex->tk==LEX_R_DEFAULT) { + JSP_MATCH(LEX_R_DEFAULT); + JSP_MATCH(':'); + JSP_SAVE_EXECUTE(); + if (hasExecuted) jspSetNoExecute(); + while (!JSP_HAS_ERROR && execInfo.lex->tk!=LEX_EOF && execInfo.lex->tk!='}') + jsvUnLock(jspeBlockOrStatement()); + JSP_RESTORE_EXECUTE(); + } + JSP_MATCH('}'); + return 0; +} + +JsVar *jspeStatementWhile() { +#ifdef JSPARSE_MAX_LOOP_ITERATIONS + int loopCount = JSPARSE_MAX_LOOP_ITERATIONS; +#endif + JsVar *cond; + bool loopCond; + bool hasHadBreak = false; + // We do repetition by pulling out the string representing our statement + // there's definitely some opportunity for optimisation here + JSP_MATCH(LEX_R_WHILE); + JSP_MATCH('('); + JslCharPos whileCondStart = execInfo.lex->tokenStart; + cond = jspeBase(); + loopCond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(cond)); + jsvUnLock(cond); + JSP_MATCH(')'); + JslCharPos whileBodyStart = execInfo.lex->tokenStart; + JSP_SAVE_EXECUTE(); + // actually try and execute first bit of while loop (we'll do the rest in the actual loop later) + if (!loopCond) jspSetNoExecute(); + execInfo.execute |= EXEC_IN_LOOP; + jsvUnLock(jspeBlockOrStatement()); + JslCharPos whileBodyEnd = execInfo.lex->tokenStart; + execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP; + if (execInfo.execute == EXEC_CONTINUE) + execInfo.execute = EXEC_YES; + if (execInfo.execute == EXEC_BREAK) { + execInfo.execute = EXEC_YES; + hasHadBreak = true; // fail loop condition, so we exit + } + if (!loopCond) JSP_RESTORE_EXECUTE(); + + while (!hasHadBreak && loopCond +#ifdef JSPARSE_MAX_LOOP_ITERATIONS + && loopCount-->0 +#endif + ) { + jslSeekTo(execInfo.lex, whileCondStart); + cond = jspeBase(); + loopCond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(cond)); + jsvUnLock(cond); + if (loopCond) { + jslSeekTo(execInfo.lex, whileBodyStart); + execInfo.execute |= EXEC_IN_LOOP; + jsvUnLock(jspeBlockOrStatement()); + execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP; + if (execInfo.execute == EXEC_CONTINUE) + execInfo.execute = EXEC_YES; + if (execInfo.execute == EXEC_BREAK) { + execInfo.execute = EXEC_YES; + hasHadBreak = true; + } + } + } + jslSeekTo(execInfo.lex, whileBodyEnd); +#ifdef JSPARSE_MAX_LOOP_ITERATIONS + if (loopCount<=0) { + jsErrorAt("WHILE Loop exceeded the maximum number of iterations (" STRINGIFY(JSPARSE_MAX_LOOP_ITERATIONS) ")", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + } +#endif + return 0; +} + +JsVar *jspeStatementFor() { + JSP_MATCH(LEX_R_FOR); + JSP_MATCH('('); + execInfo.execute |= EXEC_FOR_INIT; + // initialisation + JsVar *forStatement = 0; + // we could have 'for (;;)' - so don't munch up our semicolon if that's all we have + if (execInfo.lex->tk != ';') + forStatement = jspeStatement(); + execInfo.execute &= (JsExecFlags)~EXEC_FOR_INIT; + if (execInfo.lex->tk == LEX_R_IN) { + // for (i in array) + // where i = jsvUnLock(forStatement); + if (!jsvIsName(forStatement)) { + jsvUnLock(forStatement); + jsErrorAt("FOR a IN b - 'a' must be a variable name", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + return 0; + } + bool addedIteratorToScope = false; + if (JSP_SHOULD_EXECUTE && !forStatement->refs) { + // if the variable did not exist, add it to the scope + addedIteratorToScope = true; + jsvAddName(execInfo.parse->root, forStatement); + } + JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_R_IN, jsvUnLock(forStatement), 0); + JsVar *array = jsvSkipNameAndUnLock(jspeExpression()); + JSP_MATCH_WITH_CLEANUP_AND_RETURN(')', jsvUnLock(forStatement);jsvUnLock(array), 0); + JslCharPos forBodyStart = execInfo.lex->tokenStart; + JSP_SAVE_EXECUTE(); + jspSetNoExecute(); + execInfo.execute |= EXEC_IN_LOOP; + jsvUnLock(jspeBlockOrStatement()); + JslCharPos forBodyEnd = execInfo.lex->tokenStart; + execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP; + JSP_RESTORE_EXECUTE(); + + if (jsvIsIterable(array)) { + bool (*checkerFunction)(JsVar*) = 0; + if (jsvIsFunction(array)) checkerFunction = jsvIsInternalFunctionKey; + else if (jsvIsObject(array)) checkerFunction = jsvIsInternalObjectKey; + JsvIterator it; + jsvIteratorNew(&it, array); + bool hasHadBreak = false; + while (JSP_SHOULD_EXECUTE && jsvIteratorHasElement(&it) && !hasHadBreak) { + JsVar *loopIndexVar = jsvIteratorGetKey(&it); + bool ignore = false; + if (checkerFunction && checkerFunction(loopIndexVar)) + ignore = true; + if (!ignore) { + JsVar *indexValue = jsvIsName(loopIndexVar) ? + jsvCopyNameOnly(loopIndexVar, false/*no copy children*/, false/*not a name*/) : + loopIndexVar; + if (indexValue) { // could be out of memory + assert(!jsvIsName(indexValue) && indexValue->refs==0); + jsvSetValueOfName(forStatement, indexValue); + if (indexValue!=loopIndexVar) jsvUnLock(indexValue); + + jsvIteratorNext(&it); + + jslSeekTo(execInfo.lex, forBodyStart); + execInfo.execute |= EXEC_IN_LOOP; + jsvUnLock(jspeBlockOrStatement()); + execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP; + + if (execInfo.execute == EXEC_CONTINUE) + execInfo.execute = EXEC_YES; + if (execInfo.execute == EXEC_BREAK) { + execInfo.execute = EXEC_YES; + hasHadBreak = true; + } + } + } else + jsvIteratorNext(&it); + jsvUnLock(loopIndexVar); + } + jsvIteratorFree(&it); + } else { + jsErrorAt("FOR loop can only iterate over Arrays, Strings or Objects", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + } + + jslSeekTo(execInfo.lex, forBodyEnd); + + if (addedIteratorToScope) { + jsvRemoveChild(execInfo.parse->root, forStatement); + } + jsvUnLock(forStatement); + jsvUnLock(array); + } else { // NORMAL FOR LOOP +#ifdef JSPARSE_MAX_LOOP_ITERATIONS + int loopCount = JSPARSE_MAX_LOOP_ITERATIONS; +#endif + bool loopCond = true; + bool hasHadBreak = false; + + jsvUnLock(forStatement); + JSP_MATCH(';'); + JslCharPos forCondStart = execInfo.lex->tokenStart; + if (execInfo.lex->tk != ';') { + JsVar *cond = jspeBase(); // condition + loopCond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(cond)); + jsvUnLock(cond); + } + JSP_MATCH(';'); + JslCharPos forIterStart = execInfo.lex->tokenStart; + if (execInfo.lex->tk != ')') { // we could have 'for (;;)' + JSP_SAVE_EXECUTE(); + jspSetNoExecute(); + jsvUnLock(jspeBase()); // iterator + JSP_RESTORE_EXECUTE(); + } + JSP_MATCH(')'); + + JslCharPos forBodyStart = execInfo.lex->tokenStart; // actual for body + JSP_SAVE_EXECUTE(); + if (!loopCond) jspSetNoExecute(); + execInfo.execute |= EXEC_IN_LOOP; + jsvUnLock(jspeBlockOrStatement()); + JslCharPos forBodyEnd = execInfo.lex->tokenStart; + execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP; + if (execInfo.execute == EXEC_CONTINUE) + execInfo.execute = EXEC_YES; + if (execInfo.execute == EXEC_BREAK) { + execInfo.execute = EXEC_YES; + hasHadBreak = true; + } + if (!loopCond) JSP_RESTORE_EXECUTE(); + if (loopCond) { + jslSeekTo(execInfo.lex, forIterStart); + if (execInfo.lex->tk != ')') jsvUnLock(jspeBase()); + } + while (!hasHadBreak && JSP_SHOULD_EXECUTE && loopCond +#ifdef JSPARSE_MAX_LOOP_ITERATIONS + && loopCount-->0 +#endif + ) { + jslSeekTo(execInfo.lex, forCondStart); + ; + if (execInfo.lex->tk == ';') { + loopCond = true; + } else { + JsVar *cond = jspeBase(); + loopCond = jsvGetBoolAndUnLock(jsvSkipName(cond)); + jsvUnLock(cond); + } + if (JSP_SHOULD_EXECUTE && loopCond) { + jslSeekTo(execInfo.lex, forBodyStart); + execInfo.execute |= EXEC_IN_LOOP; + jsvUnLock(jspeBlockOrStatement()); + execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP; + if (execInfo.execute == EXEC_CONTINUE) + execInfo.execute = EXEC_YES; + if (execInfo.execute == EXEC_BREAK) { + execInfo.execute = EXEC_YES; + hasHadBreak = true; + } + } + if (JSP_SHOULD_EXECUTE && loopCond) { + jslSeekTo(execInfo.lex, forIterStart); + if (execInfo.lex->tk != ')') jsvUnLock(jspeBase()); + } + } + jslSeekTo(execInfo.lex, forBodyEnd); +#ifdef JSPARSE_MAX_LOOP_ITERATIONS + if (loopCount<=0) { + jsErrorAt("FOR Loop exceeded the maximum number of iterations ("STRINGIFY(JSPARSE_MAX_LOOP_ITERATIONS)")", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + } +#endif + } + return 0; +} + +JsVar *jspeStatementReturn() { + JsVar *result = 0; + JSP_MATCH(LEX_R_RETURN); + if (execInfo.lex->tk != ';') { + // we only want the value, so skip the name if there was one + result = jsvSkipNameAndUnLock(jspeBaseWithComma()); + } + if (JSP_SHOULD_EXECUTE) { + JsVar *resultVar = jspeiFindOnTop(JSPARSE_RETURN_VAR, false); + if (resultVar) { + jspReplaceWith(resultVar, result); + jsvUnLock(resultVar); + } else { + jsErrorAt("RETURN statement, but not in a function.\n", execInfo.lex, execInfo.lex->tokenLastEnd); + jspSetError(); + } + jspSetNoExecute(); // Stop anything else in this function executing + } + jsvUnLock(result); + return 0; +} + +JsVar *jspeStatementFunctionDecl() { + JsVar *funcName = 0; + JsVar *funcVar; + JSP_MATCH(LEX_R_FUNCTION); + if (JSP_SHOULD_EXECUTE) + funcName = jsvMakeIntoVariableName(jsvNewFromString(jslGetTokenValueAsString(execInfo.lex)), 0); + if (!funcName) { // out of memory + jspSetError(); + return 0; + } + JSP_MATCH(LEX_ID); + funcVar = jspeFunctionDefinition(); + if (JSP_SHOULD_EXECUTE) { + // find a function with the same name (or make one) + // OPT: can Find* use just a JsVar that is a 'name'? + JsVar *existingFunc = jspeiFindNameOnTop(funcName, true); + // replace it + jspReplaceWith(existingFunc, funcVar); + jsvUnLock(funcName); + funcName = existingFunc; + } + jsvUnLock(funcVar); + return funcName; +} + +JsVar *jspeStatement() { + if (execInfo.lex->tk==LEX_ID || + execInfo.lex->tk==LEX_INT || + execInfo.lex->tk==LEX_FLOAT || + execInfo.lex->tk==LEX_STR || + execInfo.lex->tk==LEX_R_NEW || + execInfo.lex->tk==LEX_R_NULL || + execInfo.lex->tk==LEX_R_UNDEFINED || + execInfo.lex->tk==LEX_R_TRUE || + execInfo.lex->tk==LEX_R_FALSE || + execInfo.lex->tk==LEX_R_THIS || + execInfo.lex->tk==LEX_R_TYPEOF || + execInfo.lex->tk==LEX_R_VOID || + execInfo.lex->tk==LEX_PLUSPLUS || + execInfo.lex->tk==LEX_MINUSMINUS || + execInfo.lex->tk=='!' || + execInfo.lex->tk=='-' || + execInfo.lex->tk=='+' || + execInfo.lex->tk=='~' || + execInfo.lex->tk=='[' || + execInfo.lex->tk=='(') { + /* Execute a simple statement that only contains basic arithmetic... */ + return jspeBaseWithComma(); + } else if (execInfo.lex->tk=='{') { + /* A block of code */ + return jspeBlock(); + } else if (execInfo.lex->tk==';') { + /* Empty statement - to allow things like ;;; */ + JSP_MATCH(';'); + return 0; + } else if (execInfo.lex->tk==LEX_R_VAR) { + return jspeStatementVar(); + } else if (execInfo.lex->tk==LEX_R_IF) { + return jspeStatementIf(); + } else if (execInfo.lex->tk==LEX_R_WHILE) { + return jspeStatementWhile(); + } else if (execInfo.lex->tk==LEX_R_FOR) { + return jspeStatementFor(); + } else if (execInfo.lex->tk==LEX_R_RETURN) { + return jspeStatementReturn(); + } else if (execInfo.lex->tk==LEX_R_FUNCTION) { + return jspeStatementFunctionDecl(); + } else if (execInfo.lex->tk==LEX_R_CONTINUE) { + JSP_MATCH(LEX_R_CONTINUE); + if (JSP_SHOULD_EXECUTE) { + if (!(execInfo.execute & EXEC_IN_LOOP)) + jsErrorAt("CONTINUE statement outside of FOR or WHILE loop", execInfo.lex, execInfo.lex->tokenLastEnd); + else + execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_RUN_MASK) | EXEC_CONTINUE; + } + } else if (execInfo.lex->tk==LEX_R_BREAK) { + JSP_MATCH(LEX_R_BREAK); + if (JSP_SHOULD_EXECUTE) { + if (!(execInfo.execute & (EXEC_IN_LOOP|EXEC_IN_SWITCH))) + jsErrorAt("BREAK statement outside of SWITCH, FOR or WHILE loop", execInfo.lex, execInfo.lex->tokenLastEnd); + else + execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_RUN_MASK) | EXEC_BREAK; + } + } else if (execInfo.lex->tk==LEX_R_SWITCH) { + return jspeStatementSwitch(); + } else JSP_MATCH(LEX_EOF); + return 0; +} + +// ----------------------------------------------------------------------------- +/// Create a new built-in object that jswrapper can use to check for built-in functions +JsVar *jspNewBuiltin(const char *instanceOf) { + JsVar *objFunc = jsvNewWithFlags(JSV_FUNCTION); + if (!objFunc) return 0; // out of memory + // set object data to be object name + if (strlen(instanceOf)==sizeof(objFunc->varData)) + memcpy(objFunc->varData.str, instanceOf, sizeof(objFunc->varData)); // no trailing zero! + else + strncpy(objFunc->varData.str, instanceOf, sizeof(objFunc->varData)); + return objFunc; +} + + +JsVar *jspNewObject(JsParse *parse, const char *name, const char *instanceOf) { + JsVar *objFuncName = jsvFindChildFromString(parse->root, instanceOf, true); + if (!objFuncName) // out of memory + return 0; + + JsVar *objFunc = jsvSkipName(objFuncName); + if (!objFunc) { + objFunc = jspNewBuiltin(instanceOf); + if (!objFunc) { // out of memory + jsvUnLock(objFuncName); + return 0; + } + + // set up name + jsvSetValueOfName(objFuncName, objFunc); + } + + JsVar *prototypeName = jsvFindChildFromString(objFunc, JSPARSE_PROTOTYPE_VAR, true); + jspEnsureIsPrototype(prototypeName); // make sure it's an object + jsvUnLock(objFunc); + if (!prototypeName) { // out of memory + jsvUnLock(objFuncName); + return 0; + } + + JsVar *obj = jsvNewWithFlags(JSV_OBJECT); + if (!obj) { // out of memory + jsvUnLock(objFuncName); + jsvUnLock(prototypeName); + return 0; + } + if (name) { + // set object data to be object name + strncpy(obj->varData.str, name, sizeof(obj->varData)); + } + // add inherits/constructor/etc + jsvUnLock(jsvAddNamedChild(obj, prototypeName, JSPARSE_INHERITS_VAR)); + jsvUnLock(prototypeName);prototypeName=0; + jsvUnLock(jsvAddNamedChild(obj, objFuncName, JSPARSE_CONSTRUCTOR_VAR)); + jsvUnLock(objFuncName); + if (name) { + JsVar *objName = jsvAddNamedChild(parse->root, obj, name); + jsvUnLock(obj); + if (!objName) { // out of memory + return 0; + } + return objName; + } else + return obj; +} + +/** Returns true if the constructor function given is the same as that + * of the object with the given name. */ +bool jspIsConstructor(JsVar *constructor, const char *constructorName) { + JsVar *objFunc = jsvObjectGetChild(execInfo.parse->root, constructorName, 0); + if (!objFunc) return false; + bool isConstructor = objFunc == constructor; + jsvUnLock(objFunc); + return isConstructor; +} + +// ----------------------------------------------------------------------------- + +void jspSoftInit(JsParse *parse) { + parse->root = jsvFindOrCreateRoot(); + // Root now has a lock and a ref +} + +/** Is v likely to have been created by this parser? */ +bool jspIsCreatedObject(JsParse *parse, JsVar *v) { + return + v==parse->root; +} + +void jspSoftKill(JsParse *parse) { + jsvUnLock(parse->root); + // Root now has just a ref +} + +void jspInit(JsParse *parse) { + jspSoftInit(parse); +} + +void jspKill(JsParse *parse) { + jspSoftKill(parse); + // Unreffing this should completely kill everything attached to root + JsVar *r = jsvFindOrCreateRoot(); + jsvUnRef(r); + jsvUnLock(r); +} + + + +JsVar *jspEvaluateVar(JsParse *parse, JsVar *str, JsVar *scope) { + JsLex lex; + JsVar *v = 0; + JSP_SAVE_EXECUTE(); + JsExecInfo oldExecInfo = execInfo; + + assert(jsvIsString(str)); + jslInit(&lex, str); + + jspeiInit(parse, &lex); + bool scopeAdded = false; + if (scope) + scopeAdded = jspeiAddScope(jsvGetRef(scope)); + while (!JSP_HAS_ERROR && execInfo.lex->tk != LEX_EOF) { + jsvUnLock(v); + v = jspeBlockOrStatement(); + } + // clean up + if (scopeAdded) jspeiRemoveScope(); + jspeiKill(); + jslKill(&lex); + + // restore state + JSP_RESTORE_EXECUTE(); + oldExecInfo.execute = execInfo.execute; // JSP_RESTORE_EXECUTE has made this ok. + execInfo = oldExecInfo; + + // It may have returned a reference, but we just want the value... + if (v) { + return jsvSkipNameAndUnLock(v); + } + // nothing returned + return 0; +} + +JsVar *jspEvaluate(JsParse *parse, const char *str) { + JsVar *v = 0; + + JsVar *evCode = jsvNewFromString(str); + if (!jsvIsMemoryFull()) + v = jspEvaluateVar(parse, evCode, 0); + jsvUnLock(evCode); + + return v; +} + +bool jspExecuteFunction(JsParse *parse, JsVar *func, JsVar *parent, int argCount, JsVar **argPtr) { + JSP_SAVE_EXECUTE(); + JsExecInfo oldExecInfo = execInfo; + + jspeiInit(parse, 0); + JsVar *resultVar = jspeFunctionCall(func, 0, parent, false, argCount, argPtr); + bool result = jsvGetBool(resultVar); + jsvUnLock(resultVar); + // clean up + jspeiKill(); + // restore state + JSP_RESTORE_EXECUTE(); + oldExecInfo.execute = execInfo.execute; // JSP_RESTORE_EXECUTE has made this ok. + execInfo = oldExecInfo; + + + return result; +} + + +/// Evaluate a JavaScript module and return its exports +JsVar *jspEvaluateModule(JsParse *parse, JsVar *moduleContents) { + assert(jsvIsString(moduleContents)); + JsVar *scope = jsvNewWithFlags(JSV_OBJECT); + if (!scope) return 0; // out of mem + JsVar *scopeExports = jsvNewWithFlags(JSV_OBJECT); + if (!scopeExports) { jsvUnLock(scope); return 0; } // out of mem + jsvUnLock(jsvAddNamedChild(scope, scopeExports, "exports")); + + jsvUnLock(jspEvaluateVar(parse, moduleContents, scope)); + + jsvUnLock(scope); + return scopeExports; +} -- cgit v1.3.1