2214 lines
75 KiB
C
2214 lines
75 KiB
C
/*
|
|
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
|
|
*
|
|
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
|
|
*
|
|
* 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;
|
|
}
|