· 6 years ago · Aug 25, 2019, 09:22 PM
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:
16
17 * The above copyright notice and this permission notice shall be included in all
18 * copies or substantial portions of the Software.
19
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 */
28
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
106
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)
114
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
119
120 */
121
122#include "TinyJS.h"
123#include <assert.h>
124
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); }
131
132#include <string>
133#include <string.h>
134#include <sstream>
135#include <cstdlib>
136#include <stdio.h>
137
138using namespace std;
139
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
148
149#ifdef __GNUC__
150#define vsprintf_s vsnprintf
151#define sprintf_s snprintf
152#define _strdup strdup
153#endif
154
155// ----------------------------------------------------------------------------------- Memory Debug
156
157#define DEBUG_MEMORY 0
158
159#if DEBUG_MEMORY
160
161vector<CScriptVar*> allocatedVars;
162vector<CScriptVarLink*> allocatedLinks;
163
164void mark_allocated(CScriptVar *v) {
165 allocatedVars.push_back(v);
166}
167
168void 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 }
175}
176
177void mark_allocated(CScriptVarLink *v) {
178 allocatedLinks.push_back(v);
179}
180
181void 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 }
188}
189
190void 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();
201}
202#endif
203
204// ----------------------------------------------------------------------------------- Utils
205bool isWhitespace(char ch) {
206 return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r');
207}
208
209bool isNumeric(char ch) {
210 return (ch>='0') && (ch<='9');
211}
212bool isNumber(const string &str) {
213 for (size_t i=0;i<str.size();i++)
214 if (!isNumeric(str[i])) return false;
215 return true;
216}
217bool isHexadecimal(char ch) {
218 return ((ch>='0') && (ch<='9')) ||
219 ((ch>='a') && (ch<='f')) ||
220 ((ch>='A') && (ch<='F'));
221}
222bool isAlpha(char ch) {
223 return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_';
224}
225
226bool 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;
235}
236
237void 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 }
244}
245
246/// convert the given string into a quoted string suitable for javascript
247std::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;
252
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 }
268
269 if (replace) {
270 nStr = nStr.substr(0, i) + replaceWith + nStr.substr(i+1);
271 i += strlen(replaceWith)-1;
272 }
273 }
274 return "\"" + nStr + "\"";
275}
276
277/** Is the string alphanumeric */
278bool 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;
285}
286
287// ----------------------------------------------------------------------------------- CSCRIPTEXCEPTION
288
289CScriptException::CScriptException(const string &exceptionText) {
290 text = exceptionText;
291}
292
293// ----------------------------------------------------------------------------------- CSCRIPTLEX
294
295CScriptLex::CScriptLex(const string &input) {
296 data = _strdup(input.c_str());
297 dataOwned = true;
298 dataStart = 0;
299 dataEnd = strlen(data);
300 reset();
301}
302
303CScriptLex::CScriptLex(CScriptLex *owner, int startChar, int endChar) {
304 data = owner->data;
305 dataOwned = false;
306 dataStart = startChar;
307 dataEnd = endChar;
308 reset();
309}
310
311CScriptLex::~CScriptLex(void)
312{
313 if (dataOwned)
314 free((void*)data);
315}
316
317void 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();
327}
328
329void 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();
337}
338
339string 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 }
388
389 ostringstream msg;
390 msg << "?[" << token << "]";
391 return msg.str();
392}
393
394void CScriptLex::getNextCh() {
395 currCh = nextCh;
396 if (dataPos < dataEnd)
397 nextCh = data[dataPos];
398 else
399 nextCh = 0;
400 dataPos++;
401}
402
403void 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;
604}
605
606string 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 }
619}
620
621
622CScriptLex *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 );
628}
629
630string 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;
648}
649
650// ----------------------------------------------------------------------------------- CSCRIPTVARLINK
651
652CScriptVarLink::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;
661}
662
663CScriptVarLink::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;
673}
674
675CScriptVarLink::~CScriptVarLink() {
676#if DEBUG_MEMORY
677 mark_deallocated(this);
678#endif
679 var->unref();
680}
681
682void CScriptVarLink::replaceWith(CScriptVar *newVar) {
683 CScriptVar *oldVar = var;
684 var = newVar->ref();
685 oldVar->unref();
686}
687
688void CScriptVarLink::replaceWith(CScriptVarLink *newVar) {
689 if (newVar)
690 replaceWith(newVar->var);
691 else
692 replaceWith(new CScriptVar());
693}
694
695int CScriptVarLink::getIntName() {
696 return atoi(name.c_str());
697}
698void CScriptVarLink::setIntName(int n) {
699 char sIdx[64];
700 sprintf_s(sIdx, sizeof(sIdx), "%d", n);
701 name = sIdx;
702}
703
704// ----------------------------------------------------------------------------------- CSCRIPTVAR
705
706CScriptVar::CScriptVar() {
707 refs = 0;
708#if DEBUG_MEMORY
709 mark_allocated(this);
710#endif
711 init();
712 flags = SCRIPTVAR_UNDEFINED;
713}
714
715CScriptVar::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;
723}
724
725
726CScriptVar::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;
739}
740
741CScriptVar::CScriptVar(double val) {
742 refs = 0;
743#if DEBUG_MEMORY
744 mark_allocated(this);
745#endif
746 init();
747 setDouble(val);
748}
749
750CScriptVar::CScriptVar(int val) {
751 refs = 0;
752#if DEBUG_MEMORY
753 mark_allocated(this);
754#endif
755 init();
756 setInt(val);
757}
758
759CScriptVar::~CScriptVar(void) {
760#if DEBUG_MEMORY
761 mark_deallocated(this);
762#endif
763 removeAllChildren();
764}
765
766void 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;
775}
776
777CScriptVar *CScriptVar::getReturnVar() {
778 return getParameter(TINYJS_RETURN_VAR);
779}
780
781void CScriptVar::setReturnVar(CScriptVar *var) {
782 findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var);
783}
784
785
786CScriptVar *CScriptVar::getParameter(const std::string &name) {
787 return findChildOrCreate(name)->var;
788}
789
790CScriptVarLink *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;
798}
799
800CScriptVarLink *CScriptVar::findChildOrCreate(const string &childName, int varFlags) {
801 CScriptVarLink *l = findChild(childName);
802 if (l) return l;
803
804 return addChild(childName, new CScriptVar(TINYJS_BLANK_DATA, varFlags));
805}
806
807CScriptVarLink *CScriptVar::findChildOrCreateByPath(const std::string &path) {
808 size_t p = path.find('.');
809 if (p == string::npos)
810 return findChildOrCreate(path);
811
812 return findChildOrCreate(path.substr(0,p), SCRIPTVAR_OBJECT)->var->
813 findChildOrCreateByPath(path.substr(p+1));
814}
815
816CScriptVarLink *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();
823
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;
835}
836
837CScriptVarLink *CScriptVar::addChildNoDup(const std::string &childName, CScriptVar *child) {
838 // if no child supplied, create one
839 if (!child)
840 child = new CScriptVar();
841
842 CScriptVarLink *v = findChild(childName);
843 if (v) {
844 v->replaceWith(child);
845 } else {
846 v = addChild(childName, child);
847 }
848
849 return v;
850}
851
852void 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);
861}
862
863void 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;
874}
875
876void 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;
885}
886
887CScriptVar *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
893}
894
895void CScriptVar::setArrayIndex(int idx, CScriptVar *value) {
896 char sIdx[64];
897 sprintf_s(sIdx, sizeof(sIdx), "%d", idx);
898 CScriptVarLink *link = findChild(sIdx);
899
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 }
909}
910
911int CScriptVar::getArrayLength() {
912 int highest = -1;
913 if (!isArray()) return 0;
914
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;
924}
925
926int CScriptVar::getChildren() {
927 int n = 0;
928 CScriptVarLink *link = firstChild;
929 while (link) {
930 n++;
931 link = link->nextSibling;
932 }
933 return n;
934}
935
936int 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;
943}
944
945double 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? */
951}
952
953const 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;
974}
975
976void CScriptVar::setInt(int val) {
977 flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER;
978 intData = val;
979 doubleData = 0;
980 data = TINYJS_BLANK_DATA;
981}
982
983void CScriptVar::setDouble(double val) {
984 flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE;
985 doubleData = val;
986 intData = 0;
987 data = TINYJS_BLANK_DATA;
988}
989
990void 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;
996}
997
998void 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();
1005}
1006
1007void 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();
1014}
1015
1016bool CScriptVar::equals(CScriptVar *v) {
1017 CScriptVar *resV = mathsOp(v, LEX_EQUAL);
1018 bool res = resV->getBool();
1019 delete resV;
1020 return res;
1021}
1022
1023CScriptVar *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;
1118}
1119
1120void 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);
1125}
1126
1127void 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;
1141
1142 addChild(child->name, copied);
1143
1144 child = child->nextSibling;
1145 }
1146 } else {
1147 setUndefined();
1148 }
1149}
1150
1151CScriptVar *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;
1163
1164 newVar->addChild(child->name, copied);
1165 child = child->nextSibling;
1166 }
1167 return newVar;
1168}
1169
1170void 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 }
1182}
1183
1184string 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;
1194}
1195
1196string 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";
1220}
1221
1222void 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!
1244
1245 for (int i=0;i<len;i++) {
1246 getArrayIndex(i)->getJSON(destination, indentedLinePrefix);
1247 if (i<len-1) destination << ",\n";
1248 }
1249
1250 destination << "\n" << linePrefix << "]";
1251 } else {
1252 // no children or a function... just write value directly
1253 destination << getParsableString();
1254 }
1255}
1256
1257
1258void CScriptVar::setCallback(JSCallback callback, void *userdata) {
1259 jsCallback = callback;
1260 jsCallbackUserData = userdata;
1261}
1262
1263CScriptVar *CScriptVar::ref() {
1264 refs++;
1265 return this;
1266}
1267
1268void CScriptVar::unref() {
1269 if (refs<=0) printf("OMFG, we have unreffed too far!\n");
1270 if ((--refs)==0) {
1271 delete this;
1272 }
1273}
1274
1275int CScriptVar::getRefs() {
1276 return refs;
1277}
1278
1279
1280// ----------------------------------------------------------------------------------- CSCRIPT
1281
1282CTinyJS::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);
1292}
1293
1294CTinyJS::~CTinyJS() {
1295 ASSERT(!l);
1296 scopes.clear();
1297 stringClass->unref();
1298 arrayClass->unref();
1299 objectClass->unref();
1300 root->unref();
1301
1302#if DEBUG_MEMORY
1303 show_allocated();
1304#endif
1305}
1306
1307void CTinyJS::trace() {
1308 root->trace();
1309}
1310
1311void 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;
1333
1334 throw new CScriptException(msg.str());
1335 }
1336 delete l;
1337 l = oldLex;
1338 scopes = oldScopes;
1339}
1340
1341CScriptVarLink CTinyJS::evaluateComplex(const string &code) {
1342 CScriptLex *oldLex = l;
1343 vector<CScriptVar*> oldScopes = scopes;
1344
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;
1369
1370 throw new CScriptException(msg.str());
1371 }
1372 delete l;
1373 l = oldLex;
1374 scopes = oldScopes;
1375
1376 if (v) {
1377 CScriptVarLink r = *v;
1378 CLEAN(v);
1379 return r;
1380 }
1381 // return undefined...
1382 return CScriptVarLink(new CScriptVar());
1383}
1384
1385string CTinyJS::evaluate(const string &code) {
1386 return evaluateComplex(code).var->getString();
1387}
1388
1389void 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(')');
1397}
1398
1399void CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata) {
1400 CScriptLex *oldLex = l;
1401 l = new CScriptLex(funcDesc);
1402
1403 CScriptVar *base = root;
1404
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 }
1418
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;
1424
1425 base->addChild(funcName, funcVar);
1426}
1427
1428CScriptVarLink *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;
1444}
1445
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 */
1450CScriptVarLink *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
1489
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;
1510
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 }
1542}
1543
1544CScriptVarLink *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;
1572
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 }
1648
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);
1661
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;
1713}
1714
1715CScriptVarLink *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;
1728}
1729
1730CScriptVarLink *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;
1743}
1744
1745CScriptVarLink *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 }
1757
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;
1783}
1784
1785CScriptVarLink *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;
1800}
1801
1802CScriptVarLink *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;
1819}
1820
1821CScriptVarLink *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;
1856}
1857
1858CScriptVarLink *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 }
1882
1883 return lhs;
1884}
1885
1886CScriptVarLink *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 }
1899
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;
1917}
1918
1919void 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 }
1934
1935}
1936
1937void 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;
2028
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);
2111}
2112
2113/// Get the given variable specified by a path (var1.var2.etc), or return 0
2114CScriptVar *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;
2129}
2130
2131/// Get the value of the given variable, or return 0
2132const 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;
2139}
2140
2141/// set the value of the given variable, return trur if it exists and gets set
2142bool 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;
2156}
2157
2158/// Finds a child, looking recursively up the scopes
2159CScriptVarLink *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;
2165
2166}
2167
2168/// Look up in any parent classes of the given object
2169CScriptVarLink *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;
2188
2189 return 0;
2190}