first commit, still mostly just touching up formatting
[tinyjs-rewrite.git] / .svn / pristine / 62 / 6253d0e0bdcc36df327e3e92379ae5b0316d73b6.svn-base
blob448eefb981956d8659e41fa02b853bba64b1191d
1 /*
2  * TinyJS
3  *
4  * A single-file Javascript-alike engine
5  *
6  * Authored By Gordon Williams <gw@pur3.co.uk>
7  *
8  * Copyright (C) 2009 Pur3 Ltd
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a copy of
11  * this software and associated documentation files (the "Software"), to deal in
12  * the Software without restriction, including without limitation the rights to
13  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
14  * of the Software, and to permit persons to whom the Software is furnished to do
15  * so, subject to the following conditions:
17  * The above copyright notice and this permission notice shall be included in all
18  * copies or substantial portions of the Software.
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26  * SOFTWARE.
27  */
29 /* Version 0.1  :  (gw) First published on Google Code
30    Version 0.11 :  Making sure the 'root' variable never changes
31                    'symbol_base' added for the current base of the sybmbol table
32    Version 0.12 :  Added findChildOrCreate, changed string passing to use references
33                    Fixed broken string encoding in getJSString()
34                    Removed getInitCode and added getJSON instead
35                    Added nil
36                    Added rough JSON parsing
37                    Improved example app
38    Version 0.13 :  Added tokenEnd/tokenLastEnd to lexer to avoid parsing whitespace
39                    Ability to define functions without names
40                    Can now do "var mine = function(a,b) { ... };"
41                    Slightly better 'trace' function
42                    Added findChildOrCreateByPath function
43                    Added simple test suite
44                    Added skipping of blocks when not executing
45    Version 0.14 :  Added parsing of more number types
46                    Added parsing of string defined with '
47                    Changed nil to null as per spec, added 'undefined'
48                    Now set variables with the correct scope, and treat unknown
49                               as 'undefined' rather than failing
50                    Added proper (I hope) handling of null and undefined
51                    Added === check
52    Version 0.15 :  Fix for possible memory leaks
53    Version 0.16 :  Removal of un-needed findRecursive calls
54                    symbol_base removed and replaced with 'scopes' stack
55                    Added reference counting a proper tree structure
56                        (Allowing pass by reference)
57                    Allowed JSON output to output IDs, not strings
58                    Added get/set for array indices
59                    Changed Callbacks to include user data pointer
60                    Added some support for objects
61                    Added more Java-esque builtin functions
62    Version 0.17 :  Now we don't deepCopy the parent object of the class
63                    Added JSON.stringify and eval()
64                    Nicer JSON indenting
65                    Fixed function output in JSON
66                    Added evaluateComplex
67                    Fixed some reentrancy issues with evaluate/execute
68    Version 0.18 :  Fixed some issues with code being executed when it shouldn't
69    Version 0.19 :  Added array.length
70                    Changed '__parent' to 'prototype' to bring it more in line with javascript
71    Version 0.20 :  Added '%' operator
72    Version 0.21 :  Added array type
73                    String.length() no more - now String.length
74                    Added extra constructors to reduce confusion
75                    Fixed checks against undefined
76    Version 0.22 :  First part of ardi's changes:
77                        sprintf -> sprintf_s
78                        extra tokens parsed
79                        array memory leak fixed
80                    Fixed memory leak in evaluateComplex
81                    Fixed memory leak in FOR loops
82                    Fixed memory leak for unary minus
83    Version 0.23 :  Allowed evaluate[Complex] to take in semi-colon separated
84                      statements and then only return the value from the last one.
85                      Also checks to make sure *everything* was parsed.
86                    Ints + doubles are now stored in binary form (faster + more precise)
87    Version 0.24 :  More useful error for maths ops
88                    Don't dump everything on a match error.
89    Version 0.25 :  Better string escaping
90    Version 0.26 :  Add CScriptVar::equals
91                    Add built-in array functions
92    Version 0.27 :  Added OZLB's TinyJS.setVariable (with some tweaks)
93                    Added OZLB's Maths Functions
94    Version 0.28 :  Ternary operator
95                    Rudimentary call stack on error
96                    Added String Character functions
97                    Added shift operators
98    Version 0.29 :  Added new object via functions
99                    Fixed getString() for double on some platforms
100    Version 0.30 :  Rlyeh Mario's patch for Math Functions on VC++
101    Version 0.31 :  Add exec() to TinyJS functions
102                    Now print quoted JSON that can be read by PHP/Python parsers
103                    Fixed postfix increment operator
104    Version 0.32 :  Fixed Math.randInt on 32 bit PCs, where it was broken
105    Version 0.33 :  Fixed Memory leak + brokenness on === comparison
107     NOTE:
108           Constructing an array with an initial length 'Array(5)' doesn't work
109           Recursive loops of data such as a.foo = a; fail to be garbage collected
110           length variable cannot be set
111           The postfix increment operator returns the current value, not the previous as it should.
112           There is no prefix increment operator
113           Arrays are implemented as a linked list - hence a lookup time is O(n)
115     TODO:
116           Utility va-args style function in TinyJS for executing a function directly
117           Merge the parsing of expressions/statements so eval("statement") works like we'd expect.
118           Move 'shift' implementation into mathsOp
120  */
122 #include "TinyJS.h"
123 #include <assert.h>
125 #define ASSERT(X) assert(X)
126 /* Frees the given link IF it isn't owned by anything else */
127 #define CLEAN(x) { CScriptVarLink *__v = x; if (__v && !__v->owned) { delete __v; } }
128 /* Create a LINK to point to VAR and free the old link.
129  * BUT this is more clever - it tries to keep the old link if it's not owned to save allocations */
130 #define CREATE_LINK(LINK, VAR) { if (!LINK || LINK->owned) LINK = new CScriptVarLink(VAR); else LINK->replaceWith(VAR); }
132 #include <string>
133 #include <string.h>
134 #include <sstream>
135 #include <cstdlib>
136 #include <stdio.h>
138 using namespace std;
140 #ifdef _WIN32
141 #ifdef _DEBUG
142    #ifndef DBG_NEW
143       #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
144       #define new DBG_NEW
145    #endif
146 #endif
147 #endif
149 #ifdef __GNUC__
150 #define vsprintf_s vsnprintf
151 #define sprintf_s snprintf
152 #define _strdup strdup
153 #endif
155 // ----------------------------------------------------------------------------------- Memory Debug
157 #define DEBUG_MEMORY 0
159 #if DEBUG_MEMORY
161 vector<CScriptVar*> allocatedVars;
162 vector<CScriptVarLink*> allocatedLinks;
164 void mark_allocated(CScriptVar *v) {
165     allocatedVars.push_back(v);
168 void mark_deallocated(CScriptVar *v) {
169     for (size_t i=0;i<allocatedVars.size();i++) {
170       if (allocatedVars[i] == v) {
171         allocatedVars.erase(allocatedVars.begin()+i);
172         break;
173       }
174     }
177 void mark_allocated(CScriptVarLink *v) {
178     allocatedLinks.push_back(v);
181 void mark_deallocated(CScriptVarLink *v) {
182     for (size_t i=0;i<allocatedLinks.size();i++) {
183       if (allocatedLinks[i] == v) {
184         allocatedLinks.erase(allocatedLinks.begin()+i);
185         break;
186       }
187     }
190 void show_allocated() {
191     for (size_t i=0;i<allocatedVars.size();i++) {
192       printf("ALLOCATED, %d refs\n", allocatedVars[i]->getRefs());
193       allocatedVars[i]->trace("  ");
194     }
195     for (size_t i=0;i<allocatedLinks.size();i++) {
196       printf("ALLOCATED LINK %s, allocated[%d] to \n", allocatedLinks[i]->name.c_str(), allocatedLinks[i]->var->getRefs());
197       allocatedLinks[i]->var->trace("  ");
198     }
199     allocatedVars.clear();
200     allocatedLinks.clear();
202 #endif
204 // ----------------------------------------------------------------------------------- Utils
205 bool isWhitespace(char ch) {
206     return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r');
209 bool isNumeric(char ch) {
210     return (ch>='0') && (ch<='9');
212 bool isNumber(const string &str) {
213     for (size_t i=0;i<str.size();i++)
214       if (!isNumeric(str[i])) return false;
215     return true;
217 bool isHexadecimal(char ch) {
218     return ((ch>='0') && (ch<='9')) ||
219            ((ch>='a') && (ch<='f')) ||
220            ((ch>='A') && (ch<='F'));
222 bool isAlpha(char ch) {
223     return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_';
226 bool isIDString(const char *s) {
227     if (!isAlpha(*s))
228         return false;
229     while (*s) {
230         if (!(isAlpha(*s) || isNumeric(*s)))
231             return false;
232         s++;
233     }
234     return true;
237 void replace(string &str, char textFrom, const char *textTo) {
238     int sLen = strlen(textTo);
239     size_t p = str.find(textFrom);
240     while (p != string::npos) {
241         str = str.substr(0, p) + textTo + str.substr(p+1);
242         p = str.find(textFrom, p+sLen);
243     }
246 /// convert the given string into a quoted string suitable for javascript
247 std::string getJSString(const std::string &str) {
248     std::string nStr = str;
249     for (size_t i=0;i<nStr.size();i++) {
250       const char *replaceWith = "";
251       bool replace = true;
253       switch (nStr[i]) {
254         case '\\': replaceWith = "\\\\"; break;
255         case '\n': replaceWith = "\\n"; break;
256         case '\r': replaceWith = "\\r"; break;
257         case '\a': replaceWith = "\\a"; break;
258         case '"': replaceWith = "\\\""; break;
259         default: {
260           int nCh = ((int)nStr[i]) &0xFF;
261           if (nCh<32 || nCh>127) {
262             char buffer[5];
263             sprintf_s(buffer, 5, "\\x%02X", nCh);
264             replaceWith = buffer;
265           } else replace=false;
266         }
267       }
269       if (replace) {
270         nStr = nStr.substr(0, i) + replaceWith + nStr.substr(i+1);
271         i += strlen(replaceWith)-1;
272       }
273     }
274     return "\"" + nStr + "\"";
277 /** Is the string alphanumeric */
278 bool isAlphaNum(const std::string &str) {
279     if (str.size()==0) return true;
280     if (!isAlpha(str[0])) return false;
281     for (size_t i=0;i<str.size();i++)
282       if (!(isAlpha(str[i]) || isNumeric(str[i])))
283         return false;
284     return true;
287 // ----------------------------------------------------------------------------------- CSCRIPTEXCEPTION
289 CScriptException::CScriptException(const string &exceptionText) {
290     text = exceptionText;
293 // ----------------------------------------------------------------------------------- CSCRIPTLEX
295 CScriptLex::CScriptLex(const string &input) {
296     data = _strdup(input.c_str());
297     dataOwned = true;
298     dataStart = 0;
299     dataEnd = strlen(data);
300     reset();
303 CScriptLex::CScriptLex(CScriptLex *owner, int startChar, int endChar) {
304     data = owner->data;
305     dataOwned = false;
306     dataStart = startChar;
307     dataEnd = endChar;
308     reset();
311 CScriptLex::~CScriptLex(void)
313     if (dataOwned)
314         free((void*)data);
317 void CScriptLex::reset() {
318     dataPos = dataStart;
319     tokenStart = 0;
320     tokenEnd = 0;
321     tokenLastEnd = 0;
322     tk = 0;
323     tkStr = "";
324     getNextCh();
325     getNextCh();
326     getNextToken();
329 void CScriptLex::match(int expected_tk) {
330     if (tk!=expected_tk) {
331         ostringstream errorString;
332         errorString << "Got " << getTokenStr(tk) << " expected " << getTokenStr(expected_tk)
333          << " at " << getPosition(tokenStart);
334         throw new CScriptException(errorString.str());
335     }
336     getNextToken();
339 string CScriptLex::getTokenStr(int token) {
340     if (token>32 && token<128) {
341         char buf[4] = "' '";
342         buf[1] = (char)token;
343         return buf;
344     }
345     switch (token) {
346         case LEX_EOF : return "EOF";
347         case LEX_ID : return "ID";
348         case LEX_INT : return "INT";
349         case LEX_FLOAT : return "FLOAT";
350         case LEX_STR : return "STRING";
351         case LEX_EQUAL : return "==";
352         case LEX_TYPEEQUAL : return "===";
353         case LEX_NEQUAL : return "!=";
354         case LEX_NTYPEEQUAL : return "!==";
355         case LEX_LEQUAL : return "<=";
356         case LEX_LSHIFT : return "<<";
357         case LEX_LSHIFTEQUAL : return "<<=";
358         case LEX_GEQUAL : return ">=";
359         case LEX_RSHIFT : return ">>";
360         case LEX_RSHIFTUNSIGNED : return ">>";
361         case LEX_RSHIFTEQUAL : return ">>=";
362         case LEX_PLUSEQUAL : return "+=";
363         case LEX_MINUSEQUAL : return "-=";
364         case LEX_PLUSPLUS : return "++";
365         case LEX_MINUSMINUS : return "--";
366         case LEX_ANDEQUAL : return "&=";
367         case LEX_ANDAND : return "&&";
368         case LEX_OREQUAL : return "|=";
369         case LEX_OROR : return "||";
370         case LEX_XOREQUAL : return "^=";
371                 // reserved words
372         case LEX_R_IF : return "if";
373         case LEX_R_ELSE : return "else";
374         case LEX_R_DO : return "do";
375         case LEX_R_WHILE : return "while";
376         case LEX_R_FOR : return "for";
377         case LEX_R_BREAK : return "break";
378         case LEX_R_CONTINUE : return "continue";
379         case LEX_R_FUNCTION : return "function";
380         case LEX_R_RETURN : return "return";
381         case LEX_R_VAR : return "var";
382         case LEX_R_TRUE : return "true";
383         case LEX_R_FALSE : return "false";
384         case LEX_R_NULL : return "null";
385         case LEX_R_UNDEFINED : return "undefined";
386         case LEX_R_NEW : return "new";
387     }
389     ostringstream msg;
390     msg << "?[" << token << "]";
391     return msg.str();
394 void CScriptLex::getNextCh() {
395     currCh = nextCh;
396     if (dataPos < dataEnd)
397         nextCh = data[dataPos];
398     else
399         nextCh = 0;
400     dataPos++;
403 void CScriptLex::getNextToken() {
404     tk = LEX_EOF;
405     tkStr.clear();
406     while (currCh && isWhitespace(currCh)) getNextCh();
407     // newline comments
408     if (currCh=='/' && nextCh=='/') {
409         while (currCh && currCh!='\n') getNextCh();
410         getNextCh();
411         getNextToken();
412         return;
413     }
414     // block comments
415     if (currCh=='/' && nextCh=='*') {
416         while (currCh && (currCh!='*' || nextCh!='/')) getNextCh();
417         getNextCh();
418         getNextCh();
419         getNextToken();
420         return;
421     }
422     // record beginning of this token
423     tokenStart = dataPos-2;
424     // tokens
425     if (isAlpha(currCh)) { //  IDs
426         while (isAlpha(currCh) || isNumeric(currCh)) {
427             tkStr += currCh;
428             getNextCh();
429         }
430         tk = LEX_ID;
431              if (tkStr=="if") tk = LEX_R_IF;
432         else if (tkStr=="else") tk = LEX_R_ELSE;
433         else if (tkStr=="do") tk = LEX_R_DO;
434         else if (tkStr=="while") tk = LEX_R_WHILE;
435         else if (tkStr=="for") tk = LEX_R_FOR;
436         else if (tkStr=="break") tk = LEX_R_BREAK;
437         else if (tkStr=="continue") tk = LEX_R_CONTINUE;
438         else if (tkStr=="function") tk = LEX_R_FUNCTION;
439         else if (tkStr=="return") tk = LEX_R_RETURN;
440         else if (tkStr=="var") tk = LEX_R_VAR;
441         else if (tkStr=="true") tk = LEX_R_TRUE;
442         else if (tkStr=="false") tk = LEX_R_FALSE;
443         else if (tkStr=="null") tk = LEX_R_NULL;
444         else if (tkStr=="undefined") tk = LEX_R_UNDEFINED;
445         else if (tkStr=="new") tk = LEX_R_NEW;
446     } else if (isNumeric(currCh)) { // Numbers
447         bool isHex = false;
448         if (currCh=='0') { tkStr += currCh; getNextCh(); }
449         if (currCh=='x') {
450           isHex = true;
451           tkStr += currCh; getNextCh();
452         }
453         tk = LEX_INT;
454         while (isNumeric(currCh) || (isHex && isHexadecimal(currCh))) {
455             tkStr += currCh;
456             getNextCh();
457         }
458         if (!isHex && currCh=='.') {
459             tk = LEX_FLOAT;
460             tkStr += '.';
461             getNextCh();
462             while (isNumeric(currCh)) {
463                 tkStr += currCh;
464                 getNextCh();
465             }
466         }
467         // do fancy e-style floating point
468         if (!isHex && (currCh=='e'||currCh=='E')) {
469           tk = LEX_FLOAT;
470           tkStr += currCh; getNextCh();
471           if (currCh=='-') { tkStr += currCh; getNextCh(); }
472           while (isNumeric(currCh)) {
473              tkStr += currCh; getNextCh();
474           }
475         }
476     } else if (currCh=='"') {
477         // strings...
478         getNextCh();
479         while (currCh && currCh!='"') {
480             if (currCh == '\\') {
481                 getNextCh();
482                 switch (currCh) {
483                 case 'n' : tkStr += '\n'; break;
484                 case '"' : tkStr += '"'; break;
485                 case '\\' : tkStr += '\\'; break;
486                 default: tkStr += currCh;
487                 }
488             } else {
489                 tkStr += currCh;
490             }
491             getNextCh();
492         }
493         getNextCh();
494         tk = LEX_STR;
495     } else if (currCh=='\'') {
496         // strings again...
497         getNextCh();
498         while (currCh && currCh!='\'') {
499             if (currCh == '\\') {
500                 getNextCh();
501                 switch (currCh) {
502                 case 'n' : tkStr += '\n'; break;
503                 case 'a' : tkStr += '\a'; break;
504                 case 'r' : tkStr += '\r'; break;
505                 case 't' : tkStr += '\t'; break;
506                 case '\'' : tkStr += '\''; break;
507                 case '\\' : tkStr += '\\'; break;
508                 case 'x' : { // hex digits
509                               char buf[3] = "??";
510                               getNextCh(); buf[0] = currCh;
511                               getNextCh(); buf[1] = currCh;
512                               tkStr += (char)strtol(buf,0,16);
513                            } break;
514                 default: if (currCh>='0' && currCh<='7') {
515                            // octal digits
516                            char buf[4] = "???";
517                            buf[0] = currCh;
518                            getNextCh(); buf[1] = currCh;
519                            getNextCh(); buf[2] = currCh;
520                            tkStr += (char)strtol(buf,0,8);
521                          } else
522                            tkStr += currCh;
523                 }
524             } else {
525                 tkStr += currCh;
526             }
527             getNextCh();
528         }
529         getNextCh();
530         tk = LEX_STR;
531     } else {
532         // single chars
533         tk = currCh;
534         if (currCh) getNextCh();
535         if (tk=='=' && currCh=='=') { // ==
536             tk = LEX_EQUAL;
537             getNextCh();
538             if (currCh=='=') { // ===
539               tk = LEX_TYPEEQUAL;
540               getNextCh();
541             }
542         } else if (tk=='!' && currCh=='=') { // !=
543             tk = LEX_NEQUAL;
544             getNextCh();
545             if (currCh=='=') { // !==
546               tk = LEX_NTYPEEQUAL;
547               getNextCh();
548             }
549         } else if (tk=='<' && currCh=='=') {
550             tk = LEX_LEQUAL;
551             getNextCh();
552         } else if (tk=='<' && currCh=='<') {
553             tk = LEX_LSHIFT;
554             getNextCh();
555             if (currCh=='=') { // <<=
556               tk = LEX_LSHIFTEQUAL;
557               getNextCh();
558             }
559         } else if (tk=='>' && currCh=='=') {
560             tk = LEX_GEQUAL;
561             getNextCh();
562         } else if (tk=='>' && currCh=='>') {
563             tk = LEX_RSHIFT;
564             getNextCh();
565             if (currCh=='=') { // >>=
566               tk = LEX_RSHIFTEQUAL;
567               getNextCh();
568             } else if (currCh=='>') { // >>>
569               tk = LEX_RSHIFTUNSIGNED;
570               getNextCh();
571             }
572         }  else if (tk=='+' && currCh=='=') {
573             tk = LEX_PLUSEQUAL;
574             getNextCh();
575         }  else if (tk=='-' && currCh=='=') {
576             tk = LEX_MINUSEQUAL;
577             getNextCh();
578         }  else if (tk=='+' && currCh=='+') {
579             tk = LEX_PLUSPLUS;
580             getNextCh();
581         }  else if (tk=='-' && currCh=='-') {
582             tk = LEX_MINUSMINUS;
583             getNextCh();
584         } else if (tk=='&' && currCh=='=') {
585             tk = LEX_ANDEQUAL;
586             getNextCh();
587         } else if (tk=='&' && currCh=='&') {
588             tk = LEX_ANDAND;
589             getNextCh();
590         } else if (tk=='|' && currCh=='=') {
591             tk = LEX_OREQUAL;
592             getNextCh();
593         } else if (tk=='|' && currCh=='|') {
594             tk = LEX_OROR;
595             getNextCh();
596         } else if (tk=='^' && currCh=='=') {
597             tk = LEX_XOREQUAL;
598             getNextCh();
599         }
600     }
601     /* This isn't quite right yet */
602     tokenLastEnd = tokenEnd;
603     tokenEnd = dataPos-3;
606 string CScriptLex::getSubString(int lastPosition) {
607     int lastCharIdx = tokenLastEnd+1;
608     if (lastCharIdx < dataEnd) {
609         /* save a memory alloc by using our data array to create the
610            substring */
611         char old = data[lastCharIdx];
612         data[lastCharIdx] = 0;
613         std::string value = &data[lastPosition];
614         data[lastCharIdx] = old;
615         return value;
616     } else {
617         return std::string(&data[lastPosition]);
618     }
622 CScriptLex *CScriptLex::getSubLex(int lastPosition) {
623     int lastCharIdx = tokenLastEnd+1;
624     if (lastCharIdx < dataEnd)
625         return new CScriptLex(this, lastPosition, lastCharIdx);
626     else
627         return new CScriptLex(this, lastPosition, dataEnd );
630 string CScriptLex::getPosition(int pos) {
631     if (pos<0) pos=tokenLastEnd;
632     int line = 1,col = 1;
633     for (int i=0;i<pos;i++) {
634         char ch;
635         if (i < dataEnd)
636             ch = data[i];
637         else
638             ch = 0;
639         col++;
640         if (ch=='\n') {
641             line++;
642             col = 0;
643         }
644     }
645     char buf[256];
646     sprintf_s(buf, 256, "(line: %d, col: %d)", line, col);
647     return buf;
650 // ----------------------------------------------------------------------------------- CSCRIPTVARLINK
652 CScriptVarLink::CScriptVarLink(CScriptVar *var, const std::string &name) {
653 #if DEBUG_MEMORY
654     mark_allocated(this);
655 #endif
656     this->name = name;
657     this->nextSibling = 0;
658     this->prevSibling = 0;
659     this->var = var->ref();
660     this->owned = false;
663 CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) {
664     // Copy constructor
665 #if DEBUG_MEMORY
666     mark_allocated(this);
667 #endif
668     this->name = link.name;
669     this->nextSibling = 0;
670     this->prevSibling = 0;
671     this->var = link.var->ref();
672     this->owned = false;
675 CScriptVarLink::~CScriptVarLink() {
676 #if DEBUG_MEMORY
677     mark_deallocated(this);
678 #endif
679     var->unref();
682 void CScriptVarLink::replaceWith(CScriptVar *newVar) {
683     CScriptVar *oldVar = var;
684     var = newVar->ref();
685     oldVar->unref();
688 void CScriptVarLink::replaceWith(CScriptVarLink *newVar) {
689     if (newVar)
690       replaceWith(newVar->var);
691     else
692       replaceWith(new CScriptVar());
695 int CScriptVarLink::getIntName() {
696     return atoi(name.c_str());
698 void CScriptVarLink::setIntName(int n) {
699     char sIdx[64];
700     sprintf_s(sIdx, sizeof(sIdx), "%d", n);
701     name = sIdx;
704 // ----------------------------------------------------------------------------------- CSCRIPTVAR
706 CScriptVar::CScriptVar() {
707     refs = 0;
708 #if DEBUG_MEMORY
709     mark_allocated(this);
710 #endif
711     init();
712     flags = SCRIPTVAR_UNDEFINED;
715 CScriptVar::CScriptVar(const string &str) {
716     refs = 0;
717 #if DEBUG_MEMORY
718     mark_allocated(this);
719 #endif
720     init();
721     flags = SCRIPTVAR_STRING;
722     data = str;
726 CScriptVar::CScriptVar(const string &varData, int varFlags) {
727     refs = 0;
728 #if DEBUG_MEMORY
729     mark_allocated(this);
730 #endif
731     init();
732     flags = varFlags;
733     if (varFlags & SCRIPTVAR_INTEGER) {
734       intData = strtol(varData.c_str(),0,0);
735     } else if (varFlags & SCRIPTVAR_DOUBLE) {
736       doubleData = strtod(varData.c_str(),0);
737     } else
738       data = varData;
741 CScriptVar::CScriptVar(double val) {
742     refs = 0;
743 #if DEBUG_MEMORY
744     mark_allocated(this);
745 #endif
746     init();
747     setDouble(val);
750 CScriptVar::CScriptVar(int val) {
751     refs = 0;
752 #if DEBUG_MEMORY
753     mark_allocated(this);
754 #endif
755     init();
756     setInt(val);
759 CScriptVar::~CScriptVar(void) {
760 #if DEBUG_MEMORY
761     mark_deallocated(this);
762 #endif
763     removeAllChildren();
766 void CScriptVar::init() {
767     firstChild = 0;
768     lastChild = 0;
769     flags = 0;
770     jsCallback = 0;
771     jsCallbackUserData = 0;
772     data = TINYJS_BLANK_DATA;
773     intData = 0;
774     doubleData = 0;
777 CScriptVar *CScriptVar::getReturnVar() {
778     return getParameter(TINYJS_RETURN_VAR);
781 void CScriptVar::setReturnVar(CScriptVar *var) {
782     findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var);
786 CScriptVar *CScriptVar::getParameter(const std::string &name) {
787     return findChildOrCreate(name)->var;
790 CScriptVarLink *CScriptVar::findChild(const string &childName) {
791     CScriptVarLink *v = firstChild;
792     while (v) {
793         if (v->name.compare(childName)==0)
794             return v;
795         v = v->nextSibling;
796     }
797     return 0;
800 CScriptVarLink *CScriptVar::findChildOrCreate(const string &childName, int varFlags) {
801     CScriptVarLink *l = findChild(childName);
802     if (l) return l;
804     return addChild(childName, new CScriptVar(TINYJS_BLANK_DATA, varFlags));
807 CScriptVarLink *CScriptVar::findChildOrCreateByPath(const std::string &path) {
808   size_t p = path.find('.');
809   if (p == string::npos)
810     return findChildOrCreate(path);
812   return findChildOrCreate(path.substr(0,p), SCRIPTVAR_OBJECT)->var->
813             findChildOrCreateByPath(path.substr(p+1));
816 CScriptVarLink *CScriptVar::addChild(const std::string &childName, CScriptVar *child) {
817   if (isUndefined()) {
818     flags = SCRIPTVAR_OBJECT;
819   }
820     // if no child supplied, create one
821     if (!child)
822       child = new CScriptVar();
824     CScriptVarLink *link = new CScriptVarLink(child, childName);
825     link->owned = true;
826     if (lastChild) {
827         lastChild->nextSibling = link;
828         link->prevSibling = lastChild;
829         lastChild = link;
830     } else {
831         firstChild = link;
832         lastChild = link;
833     }
834     return link;
837 CScriptVarLink *CScriptVar::addChildNoDup(const std::string &childName, CScriptVar *child) {
838     // if no child supplied, create one
839     if (!child)
840       child = new CScriptVar();
842     CScriptVarLink *v = findChild(childName);
843     if (v) {
844         v->replaceWith(child);
845     } else {
846         v = addChild(childName, child);
847     }
849     return v;
852 void CScriptVar::removeChild(CScriptVar *child) {
853     CScriptVarLink *link = firstChild;
854     while (link) {
855         if (link->var == child)
856             break;
857         link = link->nextSibling;
858     }
859     ASSERT(link);
860     removeLink(link);
863 void CScriptVar::removeLink(CScriptVarLink *link) {
864     if (!link) return;
865     if (link->nextSibling)
866       link->nextSibling->prevSibling = link->prevSibling;
867     if (link->prevSibling)
868       link->prevSibling->nextSibling = link->nextSibling;
869     if (lastChild == link)
870         lastChild = link->prevSibling;
871     if (firstChild == link)
872         firstChild = link->nextSibling;
873     delete link;
876 void CScriptVar::removeAllChildren() {
877     CScriptVarLink *c = firstChild;
878     while (c) {
879         CScriptVarLink *t = c->nextSibling;
880         delete c;
881         c = t;
882     }
883     firstChild = 0;
884     lastChild = 0;
887 CScriptVar *CScriptVar::getArrayIndex(int idx) {
888     char sIdx[64];
889     sprintf_s(sIdx, sizeof(sIdx), "%d", idx);
890     CScriptVarLink *link = findChild(sIdx);
891     if (link) return link->var;
892     else return new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_NULL); // undefined
895 void CScriptVar::setArrayIndex(int idx, CScriptVar *value) {
896     char sIdx[64];
897     sprintf_s(sIdx, sizeof(sIdx), "%d", idx);
898     CScriptVarLink *link = findChild(sIdx);
900     if (link) {
901       if (value->isUndefined())
902         removeLink(link);
903       else
904         link->replaceWith(value);
905     } else {
906       if (!value->isUndefined())
907         addChild(sIdx, value);
908     }
911 int CScriptVar::getArrayLength() {
912     int highest = -1;
913     if (!isArray()) return 0;
915     CScriptVarLink *link = firstChild;
916     while (link) {
917       if (isNumber(link->name)) {
918         int val = atoi(link->name.c_str());
919         if (val > highest) highest = val;
920       }
921       link = link->nextSibling;
922     }
923     return highest+1;
926 int CScriptVar::getChildren() {
927     int n = 0;
928     CScriptVarLink *link = firstChild;
929     while (link) {
930       n++;
931       link = link->nextSibling;
932     }
933     return n;
936 int CScriptVar::getInt() {
937     /* strtol understands about hex and octal */
938     if (isInt()) return intData;
939     if (isNull()) return 0;
940     if (isUndefined()) return 0;
941     if (isDouble()) return (int)doubleData;
942     return 0;
945 double CScriptVar::getDouble() {
946     if (isDouble()) return doubleData;
947     if (isInt()) return intData;
948     if (isNull()) return 0;
949     if (isUndefined()) return 0;
950     return 0; /* or NaN? */
953 const string &CScriptVar::getString() {
954     /* Because we can't return a string that is generated on demand.
955      * I should really just use char* :) */
956     static string s_null = "null";
957     static string s_undefined = "undefined";
958     if (isInt()) {
959       char buffer[32];
960       sprintf_s(buffer, sizeof(buffer), "%ld", intData);
961       data = buffer;
962       return data;
963     }
964     if (isDouble()) {
965       char buffer[32];
966       sprintf_s(buffer, sizeof(buffer), "%f", doubleData);
967       data = buffer;
968       return data;
969     }
970     if (isNull()) return s_null;
971     if (isUndefined()) return s_undefined;
972     // are we just a string here?
973     return data;
976 void CScriptVar::setInt(int val) {
977     flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER;
978     intData = val;
979     doubleData = 0;
980     data = TINYJS_BLANK_DATA;
983 void CScriptVar::setDouble(double val) {
984     flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE;
985     doubleData = val;
986     intData = 0;
987     data = TINYJS_BLANK_DATA;
990 void CScriptVar::setString(const string &str) {
991     // name sure it's not still a number or integer
992     flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_STRING;
993     data = str;
994     intData = 0;
995     doubleData = 0;
998 void CScriptVar::setUndefined() {
999     // name sure it's not still a number or integer
1000     flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_UNDEFINED;
1001     data = TINYJS_BLANK_DATA;
1002     intData = 0;
1003     doubleData = 0;
1004     removeAllChildren();
1007 void CScriptVar::setArray() {
1008     // name sure it's not still a number or integer
1009     flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_ARRAY;
1010     data = TINYJS_BLANK_DATA;
1011     intData = 0;
1012     doubleData = 0;
1013     removeAllChildren();
1016 bool CScriptVar::equals(CScriptVar *v) {
1017     CScriptVar *resV = mathsOp(v, LEX_EQUAL);
1018     bool res = resV->getBool();
1019     delete resV;
1020     return res;
1023 CScriptVar *CScriptVar::mathsOp(CScriptVar *b, int op) {
1024     CScriptVar *a = this;
1025     // Type equality check
1026     if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) {
1027       // check type first, then call again to check data
1028       bool eql = ((a->flags & SCRIPTVAR_VARTYPEMASK) ==
1029                   (b->flags & SCRIPTVAR_VARTYPEMASK));
1030       if (eql) {
1031         CScriptVar *contents = a->mathsOp(b, LEX_EQUAL);
1032         if (!contents->getBool()) eql = false;
1033         if (!contents->refs) delete contents;
1034       }
1035                  ;
1036       if (op == LEX_TYPEEQUAL)
1037         return new CScriptVar(eql);
1038       else
1039         return new CScriptVar(!eql);
1040     }
1041     // do maths...
1042     if (a->isUndefined() && b->isUndefined()) {
1043       if (op == LEX_EQUAL) return new CScriptVar(true);
1044       else if (op == LEX_NEQUAL) return new CScriptVar(false);
1045       else return new CScriptVar(); // undefined
1046     } else if ((a->isNumeric() || a->isUndefined()) &&
1047                (b->isNumeric() || b->isUndefined())) {
1048         if (!a->isDouble() && !b->isDouble()) {
1049             // use ints
1050             int da = a->getInt();
1051             int db = b->getInt();
1052             switch (op) {
1053                 case '+': return new CScriptVar(da+db);
1054                 case '-': return new CScriptVar(da-db);
1055                 case '*': return new CScriptVar(da*db);
1056                 case '/': return new CScriptVar(da/db);
1057                 case '&': return new CScriptVar(da&db);
1058                 case '|': return new CScriptVar(da|db);
1059                 case '^': return new CScriptVar(da^db);
1060                 case '%': return new CScriptVar(da%db);
1061                 case LEX_EQUAL:     return new CScriptVar(da==db);
1062                 case LEX_NEQUAL:    return new CScriptVar(da!=db);
1063                 case '<':     return new CScriptVar(da<db);
1064                 case LEX_LEQUAL:    return new CScriptVar(da<=db);
1065                 case '>':     return new CScriptVar(da>db);
1066                 case LEX_GEQUAL:    return new CScriptVar(da>=db);
1067                 default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Int datatype");
1068             }
1069         } else {
1070             // use doubles
1071             double da = a->getDouble();
1072             double db = b->getDouble();
1073             switch (op) {
1074                 case '+': return new CScriptVar(da+db);
1075                 case '-': return new CScriptVar(da-db);
1076                 case '*': return new CScriptVar(da*db);
1077                 case '/': return new CScriptVar(da/db);
1078                 case LEX_EQUAL:     return new CScriptVar(da==db);
1079                 case LEX_NEQUAL:    return new CScriptVar(da!=db);
1080                 case '<':     return new CScriptVar(da<db);
1081                 case LEX_LEQUAL:    return new CScriptVar(da<=db);
1082                 case '>':     return new CScriptVar(da>db);
1083                 case LEX_GEQUAL:    return new CScriptVar(da>=db);
1084                 default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Double datatype");
1085             }
1086         }
1087     } else if (a->isArray()) {
1088       /* Just check pointers */
1089       switch (op) {
1090            case LEX_EQUAL: return new CScriptVar(a==b);
1091            case LEX_NEQUAL: return new CScriptVar(a!=b);
1092            default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Array datatype");
1093       }
1094     } else if (a->isObject()) {
1095           /* Just check pointers */
1096           switch (op) {
1097                case LEX_EQUAL: return new CScriptVar(a==b);
1098                case LEX_NEQUAL: return new CScriptVar(a!=b);
1099                default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Object datatype");
1100           }
1101     } else {
1102        string da = a->getString();
1103        string db = b->getString();
1104        // use strings
1105        switch (op) {
1106            case '+':           return new CScriptVar(da+db, SCRIPTVAR_STRING);
1107            case LEX_EQUAL:     return new CScriptVar(da==db);
1108            case LEX_NEQUAL:    return new CScriptVar(da!=db);
1109            case '<':     return new CScriptVar(da<db);
1110            case LEX_LEQUAL:    return new CScriptVar(da<=db);
1111            case '>':     return new CScriptVar(da>db);
1112            case LEX_GEQUAL:    return new CScriptVar(da>=db);
1113            default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the string datatype");
1114        }
1115     }
1116     ASSERT(0);
1117     return 0;
1120 void CScriptVar::copySimpleData(CScriptVar *val) {
1121     data = val->data;
1122     intData = val->intData;
1123     doubleData = val->doubleData;
1124     flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK);
1127 void CScriptVar::copyValue(CScriptVar *val) {
1128     if (val) {
1129       copySimpleData(val);
1130       // remove all current children
1131       removeAllChildren();
1132       // copy children of 'val'
1133       CScriptVarLink *child = val->firstChild;
1134       while (child) {
1135         CScriptVar *copied;
1136         // don't copy the 'parent' object...
1137         if (child->name != TINYJS_PROTOTYPE_CLASS)
1138           copied = child->var->deepCopy();
1139         else
1140           copied = child->var;
1142         addChild(child->name, copied);
1144         child = child->nextSibling;
1145       }
1146     } else {
1147       setUndefined();
1148     }
1151 CScriptVar *CScriptVar::deepCopy() {
1152     CScriptVar *newVar = new CScriptVar();
1153     newVar->copySimpleData(this);
1154     // copy children
1155     CScriptVarLink *child = firstChild;
1156     while (child) {
1157         CScriptVar *copied;
1158         // don't copy the 'parent' object...
1159         if (child->name != TINYJS_PROTOTYPE_CLASS)
1160           copied = child->var->deepCopy();
1161         else
1162           copied = child->var;
1164         newVar->addChild(child->name, copied);
1165         child = child->nextSibling;
1166     }
1167     return newVar;
1170 void CScriptVar::trace(string indentStr, const string &name) {
1171     TRACE("%s'%s' = '%s' %s\n",
1172         indentStr.c_str(),
1173         name.c_str(),
1174         getString().c_str(),
1175         getFlagsAsString().c_str());
1176     string indent = indentStr+" ";
1177     CScriptVarLink *link = firstChild;
1178     while (link) {
1179       link->var->trace(indent, link->name);
1180       link = link->nextSibling;
1181     }
1184 string CScriptVar::getFlagsAsString() {
1185   string flagstr = "";
1186   if (flags&SCRIPTVAR_FUNCTION) flagstr = flagstr + "FUNCTION ";
1187   if (flags&SCRIPTVAR_OBJECT) flagstr = flagstr + "OBJECT ";
1188   if (flags&SCRIPTVAR_ARRAY) flagstr = flagstr + "ARRAY ";
1189   if (flags&SCRIPTVAR_NATIVE) flagstr = flagstr + "NATIVE ";
1190   if (flags&SCRIPTVAR_DOUBLE) flagstr = flagstr + "DOUBLE ";
1191   if (flags&SCRIPTVAR_INTEGER) flagstr = flagstr + "INTEGER ";
1192   if (flags&SCRIPTVAR_STRING) flagstr = flagstr + "STRING ";
1193   return flagstr;
1196 string CScriptVar::getParsableString() {
1197   // Numbers can just be put in directly
1198   if (isNumeric())
1199     return getString();
1200   if (isFunction()) {
1201     ostringstream funcStr;
1202     funcStr << "function (";
1203     // get list of parameters
1204     CScriptVarLink *link = firstChild;
1205     while (link) {
1206       funcStr << link->name;
1207       if (link->nextSibling) funcStr << ",";
1208       link = link->nextSibling;
1209     }
1210     // add function body
1211     funcStr << ") " << getString();
1212     return funcStr.str();
1213   }
1214   // if it is a string then we quote it
1215   if (isString())
1216     return getJSString(getString());
1217   if (isNull())
1218       return "null";
1219   return "undefined";
1222 void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) {
1223    if (isObject()) {
1224       string indentedLinePrefix = linePrefix+"  ";
1225       // children - handle with bracketed list
1226       destination << "{ \n";
1227       CScriptVarLink *link = firstChild;
1228       while (link) {
1229         destination << indentedLinePrefix;
1230         destination  << getJSString(link->name);
1231         destination  << " : ";
1232         link->var->getJSON(destination, indentedLinePrefix);
1233         link = link->nextSibling;
1234         if (link) {
1235           destination  << ",\n";
1236         }
1237       }
1238       destination << "\n" << linePrefix << "}";
1239     } else if (isArray()) {
1240       string indentedLinePrefix = linePrefix+"  ";
1241       destination << "[\n";
1242       int len = getArrayLength();
1243       if (len>10000) len=10000; // we don't want to get stuck here!
1245       for (int i=0;i<len;i++) {
1246         getArrayIndex(i)->getJSON(destination, indentedLinePrefix);
1247         if (i<len-1) destination  << ",\n";
1248       }
1250       destination << "\n" << linePrefix << "]";
1251     } else {
1252       // no children or a function... just write value directly
1253       destination << getParsableString();
1254     }
1258 void CScriptVar::setCallback(JSCallback callback, void *userdata) {
1259     jsCallback = callback;
1260     jsCallbackUserData = userdata;
1263 CScriptVar *CScriptVar::ref() {
1264     refs++;
1265     return this;
1268 void CScriptVar::unref() {
1269     if (refs<=0) printf("OMFG, we have unreffed too far!\n");
1270     if ((--refs)==0) {
1271       delete this;
1272     }
1275 int CScriptVar::getRefs() {
1276     return refs;
1280 // ----------------------------------------------------------------------------------- CSCRIPT
1282 CTinyJS::CTinyJS() {
1283     l = 0;
1284     root = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
1285     // Add built-in classes
1286     stringClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
1287     arrayClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
1288     objectClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
1289     root->addChild("String", stringClass);
1290     root->addChild("Array", arrayClass);
1291     root->addChild("Object", objectClass);
1294 CTinyJS::~CTinyJS() {
1295     ASSERT(!l);
1296     scopes.clear();
1297     stringClass->unref();
1298     arrayClass->unref();
1299     objectClass->unref();
1300     root->unref();
1302 #if DEBUG_MEMORY
1303     show_allocated();
1304 #endif
1307 void CTinyJS::trace() {
1308     root->trace();
1311 void CTinyJS::execute(const string &code) {
1312     CScriptLex *oldLex = l;
1313     vector<CScriptVar*> oldScopes = scopes;
1314     l = new CScriptLex(code);
1315 #ifdef TINYJS_CALL_STACK
1316     call_stack.clear();
1317 #endif
1318     scopes.clear();
1319     scopes.push_back(root);
1320     try {
1321         bool execute = true;
1322         while (l->tk) statement(execute);
1323     } catch (CScriptException *e) {
1324         ostringstream msg;
1325         msg << "Error " << e->text;
1326 #ifdef TINYJS_CALL_STACK
1327         for (int i=(int)call_stack.size()-1;i>=0;i--)
1328           msg << "\n" << i << ": " << call_stack.at(i);
1329 #endif
1330         msg << " at " << l->getPosition();
1331         delete l;
1332         l = oldLex;
1334         throw new CScriptException(msg.str());
1335     }
1336     delete l;
1337     l = oldLex;
1338     scopes = oldScopes;
1341 CScriptVarLink CTinyJS::evaluateComplex(const string &code) {
1342     CScriptLex *oldLex = l;
1343     vector<CScriptVar*> oldScopes = scopes;
1345     l = new CScriptLex(code);
1346 #ifdef TINYJS_CALL_STACK
1347     call_stack.clear();
1348 #endif
1349     scopes.clear();
1350     scopes.push_back(root);
1351     CScriptVarLink *v = 0;
1352     try {
1353         bool execute = true;
1354         do {
1355           CLEAN(v);
1356           v = base(execute);
1357           if (l->tk!=LEX_EOF) l->match(';');
1358         } while (l->tk!=LEX_EOF);
1359     } catch (CScriptException *e) {
1360       ostringstream msg;
1361       msg << "Error " << e->text;
1362 #ifdef TINYJS_CALL_STACK
1363       for (int i=(int)call_stack.size()-1;i>=0;i--)
1364         msg << "\n" << i << ": " << call_stack.at(i);
1365 #endif
1366       msg << " at " << l->getPosition();
1367       delete l;
1368       l = oldLex;
1370         throw new CScriptException(msg.str());
1371     }
1372     delete l;
1373     l = oldLex;
1374     scopes = oldScopes;
1376     if (v) {
1377         CScriptVarLink r = *v;
1378         CLEAN(v);
1379         return r;
1380     }
1381     // return undefined...
1382     return CScriptVarLink(new CScriptVar());
1385 string CTinyJS::evaluate(const string &code) {
1386     return evaluateComplex(code).var->getString();
1389 void CTinyJS::parseFunctionArguments(CScriptVar *funcVar) {
1390   l->match('(');
1391   while (l->tk!=')') {
1392       funcVar->addChildNoDup(l->tkStr);
1393       l->match(LEX_ID);
1394       if (l->tk!=')') l->match(',');
1395   }
1396   l->match(')');
1399 void CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata) {
1400     CScriptLex *oldLex = l;
1401     l = new CScriptLex(funcDesc);
1403     CScriptVar *base = root;
1405     l->match(LEX_R_FUNCTION);
1406     string funcName = l->tkStr;
1407     l->match(LEX_ID);
1408     /* Check for dots, we might want to do something like function String.substring ... */
1409     while (l->tk == '.') {
1410       l->match('.');
1411       CScriptVarLink *link = base->findChild(funcName);
1412       // if it doesn't exist, make an object class
1413       if (!link) link = base->addChild(funcName, new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT));
1414       base = link->var;
1415       funcName = l->tkStr;
1416       l->match(LEX_ID);
1417     }
1419     CScriptVar *funcVar = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE);
1420     funcVar->setCallback(ptr, userdata);
1421     parseFunctionArguments(funcVar);
1422     delete l;
1423     l = oldLex;
1425     base->addChild(funcName, funcVar);
1428 CScriptVarLink *CTinyJS::parseFunctionDefinition() {
1429   // actually parse a function...
1430   l->match(LEX_R_FUNCTION);
1431   string funcName = TINYJS_TEMP_NAME;
1432   /* we can have functions without names */
1433   if (l->tk==LEX_ID) {
1434     funcName = l->tkStr;
1435     l->match(LEX_ID);
1436   }
1437   CScriptVarLink *funcVar = new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION), funcName);
1438   parseFunctionArguments(funcVar->var);
1439   int funcBegin = l->tokenStart;
1440   bool noexecute = false;
1441   block(noexecute);
1442   funcVar->var->data = l->getSubString(funcBegin);
1443   return funcVar;
1446 /** Handle a function call (assumes we've parsed the function name and we're
1447  * on the start bracket). 'parent' is the object that contains this method,
1448  * if there was one (otherwise it's just a normnal function).
1449  */
1450 CScriptVarLink *CTinyJS::functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent) {
1451   if (execute) {
1452     if (!function->var->isFunction()) {
1453         string errorMsg = "Expecting '";
1454         errorMsg = errorMsg + function->name + "' to be a function";
1455         throw new CScriptException(errorMsg.c_str());
1456     }
1457     l->match('(');
1458     // create a new symbol table entry for execution of this function
1459     CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION);
1460     if (parent)
1461       functionRoot->addChildNoDup("this", parent);
1462     // grab in all parameters
1463     CScriptVarLink *v = function->var->firstChild;
1464     while (v) {
1465         CScriptVarLink *value = base(execute);
1466         if (execute) {
1467             if (value->var->isBasic()) {
1468               // pass by value
1469               functionRoot->addChild(v->name, value->var->deepCopy());
1470             } else {
1471               // pass by reference
1472               functionRoot->addChild(v->name, value->var);
1473             }
1474         }
1475         CLEAN(value);
1476         if (l->tk!=')') l->match(',');
1477         v = v->nextSibling;
1478     }
1479     l->match(')');
1480     // setup a return variable
1481     CScriptVarLink *returnVar = NULL;
1482     // execute function!
1483     // add the function's execute space to the symbol table so we can recurse
1484     CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR);
1485     scopes.push_back(functionRoot);
1486 #ifdef TINYJS_CALL_STACK
1487     call_stack.push_back(function->name + " from " + l->getPosition());
1488 #endif
1490     if (function->var->isNative()) {
1491         ASSERT(function->var->jsCallback);
1492         function->var->jsCallback(functionRoot, function->var->jsCallbackUserData);
1493     } else {
1494         /* we just want to execute the block, but something could
1495          * have messed up and left us with the wrong ScriptLex, so
1496          * we want to be careful here... */
1497         CScriptException *exception = 0;
1498         CScriptLex *oldLex = l;
1499         CScriptLex *newLex = new CScriptLex(function->var->getString());
1500         l = newLex;
1501         try {
1502           block(execute);
1503           // because return will probably have called this, and set execute to false
1504           execute = true;
1505         } catch (CScriptException *e) {
1506           exception = e;
1507         }
1508         delete newLex;
1509         l = oldLex;
1511         if (exception)
1512           throw exception;
1513     }
1514 #ifdef TINYJS_CALL_STACK
1515     if (!call_stack.empty()) call_stack.pop_back();
1516 #endif
1517     scopes.pop_back();
1518     /* get the real return var before we remove it from our function */
1519     returnVar = new CScriptVarLink(returnVarLink->var);
1520     functionRoot->removeLink(returnVarLink);
1521     delete functionRoot;
1522     if (returnVar)
1523       return returnVar;
1524     else
1525       return new CScriptVarLink(new CScriptVar());
1526   } else {
1527     // function, but not executing - just parse args and be done
1528     l->match('(');
1529     while (l->tk != ')') {
1530       CScriptVarLink *value = base(execute);
1531       CLEAN(value);
1532       if (l->tk!=')') l->match(',');
1533     }
1534     l->match(')');
1535     if (l->tk == '{') { // TODO: why is this here?
1536       block(execute);
1537     }
1538     /* function will be a blank scriptvarlink if we're not executing,
1539      * so just return it rather than an alloc/free */
1540     return function;
1541   }
1544 CScriptVarLink *CTinyJS::factor(bool &execute) {
1545     if (l->tk=='(') {
1546         l->match('(');
1547         CScriptVarLink *a = base(execute);
1548         l->match(')');
1549         return a;
1550     }
1551     if (l->tk==LEX_R_TRUE) {
1552         l->match(LEX_R_TRUE);
1553         return new CScriptVarLink(new CScriptVar(1));
1554     }
1555     if (l->tk==LEX_R_FALSE) {
1556         l->match(LEX_R_FALSE);
1557         return new CScriptVarLink(new CScriptVar(0));
1558     }
1559     if (l->tk==LEX_R_NULL) {
1560         l->match(LEX_R_NULL);
1561         return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_NULL));
1562     }
1563     if (l->tk==LEX_R_UNDEFINED) {
1564         l->match(LEX_R_UNDEFINED);
1565         return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_UNDEFINED));
1566     }
1567     if (l->tk==LEX_ID) {
1568         CScriptVarLink *a = execute ? findInScopes(l->tkStr) : new CScriptVarLink(new CScriptVar());
1569         //printf("0x%08X for %s at %s\n", (unsigned int)a, l->tkStr.c_str(), l->getPosition().c_str());
1570         /* The parent if we're executing a method call */
1571         CScriptVar *parent = 0;
1573         if (execute && !a) {
1574           /* Variable doesn't exist! JavaScript says we should create it
1575            * (we won't add it here. This is done in the assignment operator)*/
1576           a = new CScriptVarLink(new CScriptVar(), l->tkStr);
1577         }
1578         l->match(LEX_ID);
1579         while (l->tk=='(' || l->tk=='.' || l->tk=='[') {
1580             if (l->tk=='(') { // ------------------------------------- Function Call
1581                 a = functionCall(execute, a, parent);
1582             } else if (l->tk == '.') { // ------------------------------------- Record Access
1583                 l->match('.');
1584                 if (execute) {
1585                   const string &name = l->tkStr;
1586                   CScriptVarLink *child = a->var->findChild(name);
1587                   if (!child) child = findInParentClasses(a->var, name);
1588                   if (!child) {
1589                     /* if we haven't found this defined yet, use the built-in
1590                        'length' properly */
1591                     if (a->var->isArray() && name == "length") {
1592                       int l = a->var->getArrayLength();
1593                       child = new CScriptVarLink(new CScriptVar(l));
1594                     } else if (a->var->isString() && name == "length") {
1595                       int l = a->var->getString().size();
1596                       child = new CScriptVarLink(new CScriptVar(l));
1597                     } else {
1598                       child = a->var->addChild(name);
1599                     }
1600                   }
1601                   parent = a->var;
1602                   a = child;
1603                 }
1604                 l->match(LEX_ID);
1605             } else if (l->tk == '[') { // ------------------------------------- Array Access
1606                 l->match('[');
1607                 CScriptVarLink *index = base(execute);
1608                 l->match(']');
1609                 if (execute) {
1610                   CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString());
1611                   parent = a->var;
1612                   a = child;
1613                 }
1614                 CLEAN(index);
1615             } else ASSERT(0);
1616         }
1617         return a;
1618     }
1619     if (l->tk==LEX_INT || l->tk==LEX_FLOAT) {
1620         CScriptVar *a = new CScriptVar(l->tkStr,
1621             ((l->tk==LEX_INT)?SCRIPTVAR_INTEGER:SCRIPTVAR_DOUBLE));
1622         l->match(l->tk);
1623         return new CScriptVarLink(a);
1624     }
1625     if (l->tk==LEX_STR) {
1626         CScriptVar *a = new CScriptVar(l->tkStr, SCRIPTVAR_STRING);
1627         l->match(LEX_STR);
1628         return new CScriptVarLink(a);
1629     }
1630     if (l->tk=='{') {
1631         CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT);
1632         /* JSON-style object definition */
1633         l->match('{');
1634         while (l->tk != '}') {
1635           string id = l->tkStr;
1636           // we only allow strings or IDs on the left hand side of an initialisation
1637           if (l->tk==LEX_STR) l->match(LEX_STR);
1638           else l->match(LEX_ID);
1639           l->match(':');
1640           if (execute) {
1641             CScriptVarLink *a = base(execute);
1642             contents->addChild(id, a->var);
1643             CLEAN(a);
1644           }
1645           // no need to clean here, as it will definitely be used
1646           if (l->tk != '}') l->match(',');
1647         }
1649         l->match('}');
1650         return new CScriptVarLink(contents);
1651     }
1652     if (l->tk=='[') {
1653         CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY);
1654         /* JSON-style array */
1655         l->match('[');
1656         int idx = 0;
1657         while (l->tk != ']') {
1658           if (execute) {
1659             char idx_str[16]; // big enough for 2^32
1660             sprintf_s(idx_str, sizeof(idx_str), "%d",idx);
1662             CScriptVarLink *a = base(execute);
1663             contents->addChild(idx_str, a->var);
1664             CLEAN(a);
1665           }
1666           // no need to clean here, as it will definitely be used
1667           if (l->tk != ']') l->match(',');
1668           idx++;
1669         }
1670         l->match(']');
1671         return new CScriptVarLink(contents);
1672     }
1673     if (l->tk==LEX_R_FUNCTION) {
1674       CScriptVarLink *funcVar = parseFunctionDefinition();
1675         if (funcVar->name != TINYJS_TEMP_NAME)
1676           TRACE("Functions not defined at statement-level are not meant to have a name");
1677         return funcVar;
1678     }
1679     if (l->tk==LEX_R_NEW) {
1680       // new -> create a new object
1681       l->match(LEX_R_NEW);
1682       const string &className = l->tkStr;
1683       if (execute) {
1684         CScriptVarLink *objClassOrFunc = findInScopes(className);
1685         if (!objClassOrFunc) {
1686           TRACE("%s is not a valid class name", className.c_str());
1687           return new CScriptVarLink(new CScriptVar());
1688         }
1689         l->match(LEX_ID);
1690         CScriptVar *obj = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT);
1691         CScriptVarLink *objLink = new CScriptVarLink(obj);
1692         if (objClassOrFunc->var->isFunction()) {
1693           CLEAN(functionCall(execute, objClassOrFunc, obj));
1694         } else {
1695           obj->addChild(TINYJS_PROTOTYPE_CLASS, objClassOrFunc->var);
1696           if (l->tk == '(') {
1697             l->match('(');
1698             l->match(')');
1699           }
1700         }
1701         return objLink;
1702       } else {
1703         l->match(LEX_ID);
1704         if (l->tk == '(') {
1705           l->match('(');
1706           l->match(')');
1707         }
1708       }
1709     }
1710     // Nothing we can do here... just hope it's the end...
1711     l->match(LEX_EOF);
1712     return 0;
1715 CScriptVarLink *CTinyJS::unary(bool &execute) {
1716     CScriptVarLink *a;
1717     if (l->tk=='!') {
1718         l->match('!'); // binary not
1719         a = factor(execute);
1720         if (execute) {
1721             CScriptVar zero(0);
1722             CScriptVar *res = a->var->mathsOp(&zero, LEX_EQUAL);
1723             CREATE_LINK(a, res);
1724         }
1725     } else
1726         a = factor(execute);
1727     return a;
1730 CScriptVarLink *CTinyJS::term(bool &execute) {
1731     CScriptVarLink *a = unary(execute);
1732     while (l->tk=='*' || l->tk=='/' || l->tk=='%') {
1733         int op = l->tk;
1734         l->match(l->tk);
1735         CScriptVarLink *b = unary(execute);
1736         if (execute) {
1737             CScriptVar *res = a->var->mathsOp(b->var, op);
1738             CREATE_LINK(a, res);
1739         }
1740         CLEAN(b);
1741     }
1742     return a;
1745 CScriptVarLink *CTinyJS::expression(bool &execute) {
1746     bool negate = false;
1747     if (l->tk=='-') {
1748         l->match('-');
1749         negate = true;
1750     }
1751     CScriptVarLink *a = term(execute);
1752     if (negate) {
1753         CScriptVar zero(0);
1754         CScriptVar *res = zero.mathsOp(a->var, '-');
1755         CREATE_LINK(a, res);
1756     }
1758     while (l->tk=='+' || l->tk=='-' ||
1759         l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS) {
1760         int op = l->tk;
1761         l->match(l->tk);
1762         if (op==LEX_PLUSPLUS || op==LEX_MINUSMINUS) {
1763             if (execute) {
1764                 CScriptVar one(1);
1765                 CScriptVar *res = a->var->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-');
1766                 CScriptVarLink *oldValue = new CScriptVarLink(a->var);
1767                 // in-place add/subtract
1768                 a->replaceWith(res);
1769                 CLEAN(a);
1770                 a = oldValue;
1771             }
1772         } else {
1773             CScriptVarLink *b = term(execute);
1774             if (execute) {
1775                 // not in-place, so just replace
1776                 CScriptVar *res = a->var->mathsOp(b->var, op);
1777                 CREATE_LINK(a, res);
1778             }
1779             CLEAN(b);
1780         }
1781     }
1782     return a;
1785 CScriptVarLink *CTinyJS::shift(bool &execute) {
1786   CScriptVarLink *a = expression(execute);
1787   if (l->tk==LEX_LSHIFT || l->tk==LEX_RSHIFT || l->tk==LEX_RSHIFTUNSIGNED) {
1788     int op = l->tk;
1789     l->match(op);
1790     CScriptVarLink *b = base(execute);
1791     int shift = execute ? b->var->getInt() : 0;
1792     CLEAN(b);
1793     if (execute) {
1794       if (op==LEX_LSHIFT) a->var->setInt(a->var->getInt() << shift);
1795       if (op==LEX_RSHIFT) a->var->setInt(a->var->getInt() >> shift);
1796       if (op==LEX_RSHIFTUNSIGNED) a->var->setInt(((unsigned int)a->var->getInt()) >> shift);
1797     }
1798   }
1799   return a;
1802 CScriptVarLink *CTinyJS::condition(bool &execute) {
1803     CScriptVarLink *a = shift(execute);
1804     CScriptVarLink *b;
1805     while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL ||
1806            l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL ||
1807            l->tk==LEX_LEQUAL || l->tk==LEX_GEQUAL ||
1808            l->tk=='<' || l->tk=='>') {
1809         int op = l->tk;
1810         l->match(l->tk);
1811         b = shift(execute);
1812         if (execute) {
1813             CScriptVar *res = a->var->mathsOp(b->var, op);
1814             CREATE_LINK(a,res);
1815         }
1816         CLEAN(b);
1817     }
1818     return a;
1821 CScriptVarLink *CTinyJS::logic(bool &execute) {
1822     CScriptVarLink *a = condition(execute);
1823     CScriptVarLink *b;
1824     while (l->tk=='&' || l->tk=='|' || l->tk=='^' || l->tk==LEX_ANDAND || l->tk==LEX_OROR) {
1825         bool noexecute = false;
1826         int op = l->tk;
1827         l->match(l->tk);
1828         bool shortCircuit = false;
1829         bool boolean = false;
1830         // if we have short-circuit ops, then if we know the outcome
1831         // we don't bother to execute the other op. Even if not
1832         // we need to tell mathsOp it's an & or |
1833         if (op==LEX_ANDAND) {
1834             op = '&';
1835             shortCircuit = !a->var->getBool();
1836             boolean = true;
1837         } else if (op==LEX_OROR) {
1838             op = '|';
1839             shortCircuit = a->var->getBool();
1840             boolean = true;
1841         }
1842         b = condition(shortCircuit ? noexecute : execute);
1843         if (execute && !shortCircuit) {
1844             if (boolean) {
1845               CScriptVar *newa = new CScriptVar(a->var->getBool());
1846               CScriptVar *newb = new CScriptVar(b->var->getBool());
1847               CREATE_LINK(a, newa);
1848               CREATE_LINK(b, newb);
1849             }
1850             CScriptVar *res = a->var->mathsOp(b->var, op);
1851             CREATE_LINK(a, res);
1852         }
1853         CLEAN(b);
1854     }
1855     return a;
1858 CScriptVarLink *CTinyJS::ternary(bool &execute) {
1859   CScriptVarLink *lhs = logic(execute);
1860   bool noexec = false;
1861   if (l->tk=='?') {
1862     l->match('?');
1863     if (!execute) {
1864       CLEAN(lhs);
1865       CLEAN(base(noexec));
1866       l->match(':');
1867       CLEAN(base(noexec));
1868     } else {
1869       bool first = lhs->var->getBool();
1870       CLEAN(lhs);
1871       if (first) {
1872         lhs = base(execute);
1873         l->match(':');
1874         CLEAN(base(noexec));
1875       } else {
1876         CLEAN(base(noexec));
1877         l->match(':');
1878         lhs = base(execute);
1879       }
1880     }
1881   }
1883   return lhs;
1886 CScriptVarLink *CTinyJS::base(bool &execute) {
1887     CScriptVarLink *lhs = ternary(execute);
1888     if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) {
1889         /* If we're assigning to this and we don't have a parent,
1890          * add it to the symbol table root as per JavaScript. */
1891         if (execute && !lhs->owned) {
1892           if (lhs->name.length()>0) {
1893             CScriptVarLink *realLhs = root->addChildNoDup(lhs->name, lhs->var);
1894             CLEAN(lhs);
1895             lhs = realLhs;
1896           } else
1897             TRACE("Trying to assign to an un-named type\n");
1898         }
1900         int op = l->tk;
1901         l->match(l->tk);
1902         CScriptVarLink *rhs = base(execute);
1903         if (execute) {
1904             if (op=='=') {
1905                 lhs->replaceWith(rhs);
1906             } else if (op==LEX_PLUSEQUAL) {
1907                 CScriptVar *res = lhs->var->mathsOp(rhs->var, '+');
1908                 lhs->replaceWith(res);
1909             } else if (op==LEX_MINUSEQUAL) {
1910                 CScriptVar *res = lhs->var->mathsOp(rhs->var, '-');
1911                 lhs->replaceWith(res);
1912             } else ASSERT(0);
1913         }
1914         CLEAN(rhs);
1915     }
1916     return lhs;
1919 void CTinyJS::block(bool &execute) {
1920     l->match('{');
1921     if (execute) {
1922       while (l->tk && l->tk!='}')
1923         statement(execute);
1924       l->match('}');
1925     } else {
1926       // fast skip of blocks
1927       int brackets = 1;
1928       while (l->tk && brackets) {
1929         if (l->tk == '{') brackets++;
1930         if (l->tk == '}') brackets--;
1931         l->match(l->tk);
1932       }
1933     }
1937 void CTinyJS::statement(bool &execute) {
1938     if (l->tk==LEX_ID ||
1939         l->tk==LEX_INT ||
1940         l->tk==LEX_FLOAT ||
1941         l->tk==LEX_STR ||
1942         l->tk=='-') {
1943         /* Execute a simple statement that only contains basic arithmetic... */
1944         CLEAN(base(execute));
1945         l->match(';');
1946     } else if (l->tk=='{') {
1947         /* A block of code */
1948         block(execute);
1949     } else if (l->tk==';') {
1950         /* Empty statement - to allow things like ;;; */
1951         l->match(';');
1952     } else if (l->tk==LEX_R_VAR) {
1953         /* variable creation. TODO - we need a better way of parsing the left
1954          * hand side. Maybe just have a flag called can_create_var that we
1955          * set and then we parse as if we're doing a normal equals.*/
1956         l->match(LEX_R_VAR);
1957         while (l->tk != ';') {
1958           CScriptVarLink *a = 0;
1959           if (execute)
1960             a = scopes.back()->findChildOrCreate(l->tkStr);
1961           l->match(LEX_ID);
1962           // now do stuff defined with dots
1963           while (l->tk == '.') {
1964               l->match('.');
1965               if (execute) {
1966                   CScriptVarLink *lastA = a;
1967                   a = lastA->var->findChildOrCreate(l->tkStr);
1968               }
1969               l->match(LEX_ID);
1970           }
1971           // sort out initialiser
1972           if (l->tk == '=') {
1973               l->match('=');
1974               CScriptVarLink *var = base(execute);
1975               if (execute)
1976                   a->replaceWith(var);
1977               CLEAN(var);
1978           }
1979           if (l->tk != ';')
1980             l->match(',');
1981         }       
1982         l->match(';');
1983     } else if (l->tk==LEX_R_IF) {
1984         l->match(LEX_R_IF);
1985         l->match('(');
1986         CScriptVarLink *var = base(execute);
1987         l->match(')');
1988         bool cond = execute && var->var->getBool();
1989         CLEAN(var);
1990         bool noexecute = false; // because we need to be abl;e to write to it
1991         statement(cond ? execute : noexecute);
1992         if (l->tk==LEX_R_ELSE) {
1993             l->match(LEX_R_ELSE);
1994             statement(cond ? noexecute : execute);
1995         }
1996     } else if (l->tk==LEX_R_WHILE) {
1997         // We do repetition by pulling out the string representing our statement
1998         // there's definitely some opportunity for optimisation here
1999         l->match(LEX_R_WHILE);
2000         l->match('(');
2001         int whileCondStart = l->tokenStart;
2002         bool noexecute = false;
2003         CScriptVarLink *cond = base(execute);
2004         bool loopCond = execute && cond->var->getBool();
2005         CLEAN(cond);
2006         CScriptLex *whileCond = l->getSubLex(whileCondStart);
2007         l->match(')');
2008         int whileBodyStart = l->tokenStart;
2009         statement(loopCond ? execute : noexecute);
2010         CScriptLex *whileBody = l->getSubLex(whileBodyStart);
2011         CScriptLex *oldLex = l;
2012         int loopCount = TINYJS_LOOP_MAX_ITERATIONS;
2013         while (loopCond && loopCount-->0) {
2014             whileCond->reset();
2015             l = whileCond;
2016             cond = base(execute);
2017             loopCond = execute && cond->var->getBool();
2018             CLEAN(cond);
2019             if (loopCond) {
2020                 whileBody->reset();
2021                 l = whileBody;
2022                 statement(execute);
2023             }
2024         }
2025         l = oldLex;
2026         delete whileCond;
2027         delete whileBody;
2029         if (loopCount<=0) {
2030             root->trace();
2031             TRACE("WHILE Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str());
2032             throw new CScriptException("LOOP_ERROR");
2033         }
2034     } else if (l->tk==LEX_R_FOR) {
2035         l->match(LEX_R_FOR);
2036         l->match('(');
2037         statement(execute); // initialisation
2038         //l->match(';');
2039         int forCondStart = l->tokenStart;
2040         bool noexecute = false;
2041         CScriptVarLink *cond = base(execute); // condition
2042         bool loopCond = execute && cond->var->getBool();
2043         CLEAN(cond);
2044         CScriptLex *forCond = l->getSubLex(forCondStart);
2045         l->match(';');
2046         int forIterStart = l->tokenStart;
2047         CLEAN(base(noexecute)); // iterator
2048         CScriptLex *forIter = l->getSubLex(forIterStart);
2049         l->match(')');
2050         int forBodyStart = l->tokenStart;
2051         statement(loopCond ? execute : noexecute);
2052         CScriptLex *forBody = l->getSubLex(forBodyStart);
2053         CScriptLex *oldLex = l;
2054         if (loopCond) {
2055             forIter->reset();
2056             l = forIter;
2057             CLEAN(base(execute));
2058         }
2059         int loopCount = TINYJS_LOOP_MAX_ITERATIONS;
2060         while (execute && loopCond && loopCount-->0) {
2061             forCond->reset();
2062             l = forCond;
2063             cond = base(execute);
2064             loopCond = cond->var->getBool();
2065             CLEAN(cond);
2066             if (execute && loopCond) {
2067                 forBody->reset();
2068                 l = forBody;
2069                 statement(execute);
2070             }
2071             if (execute && loopCond) {
2072                 forIter->reset();
2073                 l = forIter;
2074                 CLEAN(base(execute));
2075             }
2076         }
2077         l = oldLex;
2078         delete forCond;
2079         delete forIter;
2080         delete forBody;
2081         if (loopCount<=0) {
2082             root->trace();
2083             TRACE("FOR Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str());
2084             throw new CScriptException("LOOP_ERROR");
2085         }
2086     } else if (l->tk==LEX_R_RETURN) {
2087         l->match(LEX_R_RETURN);
2088         CScriptVarLink *result = 0;
2089         if (l->tk != ';')
2090           result = base(execute);
2091         if (execute) {
2092           CScriptVarLink *resultVar = scopes.back()->findChild(TINYJS_RETURN_VAR);
2093           if (resultVar)
2094             resultVar->replaceWith(result);
2095           else
2096             TRACE("RETURN statement, but not in a function.\n");
2097           execute = false;
2098         }
2099         CLEAN(result);
2100         l->match(';');
2101     } else if (l->tk==LEX_R_FUNCTION) {
2102         CScriptVarLink *funcVar = parseFunctionDefinition();
2103         if (execute) {
2104           if (funcVar->name == TINYJS_TEMP_NAME)
2105             TRACE("Functions defined at statement-level are meant to have a name\n");
2106           else
2107             scopes.back()->addChildNoDup(funcVar->name, funcVar->var);
2108         }
2109         CLEAN(funcVar);
2110     } else l->match(LEX_EOF);
2113 /// Get the given variable specified by a path (var1.var2.etc), or return 0
2114 CScriptVar *CTinyJS::getScriptVariable(const string &path) {
2115     // traverse path
2116     size_t prevIdx = 0;
2117     size_t thisIdx = path.find('.');
2118     if (thisIdx == string::npos) thisIdx = path.length();
2119     CScriptVar *var = root;
2120     while (var && prevIdx<path.length()) {
2121         string el = path.substr(prevIdx, thisIdx-prevIdx);
2122         CScriptVarLink *varl = var->findChild(el);
2123         var = varl?varl->var:0;
2124         prevIdx = thisIdx+1;
2125         thisIdx = path.find('.', prevIdx);
2126         if (thisIdx == string::npos) thisIdx = path.length();
2127     }
2128     return var;
2131 /// Get the value of the given variable, or return 0
2132 const string *CTinyJS::getVariable(const string &path) {
2133     CScriptVar *var = getScriptVariable(path);
2134     // return result
2135     if (var)
2136         return &var->getString();
2137     else
2138         return 0;
2141 /// set the value of the given variable, return trur if it exists and gets set
2142 bool CTinyJS::setVariable(const std::string &path, const std::string &varData) {
2143     CScriptVar *var = getScriptVariable(path);
2144     // return result
2145     if (var) {
2146         if (var->isInt())
2147             var->setInt((int)strtol(varData.c_str(),0,0));
2148         else if (var->isDouble())
2149             var->setDouble(strtod(varData.c_str(),0));
2150         else
2151             var->setString(varData.c_str());
2152         return true;
2153     }    
2154     else
2155         return false;
2158 /// Finds a child, looking recursively up the scopes
2159 CScriptVarLink *CTinyJS::findInScopes(const std::string &childName) {
2160     for (int s=scopes.size()-1;s>=0;s--) {
2161       CScriptVarLink *v = scopes[s]->findChild(childName);
2162       if (v) return v;
2163     }
2164     return NULL;
2168 /// Look up in any parent classes of the given object
2169 CScriptVarLink *CTinyJS::findInParentClasses(CScriptVar *object, const std::string &name) {
2170     // Look for links to actual parent classes
2171     CScriptVarLink *parentClass = object->findChild(TINYJS_PROTOTYPE_CLASS);
2172     while (parentClass) {
2173       CScriptVarLink *implementation = parentClass->var->findChild(name);
2174       if (implementation) return implementation;
2175       parentClass = parentClass->var->findChild(TINYJS_PROTOTYPE_CLASS);
2176     }
2177     // else fake it for strings and finally objects
2178     if (object->isString()) {
2179       CScriptVarLink *implementation = stringClass->findChild(name);
2180       if (implementation) return implementation;
2181     }
2182     if (object->isArray()) {
2183       CScriptVarLink *implementation = arrayClass->findChild(name);
2184       if (implementation) return implementation;
2185     }
2186     CScriptVarLink *implementation = objectClass->findChild(name);
2187     if (implementation) return implementation;
2189     return 0;