12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213 |
- /*
- * 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;
- }
|