summaryrefslogtreecommitdiff
path: root/components/external/espruino/src/jsparse.c
diff options
context:
space:
mode:
authorshaolin <[email protected]>2014-03-04 09:14:48 +0800
committershaolin <[email protected]>2014-03-04 09:14:48 +0800
commit020e95d91b5e1d7f902674a320e42657bee3ebfe (patch)
tree65b65bfa527e0e8670dc807a8fee8d507d67ca48 /components/external/espruino/src/jsparse.c
parent0fb9c54efda7fef4346523fdc4b92512a0441ee1 (diff)
parent738871fd9ebbd53da6cfcc429dfdd5ffc17fd685 (diff)
Merge branch 'master' of https://github.com/RT-Thread/rt-thread
Diffstat (limited to 'components/external/espruino/src/jsparse.c')
-rw-r--r--components/external/espruino/src/jsparse.c2213
1 files changed, 2213 insertions, 0 deletions
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 <[email protected]>
+ *
+ * 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;i<execInfo.scopeCount;i++) {
+ //printf("%d %d\n",i,execInfo.scopes[i]);
+ JsVar *scope = jsvLock(execInfo.scopes[i]);
+ JsVar *idx = jsvMakeIntoVariableName(jsvNewFromInteger(i), scope);
+ jsvUnLock(scope);
+ if (!idx) { // out of memort
+ jspSetError();
+ return arr;
+ }
+ jsvAddName(arr, idx);
+ jsvUnLock(idx);
+ }
+ //printf("%d\n",arr->firstChild);
+ 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 (args<argCount) {
+ JsVar *param = v ? jsvLock(v) : 0;
+ bool paramDefined = jsvIsFunctionParameter(param);
+ JsVar *paramName = paramDefined ? jsvCopy(param) : jsvNewFromEmptyString();
+ paramName->flags |= 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;i<execInfo.scopeCount;i++)
+ oldScopes[i] = execInfo.scopes[i];
+ // if we have a scope var, load it up. We may not have one if there were no scopes apart from root
+ JsVar *functionScope = jsvFindChildFromString(function, JSPARSE_FUNCTION_SCOPE_NAME, false);
+ if (functionScope) {
+ JsVar *functionScopeVar = jsvLock(functionScope->firstChild);
+ //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;i<execInfo.scopeCount;i++)
+ jsvUnRefRef(execInfo.scopes[i]);
+ // restore function scopes
+ for (i=0;i<oldScopeCount;i++)
+ execInfo.scopes[i] = oldScopes[i];
+ execInfo.scopeCount = oldScopeCount;
+ }
+ }
+
+ /* Return to old 'this' var. No need to unlock as we never locked before */
+ execInfo.thisVar = oldThisVar;
+
+ /* get the real return var before we remove it from our function */
+ returnVar = jsvSkipNameAndUnLock(returnVarName);
+ if (returnVarName) // could have failed with out of memory
+ jsvSetValueOfName(returnVarName, 0); // remove return value (which helps stops circular references)
+ jsvUnLock(functionRoot);
+ if (returnVar)
+ return returnVar;
+ else
+ return 0;
+ } else if (isParsing) { // ---------------------------------- function, but not executing - just parse args and be done
+ jspeParseFunctionCallBrackets();
+ /* Do not return function, as it will be unlocked! */
+ return 0;
+ } else return 0;
+}
+
+JsVar *jspeFactorSingleId() {
+ JsVar *a = JSP_SHOULD_EXECUTE ? jspeiFindInScopes(jslGetTokenValueAsString(execInfo.lex)) : 0;
+ if (JSP_SHOULD_EXECUTE && !a) {
+ const char *tokenName = jslGetTokenValueAsString(execInfo.lex); // BEWARE - this won't hang around forever!
+ /* Special case! We haven't found the variable, so check out
+ * and see if it's one of our builtins... */
+ if (jswIsBuiltInObject(tokenName)) {
+ JsVar *obj = jspNewBuiltin(tokenName);
+ if (obj) { // not out of memory
+ a = jsvAddNamedChild(execInfo.parse->root, 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;
+}