4 * A single-file Javascript-alike engine
6 * Authored By Gordon Williams <gw@pur3.co.uk>
8 * Copyright (C) 2009 Pur3 Ltd
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
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
36 Added rough JSON parsing
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
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()
65 Fixed function output in JSON
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:
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
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
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)
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
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); }
143 #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
150 #define vsprintf_s vsnprintf
151 #define sprintf_s snprintf
152 #define _strdup strdup
155 // ----------------------------------------------------------------------------------- Memory Debug
157 #define DEBUG_MEMORY 0
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);
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);
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(" ");
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(" ");
199 allocatedVars.clear();
200 allocatedLinks.clear();
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;
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) {
230 if (!(isAlpha(*s) || isNumeric(*s)))
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);
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 = "";
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;
260 int nCh = ((int)nStr[i]) &0xFF;
261 if (nCh<32 || nCh>127) {
263 sprintf_s(buffer, 5, "\\x%02X", nCh);
264 replaceWith = buffer;
265 } else replace=false;
270 nStr = nStr.substr(0, i) + replaceWith + nStr.substr(i+1);
271 i += strlen(replaceWith)-1;
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])))
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());
299 dataEnd = strlen(data);
303 CScriptLex::CScriptLex(CScriptLex *owner, int startChar, int endChar) {
306 dataStart = startChar;
311 CScriptLex::~CScriptLex(void)
317 void CScriptLex::reset() {
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());
339 string CScriptLex::getTokenStr(int token) {
340 if (token>32 && token<128) {
342 buf[1] = (char)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 "^=";
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";
390 msg << "?[" << token << "]";
394 void CScriptLex::getNextCh() {
396 if (dataPos < dataEnd)
397 nextCh = data[dataPos];
403 void CScriptLex::getNextToken() {
406 while (currCh && isWhitespace(currCh)) getNextCh();
408 if (currCh=='/' && nextCh=='/') {
409 while (currCh && currCh!='\n') getNextCh();
415 if (currCh=='/' && nextCh=='*') {
416 while (currCh && (currCh!='*' || nextCh!='/')) getNextCh();
422 // record beginning of this token
423 tokenStart = dataPos-2;
425 if (isAlpha(currCh)) { // IDs
426 while (isAlpha(currCh) || isNumeric(currCh)) {
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
448 if (currCh=='0') { tkStr += currCh; getNextCh(); }
451 tkStr += currCh; getNextCh();
454 while (isNumeric(currCh) || (isHex && isHexadecimal(currCh))) {
458 if (!isHex && currCh=='.') {
462 while (isNumeric(currCh)) {
467 // do fancy e-style floating point
468 if (!isHex && (currCh=='e'||currCh=='E')) {
470 tkStr += currCh; getNextCh();
471 if (currCh=='-') { tkStr += currCh; getNextCh(); }
472 while (isNumeric(currCh)) {
473 tkStr += currCh; getNextCh();
476 } else if (currCh=='"') {
479 while (currCh && currCh!='"') {
480 if (currCh == '\\') {
483 case 'n' : tkStr += '\n'; break;
484 case '"' : tkStr += '"'; break;
485 case '\\' : tkStr += '\\'; break;
486 default: tkStr += currCh;
495 } else if (currCh=='\'') {
498 while (currCh && currCh!='\'') {
499 if (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
510 getNextCh(); buf[0] = currCh;
511 getNextCh(); buf[1] = currCh;
512 tkStr += (char)strtol(buf,0,16);
514 default: if (currCh>='0' && currCh<='7') {
518 getNextCh(); buf[1] = currCh;
519 getNextCh(); buf[2] = currCh;
520 tkStr += (char)strtol(buf,0,8);
534 if (currCh) getNextCh();
535 if (tk=='=' && currCh=='=') { // ==
538 if (currCh=='=') { // ===
542 } else if (tk=='!' && currCh=='=') { // !=
545 if (currCh=='=') { // !==
549 } else if (tk=='<' && currCh=='=') {
552 } else if (tk=='<' && currCh=='<') {
555 if (currCh=='=') { // <<=
556 tk = LEX_LSHIFTEQUAL;
559 } else if (tk=='>' && currCh=='=') {
562 } else if (tk=='>' && currCh=='>') {
565 if (currCh=='=') { // >>=
566 tk = LEX_RSHIFTEQUAL;
568 } else if (currCh=='>') { // >>>
569 tk = LEX_RSHIFTUNSIGNED;
572 } else if (tk=='+' && currCh=='=') {
575 } else if (tk=='-' && currCh=='=') {
578 } else if (tk=='+' && currCh=='+') {
581 } else if (tk=='-' && currCh=='-') {
584 } else if (tk=='&' && currCh=='=') {
587 } else if (tk=='&' && currCh=='&') {
590 } else if (tk=='|' && currCh=='=') {
593 } else if (tk=='|' && currCh=='|') {
596 } else if (tk=='^' && currCh=='=') {
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
611 char old = data[lastCharIdx];
612 data[lastCharIdx] = 0;
613 std::string value = &data[lastPosition];
614 data[lastCharIdx] = old;
617 return std::string(&data[lastPosition]);
622 CScriptLex *CScriptLex::getSubLex(int lastPosition) {
623 int lastCharIdx = tokenLastEnd+1;
624 if (lastCharIdx < dataEnd)
625 return new CScriptLex(this, lastPosition, lastCharIdx);
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++) {
646 sprintf_s(buf, 256, "(line: %d, col: %d)", line, col);
650 // ----------------------------------------------------------------------------------- CSCRIPTVARLINK
652 CScriptVarLink::CScriptVarLink(CScriptVar *var, const std::string &name) {
654 mark_allocated(this);
657 this->nextSibling = 0;
658 this->prevSibling = 0;
659 this->var = var->ref();
663 CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) {
666 mark_allocated(this);
668 this->name = link.name;
669 this->nextSibling = 0;
670 this->prevSibling = 0;
671 this->var = link.var->ref();
675 CScriptVarLink::~CScriptVarLink() {
677 mark_deallocated(this);
682 void CScriptVarLink::replaceWith(CScriptVar *newVar) {
683 CScriptVar *oldVar = var;
688 void CScriptVarLink::replaceWith(CScriptVarLink *newVar) {
690 replaceWith(newVar->var);
692 replaceWith(new CScriptVar());
695 int CScriptVarLink::getIntName() {
696 return atoi(name.c_str());
698 void CScriptVarLink::setIntName(int n) {
700 sprintf_s(sIdx, sizeof(sIdx), "%d", n);
704 // ----------------------------------------------------------------------------------- CSCRIPTVAR
706 CScriptVar::CScriptVar() {
709 mark_allocated(this);
712 flags = SCRIPTVAR_UNDEFINED;
715 CScriptVar::CScriptVar(const string &str) {
718 mark_allocated(this);
721 flags = SCRIPTVAR_STRING;
726 CScriptVar::CScriptVar(const string &varData, int varFlags) {
729 mark_allocated(this);
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);
741 CScriptVar::CScriptVar(double val) {
744 mark_allocated(this);
750 CScriptVar::CScriptVar(int val) {
753 mark_allocated(this);
759 CScriptVar::~CScriptVar(void) {
761 mark_deallocated(this);
766 void CScriptVar::init() {
771 jsCallbackUserData = 0;
772 data = TINYJS_BLANK_DATA;
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;
793 if (v->name.compare(childName)==0)
800 CScriptVarLink *CScriptVar::findChildOrCreate(const string &childName, int varFlags) {
801 CScriptVarLink *l = findChild(childName);
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) {
818 flags = SCRIPTVAR_OBJECT;
820 // if no child supplied, create one
822 child = new CScriptVar();
824 CScriptVarLink *link = new CScriptVarLink(child, childName);
827 lastChild->nextSibling = link;
828 link->prevSibling = lastChild;
837 CScriptVarLink *CScriptVar::addChildNoDup(const std::string &childName, CScriptVar *child) {
838 // if no child supplied, create one
840 child = new CScriptVar();
842 CScriptVarLink *v = findChild(childName);
844 v->replaceWith(child);
846 v = addChild(childName, child);
852 void CScriptVar::removeChild(CScriptVar *child) {
853 CScriptVarLink *link = firstChild;
855 if (link->var == child)
857 link = link->nextSibling;
863 void CScriptVar::removeLink(CScriptVarLink *link) {
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;
876 void CScriptVar::removeAllChildren() {
877 CScriptVarLink *c = firstChild;
879 CScriptVarLink *t = c->nextSibling;
887 CScriptVar *CScriptVar::getArrayIndex(int idx) {
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) {
897 sprintf_s(sIdx, sizeof(sIdx), "%d", idx);
898 CScriptVarLink *link = findChild(sIdx);
901 if (value->isUndefined())
904 link->replaceWith(value);
906 if (!value->isUndefined())
907 addChild(sIdx, value);
911 int CScriptVar::getArrayLength() {
913 if (!isArray()) return 0;
915 CScriptVarLink *link = firstChild;
917 if (isNumber(link->name)) {
918 int val = atoi(link->name.c_str());
919 if (val > highest) highest = val;
921 link = link->nextSibling;
926 int CScriptVar::getChildren() {
928 CScriptVarLink *link = firstChild;
931 link = link->nextSibling;
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;
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";
960 sprintf_s(buffer, sizeof(buffer), "%ld", intData);
966 sprintf_s(buffer, sizeof(buffer), "%f", doubleData);
970 if (isNull()) return s_null;
971 if (isUndefined()) return s_undefined;
972 // are we just a string here?
976 void CScriptVar::setInt(int val) {
977 flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER;
980 data = TINYJS_BLANK_DATA;
983 void CScriptVar::setDouble(double val) {
984 flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE;
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;
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;
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;
1013 removeAllChildren();
1016 bool CScriptVar::equals(CScriptVar *v) {
1017 CScriptVar *resV = mathsOp(v, LEX_EQUAL);
1018 bool res = resV->getBool();
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));
1031 CScriptVar *contents = a->mathsOp(b, LEX_EQUAL);
1032 if (!contents->getBool()) eql = false;
1033 if (!contents->refs) delete contents;
1036 if (op == LEX_TYPEEQUAL)
1037 return new CScriptVar(eql);
1039 return new CScriptVar(!eql);
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()) {
1050 int da = a->getInt();
1051 int db = b->getInt();
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");
1071 double da = a->getDouble();
1072 double db = b->getDouble();
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");
1087 } else if (a->isArray()) {
1088 /* Just check pointers */
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");
1094 } else if (a->isObject()) {
1095 /* Just check pointers */
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");
1102 string da = a->getString();
1103 string db = b->getString();
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");
1120 void CScriptVar::copySimpleData(CScriptVar *val) {
1122 intData = val->intData;
1123 doubleData = val->doubleData;
1124 flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK);
1127 void CScriptVar::copyValue(CScriptVar *val) {
1129 copySimpleData(val);
1130 // remove all current children
1131 removeAllChildren();
1132 // copy children of 'val'
1133 CScriptVarLink *child = val->firstChild;
1136 // don't copy the 'parent' object...
1137 if (child->name != TINYJS_PROTOTYPE_CLASS)
1138 copied = child->var->deepCopy();
1140 copied = child->var;
1142 addChild(child->name, copied);
1144 child = child->nextSibling;
1151 CScriptVar *CScriptVar::deepCopy() {
1152 CScriptVar *newVar = new CScriptVar();
1153 newVar->copySimpleData(this);
1155 CScriptVarLink *child = firstChild;
1158 // don't copy the 'parent' object...
1159 if (child->name != TINYJS_PROTOTYPE_CLASS)
1160 copied = child->var->deepCopy();
1162 copied = child->var;
1164 newVar->addChild(child->name, copied);
1165 child = child->nextSibling;
1170 void CScriptVar::trace(string indentStr, const string &name) {
1171 TRACE("%s'%s' = '%s' %s\n",
1174 getString().c_str(),
1175 getFlagsAsString().c_str());
1176 string indent = indentStr+" ";
1177 CScriptVarLink *link = firstChild;
1179 link->var->trace(indent, link->name);
1180 link = link->nextSibling;
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 ";
1196 string CScriptVar::getParsableString() {
1197 // Numbers can just be put in directly
1201 ostringstream funcStr;
1202 funcStr << "function (";
1203 // get list of parameters
1204 CScriptVarLink *link = firstChild;
1206 funcStr << link->name;
1207 if (link->nextSibling) funcStr << ",";
1208 link = link->nextSibling;
1210 // add function body
1211 funcStr << ") " << getString();
1212 return funcStr.str();
1214 // if it is a string then we quote it
1216 return getJSString(getString());
1222 void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) {
1224 string indentedLinePrefix = linePrefix+" ";
1225 // children - handle with bracketed list
1226 destination << "{ \n";
1227 CScriptVarLink *link = firstChild;
1229 destination << indentedLinePrefix;
1230 destination << getJSString(link->name);
1231 destination << " : ";
1232 link->var->getJSON(destination, indentedLinePrefix);
1233 link = link->nextSibling;
1235 destination << ",\n";
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";
1250 destination << "\n" << linePrefix << "]";
1252 // no children or a function... just write value directly
1253 destination << getParsableString();
1258 void CScriptVar::setCallback(JSCallback callback, void *userdata) {
1259 jsCallback = callback;
1260 jsCallbackUserData = userdata;
1263 CScriptVar *CScriptVar::ref() {
1268 void CScriptVar::unref() {
1269 if (refs<=0) printf("OMFG, we have unreffed too far!\n");
1275 int CScriptVar::getRefs() {
1280 // ----------------------------------------------------------------------------------- CSCRIPT
1282 CTinyJS::CTinyJS() {
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() {
1297 stringClass->unref();
1298 arrayClass->unref();
1299 objectClass->unref();
1307 void CTinyJS::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
1319 scopes.push_back(root);
1321 bool execute = true;
1322 while (l->tk) statement(execute);
1323 } catch (CScriptException *e) {
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);
1330 msg << " at " << l->getPosition();
1334 throw new CScriptException(msg.str());
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
1350 scopes.push_back(root);
1351 CScriptVarLink *v = 0;
1353 bool execute = true;
1357 if (l->tk!=LEX_EOF) l->match(';');
1358 } while (l->tk!=LEX_EOF);
1359 } catch (CScriptException *e) {
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);
1366 msg << " at " << l->getPosition();
1370 throw new CScriptException(msg.str());
1377 CScriptVarLink r = *v;
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) {
1391 while (l->tk!=')') {
1392 funcVar->addChildNoDup(l->tkStr);
1394 if (l->tk!=')') 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;
1408 /* Check for dots, we might want to do something like function String.substring ... */
1409 while (l->tk == '.') {
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));
1415 funcName = l->tkStr;
1419 CScriptVar *funcVar = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE);
1420 funcVar->setCallback(ptr, userdata);
1421 parseFunctionArguments(funcVar);
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;
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;
1442 funcVar->var->data = l->getSubString(funcBegin);
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).
1450 CScriptVarLink *CTinyJS::functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent) {
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());
1458 // create a new symbol table entry for execution of this function
1459 CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION);
1461 functionRoot->addChildNoDup("this", parent);
1462 // grab in all parameters
1463 CScriptVarLink *v = function->var->firstChild;
1465 CScriptVarLink *value = base(execute);
1467 if (value->var->isBasic()) {
1469 functionRoot->addChild(v->name, value->var->deepCopy());
1471 // pass by reference
1472 functionRoot->addChild(v->name, value->var);
1476 if (l->tk!=')') 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());
1490 if (function->var->isNative()) {
1491 ASSERT(function->var->jsCallback);
1492 function->var->jsCallback(functionRoot, function->var->jsCallbackUserData);
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());
1503 // because return will probably have called this, and set execute to false
1505 } catch (CScriptException *e) {
1514 #ifdef TINYJS_CALL_STACK
1515 if (!call_stack.empty()) call_stack.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;
1525 return new CScriptVarLink(new CScriptVar());
1527 // function, but not executing - just parse args and be done
1529 while (l->tk != ')') {
1530 CScriptVarLink *value = base(execute);
1532 if (l->tk!=')') l->match(',');
1535 if (l->tk == '{') { // TODO: why is this here?
1538 /* function will be a blank scriptvarlink if we're not executing,
1539 * so just return it rather than an alloc/free */
1544 CScriptVarLink *CTinyJS::factor(bool &execute) {
1547 CScriptVarLink *a = base(execute);
1551 if (l->tk==LEX_R_TRUE) {
1552 l->match(LEX_R_TRUE);
1553 return new CScriptVarLink(new CScriptVar(1));
1555 if (l->tk==LEX_R_FALSE) {
1556 l->match(LEX_R_FALSE);
1557 return new CScriptVarLink(new CScriptVar(0));
1559 if (l->tk==LEX_R_NULL) {
1560 l->match(LEX_R_NULL);
1561 return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_NULL));
1563 if (l->tk==LEX_R_UNDEFINED) {
1564 l->match(LEX_R_UNDEFINED);
1565 return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_UNDEFINED));
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);
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
1585 const string &name = l->tkStr;
1586 CScriptVarLink *child = a->var->findChild(name);
1587 if (!child) child = findInParentClasses(a->var, name);
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));
1598 child = a->var->addChild(name);
1605 } else if (l->tk == '[') { // ------------------------------------- Array Access
1607 CScriptVarLink *index = base(execute);
1610 CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString());
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));
1623 return new CScriptVarLink(a);
1625 if (l->tk==LEX_STR) {
1626 CScriptVar *a = new CScriptVar(l->tkStr, SCRIPTVAR_STRING);
1628 return new CScriptVarLink(a);
1631 CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT);
1632 /* JSON-style object definition */
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);
1641 CScriptVarLink *a = base(execute);
1642 contents->addChild(id, a->var);
1645 // no need to clean here, as it will definitely be used
1646 if (l->tk != '}') l->match(',');
1650 return new CScriptVarLink(contents);
1653 CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY);
1654 /* JSON-style array */
1657 while (l->tk != ']') {
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);
1666 // no need to clean here, as it will definitely be used
1667 if (l->tk != ']') l->match(',');
1671 return new CScriptVarLink(contents);
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");
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;
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());
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));
1695 obj->addChild(TINYJS_PROTOTYPE_CLASS, objClassOrFunc->var);
1710 // Nothing we can do here... just hope it's the end...
1715 CScriptVarLink *CTinyJS::unary(bool &execute) {
1718 l->match('!'); // binary not
1719 a = factor(execute);
1722 CScriptVar *res = a->var->mathsOp(&zero, LEX_EQUAL);
1723 CREATE_LINK(a, res);
1726 a = factor(execute);
1730 CScriptVarLink *CTinyJS::term(bool &execute) {
1731 CScriptVarLink *a = unary(execute);
1732 while (l->tk=='*' || l->tk=='/' || l->tk=='%') {
1735 CScriptVarLink *b = unary(execute);
1737 CScriptVar *res = a->var->mathsOp(b->var, op);
1738 CREATE_LINK(a, res);
1745 CScriptVarLink *CTinyJS::expression(bool &execute) {
1746 bool negate = false;
1751 CScriptVarLink *a = term(execute);
1754 CScriptVar *res = zero.mathsOp(a->var, '-');
1755 CREATE_LINK(a, res);
1758 while (l->tk=='+' || l->tk=='-' ||
1759 l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS) {
1762 if (op==LEX_PLUSPLUS || op==LEX_MINUSMINUS) {
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);
1773 CScriptVarLink *b = term(execute);
1775 // not in-place, so just replace
1776 CScriptVar *res = a->var->mathsOp(b->var, op);
1777 CREATE_LINK(a, res);
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) {
1790 CScriptVarLink *b = base(execute);
1791 int shift = execute ? b->var->getInt() : 0;
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);
1802 CScriptVarLink *CTinyJS::condition(bool &execute) {
1803 CScriptVarLink *a = shift(execute);
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=='>') {
1813 CScriptVar *res = a->var->mathsOp(b->var, op);
1821 CScriptVarLink *CTinyJS::logic(bool &execute) {
1822 CScriptVarLink *a = condition(execute);
1824 while (l->tk=='&' || l->tk=='|' || l->tk=='^' || l->tk==LEX_ANDAND || l->tk==LEX_OROR) {
1825 bool noexecute = false;
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) {
1835 shortCircuit = !a->var->getBool();
1837 } else if (op==LEX_OROR) {
1839 shortCircuit = a->var->getBool();
1842 b = condition(shortCircuit ? noexecute : execute);
1843 if (execute && !shortCircuit) {
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);
1850 CScriptVar *res = a->var->mathsOp(b->var, op);
1851 CREATE_LINK(a, res);
1858 CScriptVarLink *CTinyJS::ternary(bool &execute) {
1859 CScriptVarLink *lhs = logic(execute);
1860 bool noexec = false;
1865 CLEAN(base(noexec));
1867 CLEAN(base(noexec));
1869 bool first = lhs->var->getBool();
1872 lhs = base(execute);
1874 CLEAN(base(noexec));
1876 CLEAN(base(noexec));
1878 lhs = base(execute);
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);
1897 TRACE("Trying to assign to an un-named type\n");
1902 CScriptVarLink *rhs = base(execute);
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);
1919 void CTinyJS::block(bool &execute) {
1922 while (l->tk && l->tk!='}')
1926 // fast skip of blocks
1928 while (l->tk && brackets) {
1929 if (l->tk == '{') brackets++;
1930 if (l->tk == '}') brackets--;
1937 void CTinyJS::statement(bool &execute) {
1938 if (l->tk==LEX_ID ||
1943 /* Execute a simple statement that only contains basic arithmetic... */
1944 CLEAN(base(execute));
1946 } else if (l->tk=='{') {
1947 /* A block of code */
1949 } else if (l->tk==';') {
1950 /* Empty statement - to allow things like ;;; */
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;
1960 a = scopes.back()->findChildOrCreate(l->tkStr);
1962 // now do stuff defined with dots
1963 while (l->tk == '.') {
1966 CScriptVarLink *lastA = a;
1967 a = lastA->var->findChildOrCreate(l->tkStr);
1971 // sort out initialiser
1974 CScriptVarLink *var = base(execute);
1976 a->replaceWith(var);
1983 } else if (l->tk==LEX_R_IF) {
1986 CScriptVarLink *var = base(execute);
1988 bool cond = execute && var->var->getBool();
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);
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);
2001 int whileCondStart = l->tokenStart;
2002 bool noexecute = false;
2003 CScriptVarLink *cond = base(execute);
2004 bool loopCond = execute && cond->var->getBool();
2006 CScriptLex *whileCond = l->getSubLex(whileCondStart);
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) {
2016 cond = base(execute);
2017 loopCond = execute && cond->var->getBool();
2031 TRACE("WHILE Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str());
2032 throw new CScriptException("LOOP_ERROR");
2034 } else if (l->tk==LEX_R_FOR) {
2035 l->match(LEX_R_FOR);
2037 statement(execute); // initialisation
2039 int forCondStart = l->tokenStart;
2040 bool noexecute = false;
2041 CScriptVarLink *cond = base(execute); // condition
2042 bool loopCond = execute && cond->var->getBool();
2044 CScriptLex *forCond = l->getSubLex(forCondStart);
2046 int forIterStart = l->tokenStart;
2047 CLEAN(base(noexecute)); // iterator
2048 CScriptLex *forIter = l->getSubLex(forIterStart);
2050 int forBodyStart = l->tokenStart;
2051 statement(loopCond ? execute : noexecute);
2052 CScriptLex *forBody = l->getSubLex(forBodyStart);
2053 CScriptLex *oldLex = l;
2057 CLEAN(base(execute));
2059 int loopCount = TINYJS_LOOP_MAX_ITERATIONS;
2060 while (execute && loopCond && loopCount-->0) {
2063 cond = base(execute);
2064 loopCond = cond->var->getBool();
2066 if (execute && loopCond) {
2071 if (execute && loopCond) {
2074 CLEAN(base(execute));
2083 TRACE("FOR Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str());
2084 throw new CScriptException("LOOP_ERROR");
2086 } else if (l->tk==LEX_R_RETURN) {
2087 l->match(LEX_R_RETURN);
2088 CScriptVarLink *result = 0;
2090 result = base(execute);
2092 CScriptVarLink *resultVar = scopes.back()->findChild(TINYJS_RETURN_VAR);
2094 resultVar->replaceWith(result);
2096 TRACE("RETURN statement, but not in a function.\n");
2101 } else if (l->tk==LEX_R_FUNCTION) {
2102 CScriptVarLink *funcVar = parseFunctionDefinition();
2104 if (funcVar->name == TINYJS_TEMP_NAME)
2105 TRACE("Functions defined at statement-level are meant to have a name\n");
2107 scopes.back()->addChildNoDup(funcVar->name, funcVar->var);
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) {
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();
2131 /// Get the value of the given variable, or return 0
2132 const string *CTinyJS::getVariable(const string &path) {
2133 CScriptVar *var = getScriptVariable(path);
2136 return &var->getString();
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);
2147 var->setInt((int)strtol(varData.c_str(),0,0));
2148 else if (var->isDouble())
2149 var->setDouble(strtod(varData.c_str(),0));
2151 var->setString(varData.c_str());
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);
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);
2177 // else fake it for strings and finally objects
2178 if (object->isString()) {
2179 CScriptVarLink *implementation = stringClass->findChild(name);
2180 if (implementation) return implementation;
2182 if (object->isArray()) {
2183 CScriptVarLink *implementation = arrayClass->findChild(name);
2184 if (implementation) return implementation;
2186 CScriptVarLink *implementation = objectClass->findChild(name);
2187 if (implementation) return implementation;