diff --git a/Makefile b/Makefile
index 9b88f45..d48f859 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,11 @@ LDFLAGS=-g -rdynamic
SOURCES= \
TinyJS.cpp \
-TinyJS_Functions.cpp
+pool_allocator.cpp \
+TinyJS_Functions.cpp \
+TinyJS_MathFunctions.cpp \
+TinyJS_StringFunctions.cpp \
+TinyJS_Threading.cpp
OBJECTS=$(SOURCES:.cpp=.o)
diff --git a/Script.2012.vcxproj b/Script.2012.vcxproj
new file mode 100644
index 0000000..a804906
--- /dev/null
+++ b/Script.2012.vcxproj
@@ -0,0 +1,107 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}
+ Script
+ Win32Proj
+ Script
+
+
+
+ Application
+ false
+ false
+ MultiByte
+ true
+ v110
+
+
+ Application
+ false
+ false
+ MultiByte
+ v110
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ $(SolutionDir)$(Configuration)\
+ $(ProjectName)\$(Configuration)\
+ true
+ $(SolutionDir)$(Configuration)\
+ $(ProjectName)\$(Configuration)\
+ false
+
+
+
+ Disabled
+ %(AdditionalIncludeDirectories)
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ EnableFastChecks
+ MultiThreadedDebugDLL
+
+
+ Level4
+ EditAndContinue
+
+
+ true
+ Console
+ MachineX86
+ false
+
+
+
+
+ MaxSpeed
+ true
+ %(AdditionalIncludeDirectories)
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+ true
+
+
+ Level3
+ ProgramDatabase
+
+
+ true
+ Console
+ true
+ true
+ MachineX86
+
+
+
+
+
+
+
+ {9751c465-0294-43cd-a5d9-bd038aba3961}
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Script.2012.vcxproj.filters b/Script.2012.vcxproj.filters
new file mode 100644
index 0000000..7a7dc59
--- /dev/null
+++ b/Script.2012.vcxproj.filters
@@ -0,0 +1,22 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav
+
+
+
+
+ Quelldateien
+
+
+
\ No newline at end of file
diff --git a/Script.cpp b/Script.cpp
index f702a30..a9d06d9 100755
--- a/Script.cpp
+++ b/Script.cpp
@@ -7,78 +7,133 @@
*
* Copyright (C) 2009 Pur3 Ltd
*
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
+
+ * 42TinyJS
+ *
+ * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine
+ *
+ * Authored / Changed By Armin Diedering
*
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
+ * Copyright (C) 2010-2014 ardisoft
*
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
*/
+
/*
* This is a simple program showing how to use TinyJS
*/
#include "TinyJS.h"
-#include "TinyJS_Functions.h"
+//#include "TinyJS_Functions.h"
+//#include "TinyJS_StringFunctions.h"
+//#include "TinyJS_MathFunctions.h"
#include
#include
+#include
+#ifdef _DEBUG
+# ifndef _MSC_VER
+# define DEBUG_MEMORY 1
+# endif
+#endif
//const char *code = "var a = 5; if (a==5) a=4; else a=3;";
//const char *code = "{ var a = 4; var b = 1; while (a>0) { b = b * 2; a = a - 1; } var c = 5; }";
//const char *code = "{ var b = 1; for (var i=0;i<4;i=i+1) b = b * 2; }";
const char *code = "function myfunc(x, y) { return x + y; } var a = myfunc(1,2); print(a);";
-void js_print(CScriptVar *v, void *userdata) {
- printf("> %s\n", v->getParameter("text")->getString().c_str());
+void js_print(const CFunctionsScopePtr &v, void *) {
+ printf("> %s\n", v->getArgument("text")->toString().c_str());
}
-void js_dump(CScriptVar *v, void *userdata) {
- CTinyJS *js = (CTinyJS*)userdata;
- js->root->trace("> ");
+void js_dump(const CFunctionsScopePtr &v, void *) {
+ v->getContext()->getRoot()->trace("> ");
}
-int main(int argc, char **argv)
+char *topOfStack;
+#define sizeOfStack 1*1024*1024 /* for example 1 MB depend of Compiler-Options */
+#define sizeOfSafeStack 50*1024 /* safety area */
+
+int main(int , char **)
{
- CTinyJS *js = new CTinyJS();
- /* add the functions from TinyJS_Functions.cpp */
- registerFunctions(js);
- /* Add a native function */
- js->addNative("function print(text)", &js_print, 0);
- js->addNative("function dump()", &js_dump, js);
- /* Execute out bit of code - we could call 'evaluate' here if
- we wanted something returned */
- try {
- js->execute("var lets_quit = 0; function quit() { lets_quit = 1; }");
- js->execute("print(\"Interactive mode... Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!\");");
- } catch (CScriptException *e) {
- printf("ERROR: %s\n", e->text.c_str());
- }
+ char dummy;
+ topOfStack = &dummy;
+// printf("%i %i\n", __cplusplus, _MSC_VER);
- while (js->evaluate("lets_quit") == "0") {
- char buffer[2048];
- fgets ( buffer, sizeof(buffer), stdin );
- try {
- js->execute(buffer);
- } catch (CScriptException *e) {
- printf("ERROR: %s\n", e->text.c_str());
- }
- }
- delete js;
+// printf("Locale:%s\n",setlocale( LC_ALL, 0 ));
+// setlocale( LC_ALL, ".858" );
+// printf("Locale:%s\n",setlocale( LC_ALL, 0 ));
+ CTinyJS *js = new CTinyJS();
+ /* add the functions from TinyJS_Functions.cpp */
+// registerFunctions(js);
+// registerStringFunctions(js);
+// registerMathFunctions(js);
+ /* Add a native function */
+ js->addNative("function print(text)", &js_print, 0);
+// js->addNative("function dump()", &js_dump, js);
+ /* Execute out bit of code - we could call 'evaluate' here if
+ we wanted something returned */
+ js->setStackBase(topOfStack-(sizeOfStack-sizeOfSafeStack));
+ try {
+ js->execute("var lets_quit = 0; function quit() { lets_quit = 1; }");
+ js->execute("print(\"Interactive mode... Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!\");");
+ js->execute("print(function () {print(\"gen\");yield 5;yield 6;}().next());", "yield-test.js");
+ js->execute("for each(i in function () {print(\"gen\");yield 5;yield 6;}()) print(i);", "yield-test.js");
+ js->execute("function g(){ \n\n"
+ " throw \"error\"\n"
+ " try{ \n"
+ " yield 1; yield 2 \n"
+ " }finally{ \n"
+ " print(\"finally\") \n"
+ " yield 3; \n"
+ " throw StopIteration \n"
+ " } \n"
+ " print(\"after finally\") \n"
+ "}t=g()", "test");
+ } catch (CScriptException *e) {
+ printf("%s\n", e->toString().c_str());
+ delete e;
+ }
+ int lineNumber = 0;
+ while (js->evaluate("lets_quit") == "0") {
+ std::string buffer;
+ if(!std::getline(std::cin, buffer)) break;
+ try {
+ js->execute(buffer, "console.input", lineNumber++);
+ } catch (CScriptException *e) {
+ printf("%s\n", e->toString().c_str());
+ delete e;
+ }
+ }
+ delete js;
#ifdef _WIN32
#ifdef _DEBUG
- _CrtDumpMemoryLeaks();
+// _CrtDumpMemoryLeaks();
+/*
+ no dump momoryleaks here
+ _CrtSetDbgFlag(..) force dump memoryleake after call of all global deconstructors
+*/
+ _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
#endif
- return 0;
+ return 0;
}
diff --git a/Script.vcproj b/Script.vcproj
new file mode 100644
index 0000000..415e972
--- /dev/null
+++ b/Script.vcproj
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Script.vcxproj b/Script.vcxproj
new file mode 100644
index 0000000..94ee2ab
--- /dev/null
+++ b/Script.vcxproj
@@ -0,0 +1,103 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}
+ Script
+ Win32Proj
+
+
+
+ Application
+ false
+ false
+ MultiByte
+ true
+
+
+ Application
+ false
+ false
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ $(SolutionDir)$(Configuration)\
+ $(ProjectName)\$(Configuration)\
+ true
+ $(SolutionDir)$(Configuration)\
+ $(ProjectName)\$(Configuration)\
+ false
+
+
+
+ Disabled
+ %(AdditionalIncludeDirectories)
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ EnableFastChecks
+ MultiThreadedDebugDLL
+
+
+ Level4
+ EditAndContinue
+
+
+ true
+ Console
+ MachineX86
+
+
+
+
+ MaxSpeed
+ true
+ %(AdditionalIncludeDirectories)
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+ true
+
+
+ Level3
+ ProgramDatabase
+
+
+ true
+ Console
+ true
+ true
+ MachineX86
+
+
+
+
+
+
+
+ {9751c465-0294-43cd-a5d9-bd038aba3961}
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Script.vcxproj.filters b/Script.vcxproj.filters
new file mode 100644
index 0000000..7a7dc59
--- /dev/null
+++ b/Script.vcxproj.filters
@@ -0,0 +1,22 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav
+
+
+
+
+ Quelldateien
+
+
+
\ No newline at end of file
diff --git a/TinyJS.cpp b/TinyJS.cpp
index 8ffa421..9cabbef 100755
--- a/TinyJS.cpp
+++ b/TinyJS.cpp
@@ -1,1996 +1,6553 @@
-/*
- * TinyJS
- *
- * A single-file Javascript-alike engine
- *
- * Authored By Gordon Williams
- *
- * Copyright (C) 2009 Pur3 Ltd
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-/* Version 0.1 : (gw) First published on Google Code
- Version 0.11 : Making sure the 'root' variable never changes
- 'symbol_base' added for the current base of the sybmbol table
- Version 0.12 : Added findChildOrCreate, changed string passing to use references
- Fixed broken string encoding in getJSString()
- Removed getInitCode and added getJSON instead
- Added nil
- Added rough JSON parsing
- Improved example app
- Version 0.13 : Added tokenEnd/tokenLastEnd to lexer to avoid parsing whitespace
- Ability to define functions without names
- Can now do "var mine = function(a,b) { ... };"
- Slightly better 'trace' function
- Added findChildOrCreateByPath function
- Added simple test suite
- Added skipping of blocks when not executing
- Version 0.14 : Added parsing of more number types
- Added parsing of string defined with '
- Changed nil to null as per spec, added 'undefined'
- Now set variables with the correct scope, and treat unknown
- as 'undefined' rather than failing
- Added proper (I hope) handling of null and undefined
- Added === check
- Version 0.15 : Fix for possible memory leaks
- Version 0.16 : Removal of un-needed findRecursive calls
- symbol_base removed and replaced with 'scopes' stack
- Added reference counting a proper tree structure
- (Allowing pass by reference)
- Allowed JSON output to output IDs, not strings
- Added get/set for array indices
- Changed Callbacks to include user data pointer
- Added some support for objects
- Added more Java-esque builtin functions
- Version 0.17 : Now we don't deepCopy the parent object of the class
- Added JSON.stringify and eval()
- Nicer JSON indenting
- Fixed function output in JSON
- Added evaluateComplex
- Fixed some reentrancy issues with evaluate/execute
- Version 0.18 : Fixed some issues with code being executed when it shouldn't
- Version 0.19 : Added array.length
- Changed '__parent' to 'prototype' to bring it more in line with javascript
- Version 0.20 : Added '%' operator
- Version 0.21 : Added array type
- String.length() no more - now String.length
- Added extra constructors to reduce confusion
- Fixed checks against undefined
- Version 0.22 : First part of ardi's changes:
- sprintf -> sprintf_s
- extra tokens parsed
- array memory leak fixed
- Fixed memory leak in evaluateComplex
- Fixed memory leak in FOR loops
- Fixed memory leak for unary minus
- Version 0.23 : Allowed evaluate[Complex] to take in semi-colon separated
- statements and then only return the value from the last one.
- Also checks to make sure *everything* was parsed.
- Ints + doubles are now stored in binary form (faster + more precise)
-
- NOTE: This doesn't support constructors for objects
- Recursive loops of data such as a.foo = a; fail to be garbage collected
- 'length' cannot be set
- There is no ternary operator implemented yet
- The postfix increment operator returns the current value, not the
- previous as it should.
- Arrays are implemented as a linked list - hence a lookup is O(n)
-
- TODO:
- Utility va-args style function in TinyJS for executing a function directly
-
- */
-
-#include "TinyJS.h"
-#include
-
-#define ASSERT(X) assert(X)
-/* Frees the given link IF it isn't owned by anything else */
-#define CLEAN(x) { CScriptVarLink *__v = x; if (__v && !__v->owned) { delete __v; } }
-/* Create a LINK to point to VAR and free the old link.
- * BUT this is more clever - it tries to keep the old link if it's not owned to save allocations */
-#define CREATE_LINK(LINK, VAR) { if (!LINK || LINK->owned) LINK = new CScriptVarLink(VAR); else LINK->replaceWith(VAR); }
-
-#include
-#include
-#include
-#include
-#include
-
-using namespace std;
-
-#ifdef _WIN32
-#ifdef _DEBUG
- #ifndef DBG_NEW
- #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
- #define new DBG_NEW
- #endif
-#endif
-#endif
-
-#ifdef __GNUC__
-#define vsprintf_s vsnprintf
-#define sprintf_s snprintf
-#define _strdup strdup
-#endif
-
-// ----------------------------------------------------------------------------------- Memory Debug
-
-#define DEBUG_MEMORY 0
-
-#if DEBUG_MEMORY
-
-vector allocatedVars;
-vector allocatedLinks;
-
-void mark_allocated(CScriptVar *v) {
- allocatedVars.push_back(v);
-}
-
-void mark_deallocated(CScriptVar *v) {
- for (size_t i=0;igetRefs());
- allocatedVars[i]->trace(" ");
- }
- for (size_t i=0;iname.c_str(), allocatedLinks[i]->var->getRefs());
- allocatedLinks[i]->var->trace(" ");
- }
- allocatedVars.clear();
- allocatedLinks.clear();
-}
-#endif
-
-// ----------------------------------------------------------------------------------- Utils
-bool isWhitespace(char ch) {
- return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r');
-}
-
-bool isNumeric(char ch) {
- return (ch>='0') && (ch<='9');
-}
-bool isNumber(const string &str) {
- for (size_t i=0;i='0') && (ch<='9')) ||
- ((ch>='a') && (ch<='f')) ||
- ((ch>='A') && (ch<='F'));
-}
-bool isAlpha(char ch) {
- return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_';
-}
-
-bool isIDString(const char *s) {
- if (!isAlpha(*s))
- return false;
- while (*s) {
- if (!(isAlpha(*s) || isNumeric(*s)))
- return false;
- s++;
- }
- return true;
-}
-
-void replace(string &str, char textFrom, const char *textTo) {
- int sLen = strlen(textTo);
- size_t p = str.find(textFrom);
- while (p != string::npos) {
- str = str.substr(0, p) + textTo + str.substr(p+1);
- p = str.find(textFrom, p+sLen);
- }
-}
-
-/// convert the given string into a quoted string suitable for javascript
-std::string getJSString(const std::string &str) {
- std::string nStr = str;
- for (size_t i=0;idata;
- dataOwned = false;
- dataStart = startChar;
- dataEnd = endChar;
- reset();
-}
-
-CScriptLex::~CScriptLex(void)
-{
- if (dataOwned)
- free((void*)data);
-}
-
-void CScriptLex::reset() {
- dataPos = dataStart;
- tokenStart = 0;
- tokenEnd = 0;
- tokenLastEnd = 0;
- tk = 0;
- tkStr = "";
- getNextCh();
- getNextCh();
- getNextToken();
-}
-
-void CScriptLex::match(int expected_tk) {
- if (tk!=expected_tk) {
- ostringstream errorString;
- errorString << "Got " << getTokenStr(tk) << " expected " << getTokenStr(expected_tk)
- << " at " << getPosition(tokenStart) << " in '" << data << "'";
- throw new CScriptException(errorString.str());
- }
- getNextToken();
-}
-
-string CScriptLex::getTokenStr(int token) {
- if (token>32 && token<128) {
- char buf[4] = "' '";
- buf[1] = (char)token;
- return buf;
- }
- switch (token) {
- case LEX_EOF : return "EOF";
- case LEX_ID : return "ID";
- case LEX_INT : return "INT";
- case LEX_FLOAT : return "FLOAT";
- case LEX_STR : return "STRING";
- case LEX_EQUAL : return "==";
- case LEX_TYPEEQUAL : return "===";
- case LEX_NEQUAL : return "!=";
- case LEX_NTYPEEQUAL : return "!==";
- case LEX_LEQUAL : return "<=";
- case LEX_LSHIFT : return "<<";
- case LEX_LSHIFTEQUAL : return "<<=";
- case LEX_GEQUAL : return ">=";
- case LEX_RSHIFT : return ">>";
- case LEX_RSHIFTEQUAL : return ">>=";
- case LEX_PLUSEQUAL : return "+=";
- case LEX_MINUSEQUAL : return "-=";
- case LEX_PLUSPLUS : return "++";
- case LEX_MINUSMINUS : return "--";
- case LEX_ANDEQUAL : return "&=";
- case LEX_ANDAND : return "&&";
- case LEX_OREQUAL : return "|=";
- case LEX_OROR : return "||";
- case LEX_XOREQUAL : return "^=";
- // reserved words
- case LEX_R_IF : return "if";
- case LEX_R_ELSE : return "else";
- case LEX_R_DO : return "do";
- case LEX_R_WHILE : return "while";
- case LEX_R_FOR : return "for";
- case LEX_R_BREAK : return "break";
- case LEX_R_CONTINUE : return "continue";
- case LEX_R_FUNCTION : return "function";
- case LEX_R_RETURN : return "return";
- case LEX_R_VAR : return "var";
- case LEX_R_TRUE : return "true";
- case LEX_R_FALSE : return "false";
- case LEX_R_NULL : return "null";
- case LEX_R_UNDEFINED : return "undefined";
- case LEX_R_NEW : return "new";
- }
-
- ostringstream msg;
- msg << "?[" << token << "]";
- return msg.str();
-}
-
-void CScriptLex::getNextCh() {
- currCh = nextCh;
- if (dataPos < dataEnd)
- nextCh = data[dataPos];
- else
- nextCh = 0;
- dataPos++;
-}
-
-void CScriptLex::getNextToken() {
- tk = LEX_EOF;
- tkStr.clear();
- while (currCh && isWhitespace(currCh)) getNextCh();
- // newline comments
- if (currCh=='/' && nextCh=='/') {
- while (currCh && currCh!='\n') getNextCh();
- getNextCh();
- getNextToken();
- return;
- }
- // block comments
- if (currCh=='/' && nextCh=='*') {
- while (currCh && (currCh!='*' || nextCh!='/')) getNextCh();
- getNextCh();
- getNextCh();
- getNextToken();
- return;
- }
- // record beginning of this token
- tokenStart = dataPos-2;
- // tokens
- if (isAlpha(currCh)) { // IDs
- while (isAlpha(currCh) || isNumeric(currCh)) {
- tkStr += currCh;
- getNextCh();
- }
- tk = LEX_ID;
- if (tkStr=="if") tk = LEX_R_IF;
- else if (tkStr=="else") tk = LEX_R_ELSE;
- else if (tkStr=="do") tk = LEX_R_DO;
- else if (tkStr=="while") tk = LEX_R_WHILE;
- else if (tkStr=="for") tk = LEX_R_FOR;
- else if (tkStr=="break") tk = LEX_R_BREAK;
- else if (tkStr=="continue") tk = LEX_R_CONTINUE;
- else if (tkStr=="function") tk = LEX_R_FUNCTION;
- else if (tkStr=="return") tk = LEX_R_RETURN;
- else if (tkStr=="var") tk = LEX_R_VAR;
- else if (tkStr=="true") tk = LEX_R_TRUE;
- else if (tkStr=="false") tk = LEX_R_FALSE;
- else if (tkStr=="null") tk = LEX_R_NULL;
- else if (tkStr=="undefined") tk = LEX_R_UNDEFINED;
- else if (tkStr=="new") tk = LEX_R_NEW;
- } else if (isNumeric(currCh)) { // Numbers
- bool isHex = false;
- if (currCh=='0') { tkStr += currCh; getNextCh(); }
- if (currCh=='x') {
- isHex = true;
- tkStr += currCh; getNextCh();
- }
- tk = LEX_INT;
- while (isNumeric(currCh) || (isHex && isHexadecimal(currCh))) {
- tkStr += currCh;
- getNextCh();
- }
- if (!isHex && currCh=='.') {
- tk = LEX_FLOAT;
- tkStr += '.';
- getNextCh();
- while (isNumeric(currCh)) {
- tkStr += currCh;
- getNextCh();
- }
- }
- // do fancy e-style floating point
- if (!isHex && currCh=='e') {
- tk = LEX_FLOAT;
- tkStr += currCh; getNextCh();
- if (currCh=='-') { tkStr += currCh; getNextCh(); }
- while (isNumeric(currCh)) {
- tkStr += currCh; getNextCh();
- }
- }
- } else if (currCh=='"') {
- // strings...
- getNextCh();
- while (currCh && currCh!='"') {
- if (currCh == '\\') {
- getNextCh();
- switch (currCh) {
- case 'n' : tkStr += '\n'; break;
- case '"' : tkStr += '"'; break;
- case '\\' : tkStr += '\\'; break;
- default: tkStr += currCh;
- }
- } else {
- tkStr += currCh;
- }
- getNextCh();
- }
- getNextCh();
- tk = LEX_STR;
- } else if (currCh=='\'') {
- // strings again...
- getNextCh();
- while (currCh && currCh!='\'') {
- if (currCh == '\\') {
- getNextCh();
- switch (currCh) {
- case 'n' : tkStr += '\n'; break;
- case '\'' : tkStr += '\''; break;
- case '\\' : tkStr += '\\'; break;
- default: tkStr += currCh;
- }
- } else {
- tkStr += currCh;
- }
- getNextCh();
- }
- getNextCh();
- tk = LEX_STR;
- } else {
- // single chars
- tk = currCh;
- if (currCh) getNextCh();
- if (tk=='=' && currCh=='=') { // ==
- tk = LEX_EQUAL;
- getNextCh();
- if (currCh=='=') { // ===
- tk = LEX_TYPEEQUAL;
- getNextCh();
- }
- } else if (tk=='!' && currCh=='=') { // !=
- tk = LEX_NEQUAL;
- getNextCh();
- if (currCh=='=') { // !==
- tk = LEX_NTYPEEQUAL;
- getNextCh();
- }
- } else if (tk=='<' && currCh=='=') {
- tk = LEX_LEQUAL;
- getNextCh();
- } else if (tk=='<' && currCh=='<') {
- tk = LEX_LSHIFT;
- getNextCh();
- if (currCh=='=') { // <<=
- tk = LEX_LSHIFTEQUAL;
- getNextCh();
- }
- } else if (tk=='>' && currCh=='=') {
- tk = LEX_GEQUAL;
- getNextCh();
- } else if (tk=='>' && currCh=='>') {
- tk = LEX_RSHIFT;
- getNextCh();
- if (currCh=='=') { // <<=
- tk = LEX_RSHIFTEQUAL;
- getNextCh();
- }
- } else if (tk=='+' && currCh=='=') {
- tk = LEX_PLUSEQUAL;
- getNextCh();
- } else if (tk=='-' && currCh=='=') {
- tk = LEX_MINUSEQUAL;
- getNextCh();
- } else if (tk=='+' && currCh=='+') {
- tk = LEX_PLUSPLUS;
- getNextCh();
- } else if (tk=='-' && currCh=='-') {
- tk = LEX_MINUSMINUS;
- getNextCh();
- } else if (tk=='&' && currCh=='=') {
- tk = LEX_ANDEQUAL;
- getNextCh();
- } else if (tk=='&' && currCh=='&') {
- tk = LEX_ANDAND;
- getNextCh();
- } else if (tk=='|' && currCh=='=') {
- tk = LEX_OREQUAL;
- getNextCh();
- } else if (tk=='|' && currCh=='|') {
- tk = LEX_OROR;
- getNextCh();
- } else if (tk=='^' && currCh=='=') {
- tk = LEX_XOREQUAL;
- getNextCh();
- }
- }
- /* This isn't quite right yet */
- tokenLastEnd = tokenEnd;
- tokenEnd = dataPos-3;
-}
-
-string CScriptLex::getSubString(int lastPosition) {
- int lastCharIdx = tokenLastEnd+1;
- if (lastCharIdx < dataEnd) {
- /* save a memory alloc by using our data array to create the
- substring */
- char old = data[lastCharIdx];
- data[lastCharIdx] = 0;
- std::string value = &data[lastPosition];
- data[lastCharIdx] = old;
- return value;
- } else {
- return std::string(&data[lastPosition]);
- }
-}
-
-
-CScriptLex *CScriptLex::getSubLex(int lastPosition) {
- int lastCharIdx = tokenLastEnd+1;
- if (lastCharIdx < dataEnd)
- return new CScriptLex( this, lastPosition, lastCharIdx);
- else
- return new CScriptLex( this, lastPosition, dataEnd );
-}
-
-string CScriptLex::getPosition(int pos) {
- if (pos<0) pos=tokenLastEnd;
- int line = 1,col = 1;
- for (int i=0;iname = name;
- this->nextSibling = 0;
- this->prevSibling = 0;
- this->var = var->ref();
- this->owned = false;
-}
-
-CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) {
- // Copy constructor
-#if DEBUG_MEMORY
- mark_allocated(this);
-#endif
- this->name = link.name;
- this->nextSibling = 0;
- this->prevSibling = 0;
- this->var = link.var->ref();
- this->owned = false;
-}
-
-CScriptVarLink::~CScriptVarLink() {
-#if DEBUG_MEMORY
- mark_deallocated(this);
-#endif
- var->unref();
-}
-
-void CScriptVarLink::replaceWith(CScriptVar *newVar) {
- CScriptVar *oldVar = var;
- var = newVar->ref();
- oldVar->unref();
-}
-
-void CScriptVarLink::replaceWith(CScriptVarLink *newVar) {
- if (newVar)
- replaceWith(newVar->var);
- else
- replaceWith(new CScriptVar());
-}
-
-// ----------------------------------------------------------------------------------- CSCRIPTVAR
-
-CScriptVar::CScriptVar() {
- refs = 0;
-#if DEBUG_MEMORY
- mark_allocated(this);
-#endif
- init();
- flags = SCRIPTVAR_UNDEFINED;
-}
-
-CScriptVar::CScriptVar(const string &str) {
- refs = 0;
-#if DEBUG_MEMORY
- mark_allocated(this);
-#endif
- init();
- flags = SCRIPTVAR_STRING;
- data = str;
-}
-
-
-CScriptVar::CScriptVar(const string &varData, int varFlags) {
- refs = 0;
-#if DEBUG_MEMORY
- mark_allocated(this);
-#endif
- init();
- flags = varFlags;
- if (varFlags & SCRIPTVAR_INTEGER) {
- intData = strtol(varData.c_str(),0,0);
- } else if (varFlags & SCRIPTVAR_DOUBLE) {
- doubleData = strtod(varData.c_str(),0);
- } else
- data = varData;
-}
-
-CScriptVar::CScriptVar(double val) {
- refs = 0;
-#if DEBUG_MEMORY
- mark_allocated(this);
-#endif
- init();
- setDouble(val);
-}
-
-CScriptVar::CScriptVar(int val) {
- refs = 0;
-#if DEBUG_MEMORY
- mark_allocated(this);
-#endif
- init();
- setInt(val);
-}
-
-CScriptVar::~CScriptVar(void) {
-#if DEBUG_MEMORY
- mark_deallocated(this);
-#endif
- removeAllChildren();
-}
-
-void CScriptVar::init() {
- firstChild = 0;
- lastChild = 0;
- flags = 0;
- jsCallback = 0;
- jsCallbackUserData = 0;
- data = TINYJS_BLANK_DATA;
- intData = 0;
- doubleData = 0;
-}
-
-CScriptVar *CScriptVar::getReturnVar() {
- return getParameter(TINYJS_RETURN_VAR);
-}
-
-void CScriptVar::setReturnVar(CScriptVar *var) {
- findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var);
-}
-
-
-CScriptVar *CScriptVar::getParameter(const std::string &name) {
- return findChildOrCreate(name)->var;
-}
-
-CScriptVarLink *CScriptVar::findChild(const string &childName) {
- CScriptVarLink *v = firstChild;
- while (v) {
- if (v->name.compare(childName)==0)
- return v;
- v = v->nextSibling;
- }
- return 0;
-}
-
-CScriptVarLink *CScriptVar::findChildOrCreate(const string &childName, int varFlags) {
- CScriptVarLink *l = findChild(childName);
- if (l) return l;
-
- return addChild(childName, new CScriptVar(TINYJS_BLANK_DATA, varFlags));
-}
-
-CScriptVarLink *CScriptVar::findChildOrCreateByPath(const std::string &path) {
- size_t p = path.find('.');
- if (p == string::npos)
- return findChildOrCreate(path);
-
- return findChildOrCreate(path.substr(0,p), SCRIPTVAR_OBJECT)->var->
- findChildOrCreateByPath(path.substr(p+1));
-}
-
-CScriptVarLink *CScriptVar::addChild(const std::string &childName, CScriptVar *child) {
- if (isUndefined()) {
- flags = SCRIPTVAR_OBJECT;
- }
- // if no child supplied, create one
- if (!child)
- child = new CScriptVar();
-
- CScriptVarLink *link = new CScriptVarLink(child, childName);
- link->owned = true;
- if (lastChild) {
- lastChild->nextSibling = link;
- link->prevSibling = lastChild;
- lastChild = link;
- } else {
- firstChild = link;
- lastChild = link;
- }
- return link;
-}
-
-CScriptVarLink *CScriptVar::addChildNoDup(const std::string &childName, CScriptVar *child) {
- // if no child supplied, create one
- if (!child)
- child = new CScriptVar();
-
- CScriptVarLink *v = findChild(childName);
- if (v) {
- v->replaceWith(child);
- } else {
- v = addChild(childName, child);
- }
-
- return v;
-}
-
-void CScriptVar::removeChild(CScriptVar *child) {
- CScriptVarLink *link = firstChild;
- while (link) {
- if (link->var == child)
- break;
- link = link->nextSibling;
- }
- ASSERT(link);
- removeLink(link);
-}
-
-void CScriptVar::removeLink(CScriptVarLink *link) {
- if (!link) return;
- if (link->nextSibling)
- link->nextSibling->prevSibling = link->prevSibling;
- if (link->prevSibling)
- link->prevSibling->nextSibling = link->nextSibling;
- if (lastChild == link)
- lastChild = link->prevSibling;
- if (firstChild == link)
- firstChild = link->nextSibling;
- delete link;
-}
-
-void CScriptVar::removeAllChildren() {
- CScriptVarLink *c = firstChild;
- while (c) {
- CScriptVarLink *t = c->nextSibling;
- delete c;
- c = t;
- }
- firstChild = 0;
- lastChild = 0;
-}
-
-CScriptVar *CScriptVar::getArrayIndex(int idx) {
- char sIdx[64];
- sprintf_s(sIdx, sizeof(sIdx), "%d", idx);
- CScriptVarLink *link = findChild(sIdx);
- if (link) return link->var;
- else return new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_NULL); // undefined
-}
-
-void CScriptVar::setArrayIndex(int idx, CScriptVar *value) {
- char sIdx[64];
- sprintf_s(sIdx, sizeof(sIdx), "%d", idx);
- CScriptVarLink *link = findChild(sIdx);
-
- if (link) {
- if (value->isUndefined())
- removeLink(link);
- else
- link->replaceWith(value);
- } else {
- if (!value->isUndefined())
- addChild(sIdx, value);
- }
-}
-
-int CScriptVar::getArrayLength() {
- int highest = -1;
- if (!isArray()) return 0;
-
- CScriptVarLink *link = firstChild;
- while (link) {
- if (isNumber(link->name)) {
- int val = atoi(link->name.c_str());
- if (val > highest) highest = val;
- }
- link = link->nextSibling;
- }
- return highest+1;
-}
-
-int CScriptVar::getChildren() {
- int n = 0;
- CScriptVarLink *link = firstChild;
- while (link) {
- n++;
- link = link->nextSibling;
- }
- return n;
-}
-
-int CScriptVar::getInt() {
- /* strtol understands about hex and octal */
- if (isInt()) return intData;
- if (isNull()) return 0;
- if (isUndefined()) return 0;
- if (isDouble()) return (int)doubleData;
- return 0;
-}
-
-double CScriptVar::getDouble() {
- if (isDouble()) return doubleData;
- if (isInt()) return intData;
- if (isNull()) return 0;
- if (isUndefined()) return 0;
- return 0; /* or NaN? */
-}
-
-const string &CScriptVar::getString() {
- /* Because we can't return a string that is generated on demand.
- * I should really just use char* :) */
- static string s_null = "null";
- static string s_undefined = "undefined";
- if (isInt()) {
- char buffer[32];
- sprintf_s(buffer, sizeof(buffer), "%ld", intData);
- data = buffer;
- return data;
- }
- if (isDouble()) {
- char buffer[32];
- sprintf_s(buffer, sizeof(buffer), "%lf", doubleData);
- data = buffer;
- return data;
- }
- if (isNull()) return s_null;
- if (isUndefined()) return s_undefined;
- // are we just a string here?
- return data;
-}
-
-void CScriptVar::setInt(int val) {
- flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER;
- intData = val;
- doubleData = 0;
- data = TINYJS_BLANK_DATA;
-}
-
-void CScriptVar::setDouble(double val) {
- flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE;
- doubleData = val;
- intData = 0;
- data = TINYJS_BLANK_DATA;
-}
-
-void CScriptVar::setString(const string &str) {
- // name sure it's not still a number or integer
- flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_STRING;
- data = str;
- intData = 0;
- doubleData = 0;
-}
-
-void CScriptVar::setUndefined() {
- // name sure it's not still a number or integer
- flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_UNDEFINED;
- data = TINYJS_BLANK_DATA;
- intData = 0;
- doubleData = 0;
- removeAllChildren();
-}
-
-CScriptVar *CScriptVar::mathsOp(CScriptVar *b, int op) {
- CScriptVar *a = this;
- // Type equality check
- if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) {
- // check type first, then call again to check data
- bool eql = ((a->flags & SCRIPTVAR_VARTYPEMASK) ==
- (b->flags & SCRIPTVAR_VARTYPEMASK)) &&
- a->mathsOp(b, LEX_EQUAL);
- if (op == LEX_TYPEEQUAL)
- return new CScriptVar(eql);
- else
- return new CScriptVar(!eql);
- }
- // do maths...
- if (a->isUndefined() && b->isUndefined()) {
- if (op == LEX_EQUAL) return new CScriptVar(true);
- else if (op == LEX_NEQUAL) return new CScriptVar(false);
- else return new CScriptVar(); // undefined
- } else if ((a->isNumeric() || a->isUndefined()) &&
- (b->isNumeric() || b->isUndefined())) {
- if (!a->isDouble() && !b->isDouble()) {
- // use ints
- int da = a->getInt();
- int db = b->getInt();
- switch (op) {
- case '+': return new CScriptVar(da+db);
- case '-': return new CScriptVar(da-db);
- case '*': return new CScriptVar(da*db);
- case '/': return new CScriptVar(da/db);
- case '&': return new CScriptVar(da&db);
- case '|': return new CScriptVar(da|db);
- case '^': return new CScriptVar(da^db);
- case '%': return new CScriptVar(da%db);
- case LEX_EQUAL: return new CScriptVar(da==db);
- case LEX_NEQUAL: return new CScriptVar(da!=db);
- case '<': return new CScriptVar(da': return new CScriptVar(da>db);
- case LEX_GEQUAL: return new CScriptVar(da>=db);
- default: throw new CScriptException("This operation not supported on the Int datatype");
- }
- } else {
- // use doubles
- double da = a->getDouble();
- double db = b->getDouble();
- switch (op) {
- case '+': return new CScriptVar(da+db);
- case '-': return new CScriptVar(da-db);
- case '*': return new CScriptVar(da*db);
- case '/': return new CScriptVar(da/db);
- case LEX_EQUAL: return new CScriptVar(da==db);
- case LEX_NEQUAL: return new CScriptVar(da!=db);
- case '<': return new CScriptVar(da': return new CScriptVar(da>db);
- case LEX_GEQUAL: return new CScriptVar(da>=db);
- default: throw new CScriptException("This operation not supported on the Double datatype");
- }
- }
- } else if (a->isArray()) {
- /* Just check pointers */
- switch (op) {
- case LEX_EQUAL: return new CScriptVar(a==b);
- case LEX_NEQUAL: return new CScriptVar(a!=b);
- default: throw new CScriptException("This operation not supported on the Array datatype");
- }
- } else if (a->isObject()) {
- /* Just check pointers */
- switch (op) {
- case LEX_EQUAL: return new CScriptVar(a==b);
- case LEX_NEQUAL: return new CScriptVar(a!=b);
- default: throw new CScriptException("This operation not supported on the Object datatype");
- }
- } else {
- string da = a->getString();
- string db = b->getString();
- // use strings
- switch (op) {
- case '+': return new CScriptVar(da+db, SCRIPTVAR_STRING);
- case LEX_EQUAL: return new CScriptVar(da==db);
- case LEX_NEQUAL: return new CScriptVar(da!=db);
- case '<': return new CScriptVar(da': return new CScriptVar(da>db);
- case LEX_GEQUAL: return new CScriptVar(da>=db);
- default: throw new CScriptException("This operation not supported on the string datatype");
- }
- }
- ASSERT(0);
- return 0;
-}
-
-void CScriptVar::copySimpleData(CScriptVar *val) {
- data = val->data;
- intData = val->intData;
- doubleData = val->doubleData;
- flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK);
-}
-
-void CScriptVar::copyValue(CScriptVar *val) {
- if (val) {
- copySimpleData(val);
- // remove all current children
- removeAllChildren();
- // copy children of 'val'
- CScriptVarLink *child = val->firstChild;
- while (child) {
- CScriptVar *copied;
- // don't copy the 'parent' object...
- if (child->name != TINYJS_PROTOTYPE_CLASS)
- copied = child->var->deepCopy();
- else
- copied = child->var;
-
- addChild(child->name, copied);
-
- child = child->nextSibling;
- }
- } else {
- setUndefined();
- }
-}
-
-CScriptVar *CScriptVar::deepCopy() {
- CScriptVar *newVar = new CScriptVar();
- newVar->copySimpleData(this);
- // copy children
- CScriptVarLink *child = firstChild;
- while (child) {
- CScriptVar *copied;
- // don't copy the 'parent' object...
- if (child->name != TINYJS_PROTOTYPE_CLASS)
- copied = child->var->deepCopy();
- else
- copied = child->var;
-
- newVar->addChild(child->name, copied);
- child = child->nextSibling;
- }
- return newVar;
-}
-
-void CScriptVar::trace(string indentStr, const string &name) {
- TRACE("%s'%s' = '%s' %s\n",
- indentStr.c_str(),
- name.c_str(),
- getString().c_str(),
- getFlagsAsString().c_str());
- string indent = indentStr+" ";
- CScriptVarLink *link = firstChild;
- while (link) {
- link->var->trace(indent, link->name);
- link = link->nextSibling;
- }
-}
-
-string CScriptVar::getFlagsAsString() {
- string flagstr = "";
- if (flags&SCRIPTVAR_FUNCTION) flagstr = flagstr + "FUNCTION ";
- if (flags&SCRIPTVAR_OBJECT) flagstr = flagstr + "OBJECT ";
- if (flags&SCRIPTVAR_ARRAY) flagstr = flagstr + "ARRAY ";
- if (flags&SCRIPTVAR_NATIVE) flagstr = flagstr + "NATIVE ";
- if (flags&SCRIPTVAR_DOUBLE) flagstr = flagstr + "DOUBLE ";
- if (flags&SCRIPTVAR_INTEGER) flagstr = flagstr + "INTEGER ";
- if (flags&SCRIPTVAR_STRING) flagstr = flagstr + "STRING ";
- return flagstr;
-}
-
-string CScriptVar::getParsableString() {
- // Numbers can just be put in directly
- if (isNumeric())
- return getString();
- if (isFunction()) {
- ostringstream funcStr;
- funcStr << "function (";
- // get list of parameters
- CScriptVarLink *link = firstChild;
- while (link) {
- funcStr << link->name;
- if (link->nextSibling) funcStr << ",";
- link = link->nextSibling;
- }
- // add function body
- funcStr << ") " << getString();
- return funcStr.str();
- }
- // if it is a string then we quote it
- if (isString())
- return getJSString(getString());
- if (isNull())
- return "null";
- return "undefined";
-}
-
-void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) {
- if (isObject()) {
- string indentedLinePrefix = linePrefix+" ";
- // children - handle with bracketed list
- destination << "{ \n";
- CScriptVarLink *link = firstChild;
- while (link) {
- destination << indentedLinePrefix;
- if (isAlphaNum(link->name))
- destination << link->name;
- else
- destination << getJSString(link->name);
- destination << " : ";
- link->var->getJSON(destination, indentedLinePrefix);
- link = link->nextSibling;
- if (link) {
- destination << ",\n";
- }
- }
- destination << "\n" << linePrefix << "}";
- } else if (isArray()) {
- string indentedLinePrefix = linePrefix+" ";
- destination << "[\n";
- int len = getArrayLength();
- if (len>10000) len=10000; // we don't want to get stuck here!
-
- for (int i=0;igetJSON(destination, indentedLinePrefix);
- if (iref();
- // Add built-in classes
- stringClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
- arrayClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
- objectClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref();
- root->addChild("String", stringClass);
- root->addChild("Array", arrayClass);
- root->addChild("Object", objectClass);
-}
-
-CTinyJS::~CTinyJS() {
- ASSERT(!l);
- scopes.clear();
- stringClass->unref();
- arrayClass->unref();
- objectClass->unref();
- root->unref();
-
-#if DEBUG_MEMORY
- show_allocated();
-#endif
-}
-
-void CTinyJS::trace() {
- root->trace();
-}
-
-void CTinyJS::execute(const string &code) {
- CScriptLex *oldLex = l;
- vector oldScopes = scopes;
- l = new CScriptLex(code);
- scopes.clear();
- scopes.push_back(root);
- try {
- bool execute = true;
- while (l->tk) statement(execute);
- } catch (CScriptException *e) {
- ostringstream msg;
- msg << "Error " << e->text << " at " << l->getPosition();
- delete l;
- l = oldLex;
- throw new CScriptException(msg.str());
- }
- delete l;
- l = oldLex;
- scopes = oldScopes;
-}
-
-CScriptVarLink CTinyJS::evaluateComplex(const string &code) {
- CScriptLex *oldLex = l;
- vector oldScopes = scopes;
-
- l = new CScriptLex(code);
- scopes.clear();
- scopes.push_back(root);
- CScriptVarLink *v = 0;
- try {
- bool execute = true;
- do {
- CLEAN(v);
- v = base(execute);
- if (l->tk!=LEX_EOF) l->match(';');
- } while (l->tk!=LEX_EOF);
- } catch (CScriptException *e) {
- ostringstream msg;
- msg << "Error " << e->text << " at " << l->getPosition();
- delete l;
- l = oldLex;
- throw new CScriptException(msg.str());
- }
- delete l;
- l = oldLex;
- scopes = oldScopes;
-
- if (v) {
- CScriptVarLink r = *v;
- CLEAN(v);
- return r;
- }
- // return undefined...
- return CScriptVarLink(new CScriptVar());
-}
-
-string CTinyJS::evaluate(const string &code) {
- return evaluateComplex(code).var->getString();
-}
-
-void CTinyJS::parseFunctionArguments(CScriptVar *funcVar) {
- l->match('(');
- while (l->tk!=')') {
- funcVar->addChildNoDup(l->tkStr);
- l->match(LEX_ID);
- if (l->tk!=')') l->match(',');
- }
- l->match(')');
-}
-
-void CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata) {
- CScriptLex *oldLex = l;
- l = new CScriptLex(funcDesc);
-
- CScriptVar *base = root;
-
- l->match(LEX_R_FUNCTION);
- string funcName = l->tkStr;
- l->match(LEX_ID);
- /* Check for dots, we might want to do something like function String.substring ... */
- while (l->tk == '.') {
- l->match('.');
- CScriptVarLink *link = base->findChild(funcName);
- // if it doesn't exist, make an object class
- if (!link) link = base->addChild(funcName, new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT));
- base = link->var;
- funcName = l->tkStr;
- l->match(LEX_ID);
- }
-
- CScriptVar *funcVar = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE);
- funcVar->setCallback(ptr, userdata);
- parseFunctionArguments(funcVar);
- delete l;
- l = oldLex;
-
- base->addChild(funcName, funcVar);
-}
-
-CScriptVarLink *CTinyJS::parseFunctionDefinition() {
- // actually parse a function...
- l->match(LEX_R_FUNCTION);
- string funcName = TINYJS_TEMP_NAME;
- /* we can have functions without names */
- if (l->tk==LEX_ID) {
- funcName = l->tkStr;
- l->match(LEX_ID);
- }
- CScriptVarLink *funcVar = new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION), funcName);
- parseFunctionArguments(funcVar->var);
- int funcBegin = l->tokenStart;
- bool noexecute = false;
- block(noexecute);
- funcVar->var->data = l->getSubString(funcBegin);
- return funcVar;
-}
-
-CScriptVarLink *CTinyJS::factor(bool &execute) {
- if (l->tk=='(') {
- l->match('(');
- CScriptVarLink *a = base(execute);
- l->match(')');
- return a;
- }
- if (l->tk==LEX_R_TRUE) {
- l->match(LEX_R_TRUE);
- return new CScriptVarLink(new CScriptVar(1));
- }
- if (l->tk==LEX_R_FALSE) {
- l->match(LEX_R_FALSE);
- return new CScriptVarLink(new CScriptVar(0));
- }
- if (l->tk==LEX_R_NULL) {
- l->match(LEX_R_NULL);
- return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_NULL));
- }
- if (l->tk==LEX_R_UNDEFINED) {
- l->match(LEX_R_UNDEFINED);
- return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_UNDEFINED));
- }
- if (l->tk==LEX_ID) {
- CScriptVarLink *a = execute ? findInScopes(l->tkStr) : new CScriptVarLink(new CScriptVar());
- //printf("0x%08X for %s at %s\n", (unsigned int)a, l->tkStr.c_str(), l->getPosition().c_str());
- /* The parent if we're executing a method call */
- CScriptVar *parent = 0;
-
- if (execute && !a) {
- /* Variable doesn't exist! JavaScript says we should create it
- * (we won't add it here. This is done in the assignment operator)*/
- a = new CScriptVarLink(new CScriptVar(), l->tkStr);
- }
- l->match(LEX_ID);
- while (l->tk=='(' || l->tk=='.' || l->tk=='[') {
- if (l->tk=='(') { // ------------------------------------- Function Call
- if (execute) {
- if (!a->var->isFunction()) {
- string errorMsg = "Expecting '";
- errorMsg = errorMsg + a->name + "' to be a function";
- throw new CScriptException(errorMsg.c_str());
- }
- l->match('(');
- // create a new symbol table entry for execution of this function
- CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION);
- if (parent)
- functionRoot->addChildNoDup("this", parent);
- // grab in all parameters
- CScriptVarLink *v = a->var->firstChild;
- while (v) {
- CScriptVarLink *value = base(execute);
- if (execute) {
- if (value->var->isBasic()) {
- // pass by value
- functionRoot->addChild(v->name, value->var->deepCopy());
- } else {
- // pass by reference
- functionRoot->addChild(v->name, value->var);
- }
- }
- CLEAN(value);
- if (l->tk!=')') l->match(',');
- v = v->nextSibling;
- }
- l->match(')');
- // setup a return variable
- CScriptVarLink *returnVar = NULL;
- // execute function!
- // add the function's execute space to the symbol table so we can recurse
- CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR);
- scopes.push_back(functionRoot);
-
- if (a->var->isNative()) {
- ASSERT(a->var->jsCallback);
- a->var->jsCallback(functionRoot, a->var->jsCallbackUserData);
- } else {
- /* we just want to execute the block, but something could
- * have messed up and left us with the wrong ScriptLex, so
- * we want to be careful here... */
- CScriptException *exception = 0;
- CScriptLex *oldLex = l;
- CScriptLex *newLex = new CScriptLex(a->var->getString());
- l = newLex;
- try {
- block(execute);
- // because return will probably have called this, and set execute to false
- execute = true;
- } catch (CScriptException *e) {
- exception = e;
- }
- delete newLex;
- l = oldLex;
-
- if (exception)
- throw exception;
- }
-
- scopes.pop_back();
- /* get the real return var before we remove it from our function */
- returnVar = new CScriptVarLink(returnVarLink->var);
- functionRoot->removeLink(returnVarLink);
- delete functionRoot;
- if (returnVar)
- a = returnVar;
- else
- a = new CScriptVarLink(new CScriptVar());
- } else {
- // function, but not executing - just parse args and be done
- l->match('(');
- while (l->tk != ')') {
- CScriptVarLink *value = base(execute);
- CLEAN(value);
- if (l->tk!=')') l->match(',');
- }
- l->match(')');
- if (l->tk == '{') {
- block(execute);
- }
- }
- } else if (l->tk == '.') { // ------------------------------------- Record Access
- l->match('.');
- if (execute) {
- const string &name = l->tkStr;
- CScriptVarLink *child = a->var->findChild(name);
- if (!child) child = findInParentClasses(a->var, name);
- if (!child) {
- /* if we haven't found this defined yet, use the built-in
- 'length' properly */
- if (a->var->isArray() && name == "length") {
- int l = a->var->getArrayLength();
- child = new CScriptVarLink(new CScriptVar(l));
- } else if (a->var->isString() && name == "length") {
- int l = a->var->getString().size();
- child = new CScriptVarLink(new CScriptVar(l));
- } else {
- child = a->var->addChild(name);
- }
- }
- parent = a->var;
- a = child;
- }
- l->match(LEX_ID);
- } else if (l->tk == '[') { // ------------------------------------- Array Access
- l->match('[');
- CScriptVarLink *index = expression(execute);
- l->match(']');
- if (execute) {
- CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString());
- parent = a->var;
- a = child;
- }
- CLEAN(index);
- } else ASSERT(0);
- }
- return a;
- }
- if (l->tk==LEX_INT || l->tk==LEX_FLOAT) {
- CScriptVar *a = new CScriptVar(l->tkStr,
- ((l->tk==LEX_INT)?SCRIPTVAR_INTEGER:SCRIPTVAR_DOUBLE));
- l->match(l->tk);
- return new CScriptVarLink(a);
- }
- if (l->tk==LEX_STR) {
- CScriptVar *a = new CScriptVar(l->tkStr, SCRIPTVAR_STRING);
- l->match(LEX_STR);
- return new CScriptVarLink(a);
- }
- if (l->tk=='{') {
- CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT);
- /* JSON-style object definition */
- l->match('{');
- while (l->tk != '}') {
- string id = l->tkStr;
- // we only allow strings or IDs on the left hand side of an initialisation
- if (l->tk==LEX_STR) l->match(LEX_STR);
- else l->match(LEX_ID);
- l->match(':');
- if (execute) {
- CScriptVarLink *a = base(execute);
- contents->addChild(id, a->var);
- CLEAN(a);
- }
- // no need to clean here, as it will definitely be used
- if (l->tk != '}') l->match(',');
- }
-
- l->match('}');
- return new CScriptVarLink(contents);
- }
- if (l->tk=='[') {
- CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY);
- /* JSON-style array */
- l->match('[');
- int idx = 0;
- while (l->tk != ']') {
- if (execute) {
- char idx_str[16]; // big enough for 2^32
- sprintf_s(idx_str, sizeof(idx_str), "%d",idx);
-
- CScriptVarLink *a = base(execute);
- contents->addChild(idx_str, a->var);
- CLEAN(a);
- }
- // no need to clean here, as it will definitely be used
- if (l->tk != ']') l->match(',');
- idx++;
- }
- l->match(']');
- return new CScriptVarLink(contents);
- }
- if (l->tk==LEX_R_FUNCTION) {
- CScriptVarLink *funcVar = parseFunctionDefinition();
- if (funcVar->name != TINYJS_TEMP_NAME)
- TRACE("Functions not defined at statement-level are not meant to have a name");
- return funcVar;
- }
- if (l->tk==LEX_R_NEW) {
- // new -> create a new object
- l->match(LEX_R_NEW);
- const string &className = l->tkStr;
- if (execute) {
- CScriptVarLink *objClass = findInScopes(className);
- if (!objClass) {
- TRACE("%s is not a valid class name", className.c_str());
- return new CScriptVarLink(new CScriptVar());
- }
- l->match(LEX_ID);
- CScriptVar *obj = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT);
- obj->addChild(TINYJS_PROTOTYPE_CLASS, objClass->var);
- if (l->tk == '(') {
- l->match('(');
- l->match(')');
- }
- // TODO: Object constructors
- return new CScriptVarLink(obj);
- } else {
- l->match(LEX_ID);
- if (l->tk == '(') {
- l->match('(');
- l->match(')');
- }
- }
- }
- // Nothing we can do here... just hope it's the end...
- l->match(LEX_EOF);
- return 0;
-}
-
-CScriptVarLink *CTinyJS::unary(bool &execute) {
- CScriptVarLink *a;
- if (l->tk=='!') {
- l->match('!'); // binary not
- a = factor(execute);
- if (execute) {
- CScriptVar zero(0);
- CScriptVar *res = a->var->mathsOp(&zero, LEX_EQUAL);
- CREATE_LINK(a, res);
- }
- } else
- a = factor(execute);
- return a;
-}
-
-CScriptVarLink *CTinyJS::term(bool &execute) {
- CScriptVarLink *a = unary(execute);
- while (l->tk=='*' || l->tk=='/' || l->tk=='%') {
- int op = l->tk;
- l->match(l->tk);
- CScriptVarLink *b = unary(execute);
- if (execute) {
- CScriptVar *res = a->var->mathsOp(b->var, op);
- CREATE_LINK(a, res);
- }
- CLEAN(b);
- }
- return a;
-}
-
-CScriptVarLink *CTinyJS::expression(bool &execute) {
- bool negate = false;
- if (l->tk=='-') {
- l->match('-');
- negate = true;
- }
- CScriptVarLink *a = term(execute);
- if (negate) {
- CScriptVar zero(0);
- CScriptVar *res = zero.mathsOp(a->var, '-');
- CREATE_LINK(a, res);
- }
-
- while (l->tk=='+' || l->tk=='-' ||
- l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS) {
- int op = l->tk;
- l->match(l->tk);
- if (op==LEX_PLUSPLUS || op==LEX_MINUSMINUS) {
- if (execute) {
- CScriptVar one(1);
- CScriptVar *res = a->var->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-');
- // in-place add/subtract
- a->replaceWith(res);
- }
- } else {
- CScriptVarLink *b = term(execute);
- if (execute) {
- // not in-place, so just replace
- CScriptVar *res = a->var->mathsOp(b->var, op);
- CREATE_LINK(a, res);
- }
- CLEAN(b);
- }
- }
- return a;
-}
-
-CScriptVarLink *CTinyJS::condition(bool &execute) {
- CScriptVarLink *a = expression(execute);
- CScriptVarLink *b;
- while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL ||
- l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL ||
- l->tk==LEX_LEQUAL || l->tk==LEX_GEQUAL ||
- l->tk=='<' || l->tk=='>') {
- int op = l->tk;
- l->match(l->tk);
- b = expression(execute);
- if (execute) {
- CScriptVar *res = a->var->mathsOp(b->var, op);
- CREATE_LINK(a,res);
- }
- CLEAN(b);
- }
- return a;
-}
-
-CScriptVarLink *CTinyJS::logic(bool &execute) {
- CScriptVarLink *a = condition(execute);
- CScriptVarLink *b;
- while (l->tk=='&' || l->tk=='|' || l->tk=='^' || l->tk==LEX_ANDAND || l->tk==LEX_OROR) {
- bool noexecute = false;
- int op = l->tk;
- l->match(l->tk);
- bool shortCircuit = false;
- bool boolean = false;
- // if we have short-circuit ops, then if we know the outcome
- // we don't bother to execute the other op. Even if not
- // we need to tell mathsOp it's an & or |
- if (op==LEX_ANDAND) {
- op = '&';
- shortCircuit = !a->var->getBool();
- boolean = true;
- } else if (op==LEX_OROR) {
- op = '|';
- shortCircuit = a->var->getBool();
- boolean = true;
- }
- b = condition(shortCircuit ? noexecute : execute);
- if (execute && !shortCircuit) {
- if (boolean) {
- CScriptVar *newa = new CScriptVar(a->var->getBool());
- CScriptVar *newb = new CScriptVar(b->var->getBool());
- CREATE_LINK(a, newa);
- CREATE_LINK(b, newb);
- }
- CScriptVar *res = a->var->mathsOp(b->var, op);
- CREATE_LINK(a, res);
- }
- CLEAN(b);
- }
- return a;
-}
-
-CScriptVarLink *CTinyJS::base(bool &execute) {
- CScriptVarLink *lhs = logic(execute);
- if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) {
- /* If we're assigning to this and we don't have a parent,
- * add it to the symbol table root as per JavaScript. */
- if (execute && !lhs->owned) {
- if (lhs->name.length()>0) {
- CScriptVarLink *realLhs = root->addChildNoDup(lhs->name, lhs->var);
- CLEAN(lhs);
- lhs = realLhs;
- } else
- TRACE("Trying to assign to an un-named type\n");
- }
-
- int op = l->tk;
- l->match(l->tk);
- CScriptVarLink *rhs = base(execute);
- if (execute) {
- if (op=='=') {
- lhs->replaceWith(rhs);
- } else if (op==LEX_PLUSEQUAL) {
- CScriptVar *res = lhs->var->mathsOp(rhs->var, '+');
- lhs->replaceWith(res);
- } else if (op==LEX_MINUSEQUAL) {
- CScriptVar *res = lhs->var->mathsOp(rhs->var, '-');
- lhs->replaceWith(res);
- } else ASSERT(0);
- }
- CLEAN(rhs);
- }
- return lhs;
-}
-
-void CTinyJS::block(bool &execute) {
- l->match('{');
- if (execute) {
- while (l->tk && l->tk!='}')
- statement(execute);
- l->match('}');
- } else {
- // fast skip of blocks
- int brackets = 1;
- while (l->tk && brackets) {
- if (l->tk == '{') brackets++;
- if (l->tk == '}') brackets--;
- l->match(l->tk);
- }
- }
-
-}
-
-void CTinyJS::statement(bool &execute) {
- if (l->tk==LEX_ID ||
- l->tk==LEX_INT ||
- l->tk==LEX_FLOAT ||
- l->tk==LEX_STR ||
- l->tk=='-') {
- /* Execute a simple statement that only contains basic arithmetic... */
- CLEAN(base(execute));
- l->match(';');
- } else if (l->tk=='{') {
- /* A block of code */
- block(execute);
- } else if (l->tk==';') {
- /* Empty statement - to allow things like ;;; */
- l->match(';');
- } else if (l->tk==LEX_R_VAR) {
- /* variable creation. TODO - we need a better way of parsing the left
- * hand side. Maybe just have a flag called can_create_var that we
- * set and then we parse as if we're doing a normal equals.*/
- l->match(LEX_R_VAR);
- CScriptVarLink *a = 0;
- if (execute)
- a = scopes.back()->findChildOrCreate(l->tkStr);
- l->match(LEX_ID);
- // now do stuff defined with dots
- while (l->tk == '.') {
- l->match('.');
- if (execute) {
- CScriptVarLink *lastA = a;
- a = lastA->var->findChildOrCreate(l->tkStr);
- }
- l->match(LEX_ID);
- }
- // sort out initialiser
- if (l->tk == '=') {
- l->match('=');
- CScriptVarLink *var = base(execute);
- if (execute)
- a->replaceWith(var);
- CLEAN(var);
- }
- l->match(';');
- } else if (l->tk==LEX_R_IF) {
- l->match(LEX_R_IF);
- l->match('(');
- CScriptVarLink *var = base(execute);
- l->match(')');
- bool cond = execute && var->var->getBool();
- CLEAN(var);
- bool noexecute = false; // because we need to be abl;e to write to it
- statement(cond ? execute : noexecute);
- if (l->tk==LEX_R_ELSE) {
- l->match(LEX_R_ELSE);
- statement(cond ? noexecute : execute);
- }
- } else if (l->tk==LEX_R_WHILE) {
- // We do repetition by pulling out the string representing our statement
- // there's definitely some opportunity for optimisation here
- l->match(LEX_R_WHILE);
- l->match('(');
- int whileCondStart = l->tokenStart;
- bool noexecute = false;
- CScriptVarLink *cond = base(execute);
- bool loopCond = execute && cond->var->getBool();
- CLEAN(cond);
- CScriptLex *whileCond = l->getSubLex(whileCondStart);
- l->match(')');
- int whileBodyStart = l->tokenStart;
- statement(loopCond ? execute : noexecute);
- CScriptLex *whileBody = l->getSubLex(whileBodyStart);
- CScriptLex *oldLex = l;
- int loopCount = TINYJS_LOOP_MAX_ITERATIONS;
- while (loopCond && loopCount-->0) {
- whileCond->reset();
- l = whileCond;
- cond = base(execute);
- loopCond = execute && cond->var->getBool();
- CLEAN(cond);
- if (loopCond) {
- whileBody->reset();
- l = whileBody;
- statement(execute);
- }
- }
- l = oldLex;
- delete whileCond;
- delete whileBody;
-
- if (loopCount<=0) {
- root->trace();
- TRACE("WHILE Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str());
- throw new CScriptException("LOOP_ERROR");
- }
- } else if (l->tk==LEX_R_FOR) {
- l->match(LEX_R_FOR);
- l->match('(');
- statement(execute); // initialisation
- //l->match(';');
- int forCondStart = l->tokenStart;
- bool noexecute = false;
- CScriptVarLink *cond = base(execute); // condition
- bool loopCond = execute && cond->var->getBool();
- CLEAN(cond);
- CScriptLex *forCond = l->getSubLex(forCondStart);
- l->match(';');
- int forIterStart = l->tokenStart;
- CLEAN(base(noexecute)); // iterator
- CScriptLex *forIter = l->getSubLex(forIterStart);
- l->match(')');
- int forBodyStart = l->tokenStart;
- statement(loopCond ? execute : noexecute);
- CScriptLex *forBody = l->getSubLex(forBodyStart);
- CScriptLex *oldLex = l;
- if (loopCond) {
- forIter->reset();
- l = forIter;
- CLEAN(base(execute));
- }
- int loopCount = TINYJS_LOOP_MAX_ITERATIONS;
- while (execute && loopCond && loopCount-->0) {
- forCond->reset();
- l = forCond;
- cond = base(execute);
- loopCond = cond->var->getBool();
- CLEAN(cond);
- if (execute && loopCond) {
- forBody->reset();
- l = forBody;
- statement(execute);
- }
- if (execute && loopCond) {
- forIter->reset();
- l = forIter;
- CLEAN(base(execute));
- }
- }
- l = oldLex;
- delete forCond;
- delete forIter;
- delete forBody;
- if (loopCount<=0) {
- root->trace();
- TRACE("FOR Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str());
- throw new CScriptException("LOOP_ERROR");
- }
- } else if (l->tk==LEX_R_RETURN) {
- l->match(LEX_R_RETURN);
- CScriptVarLink *result = 0;
- if (l->tk != ';')
- result = base(execute);
- if (execute) {
- CScriptVarLink *resultVar = scopes.back()->findChild(TINYJS_RETURN_VAR);
- if (resultVar)
- resultVar->replaceWith(result);
- else
- TRACE("RETURN statement, but not in a function.\n");
- execute = false;
- }
- CLEAN(result);
- l->match(';');
- } else if (l->tk==LEX_R_FUNCTION) {
- CScriptVarLink *funcVar = parseFunctionDefinition();
- if (execute) {
- if (funcVar->name == TINYJS_TEMP_NAME)
- TRACE("Functions defined at statement-level are meant to have a name\n");
- else
- scopes.back()->addChildNoDup(funcVar->name, funcVar->var);
- }
- CLEAN(funcVar);
- } else l->match(LEX_EOF);
-}
-
-/// Get the value of the given variable, or return 0
-const string *CTinyJS::getVariable(const string &path) {
- // traverse path
- size_t prevIdx = 0;
- size_t thisIdx = path.find('.');
- if (thisIdx == string::npos) thisIdx = path.length();
- CScriptVar *var = root;
- while (var && prevIdxfindChild(el);
- var = varl?varl->var:0;
- prevIdx = thisIdx+1;
- thisIdx = path.find('.', prevIdx);
- if (thisIdx == string::npos) thisIdx = path.length();
- }
- // return result
- if (var)
- return &var->getString();
- else
- return 0;
-}
-
-/// Finds a child, looking recursively up the scopes
-CScriptVarLink *CTinyJS::findInScopes(const std::string &childName) {
- for (int s=scopes.size()-1;s>=0;s--) {
- CScriptVarLink *v = scopes[s]->findChild(childName);
- if (v) return v;
- }
- return NULL;
-
-}
-
-/// Look up in any parent classes of the given object
-CScriptVarLink *CTinyJS::findInParentClasses(CScriptVar *object, const std::string &name) {
- // Look for links to actual parent classes
- CScriptVarLink *parentClass = object->findChild(TINYJS_PROTOTYPE_CLASS);
- while (parentClass) {
- CScriptVarLink *implementation = parentClass->var->findChild(name);
- if (implementation) return implementation;
- parentClass = parentClass->var->findChild(TINYJS_PROTOTYPE_CLASS);
- }
- // else fake it for strings and finally objects
- if (object->isString()) {
- CScriptVarLink *implementation = stringClass->findChild(name);
- if (implementation) return implementation;
- }
- if (object->isArray()) {
- CScriptVarLink *implementation = arrayClass->findChild(name);
- if (implementation) return implementation;
- }
- CScriptVarLink *implementation = objectClass->findChild(name);
- if (implementation) return implementation;
-
- return 0;
-}
+/*
+ * TinyJS
+ *
+ * A single-file Javascript-alike engine
+ *
+ * Authored By Gordon Williams
+ *
+ * Copyright (C) 2009 Pur3 Ltd
+ *
+
+ * 42TinyJS
+ *
+ * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine
+ *
+ * Authored / Changed By Armin Diedering
+ *
+ * Copyright (C) 2010-2014 ardisoft
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifdef _DEBUG
+# ifndef _MSC_VER
+# define DEBUG_MEMORY 1
+# endif
+#endif
+#include
+#include
+#include
+
+#include "TinyJS.h"
+
+#ifndef ASSERT
+# define ASSERT(X) assert(X)
+#endif
+
+#ifndef NO_REGEXP
+# if defined HAVE_TR1_REGEX
+# include
+ using namespace std::tr1;
+# elif defined HAVE_BOOST_REGEX
+# include
+ using namespace boost;
+# else
+# include
+# endif
+#else
+# include
+# include
+# include
+#endif
+
+using namespace std;
+
+// -----------------------------------------------------------------------------------
+//////////////////////////////////////////////////////////////////////////
+/// Memory Debug
+//////////////////////////////////////////////////////////////////////////
+
+//#define DEBUG_MEMORY 1
+
+#if DEBUG_MEMORY
+
+vector allocatedVars;
+vector allocatedLinks;
+
+void mark_allocated(CScriptVar *v) {
+ allocatedVars.push_back(v);
+}
+
+void mark_deallocated(CScriptVar *v) {
+ for (size_t i=0;igetRefs());
+ allocatedVars[i]->trace(" ");
+ }
+ for (size_t i=0;igetName().c_str(), allocatedLinks[i]->getVarPtr()->getRefs());
+ allocatedLinks[i]->getVarPtr()->trace(" ");
+ }
+ allocatedVars.clear();
+ allocatedLinks.clear();
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////
+/// Utils
+//////////////////////////////////////////////////////////////////////////
+
+inline bool isWhitespace(char ch) {
+ return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r');
+}
+
+inline bool isNumeric(char ch) {
+ return (ch>='0') && (ch<='9');
+}
+uint32_t isArrayIndex(const string &str) {
+ if(str.size()==0 || !isNumeric(str[0]) || (str.size()>1 && str[0]=='0') ) return -1; // empty or (more as 1 digit and beginning with '0')
+ CNumber idx;
+ const char *endptr;
+ idx.parseInt(str.c_str(), 10, &endptr);
+ if(*endptr || idx>uint32_t(0xFFFFFFFFUL)) return -1;
+ return idx.toUInt32();
+}
+inline bool isHexadecimal(char ch) {
+ return ((ch>='0') && (ch<='9')) || ((ch>='a') && (ch<='f')) || ((ch>='A') && (ch<='F'));
+}
+inline bool isOctal(char ch) {
+ return ((ch>='0') && (ch<='7'));
+}
+inline bool isAlpha(char ch) {
+ return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_' || ch=='$';
+}
+
+bool isIDString(const char *s) {
+ if (!isAlpha(*s))
+ return false;
+ while (*s) {
+ if (!(isAlpha(*s) || isNumeric(*s)))
+ return false;
+ s++;
+ }
+ return true;
+}
+
+void replace(string &str, char textFrom, const char *textTo) {
+ int sLen = strlen(textTo);
+ size_t p = str.find(textFrom);
+ while (p != string::npos) {
+ str = str.substr(0, p) + textTo + str.substr(p+1);
+ p = str.find(textFrom, p+sLen);
+ }
+}
+string int2string(int32_t intData) {
+ ostringstream str;
+ str << intData;
+ return str.str();
+}
+string int2string(uint32_t intData) {
+ ostringstream str;
+ str << intData;
+ return str.str();
+}
+string float2string(const double &floatData) {
+ ostringstream str;
+ str.unsetf(ios::floatfield);
+#if (defined(_MSC_VER) && _MSC_VER >= 1600) || __cplusplus >= 201103L
+ str.precision(numeric_limits::max_digits10);
+#else
+ str.precision(numeric_limits::digits10+2);
+#endif
+ str << floatData;
+ return str.str();
+}
+/// convert the given string into a quoted string suitable for javascript
+string getJSString(const string &str) {
+ char buffer[5] = "\\x00";
+ string nStr; nStr.reserve(str.length());
+ nStr.push_back('\"');
+ for (string::const_iterator i=str.begin();i!=str.end();i++) {
+ const char *replaceWith = 0;
+ switch (*i) {
+ case '\\': replaceWith = "\\\\"; break;
+ case '\n': replaceWith = "\\n"; break;
+ case '\r': replaceWith = "\\r"; break;
+ case '\a': replaceWith = "\\a"; break;
+ case '\b': replaceWith = "\\b"; break;
+ case '\f': replaceWith = "\\f"; break;
+ case '\t': replaceWith = "\\t"; break;
+ case '\v': replaceWith = "\\v"; break;
+ case '"': replaceWith = "\\\""; break;
+ default: {
+ int nCh = ((unsigned char)*i) & 0xff;
+ if(nCh<32 || nCh>127) {
+ static char hex[] = "0123456789ABCDEF";
+ buffer[2] = hex[(nCh>>4)&0x0f];
+ buffer[3] = hex[nCh&0x0f];
+ replaceWith = buffer;
+ };
+ }
+ }
+ if (replaceWith)
+ nStr.append(replaceWith);
+ else
+ nStr.push_back(*i);
+ }
+ nStr.push_back('\"');
+ return nStr;
+}
+
+static inline string getIDString(const string& str) {
+ if(isIDString(str.c_str()) && CScriptToken::isReservedWord(str)==LEX_ID)
+ return str;
+ return getJSString(str);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptException
+//////////////////////////////////////////////////////////////////////////
+
+string CScriptException::toString() {
+ ostringstream msg;
+ msg << ERROR_NAME[errorType] << ": " << message;
+ if(lineNumber >= 0) msg << " at Line:" << lineNumber+1;
+ if(column >=0) msg << " Column:" << column+1;
+ if(fileName.length()) msg << " in " << fileName;
+ return msg.str();
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptLex
+//////////////////////////////////////////////////////////////////////////
+
+CScriptLex::CScriptLex(const char *Code, const string &File, int Line, int Column) : data(Code) {
+ currentFile = File;
+ pos.currentLineStart = pos.tokenStart = data;
+ pos.currentLine = Line;
+ reset(pos);
+}
+
+void CScriptLex::reset(const POS &toPos) { ///< Reset this lex so we can start again
+ dataPos = toPos.tokenStart;
+ tk = last_tk = 0;
+ tkStr = "";
+ pos = toPos;
+ lineBreakBeforeToken = false;
+ currCh = nextCh = 0;
+ getNextCh(); // currCh
+ getNextCh(); // nextCh
+ getNextToken();
+}
+
+void CScriptLex::check(int expected_tk, int alternate_tk/*=-1*/) {
+ if (expected_tk==';' && tk==LEX_EOF) return; // ignore last missing ';'
+ if (tk!=expected_tk && tk!=alternate_tk) {
+ ostringstream errorString;
+ if(expected_tk == LEX_EOF)
+ errorString << "Got unexpected " << CScriptToken::getTokenStr(tk);
+ else
+ errorString << "Got '" << CScriptToken::getTokenStr(tk) << "' expected '" << CScriptToken::getTokenStr(expected_tk) << "'";
+ if(alternate_tk!=-1) errorString << " or '" << CScriptToken::getTokenStr(alternate_tk) << "'";
+ throw new CScriptException(SyntaxError, errorString.str(), currentFile, pos.currentLine, currentColumn());
+ }
+}
+void CScriptLex::match(int expected_tk1, int alternate_tk/*=-1*/) {
+ check(expected_tk1, alternate_tk);
+ int line = pos.currentLine;
+ getNextToken();
+ lineBreakBeforeToken = line != pos.currentLine;
+}
+
+void CScriptLex::getNextCh() {
+ if(currCh == '\n') { // Windows or Linux
+ pos.currentLine++;
+ pos.tokenStart = pos.currentLineStart = dataPos - (nextCh == LEX_EOF ? 0 : 1);
+ }
+ currCh = nextCh;
+ if ( (nextCh = *dataPos) != LEX_EOF ) dataPos++; // stay on EOF
+// if(nextCh == -124) nextCh = '\n'; //
+ if(currCh == '\r') { // Windows or Mac
+ if(nextCh == '\n')
+ getNextCh(); // Windows '\r\n\' --> skip '\r'
+ else
+ currCh = '\n'; // Mac (only '\r') --> convert '\r' to '\n'
+ }
+}
+
+static uint16_t not_allowed_tokens_befor_regexp[] = {LEX_ID, LEX_INT, LEX_FLOAT, LEX_STR, LEX_R_TRUE, LEX_R_FALSE, LEX_R_NULL, ']', ')', '.', LEX_PLUSPLUS, LEX_MINUSMINUS, LEX_EOF};
+void CScriptLex::getNextToken() {
+ while (currCh && isWhitespace(currCh)) getNextCh();
+ // newline comments
+ if (currCh=='/' && nextCh=='/') {
+ while (currCh && currCh!='\n') getNextCh();
+ getNextCh();
+ getNextToken();
+ return;
+ }
+ // block comments
+ if (currCh=='/' && nextCh=='*') {
+ while (currCh && (currCh!='*' || nextCh!='/')) getNextCh();
+ getNextCh();
+ getNextCh();
+ getNextToken();
+ return;
+ }
+ last_tk = tk;
+ tk = LEX_EOF;
+ tkStr.clear();
+ // record beginning of this token
+ pos.tokenStart = dataPos - (nextCh == LEX_EOF ? (currCh == LEX_EOF ? 0 : 1) : 2);
+ // tokens
+ if (isAlpha(currCh)) { // IDs
+ while (isAlpha(currCh) || isNumeric(currCh)) {
+ tkStr += currCh;
+ getNextCh();
+ }
+ tk = CScriptToken::isReservedWord(tkStr);
+#ifdef NO_GENERATORS
+ if(tk == LEX_R_YIELD)
+ throw new CScriptException(Error, "42TinyJS was built without support of generators (yield expression)", currentFile, pos.currentLine, currentColumn());
+#endif
+
+ } else if (isNumeric(currCh) || (currCh=='.' && isNumeric(nextCh))) { // Numbers
+ if(currCh=='.') tkStr+='0';
+ bool isHex = false, isOct=false;
+ if (currCh=='0') {
+ tkStr += currCh; getNextCh();
+ if(isOctal(currCh)) isOct = true;
+ }
+ if (currCh=='x' || currCh=='X') {
+ isHex = true;
+ tkStr += currCh; getNextCh();
+ }
+ tk = LEX_INT;
+ while (isOctal(currCh) || (!isOct && isNumeric(currCh)) || (isHex && isHexadecimal(currCh))) {
+ tkStr += currCh;
+ getNextCh();
+ }
+ if (!isHex && !isOct && currCh=='.') {
+ tk = LEX_FLOAT;
+ tkStr += '.';
+ getNextCh();
+ while (isNumeric(currCh)) {
+ tkStr += currCh;
+ getNextCh();
+ }
+ }
+ // do fancy e-style floating point
+ if (!isHex && !isOct && (currCh=='e' || currCh=='E')) {
+ tk = LEX_FLOAT;
+ tkStr += currCh; getNextCh();
+ if (currCh=='-') { tkStr += currCh; getNextCh(); }
+ while (isNumeric(currCh)) {
+ tkStr += currCh; getNextCh();
+ }
+ }
+ } else if (currCh=='"' || currCh=='\'') { // strings...
+ char endCh = currCh;
+ getNextCh();
+ while (currCh && currCh!=endCh && currCh!='\n') {
+ if (currCh == '\\') {
+ getNextCh();
+ switch (currCh) {
+ case '\n' : break; // ignore newline after '\'
+ case 'n': tkStr += '\n'; break;
+ case 'r': tkStr += '\r'; break;
+ case 'a': tkStr += '\a'; break;
+ case 'b': tkStr += '\b'; break;
+ case 'f': tkStr += '\f'; break;
+ case 't': tkStr += '\t'; break;
+ case 'v': tkStr += '\v'; break;
+ case 'x': { // hex digits
+ getNextCh();
+ if(isHexadecimal(currCh)) {
+ char buf[3]="\0\0";
+ buf[0] = currCh;
+ for(int i=0; i<2 && isHexadecimal(nextCh); i++) {
+ getNextCh(); buf[i] = currCh;
+ }
+ tkStr += (char)strtol(buf, 0, 16);
+ } else
+ throw new CScriptException(SyntaxError, "malformed hexadezimal character escape sequence", currentFile, pos.currentLine, currentColumn());
+ }
+ default: {
+ if(isOctal(currCh)) {
+ char buf[4]="\0\0\0";
+ buf[0] = currCh;
+ for(int i=1; i<3 && isOctal(nextCh); i++) {
+ getNextCh(); buf[i] = currCh;
+ }
+ tkStr += (char)strtol(buf, 0, 8);
+ }
+ else tkStr += currCh;
+ }
+ }
+ } else {
+ tkStr += currCh;
+ }
+ getNextCh();
+ }
+ if(currCh != endCh)
+ throw new CScriptException(SyntaxError, "unterminated string literal", currentFile, pos.currentLine, currentColumn());
+ getNextCh();
+ tk = LEX_STR;
+ } else {
+ // single chars
+ tk = currCh;
+ if (currCh) getNextCh();
+ if (tk=='=' && currCh=='=') { // ==
+ tk = LEX_EQUAL;
+ getNextCh();
+ if (currCh=='=') { // ===
+ tk = LEX_TYPEEQUAL;
+ getNextCh();
+ }
+ } else if (tk=='!' && currCh=='=') { // !=
+ tk = LEX_NEQUAL;
+ getNextCh();
+ if (currCh=='=') { // !==
+ tk = LEX_NTYPEEQUAL;
+ getNextCh();
+ }
+ } else if (tk=='<') {
+ if (currCh=='=') { // <=
+ tk = LEX_LEQUAL;
+ getNextCh();
+ } else if (currCh=='<') { // <<
+ tk = LEX_LSHIFT;
+ getNextCh();
+ if (currCh=='=') { // <<=
+ tk = LEX_LSHIFTEQUAL;
+ getNextCh();
+ }
+ }
+ } else if (tk=='>') {
+ if (currCh=='=') { // >=
+ tk = LEX_GEQUAL;
+ getNextCh();
+ } else if (currCh=='>') { // >>
+ tk = LEX_RSHIFT;
+ getNextCh();
+ if (currCh=='=') { // >>=
+ tk = LEX_RSHIFTEQUAL;
+ getNextCh();
+ } else if (currCh=='>') { // >>>
+ tk = LEX_RSHIFTU;
+ getNextCh();
+ if (currCh=='=') { // >>>=
+ tk = LEX_RSHIFTUEQUAL;
+ getNextCh();
+ }
+ }
+ }
+ } else if (tk=='+') {
+ if (currCh=='=') { // +=
+ tk = LEX_PLUSEQUAL;
+ getNextCh();
+ } else if (currCh=='+') { // ++
+ tk = LEX_PLUSPLUS;
+ getNextCh();
+ }
+ } else if (tk=='-') {
+ if (currCh=='=') { // -=
+ tk = LEX_MINUSEQUAL;
+ getNextCh();
+ } else if (currCh=='-') { // --
+ tk = LEX_MINUSMINUS;
+ getNextCh();
+ }
+ } else if (tk=='&') {
+ if (currCh=='=') { // &=
+ tk = LEX_ANDEQUAL;
+ getNextCh();
+ } else if (currCh=='&') { // &&
+ tk = LEX_ANDAND;
+ getNextCh();
+ }
+ } else if (tk=='|') {
+ if (currCh=='=') { // |=
+ tk = LEX_OREQUAL;
+ getNextCh();
+ } else if (currCh=='|') { // ||
+ tk = LEX_OROR;
+ getNextCh();
+ }
+ } else if (tk=='^' && currCh=='=') {
+ tk = LEX_XOREQUAL;
+ getNextCh();
+ } else if (tk=='*' && currCh=='=') {
+ tk = LEX_ASTERISKEQUAL;
+ getNextCh();
+ } else if (tk=='/') {
+ // check if it's a RegExp-Literal
+ tk = LEX_REGEXP;
+ for(uint16_t *p = not_allowed_tokens_befor_regexp; *p; p++) {
+ if(*p==last_tk) { tk = '/'; break; }
+ }
+ if(tk == LEX_REGEXP) {
+#ifdef NO_REGEXP
+ throw new CScriptException(Error, "42TinyJS was built without support of regular expressions", currentFile, pos.currentLine, currentColumn());
+#endif
+ tkStr = "/";
+ while (currCh && currCh!='/' && currCh!='\n') {
+ if (currCh == '\\' && nextCh == '/') {
+ tkStr.append(1, currCh);
+ getNextCh();
+ }
+ tkStr.append(1, currCh);
+ getNextCh();
+ }
+ if(currCh == '/') {
+#ifndef NO_REGEXP
+ try { regex(tkStr.substr(1), regex_constants::ECMAScript); } catch(regex_error e) {
+ throw new CScriptException(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code()), currentFile, pos.currentLine, currentColumn());
+ }
+#endif /* NO_REGEXP */
+ do {
+ tkStr.append(1, currCh);
+ getNextCh();
+ } while (currCh=='g' || currCh=='i' || currCh=='m' || currCh=='y');
+ } else
+ throw new CScriptException(SyntaxError, "unterminated regular expression literal", currentFile, pos.currentLine, currentColumn());
+ } else if(currCh=='=') {
+ tk = LEX_SLASHEQUAL;
+ getNextCh();
+ }
+ } else if (tk=='%' && currCh=='=') {
+ tk = LEX_PERCENTEQUAL;
+ getNextCh();
+ }
+ }
+ /* This isn't quite right yet */
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptTokenDataForwards
+//////////////////////////////////////////////////////////////////////////
+
+bool CScriptTokenDataForwards::compare_fnc_token_by_name::operator()(const CScriptToken& lhs, const CScriptToken& rhs) const {
+ return lhs.Fnc().name < rhs.Fnc().name;
+}
+bool CScriptTokenDataForwards::checkRedefinition(const string &Str, bool checkVarsInLetScope) {
+ STRING_SET_it it = varNames[LETS].find(Str);
+ if(it!=varNames[LETS].end()) return false;
+ else if(checkVarsInLetScope) {
+ STRING_SET_it it = vars_in_letscope.find(Str);
+ if(it!=vars_in_letscope.end()) return false;
+ }
+ return true;
+}
+
+void CScriptTokenDataForwards::addVars( STRING_VECTOR_t &Vars ) {
+ varNames[VARS].insert(Vars.begin(), Vars.end());
+}
+void CScriptTokenDataForwards::addConsts( STRING_VECTOR_t &Vars ) {
+ varNames[CONSTS].insert(Vars.begin(), Vars.end());
+}
+std::string CScriptTokenDataForwards::addVarsInLetscope( STRING_VECTOR_t &Vars )
+{
+ for(STRING_VECTOR_it it=Vars.begin(); it!=Vars.end(); ++it) {
+ if(!checkRedefinition(*it, false)) return *it;
+ vars_in_letscope.insert(*it);
+ }
+ return "";
+}
+
+std::string CScriptTokenDataForwards::addLets( STRING_VECTOR_t &Lets )
+{
+ for(STRING_VECTOR_it it=Lets.begin(); it!=Lets.end(); ++it) {
+ if(!checkRedefinition(*it, true)) return *it;
+ varNames[LETS].insert(*it);
+ }
+ return "";
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptTokenDataLoop
+//////////////////////////////////////////////////////////////////////////
+
+std::string CScriptTokenDataLoop::getParsableString(const string &IndentString/*=""*/, const string &Indent/*=""*/ ) {
+ static const char *heads[] = {"for each(", "for(", "for(", "for(", "while(", "do "};
+ static const char *ops[] = {" in ", " in ", " of ", "; "};
+ string out = heads[type];
+ if(init.size() && type==FOR)out.append(CScriptToken::getParsableString(init));
+ if(type<=WHILE) out.append(CScriptToken::getParsableString(condition.begin(), condition.end()-(type>=FOR ? 0 : 7)));
+ if(type<=FOR) out.append(ops[type]);
+ if(iter.size()) out.append(CScriptToken::getParsableString(iter));
+ out.append(")");
+ out.append(CScriptToken::getParsableString(body));
+ if(type==DO)out.append(" while(").append(CScriptToken::getParsableString(condition)).append(");");
+ return out;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptTokenDataTry
+//////////////////////////////////////////////////////////////////////////
+
+std::string CScriptTokenDataTry::getParsableString( const string &IndentString/*=""*/, const string &Indent/*=""*/ ) {
+ string out = "try ";
+ string nl = Indent.size() ? "\n"+IndentString : " ";
+
+ out.append(CScriptToken::getParsableString(tryBlock, IndentString, Indent));
+ for(CScriptTokenDataTry::CatchBlock_it catchBlock = catchBlocks.begin(); catchBlock!=catchBlocks.end(); catchBlock++) {
+ out.append(nl).append("catch(").append(catchBlock->indentifiers->getParsableString());
+ if(catchBlock->condition.size()>1) {
+ out.append(" if ").append(CScriptToken::getParsableString(catchBlock->condition.begin()+1, catchBlock->condition.end()));
+ }
+ out.append(") ").append(CScriptToken::getParsableString(catchBlock->block, IndentString, Indent));
+ }
+ if(finallyBlock.size())
+ out.append(nl).append("finally ").append(CScriptToken::getParsableString(finallyBlock, IndentString, Indent));
+ return out;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptTokenDataFnc
+//////////////////////////////////////////////////////////////////////////
+
+string CScriptTokenDataFnc::getArgumentsString()
+{
+ ostringstream destination;
+ destination << "(";
+ if(arguments.size()) {
+ const char *comma = "";
+ for(TOKEN_VECT_it argument=arguments.begin(); argument!=arguments.end(); ++argument, comma=", ") {
+ if(argument->token == LEX_ID)
+ destination << comma << argument->String();
+ else {
+ vector isObject(1, false);
+ for(DESTRUCTURING_VARS_it it=argument->DestructuringVar().vars.begin(); it!=argument->DestructuringVar().vars.end(); ++it) {
+ if(it->second == "}" || it->second == "]") {
+ destination << it->second;
+ isObject.pop_back();
+ } else {
+ destination << comma;
+ if(it->second == "[" || it->second == "{") {
+ comma = "";
+ if(isObject.back() && it->first.length())
+ destination << getIDString(it->first) << ":";
+ destination << it->second;
+ isObject.push_back(it->second == "{");
+ } else {
+ comma = ", ";
+ if(it->second.empty())
+ continue; // skip empty entries
+ if(isObject.back() && it->first!=it->second)
+ destination << getIDString(it->first) << ":";
+ destination << it->second;
+ }
+ }
+ }
+ }
+ }
+ }
+ destination << ") ";
+ return destination.str();
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptTokenDataDestructuringVar
+//////////////////////////////////////////////////////////////////////////
+
+void CScriptTokenDataDestructuringVar::getVarNames(STRING_VECTOR_t Names) {
+ for(DESTRUCTURING_VARS_it it = vars.begin(); it != vars.end(); ++it) {
+ if(it->second.size() && it->second.find_first_of("{[]}") == string::npos)
+ Names.push_back(it->second);
+ }
+}
+
+std::string CScriptTokenDataDestructuringVar::getParsableString()
+{
+ string out;
+ const char *comma = "";
+ vector isObject(1, false);
+ for(DESTRUCTURING_VARS_it it=vars.begin(); it!=vars.end(); ++it) {
+ if(it->second == "}" || it->second == "]") {
+ out.append(it->second);
+ isObject.pop_back();
+ } else {
+ out.append(comma);
+ if(it->second == "[" || it->second == "{") {
+ comma = "";
+ if(isObject.back() && it->first.length())
+ out.append(getIDString(it->first)).append(":");
+ out.append(it->second);
+ isObject.push_back(it->second == "{");
+ } else {
+ comma = ", ";
+ if(it->second.empty())
+ continue; // skip empty entries
+ if(isObject.back() && it->first!=it->second)
+ out.append(getIDString(it->first)).append(":");
+ out.append(it->second);
+ }
+ }
+ }
+ return out;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptTokenDataObjectLiteral
+//////////////////////////////////////////////////////////////////////////
+
+void CScriptTokenDataObjectLiteral::setMode(bool Destructuring) {
+ structuring = !(destructuring = Destructuring);
+ for(vector::iterator it=elements.begin(); it!=elements.end(); ++it) {
+ if(it->value.size() && it->value.front().token == LEX_T_OBJECT_LITERAL) {
+ CScriptTokenDataObjectLiteral& e = it->value.front().Object();
+ if(e.destructuring && e.structuring)
+ e.setMode(Destructuring);
+ }
+ }
+}
+
+string CScriptTokenDataObjectLiteral::getParsableString()
+{
+ string out = type == OBJECT ? "{ " : "[ ";
+ const char *comma = "";
+ for(vector::iterator it=elements.begin(); it!=elements.end(); ++it) {
+ out.append(comma); comma=", ";
+ if(it->value.empty()) continue;
+ if(type == OBJECT)
+ out.append(getIDString(it->id)).append(" : ");
+ out.append(CScriptToken::getParsableString(it->value));
+ }
+ out.append(type == OBJECT ? " }" : " ]");
+ return out;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptToken
+//////////////////////////////////////////////////////////////////////////
+
+typedef struct { int id; const char *str; bool need_space; } token2str_t;
+static token2str_t reserved_words_begin[] ={
+ // reserved words
+ { LEX_R_IF, "if", true },
+ { LEX_R_ELSE, "else", true },
+ { LEX_R_DO, "do", true },
+ { LEX_R_WHILE, "while", true },
+ { LEX_R_FOR, "for", true },
+ { LEX_R_IN, "in", true },
+ { LEX_R_BREAK, "break", true },
+ { LEX_R_CONTINUE, "continue", true },
+ { LEX_R_FUNCTION, "function", true },
+ { LEX_R_RETURN, "return", true },
+ { LEX_R_VAR, "var", true },
+ { LEX_R_LET, "let", true },
+ { LEX_R_CONST, "const", true },
+ { LEX_R_WITH, "with", true },
+ { LEX_R_TRUE, "true", true },
+ { LEX_R_FALSE, "false", true },
+ { LEX_R_NULL, "null", true },
+ { LEX_R_NEW, "new", true },
+ { LEX_R_TRY, "try", true },
+ { LEX_R_CATCH, "catch", true },
+ { LEX_R_FINALLY, "finally", true },
+ { LEX_R_THROW, "throw", true },
+ { LEX_R_TYPEOF, "typeof", true },
+ { LEX_R_VOID, "void", true },
+ { LEX_R_DELETE, "delete", true },
+ { LEX_R_INSTANCEOF, "instanceof", true },
+ { LEX_R_SWITCH, "switch", true },
+ { LEX_R_CASE, "case", true },
+ { LEX_R_DEFAULT, "default", true },
+ { LEX_R_YIELD, "yield", true },
+};
+#define ARRAY_LENGTH(array) (sizeof(array)/sizeof(array[0]))
+#define ARRAY_END(array) (&array[ARRAY_LENGTH(array)])
+static token2str_t *reserved_words_end = ARRAY_END(reserved_words_begin);//&reserved_words_begin[ARRAY_LENGTH(reserved_words_begin)];
+static token2str_t *str2reserved_begin[sizeof(reserved_words_begin)/sizeof(reserved_words_begin[0])];
+static token2str_t **str2reserved_end = &str2reserved_begin[sizeof(str2reserved_begin)/sizeof(str2reserved_begin[0])];
+static token2str_t tokens2str_begin[] = {
+ { LEX_EOF, "EOF", false },
+ { LEX_ID, "ID", true },
+ { LEX_INT, "INT", true },
+ { LEX_FLOAT, "FLOAT", true },
+ { LEX_STR, "STRING", true },
+ { LEX_REGEXP, "REGEXP", true },
+ { LEX_EQUAL, "==", false },
+ { LEX_TYPEEQUAL, "===", false },
+ { LEX_NEQUAL, "!=", false },
+ { LEX_NTYPEEQUAL, "!==", false },
+ { LEX_LEQUAL, "<=", false },
+ { LEX_LSHIFT, "<<", false },
+ { LEX_LSHIFTEQUAL, "<<=", false },
+ { LEX_GEQUAL, ">=", false },
+ { LEX_RSHIFT, ">>", false },
+ { LEX_RSHIFTEQUAL, ">>=", false },
+ { LEX_RSHIFTU, ">>>", false },
+ { LEX_RSHIFTUEQUAL, ">>>=", false },
+ { LEX_PLUSEQUAL, "+=", false },
+ { LEX_MINUSEQUAL, "-=", false },
+ { LEX_PLUSPLUS, "++", false },
+ { LEX_MINUSMINUS, "--", false },
+ { LEX_ANDEQUAL, "&=", false },
+ { LEX_ANDAND, "&&", false },
+ { LEX_OREQUAL, "|=", false },
+ { LEX_OROR, "||", false },
+ { LEX_XOREQUAL, "^=", false },
+ { LEX_ASTERISKEQUAL, "*=", false },
+ { LEX_SLASHEQUAL, "/=", false },
+ { LEX_PERCENTEQUAL, "%=", false },
+ // special tokens
+ { LEX_T_OF, "of", true },
+ { LEX_T_FUNCTION_OPERATOR, "function", true },
+ { LEX_T_GET, "get", true },
+ { LEX_T_SET, "set", true },
+ { LEX_T_EXCEPTION_VAR, "LEX_T_EXCEPTION_VAR", false },
+ { LEX_T_SKIP, "LEX_SKIP", false },
+ { LEX_T_DUMMY_LABEL, "LABEL", true },
+ { LEX_T_LABEL, "LABEL", true },
+ { LEX_T_LOOP, "LEX_LOOP", true },
+ { LEX_T_FOR_IN, "LEX_FOR_IN", true },
+ { LEX_T_FORWARD, "LEX_T_FORWARD", false },
+ { LEX_T_OBJECT_LITERAL, "LEX_OBJECT_LITERAL", false },
+ { LEX_T_DESTRUCTURING_VAR, "Destructuring Var", false },
+};
+static token2str_t *tokens2str_end = &tokens2str_begin[sizeof(tokens2str_begin)/sizeof(tokens2str_begin[0])];
+struct token2str_cmp_t {
+ bool operator()(const token2str_t &lhs, const token2str_t &rhs) {
+ return lhs.id < rhs.id;
+ }
+ bool operator()(const token2str_t &lhs, int rhs) {
+ return lhs.id < rhs;
+ }
+ bool operator()(const token2str_t *lhs, const token2str_t *rhs) {
+ return strcmp(lhs->str, rhs->str)<0;
+ }
+ bool operator()(const token2str_t *lhs, const char *rhs) {
+ return strcmp(lhs->str, rhs)<0;
+ }
+};
+static bool tokens2str_sort() {
+// printf("tokens2str_sort called\n");
+ sort(tokens2str_begin, tokens2str_end, token2str_cmp_t());
+ sort(reserved_words_begin, reserved_words_end, token2str_cmp_t());
+ for(unsigned int i=0; icurrentLine()), column(l->currentColumn()), token(l->tk), intData(0)
+{
+ if(token == LEX_INT || LEX_TOKEN_DATA_FLOAT(token)) {
+ CNumber number(l->tkStr);
+ if(number.isInfinity())
+ token=LEX_ID, (tokenData=new CScriptTokenDataString("Infinity"))->ref();
+ else if(number.isInt32())
+ token=LEX_INT, intData=number.toInt32();
+ else
+ token=LEX_FLOAT, floatData=new double(number.toDouble());
+ } else if(LEX_TOKEN_DATA_STRING(token))
+ (tokenData = new CScriptTokenDataString(l->tkStr))->ref();
+ else if(LEX_TOKEN_DATA_FUNCTION(token))
+ (tokenData = new CScriptTokenDataFnc)->ref();
+ else if (LEX_TOKEN_DATA_LOOP(token))
+ (tokenData = new CScriptTokenDataLoop)->ref();
+ else if (LEX_TOKEN_DATA_TRY(token))
+ (tokenData = new CScriptTokenDataTry)->ref();
+ if(Match>=0)
+ l->match(Match, Alternate);
+ else
+ l->match(l->tk);
+#ifdef _DEBUG
+ token_str = getTokenStr(token);
+#endif
+}
+CScriptToken::CScriptToken(uint16_t Tk, int IntData) : line(0), column(0), token(Tk), intData(0) {
+ if (LEX_TOKEN_DATA_SIMPLE(token))
+ intData = IntData;
+ else if (LEX_TOKEN_DATA_FUNCTION(token))
+ (tokenData = new CScriptTokenDataFnc)->ref();
+ else if (LEX_TOKEN_DATA_DESTRUCTURING_VAR(token))
+ (tokenData = new CScriptTokenDataDestructuringVar)->ref();
+ else if (LEX_TOKEN_DATA_OBJECT_LITERAL(token))
+ (tokenData = new CScriptTokenDataObjectLiteral)->ref();
+ else if (LEX_TOKEN_DATA_LOOP(token))
+ (tokenData = new CScriptTokenDataLoop)->ref();
+ else if (LEX_TOKEN_DATA_TRY(token))
+ (tokenData = new CScriptTokenDataTry)->ref();
+ else if (LEX_TOKEN_DATA_FORWARDER(token))
+ (tokenData = new CScriptTokenDataForwards)->ref();
+ else
+ ASSERT(0);
+#ifdef _DEBUG
+ token_str = getTokenStr(token);
+#endif
+}
+
+CScriptToken::CScriptToken(uint16_t Tk, const string &TkStr) : line(0), column(0), token(Tk), intData(0) {
+ ASSERT(LEX_TOKEN_DATA_STRING(token));
+ (tokenData = new CScriptTokenDataString(TkStr))->ref();
+#ifdef _DEBUG
+ token_str = getTokenStr(token);
+#endif
+}
+
+CScriptToken &CScriptToken::operator =(const CScriptToken &Copy)
+{
+ if(this == &Copy) return *this;
+ clear();
+#ifdef _DEBUG
+ token_str = Copy.token_str;
+#endif
+ line = Copy.line;
+ column = Copy.column;
+ token = Copy.token;
+ if(LEX_TOKEN_DATA_FLOAT(token))
+ floatData = new double(*Copy.floatData);
+ else if(!LEX_TOKEN_DATA_SIMPLE(token))
+ (tokenData = Copy.tokenData)->ref();
+ else
+ intData = Copy.intData;
+ return *this;
+}
+string CScriptToken::getParsableString(TOKEN_VECT &Tokens, const string &IndentString, const string &Indent) {
+ return getParsableString(Tokens.begin(), Tokens.end(), IndentString, Indent);
+}
+string CScriptToken::getParsableString(TOKEN_VECT_it Begin, TOKEN_VECT_it End, const string &IndentString, const string &Indent) {
+ ostringstream destination;
+ string nl = Indent.size() ? "\n" : " ";
+ string my_indentString = IndentString;
+ bool add_nl=false, block_start=false, need_space=false;
+ int skip_collon = 0;
+
+ for(TOKEN_VECT_it it=Begin; it != End; ++it) {
+ string OutString;
+ if(add_nl) OutString.append(nl).append(my_indentString);
+ bool old_block_start = block_start;
+ bool old_need_space = need_space;
+ add_nl = block_start = need_space =false;
+ if(it->token == LEX_STR)
+ OutString.append(getJSString(it->String())), need_space=true;
+ else if(LEX_TOKEN_DATA_STRING(it->token))
+ OutString.append(it->String()), need_space=true;
+ else if(LEX_TOKEN_DATA_FLOAT(it->token))
+ OutString.append(CNumber(it->Float()).toString()), need_space=true;
+ else if(it->token == LEX_INT)
+ OutString.append(CNumber(it->Int()).toString()), need_space=true;
+ else if(LEX_TOKEN_DATA_FUNCTION(it->token)) {
+ OutString.append("function ");
+ if(it->Fnc().name.size() )
+ OutString.append(it->Fnc().name);
+ OutString.append(it->Fnc().getArgumentsString());
+ OutString.append(getParsableString(it->Fnc().body, my_indentString, Indent));
+ if(it->Fnc().body.front().token != '{') {
+ OutString.append(";");
+ }
+ } else if(LEX_TOKEN_DATA_LOOP(it->token)) {
+ OutString.append(it->Loop().getParsableString(my_indentString, Indent));
+ } else if(LEX_TOKEN_DATA_TRY(it->token)) {
+ OutString.append(it->Try().getParsableString(my_indentString, Indent));
+ } else if(LEX_TOKEN_DATA_DESTRUCTURING_VAR(it->token)) {
+ OutString.append(it->DestructuringVar().getParsableString());
+ } else if(LEX_TOKEN_DATA_OBJECT_LITERAL(it->token)) {
+ OutString.append(it->Object().getParsableString());
+ } else if(it->token == '{') {
+ OutString.append("{");
+ my_indentString.append(Indent);
+ add_nl = block_start = true;
+ } else if(it->token == '}') {
+ my_indentString.resize(my_indentString.size() - min(my_indentString.size(),Indent.size()));
+ if(old_block_start)
+ OutString = "}";
+ else
+ OutString = nl + my_indentString + "}";
+ add_nl = true;
+ } else if(it->token == LEX_T_SKIP) {
+ // ignore SKIP-Token
+ } else if(it->token == LEX_T_FORWARD) {
+ // ignore Forwarder-Token
+ } else if(it->token == LEX_R_FOR) {
+ OutString.append(CScriptToken::getTokenStr(it->token));
+ skip_collon=2;
+ } else {
+ OutString.append(CScriptToken::getTokenStr(it->token,&need_space));
+ if(it->token==';') {
+ if(skip_collon) { --skip_collon; }
+ else add_nl=true;
+ }
+ }
+ if(need_space && old_need_space) destination << " ";
+ destination << OutString;
+ }
+ return destination.str();
+
+}
+
+void CScriptToken::clear()
+{
+ if(LEX_TOKEN_DATA_FLOAT(token))
+ delete floatData;
+ else if(!LEX_TOKEN_DATA_SIMPLE(token))
+ tokenData->unref();
+ token = 0;
+}
+string CScriptToken::getTokenStr( int token, bool *need_space/*=0*/ )
+{
+ if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort();
+ token2str_t *found = lower_bound(reserved_words_begin, reserved_words_end, token, token2str_cmp_t());
+ if(found != reserved_words_end && found->id==token) {
+ if(need_space) *need_space=found->need_space;
+ return found->str;
+ }
+ found = lower_bound(tokens2str_begin, tokens2str_end, token, token2str_cmp_t());
+ if(found != tokens2str_end && found->id==token) {
+ if(need_space) *need_space=found->need_space;
+ return found->str;
+ }
+ if(need_space) *need_space=false;
+
+ if (token>32 && token<128) {
+ char buf[2] = " ";
+ buf[0] = (char)token;
+ return buf;
+ }
+
+ ostringstream msg;
+ msg << "?[" << token << "]";
+ return msg.str();
+}
+const char *CScriptToken::isReservedWord(int Token) {
+ if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort();
+ token2str_t *found = lower_bound(reserved_words_begin, reserved_words_end, Token, token2str_cmp_t());
+ if(found != reserved_words_end && found->id==Token) {
+ return found->str;
+ }
+ return 0;
+}
+int CScriptToken::isReservedWord(const string &Str) {
+ const char *str = Str.c_str();
+ if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort();
+ token2str_t **found = lower_bound(str2reserved_begin, str2reserved_end, str, token2str_cmp_t());
+ if(found != str2reserved_end && strcmp((*found)->str, str)==0) {
+ return (*found)->id;
+ }
+ return LEX_ID;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptTokenizer
+//////////////////////////////////////////////////////////////////////////
+
+CScriptTokenizer::CScriptTokenizer() : l(0), prevPos(&tokens) {
+}
+CScriptTokenizer::CScriptTokenizer(CScriptLex &Lexer) : l(0), prevPos(&tokens) {
+ tokenizeCode(Lexer);
+}
+CScriptTokenizer::CScriptTokenizer(const char *Code, const string &File, int Line, int Column) : l(0), prevPos(&tokens) {
+ CScriptLex lexer(Code, File, Line, Column);
+ tokenizeCode(lexer);
+}
+void CScriptTokenizer::tokenizeCode(CScriptLex &Lexer) {
+ try {
+ l=&Lexer;
+ tokens.clear();
+ tokenScopeStack.clear();
+ ScriptTokenState state;
+ pushForwarder(state);
+ if(l->tk == '�') { // special-Token at Start means the code begins not at Statement-Level
+ l->match('�');
+ tokenizeLiteral(state, 0);
+ } else do {
+ tokenizeStatement(state, 0);
+ } while (l->tk!=LEX_EOF);
+ pushToken(state.Tokens, LEX_EOF); // add LEX_EOF-Token
+ removeEmptyForwarder(state);
+// TOKEN_VECT(tokens).swap(tokens);// tokens.shrink_to_fit();
+ tokens.swap(state.Tokens);
+ pushTokenScope(tokens);
+ currentFile = l->currentFile;
+ tk = getToken().token;
+ } catch (...) {
+ l=0;
+ throw;
+ }
+}
+
+void CScriptTokenizer::getNextToken() {
+ prevPos = tokenScopeStack.back();
+ if(getToken().token == LEX_EOF)
+ return;
+ ScriptTokenPosition &_TokenPos = tokenScopeStack.back();
+ _TokenPos.pos++;
+ if(_TokenPos.pos == _TokenPos.tokens->end())
+ tokenScopeStack.pop_back();
+// ScriptTokenPosition &TokenPos = tokenScopeStack.back();
+ tk = getToken().token;
+}
+
+
+
+void CScriptTokenizer::match(int ExpectedToken, int AlternateToken/*=-1*/) {
+ if(check(ExpectedToken, AlternateToken))
+ getNextToken();
+}
+bool CScriptTokenizer::check(int ExpectedToken, int AlternateToken/*=-1*/) {
+ int currentToken = getToken().token;
+ if (ExpectedToken==';' && (currentToken==LEX_EOF || currentToken=='}')) return false; // ignore last missing ';'
+ if (currentToken!=ExpectedToken && currentToken!=AlternateToken) {
+ ostringstream errorString;
+ if(ExpectedToken == LEX_EOF)
+ errorString << "Got unexpected " << CScriptToken::getTokenStr(currentToken);
+ else {
+ errorString << "Got '" << CScriptToken::getTokenStr(currentToken) << "' expected '" << CScriptToken::getTokenStr(ExpectedToken) << "'";
+ if(AlternateToken!=-1) errorString << " or '" << CScriptToken::getTokenStr(AlternateToken) << "'";
+ }
+ throw new CScriptException(SyntaxError, errorString.str(), currentFile, currentLine(), currentColumn());
+ }
+ return true;
+}
+void CScriptTokenizer::pushTokenScope(TOKEN_VECT &Tokens) {
+ tokenScopeStack.push_back(ScriptTokenPosition(&Tokens));
+ tk = getToken().token;
+}
+
+void CScriptTokenizer::setPos(ScriptTokenPosition &TokenPos) {
+ ASSERT( TokenPos.tokens == tokenScopeStack.back().tokens);
+ tokenScopeStack.back().pos = TokenPos.pos;
+ tk = getToken().token;
+}
+void CScriptTokenizer::skip(int Tokens) {
+ ASSERT(tokenScopeStack.back().tokens->end()-tokenScopeStack.back().pos-Tokens>=0);
+ tokenScopeStack.back().pos+=Tokens-1;
+ getNextToken();
+}
+
+static inline void setTokenSkip(CScriptTokenizer::ScriptTokenState &State) {
+ int tokenBeginIdx = State.Marks.back();
+ State.Marks.pop_back();
+ State.Tokens[tokenBeginIdx].Int() = State.Tokens.size()-tokenBeginIdx;
+}
+
+enum {
+ TOKENIZE_FLAGS_canLabel = 1<<0,
+ TOKENIZE_FLAGS_canBreak = 1<<1,
+ TOKENIZE_FLAGS_canContinue = 1<<2,
+ TOKENIZE_FLAGS_canReturn = 1<<3,
+ TOKENIZE_FLAGS_canYield = 1<<4,
+ TOKENIZE_FLAGS_asStatement = 1<<5,
+ TOKENIZE_FLAGS_noIn = 1<<6,
+ TOKENIZE_FLAGS_isAccessor = 1<<7,
+ TOKENIZE_FLAGS_callForNew = 1<<8,
+ TOKENIZE_FLAGS_noBlockStart = 1<<9,
+ TOKENIZE_FLAGS_nestedObject = 1<<10,
+};
+void CScriptTokenizer::tokenizeTry(ScriptTokenState &State, int Flags) {
+ l->match(LEX_R_TRY);
+ CScriptToken TryToken(LEX_T_TRY);
+ CScriptTokenDataTry &TryData = TryToken.Try();
+ pushToken(State.Tokens, TryToken);
+
+ TOKEN_VECT mainTokens;
+ State.Tokens.swap(mainTokens);
+
+ // try-block
+ tokenizeBlock(State, Flags);
+ State.Tokens.swap(TryData.tryBlock);
+
+ // catch-blocks
+ l->check(LEX_R_CATCH, LEX_R_FINALLY);
+ bool unconditionalCatch = false;
+ while(l->tk == LEX_R_CATCH) {
+ if(unconditionalCatch) throw new CScriptException(SyntaxError, "catch after unconditional catch", l->currentFile, l->currentLine(), l->currentColumn());
+
+ // vars & condition
+ l->match(LEX_R_CATCH);
+ l->match('(');
+ TryData.catchBlocks.resize(TryData.catchBlocks.size()+1);
+ CScriptTokenDataTry::CatchBlock &catchBlock = TryData.catchBlocks.back();
+ pushForwarder(State, true);
+ STRING_VECTOR_t vars;
+ catchBlock.indentifiers = tokenizeVarIdentifier(&vars).DestructuringVar();
+ State.Forwarders.back()->addLets(vars);
+ if(l->tk == LEX_R_IF) {
+ l->match(LEX_R_IF);
+ tokenizeExpression(State, Flags);
+ } else
+ unconditionalCatch = true;
+ State.Tokens.swap(catchBlock.condition);
+ l->match(')');
+
+ // catch-block
+ tokenizeBlock(State, Flags | TOKENIZE_FLAGS_noBlockStart);
+ State.Tokens.swap(catchBlock.block);
+ State.Forwarders.pop_back();
+ }
+ // finally-block
+ if(l->tk == LEX_R_FINALLY) {
+ l->match(LEX_R_FINALLY);
+ tokenizeBlock(State, Flags);
+ State.Tokens.swap(TryData.finallyBlock);
+ }
+ State.Tokens.swap(mainTokens);
+}
+void CScriptTokenizer::tokenizeSwitch(ScriptTokenState &State, int Flags) {
+
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx
+ pushToken(State.Tokens, '(');
+ tokenizeExpression(State, Flags);
+ pushToken(State.Tokens, ')');
+
+ State.Marks.push_back(pushToken(State.Tokens, '{')); // push Token & push blockBeginIdx
+ pushForwarder(State);
+
+
+ vector::size_type MarksSize = State.Marks.size();
+ Flags |= TOKENIZE_FLAGS_canBreak;
+ for(bool hasDefault=false;;) {
+ if( l->tk == LEX_R_CASE || l->tk == LEX_R_DEFAULT) {
+ if(l->tk == LEX_R_CASE) {
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push caseBeginIdx
+ State.Marks.push_back(pushToken(State.Tokens,CScriptToken(LEX_T_SKIP))); // skipper to skip case-expression
+ tokenizeExpression(State, Flags);
+ setTokenSkip(State);
+ } else { // default
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push caseBeginIdx
+ if(hasDefault) throw new CScriptException(SyntaxError, "more than one switch default", l->currentFile, l->currentLine(), l->currentColumn());
+ hasDefault = true;
+ }
+
+ State.Marks.push_back(pushToken(State.Tokens, ':'));
+ while(l->tk != '}' && l->tk != LEX_R_CASE && l->tk != LEX_R_DEFAULT && l->tk != LEX_EOF )
+ tokenizeStatement(State, Flags);
+ setTokenSkip(State);
+ } else if(l->tk == '}')
+ break;
+ else
+ throw new CScriptException(SyntaxError, "invalid switch statement", l->currentFile, l->currentLine(), l->currentColumn());
+ }
+ while(MarksSize < State.Marks.size()) setTokenSkip(State);
+ removeEmptyForwarder(State); // remove Forwarder if empty
+ pushToken(State.Tokens, '}');
+ setTokenSkip(State); // switch-block
+ setTokenSkip(State); // switch-statement
+}
+void CScriptTokenizer::tokenizeWith(ScriptTokenState &State, int Flags) {
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx
+
+ pushToken(State.Tokens, '(');
+ tokenizeExpression(State, Flags);
+ pushToken(State.Tokens, ')');
+ tokenizeStatementNoLet(State, Flags);
+
+ setTokenSkip(State);
+}
+
+static inline uint32_t GetLoopLabels(CScriptTokenizer::ScriptTokenState &State, CScriptTokenDataLoop &LoopData) {
+ uint32_t label_count = 0;
+ if(State.Tokens.size()>=2) {
+ for(TOKEN_VECT::reverse_iterator it = State.Tokens.rbegin(); it!=State.Tokens.rend(); ++it) {
+ if(it->token == ':' && (++it)->token == LEX_T_LABEL) {
+ ++label_count;
+ LoopData.labels.push_back(it->String());
+ State.LoopLabels.push_back(it->String());
+ it->token = LEX_T_DUMMY_LABEL;
+ } else
+ break;
+ }
+ }
+ return label_count;
+}
+static inline void PopLoopLabels(uint32_t label_count, STRING_VECTOR_t &LoopLabels) {
+ ASSERT(label_count <= LoopLabels.size());
+ LoopLabels.resize(LoopLabels.size()-label_count);
+}
+void CScriptTokenizer::tokenizeWhileAndDo(ScriptTokenState &State, int Flags) {
+
+ bool do_while = l->tk==LEX_R_DO;
+
+ CScriptToken LoopToken(LEX_T_LOOP);
+ CScriptTokenDataLoop &LoopData = LoopToken.Loop();
+ LoopData.type = do_while ? CScriptTokenDataLoop::DO : CScriptTokenDataLoop::WHILE;
+
+ // get loop-labels
+ uint32_t label_count = GetLoopLabels(State, LoopData);
+
+ l->match(l->tk); // match while or do
+
+ pushToken(State.Tokens, LoopToken);
+
+ TOKEN_VECT mainTokens;
+ State.Tokens.swap(mainTokens);
+
+ if(!do_while) {
+ l->match('(');
+ tokenizeExpression(State, Flags);
+ State.Tokens.swap(LoopData.condition);
+ l->match(')');
+ }
+ tokenizeStatementNoLet(State, Flags | TOKENIZE_FLAGS_canBreak | TOKENIZE_FLAGS_canContinue);
+ State.Tokens.swap(LoopData.body);
+ if(do_while) {
+ l->match(LEX_R_WHILE);
+ l->match('(');
+ tokenizeExpression(State, Flags);
+ State.Tokens.swap(LoopData.condition);
+ l->match(')');
+ l->match(';');
+ }
+ State.Tokens.swap(mainTokens);
+ PopLoopLabels(label_count, State.LoopLabels);
+}
+
+void CScriptTokenizer::tokenizeIf(ScriptTokenState &State, int Flags) {
+
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx
+
+ pushToken(State.Tokens, '(');
+ tokenizeExpression(State, Flags);
+ pushToken(State.Tokens, ')');
+ State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx
+ tokenizeStatementNoLet(State, Flags);
+
+ setTokenSkip(State);
+
+ if(l->tk == LEX_R_ELSE) {
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx
+ tokenizeStatementNoLet(State, Flags);
+ setTokenSkip(State);
+ }
+
+ setTokenSkip(State);
+}
+
+void CScriptTokenizer::tokenizeFor(ScriptTokenState &State, int Flags) {
+ bool for_in=false, for_of=false, for_each_in=false;
+ CScriptToken LoopToken(LEX_T_LOOP);
+ CScriptTokenDataLoop &LoopData = LoopToken.Loop();
+
+ // get loop-labels
+ uint32_t label_count = GetLoopLabels(State, LoopData);
+
+ l->match(LEX_R_FOR);
+ if((for_in = for_each_in = (l->tk == LEX_ID && l->tkStr == "each")))
+ l->match(LEX_ID); // match "each"
+
+ pushToken(State.Tokens, LoopToken);
+
+ l->match('(');
+ TOKEN_VECT mainTokens;
+ State.Tokens.swap(mainTokens);
+
+ bool haveLetScope = false;
+
+ if(l->tk == LEX_R_VAR || l->tk == LEX_R_LET) {
+ if(l->tk == LEX_R_VAR)
+ tokenizeVarNoConst(State, Flags | TOKENIZE_FLAGS_noIn);
+ else { //if(l->tk == LEX_R_LET)
+ haveLetScope = true;
+ pushForwarder(State, true); // no clean up empty tokenizer
+ tokenizeLet(State, Flags | TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_asStatement);
+ }
+ } else if(l->tk!=';') {
+ tokenizeExpression(State, Flags | TOKENIZE_FLAGS_noIn);
+ }
+ if((for_in=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) {
+ if(!State.LeftHand)
+ throw new CScriptException(ReferenceError, "invalid for/in left-hand side", l->currentFile, l->currentLine(), l->currentColumn());
+ if(l->tk==LEX_ID && l->tkStr=="of") l->tk = LEX_T_OF; // fake token
+ if((for_of = (!for_each_in && l->tk==LEX_T_OF))) {
+ l->match(LEX_T_OF);
+ LoopData.type = CScriptTokenDataLoop::FOR_OF;
+ } else {
+ l->match(LEX_R_IN);
+ LoopData.type = for_each_in ? CScriptTokenDataLoop::FOR_EACH : CScriptTokenDataLoop::FOR_IN;
+ }
+ State.Tokens.swap(LoopData.condition);
+
+ if(LoopData.condition.front().token == LEX_T_FORWARD) {
+ LoopData.init.push_back(LoopData.condition.front());
+ LoopData.condition.erase(LoopData.condition.begin());
+ }
+ mainTokens.back().token = LEX_T_FOR_IN;
+ } else {
+ l->check(';'); // no automatic ;-injection
+ pushToken(State.Tokens, ';');
+ State.Tokens.swap(LoopData.init);
+ if(l->tk != ';') tokenizeExpression(State, Flags);
+ l->check(';'); // no automatic ;-injection
+ l->match(';'); // no automatic ;-injection
+ State.Tokens.swap(LoopData.condition);
+ }
+
+ if(for_in || l->tk != ')') tokenizeExpression(State, Flags);
+ l->match(')');
+ State.Tokens.swap(LoopData.iter);
+ Flags = (Flags & (TOKENIZE_FLAGS_canReturn | TOKENIZE_FLAGS_canYield)) | TOKENIZE_FLAGS_canBreak | TOKENIZE_FLAGS_canContinue;
+ if(haveLetScope) Flags |= TOKENIZE_FLAGS_noBlockStart;
+ tokenizeStatementNoLet(State, Flags);
+ if(haveLetScope) State.Forwarders.pop_back();
+
+ State.Tokens.swap(LoopData.body);
+ State.Tokens.swap(mainTokens);
+ if(for_in) {
+ LoopData.condition.push_back('=');
+ LoopData.condition.push_back(LEX_T_EXCEPTION_VAR);
+ LoopData.condition.push_back('.');
+ LoopData.condition.push_back(CScriptToken(LEX_ID, "next"));
+ LoopData.condition.push_back('(');
+ LoopData.condition.push_back(')');
+ LoopData.condition.push_back(';');
+ }
+ PopLoopLabels(label_count, State.LoopLabels);
+}
+
+static void tokenizeVarIdentifierDestructuring( CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, const std::string &Path, STRING_VECTOR_t *VarNames );
+static void tokenizeVarIdentifierDestructuringObject(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, STRING_VECTOR_t *VarNames) {
+ Lexer->match('{');
+ while(Lexer->tk != '}') {
+ CScriptLex::POS prev_pos = Lexer->pos;
+ string Path = Lexer->tkStr;
+ Lexer->match(LEX_ID, LEX_STR);
+ if(Lexer->tk == ':') {
+ Lexer->match(':');
+ tokenizeVarIdentifierDestructuring(Lexer, Vars, Path, VarNames);
+ } else {
+ Lexer->reset(prev_pos);
+ if(VarNames) VarNames->push_back(Lexer->tkStr);
+ Vars.push_back(DESTRUCTURING_VAR_t(Lexer->tkStr, Lexer->tkStr));
+ Lexer->match(LEX_ID);
+ }
+ if (Lexer->tk!='}') Lexer->match(',', '}');
+ }
+ Lexer->match('}');
+}
+static void tokenizeVarIdentifierDestructuringArray(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, STRING_VECTOR_t *VarNames) {
+ int idx = 0;
+ Lexer->match('[');
+ while(Lexer->tk != ']') {
+ if(Lexer->tk == ',')
+ Vars.push_back(DESTRUCTURING_VAR_t("", "")); // empty
+ else
+ tokenizeVarIdentifierDestructuring(Lexer, Vars, int2string(idx), VarNames);
+ ++idx;
+ if (Lexer->tk!=']') Lexer->match(',',']');
+ }
+ Lexer->match(']');
+}
+static void tokenizeVarIdentifierDestructuring(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, const std::string &Path, STRING_VECTOR_t *VarNames ) {
+ if(Lexer->tk == '[') {
+ Vars.push_back(DESTRUCTURING_VAR_t(Path, "[")); // marks array begin
+ tokenizeVarIdentifierDestructuringArray(Lexer, Vars, VarNames);
+ Vars.push_back(DESTRUCTURING_VAR_t("", "]")); // marks array end
+ } else if(Lexer->tk == '{') {
+ Vars.push_back(DESTRUCTURING_VAR_t(Path, "{")); // marks object begin
+ tokenizeVarIdentifierDestructuringObject(Lexer, Vars, VarNames);
+ Vars.push_back(DESTRUCTURING_VAR_t("", "}")); // marks object end
+ } else {
+ if(VarNames) VarNames->push_back(Lexer->tkStr);
+ Vars.push_back(DESTRUCTURING_VAR_t(Path, Lexer->tkStr));
+ Lexer->match(LEX_ID);
+ }
+}
+CScriptToken CScriptTokenizer::tokenizeVarIdentifier( STRING_VECTOR_t *VarNames/*=0*/, bool *NeedAssignment/*=0*/ ) {
+ CScriptToken token(LEX_T_DESTRUCTURING_VAR);
+ if(NeedAssignment) *NeedAssignment=(l->tk == '[' || l->tk=='{');
+ token.column = l->currentColumn();
+ token.line = l->currentLine();
+ tokenizeVarIdentifierDestructuring(l, token.DestructuringVar().vars, "", VarNames);
+ return token;
+}
+
+void CScriptTokenizer::tokenizeFunction(ScriptTokenState &State, int Flags, bool noLetDef/*=false*/) {
+ bool forward = false;
+ bool Statement = (Flags & TOKENIZE_FLAGS_asStatement) != 0;
+ bool Accessor = (Flags & TOKENIZE_FLAGS_isAccessor) != 0;
+ CScriptLex::POS functionPos = l->pos;
+
+ int tk = l->tk;
+ if(Accessor) {
+ tk = State.Tokens.back().String()=="get"?LEX_T_GET:LEX_T_SET;
+ State.Tokens.pop_back();
+ } else {
+ l->match(LEX_R_FUNCTION);
+ if(!Statement) tk = LEX_T_FUNCTION_OPERATOR;
+ }
+ if(tk == LEX_R_FUNCTION) // only forward functions
+ forward = !noLetDef && State.Forwarders.front() == State.Forwarders.back();
+
+ CScriptToken FncToken(tk);
+ CScriptTokenDataFnc &FncData = FncToken.Fnc();
+
+ if(l->tk == LEX_ID || Accessor) {
+ FncData.name = l->tkStr;
+ l->match(LEX_ID, LEX_STR);
+ } else if(Statement)
+ throw new CScriptException(SyntaxError, "Function statement requires a name.", l->currentFile, l->currentLine(), l->currentColumn());
+ l->match('(');
+ while(l->tk != ')') {
+ FncData.arguments.push_back(tokenizeVarIdentifier());
+ if (l->tk!=')') l->match(',',')');
+ }
+ // l->match(')');
+ // to allow regexp at the beginning of a lambda-function fake last token
+ l->tk = '{';
+ l->match('{');
+ FncData.file = l->currentFile;
+ FncData.line = l->currentLine();
+
+ ScriptTokenState functionState;
+ functionState.HaveReturnValue = functionState.FunctionIsGenerator = false;
+ if(l->tk == '{' || tk==LEX_T_GET || tk==LEX_T_SET)
+ tokenizeBlock(functionState, TOKENIZE_FLAGS_canReturn | TOKENIZE_FLAGS_canYield);
+ else {
+ tokenizeExpression(functionState, TOKENIZE_FLAGS_canYield);
+ l->match(';');
+ functionState.HaveReturnValue = true;
+ }
+ if(functionState.HaveReturnValue == true && functionState.FunctionIsGenerator == true)
+ throw new CScriptException(TypeError, "generator function returns a value.", l->currentFile, functionPos.currentLine, functionPos.currentColumn());
+ FncData.isGenerator = functionState.FunctionIsGenerator;
+
+ functionState.Tokens.swap(FncData.body);
+ if(forward) {
+ State.Forwarders.front()->functions.insert(FncToken);
+ FncToken.token = LEX_T_FUNCTION_PLACEHOLDER;
+ }
+ State.Tokens.push_back(FncToken);
+}
+
+void CScriptTokenizer::tokenizeLet(ScriptTokenState &State, int Flags, bool noLetDef/*=false*/) {
+ bool Definition = (Flags & TOKENIZE_FLAGS_asStatement)!=0;
+ bool noIN = (Flags & TOKENIZE_FLAGS_noIn)!=0;
+ bool Statement = Definition & !noIN;
+ bool Expression = !Definition;
+ Flags &= ~(TOKENIZE_FLAGS_asStatement);
+ if(!Definition) noIN=false, Flags &= ~TOKENIZE_FLAGS_noIn;
+
+ bool foundIN = false;
+ bool leftHand = true;
+ int currLine = l->currentLine(), currColumn = l->currentColumn();
+
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx
+
+ if(l->tk == '(' || !Definition) { // no definition needs statement or expression
+ leftHand = false;
+ Expression = true;
+ pushToken(State.Tokens, '(');
+ pushForwarder(State);
+ } else if(noLetDef)
+ throw new CScriptException(SyntaxError, "let declaration not directly within block", l->currentFile, currLine, currColumn);
+ STRING_VECTOR_t vars;
+ for(;;) {
+ bool needAssignment = false;
+ State.Tokens.push_back(tokenizeVarIdentifier(&vars, &needAssignment));
+ if(noIN && (foundIN=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of"))))
+ break;
+ if(needAssignment || l->tk=='=') {
+ leftHand = false;
+ pushToken(State.Tokens, '=');
+ tokenizeAssignment(State, Flags);
+ if(noIN && (foundIN=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of"))))
+ break;
+ }
+ if(l->tk==',') {
+ leftHand = false;
+ pushToken(State.Tokens);
+ }
+ else
+ break;
+ }
+ if(Expression) {
+ string redeclared = State.Forwarders.back()->addLets(vars);
+ if(redeclared.size())
+ throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn);
+ if(!foundIN) {
+ pushToken(State.Tokens, ')');
+ if(Statement) {
+ if(l->tk == '{') // no extra BlockStart by expression
+ tokenizeBlock(State, Flags|=TOKENIZE_FLAGS_noBlockStart);
+ else
+ tokenizeStatementNoLet(State, Flags);
+ } else
+ tokenizeAssignment(State, Flags);
+ }
+ // never remove Forwarder-token here -- popForwarder(Tokens, BlockStart, Marks);
+ State.Forwarders.back()->vars_in_letscope.clear(); // only clear vars_in_letscope
+ State.Marks.pop_back();
+ } else {
+ if(!noIN) pushToken(State.Tokens, ';');
+
+ string redeclared;
+ if(State.Forwarders.size()<=1) {
+ // Currently it is allowed in javascript, to redeclare "let"-declared vars
+ // in root- or function-scopes. In this case, "let" handled like "var"
+ // To prevent redeclaration in root- or function-scopes define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES
+#ifdef PREVENT_REDECLARATION_IN_FUNCTION_SCOPES
+ redeclared = State.Forwarders.front()->addLets(vars);
+#else
+ State.Forwarders.front()->addVars(vars);
+#endif
+ } else
+ redeclared = State.Forwarders.back()->addLets(vars);
+ if(redeclared.size())
+ throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn);
+ }
+ setTokenSkip(State);
+ if(leftHand) State.LeftHand = true;
+}
+
+void CScriptTokenizer::tokenizeVarNoConst( ScriptTokenState &State, int Flags) {
+ l->check(LEX_R_VAR);
+ tokenizeVarAndConst(State, Flags);
+}
+void CScriptTokenizer::tokenizeVarAndConst( ScriptTokenState &State, int Flags) {
+ bool noIN = (Flags & TOKENIZE_FLAGS_noIn)!=0;
+ int currLine = l->currentLine(), currColumn = l->currentColumn();
+
+ bool leftHand = true;
+ int tk = l->tk;
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx
+
+ STRING_VECTOR_t vars;
+ for(;;)
+ {
+ bool needAssignment = false;
+ State.Tokens.push_back(tokenizeVarIdentifier(&vars, &needAssignment));
+ if(noIN && (l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))
+ break;
+ if(needAssignment || l->tk=='=') {
+ leftHand = false;
+ pushToken(State.Tokens, '=');
+ tokenizeAssignment(State, Flags);
+ if(noIN && (l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))
+ break;
+ }
+ if(l->tk==',') {
+ leftHand = false;
+ pushToken(State.Tokens);
+ }
+ else
+ break;
+ }
+ if(!noIN) pushToken(State.Tokens, ';');
+
+ setTokenSkip(State);
+
+ if(tk==LEX_R_VAR)
+ State.Forwarders.front()->addVars(vars);
+ else
+ State.Forwarders.front()->addConsts(vars);
+ string redeclared;
+ if(State.Forwarders.size()>1) // have let-scope
+ redeclared = State.Forwarders.back()->addVarsInLetscope(vars);
+#ifdef PREVENT_REDECLARATION_IN_FUNCTION_SCOPES
+ else
+ redeclared = State.Forwarders.front()->addVarsInLetscope(vars);
+#endif
+ if(redeclared.size())
+ throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn);
+ if(leftHand) State.LeftHand = true;
+}
+
+void CScriptTokenizer::_tokenizeLiteralObject(ScriptTokenState &State, int Flags) {
+ bool forFor = (Flags & TOKENIZE_FLAGS_noIn)!=0;
+ bool nestedObject = (Flags & TOKENIZE_FLAGS_nestedObject) != 0;
+ Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_nestedObject);
+ CScriptToken ObjectToken(LEX_T_OBJECT_LITERAL);
+ CScriptTokenDataObjectLiteral &Objc = ObjectToken.Object();
+
+ Objc.type = CScriptTokenDataObjectLiteral::OBJECT;
+ Objc.destructuring = Objc.structuring = true;
+
+ string msg, msgFile;
+ int msgLine=0, msgColumn=0;
+
+ l->match('{');
+ while (l->tk != '}') {
+ CScriptTokenDataObjectLiteral::ELEMENT element;
+ bool assign = false;
+ if(CScriptToken::isReservedWord(l->tk))
+ l->tk = LEX_ID; // fake reserved-word as member.ID
+ if(l->tk == LEX_ID) {
+ element.id = l->tkStr;
+ CScriptToken Token(l, LEX_ID);
+ if((l->tk==LEX_ID || l->tk==LEX_STR ) && (element.id=="get" || element.id=="set")) {
+ element.id = l->tkStr;
+ element.value.push_back(Token);
+ State.Tokens.swap(element.value);
+ tokenizeFunction(State, Flags|TOKENIZE_FLAGS_isAccessor);
+ State.Tokens.swap(element.value);
+ Objc.destructuring = false;
+ } else {
+ if(Objc.destructuring && (l->tk == ',' || l->tk == '}')) {
+ if(!msg.size()) {
+ Objc.structuring = false;
+ msg.append("Got '").append(CScriptToken::getTokenStr(l->tk)).append("' expected ':'");
+ msgFile = l->currentFile;
+ msgLine = l->currentLine();
+ msgColumn = l->currentColumn();
+ ;
+ }
+ element.value.push_back(Token);
+ } else
+ assign = true;
+ }
+ } else if(l->tk == LEX_INT) {
+ element.id = int2string((int32_t)strtol(l->tkStr.c_str(),0,0));
+ l->match(LEX_INT);
+ assign = true;
+ } else if(l->tk == LEX_FLOAT) {
+ element.id = float2string(strtod(l->tkStr.c_str(),0));
+ l->match(LEX_FLOAT);
+ assign = true;
+ } else if(LEX_TOKEN_DATA_STRING(l->tk) && l->tk != LEX_REGEXP) {
+ element.id = l->tkStr;
+ l->match(l->tk);
+ assign = true;
+ } else
+ l->match(LEX_ID, LEX_STR);
+ if(assign) {
+ l->match(':');
+ int dFlags = Flags | (l->tk == '{' || l->tk == '[') ? TOKENIZE_FLAGS_nestedObject : 0;
+ State.pushLeftHandState();
+ State.Tokens.swap(element.value);
+ tokenizeAssignment(State, dFlags);
+ State.Tokens.swap(element.value);
+ if(Objc.destructuring) Objc.destructuring = State.LeftHand;
+ State.popLeftHandeState();
+ }
+
+ if(!Objc.destructuring && msg.size())
+ throw new CScriptException(SyntaxError, msg, msgFile, msgLine, msgColumn);
+ Objc.elements.push_back(element);
+ if (l->tk != '}') l->match(',', '}');
+ }
+ l->match('}');
+ if(Objc.destructuring && Objc.structuring) {
+ if(nestedObject) {
+ if(l->tk!=',' && l->tk!='}' && l->tk!='=')
+ Objc.destructuring = false;
+ }
+ else
+ Objc.setMode(l->tk=='=' || (forFor && (l->tk==LEX_R_IN ||(l->tk==LEX_ID && l->tkStr=="of"))));
+ } else {
+ if(!Objc.destructuring && msg.size())
+ throw new CScriptException(SyntaxError, msg, msgFile, msgLine, msgColumn);
+ if(!nestedObject) Objc.setMode(Objc.destructuring);
+ }
+
+ if(Objc.destructuring)
+ State.LeftHand = true;
+ State.Tokens.push_back(ObjectToken);
+}
+void CScriptTokenizer::_tokenizeLiteralArray(ScriptTokenState &State, int Flags) {
+ bool forFor = (Flags & TOKENIZE_FLAGS_noIn)!=0;
+ bool nestedObject = (Flags & TOKENIZE_FLAGS_nestedObject) != 0;
+ Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_nestedObject);
+ CScriptToken ObjectToken(LEX_T_OBJECT_LITERAL);
+ CScriptTokenDataObjectLiteral &Objc = ObjectToken.Object();
+
+ Objc.type = CScriptTokenDataObjectLiteral::ARRAY;
+ Objc.destructuring = Objc.structuring = true;
+ int idx = 0;
+
+ l->match('[');
+ while (l->tk != ']') {
+ CScriptTokenDataObjectLiteral::ELEMENT element;
+ element.id = int2string(idx++);
+ if(l->tk != ',') {
+ int dFlags = Flags | (l->tk == '{' || l->tk == '[') ? TOKENIZE_FLAGS_nestedObject : 0;
+ State.pushLeftHandState();
+ State.Tokens.swap(element.value);
+ tokenizeAssignment(State, dFlags);
+ State.Tokens.swap(element.value);
+ if(Objc.destructuring) Objc.destructuring = State.LeftHand;
+ State.popLeftHandeState();
+ }
+ Objc.elements.push_back(element);
+ if (l->tk != ']') l->match(',', ']');
+ }
+ l->match(']');
+ if(Objc.destructuring && Objc.structuring) {
+ if(nestedObject) {
+ if(l->tk!=',' && l->tk!=']' && l->tk!='=')
+ Objc.destructuring = false;
+ }
+ else
+ Objc.setMode(l->tk=='=' || (forFor && (l->tk==LEX_R_IN ||(l->tk==LEX_ID && l->tkStr=="of"))));
+ } else
+ if(!nestedObject) Objc.setMode(Objc.destructuring);
+ if(Objc.destructuring)
+ State.LeftHand = true;
+ State.Tokens.push_back(ObjectToken);
+}
+
+void CScriptTokenizer::tokenizeLiteral(ScriptTokenState &State, int Flags) {
+ State.LeftHand = 0;
+ bool canLabel = Flags & TOKENIZE_FLAGS_canLabel; Flags &= ~TOKENIZE_FLAGS_canLabel;
+ int ObjectLiteralFlags = Flags;
+ Flags &= ~TOKENIZE_FLAGS_noIn;
+ switch(l->tk) {
+ case LEX_ID:
+ {
+ string label = l->tkStr;
+ pushToken(State.Tokens);
+ if(l->tk==':' && canLabel) {
+ if(find(State.Labels.begin(), State.Labels.end(), label) != State.Labels.end())
+ throw new CScriptException(SyntaxError, "dublicate label '"+label+"'", l->currentFile, l->currentLine(), l->currentColumn()-label.size());
+ State.Tokens[State.Tokens.size()-1].token = LEX_T_LABEL; // change LEX_ID to LEX_T_LABEL
+ State.Labels.push_back(label);
+ } else if(label=="this") {
+ if( l->tk == '=' || (l->tk >= LEX_ASSIGNMENTS_BEGIN && l->tk <= LEX_ASSIGNMENTS_END) )
+ throw new CScriptException(SyntaxError, "invalid assignment left-hand side", l->currentFile, l->currentLine(), l->currentColumn()-label.size());
+ if( l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS )
+ throw new CScriptException(SyntaxError, l->tk==LEX_PLUSPLUS?"invalid increment operand":"invalid decrement operand", l->currentFile, l->currentLine(), l->currentColumn()-label.size());
+ } else
+ State.LeftHand = true;
+ }
+ break;
+ case LEX_INT:
+ case LEX_FLOAT:
+ case LEX_STR:
+ case LEX_REGEXP:
+ case LEX_R_TRUE:
+ case LEX_R_FALSE:
+ case LEX_R_NULL:
+ pushToken(State.Tokens);
+ break;
+ case '{':
+ _tokenizeLiteralObject(State, ObjectLiteralFlags);
+ break;
+ case '[':
+ _tokenizeLiteralArray(State, ObjectLiteralFlags);
+ break;
+ case LEX_R_LET: // let as expression
+ tokenizeLet(State, Flags);
+ break;
+ case LEX_R_FUNCTION:
+ tokenizeFunction(State, Flags);
+ break;
+ case LEX_R_NEW:
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx
+ {
+ tokenizeFunctionCall(State, (Flags | TOKENIZE_FLAGS_callForNew) & ~TOKENIZE_FLAGS_noIn);
+ State.LeftHand = 0;
+ }
+ setTokenSkip(State);
+ break;
+#ifndef NO_GENERATORS
+ case LEX_R_YIELD:
+ if( (Flags & TOKENIZE_FLAGS_canYield)==0)
+ throw new CScriptException(SyntaxError, "'yield' expression, but not in a function.", l->currentFile, l->currentLine(), l->currentColumn());
+ pushToken(State.Tokens);
+ if(l->tk != ';' && l->tk != '}' && !l->lineBreakBeforeToken) {
+ tokenizeExpression(State, Flags);
+ }
+ State.FunctionIsGenerator = true;
+ break;
+#endif
+ case '(':
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx
+ tokenizeExpression(State, Flags & ~TOKENIZE_FLAGS_noIn);
+ State.LeftHand = 0;
+ pushToken(State.Tokens, ')');
+ setTokenSkip(State);
+ break;
+ default:
+ l->check(LEX_EOF);
+ }
+}
+void CScriptTokenizer::tokenizeMember(ScriptTokenState &State, int Flags) {
+ while(l->tk == '.' || l->tk == '[') {
+ if(l->tk == '.') {
+ pushToken(State.Tokens);
+ if(CScriptToken::isReservedWord(l->tk))
+ l->tk = LEX_ID; // fake reserved-word as member.ID
+ pushToken(State.Tokens , LEX_ID);
+ } else {
+ State.Marks.push_back(pushToken(State.Tokens));
+ State.pushLeftHandState();
+ tokenizeExpression(State, Flags & ~TOKENIZE_FLAGS_noIn);
+ State.popLeftHandeState();
+ pushToken(State.Tokens, ']');
+ setTokenSkip(State);
+ }
+ State.LeftHand = true;
+ }
+}
+void CScriptTokenizer::tokenizeFunctionCall(ScriptTokenState &State, int Flags) {
+ bool for_new = (Flags & TOKENIZE_FLAGS_callForNew)!=0; Flags &= ~TOKENIZE_FLAGS_callForNew;
+ tokenizeLiteral(State, Flags);
+ tokenizeMember(State, Flags);
+ while(l->tk == '(') {
+ State.LeftHand = false;
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx
+ State.pushLeftHandState();
+ while(l->tk!=')') {
+ tokenizeAssignment(State, Flags & ~TOKENIZE_FLAGS_noIn);
+ if (l->tk!=')') pushToken(State.Tokens, ',', ')');
+ }
+ State.popLeftHandeState();
+ pushToken(State.Tokens);
+ setTokenSkip(State);
+ if(for_new) break;
+ tokenizeMember(State, Flags);
+ }
+}
+
+void CScriptTokenizer::tokenizeSubExpression(ScriptTokenState &State, int Flags) {
+ static int Left2Right_begin[] = {
+ /* Precedence 5 */ '*', '/', '%',
+ /* Precedence 6 */ '+', '-',
+ /* Precedence 7 */ LEX_LSHIFT, LEX_RSHIFT, LEX_RSHIFTU,
+ /* Precedence 8 */ LEX_EQUAL, LEX_NEQUAL, LEX_TYPEEQUAL, LEX_NTYPEEQUAL,
+ /* Precedence 9 */ '<', LEX_LEQUAL, '>', LEX_GEQUAL, LEX_R_IN, LEX_R_INSTANCEOF,
+ /* Precedence 10-12 */ '&', '^', '|',
+ };
+ static int *Left2Right_end = &Left2Right_begin[sizeof(Left2Right_begin)/sizeof(Left2Right_begin[0])];
+ static bool Left2Right_sorted = false;
+ if(!Left2Right_sorted) Left2Right_sorted = (sort(Left2Right_begin, Left2Right_end), true);
+ bool noLeftHand = false;
+ for(;;) {
+ bool right2left_end = false;
+ while(!right2left_end) {
+ switch(l->tk) {
+ case '-':
+ case '+':
+ case '!':
+ case '~':
+ case LEX_R_TYPEOF:
+ case LEX_R_VOID:
+ case LEX_R_DELETE:
+ Flags &= ~TOKENIZE_FLAGS_canLabel;
+ noLeftHand = true;
+ pushToken(State.Tokens); // Precedence 3
+ break;
+ case LEX_PLUSPLUS: // pre-increment
+ case LEX_MINUSMINUS: // pre-decrement
+ {
+ int tk = l->tk;
+ Flags &= ~TOKENIZE_FLAGS_canLabel;
+ noLeftHand = true;
+ pushToken(State.Tokens); // Precedence 4
+ if(l->tk == LEX_ID && l->tkStr == "this")
+ throw new CScriptException(SyntaxError, tk==LEX_PLUSPLUS?"invalid increment operand":"invalid decrement operand", l->currentFile, l->currentLine(), l->currentColumn());
+ }
+ default:
+ right2left_end = true;
+ }
+ }
+ tokenizeFunctionCall(State, Flags);
+
+ if (!l->lineBreakBeforeToken && (l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS)) { // post-in-/de-crement
+ noLeftHand = true;;
+ pushToken(State.Tokens); // Precedence 4
+ }
+ if(Flags&TOKENIZE_FLAGS_noIn && l->tk==LEX_R_IN)
+ break;
+ int *found = lower_bound(Left2Right_begin, Left2Right_end, l->tk);
+ if(found != Left2Right_end && *found == l->tk) {
+ noLeftHand = true;
+ pushToken(State.Tokens); // Precedence 5-14
+ }
+ else
+ break;
+ }
+ if(noLeftHand) State.LeftHand = false;
+}
+
+void CScriptTokenizer::tokenizeLogic(ScriptTokenState &State, int Flags, int op /*= LEX_OROR*/, int op_n /*= LEX_ANDAND*/) {
+ op_n ? tokenizeLogic(State, Flags, op_n, 0) : tokenizeSubExpression(State, Flags);
+ if(l->tk==op) {
+ unsigned int marks_count = State.Marks.size();
+ while(l->tk==op) {
+ State.Marks.push_back(pushToken(State.Tokens));
+ op_n ? tokenizeLogic(State, Flags, op_n, 0) : tokenizeSubExpression(State, Flags);
+ }
+ while(State.Marks.size()>marks_count) setTokenSkip(State);
+ State.LeftHand = false;
+ }
+}
+
+void CScriptTokenizer::tokenizeCondition(ScriptTokenState &State, int Flags) {
+ tokenizeLogic(State, Flags);
+ if(l->tk == '?') {
+ Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_canLabel);
+ State.Marks.push_back(pushToken(State.Tokens));
+ tokenizeAssignment(State, Flags);
+ setTokenSkip(State);
+ State.Marks.push_back(pushToken(State.Tokens, ':'));
+ tokenizeAssignment(State, Flags);
+ setTokenSkip(State);
+ State.LeftHand = false;
+ }
+}
+void CScriptTokenizer::tokenizeAssignment(ScriptTokenState &State, int Flags) {
+ tokenizeCondition(State, Flags);
+ if (l->tk=='=' || (l->tk>=LEX_ASSIGNMENTS_BEGIN && l->tk<=LEX_ASSIGNMENTS_END) ) {
+ if(!State.LeftHand)
+ throw new CScriptException(ReferenceError, "invalid assignment left-hand side", l->currentFile, l->currentLine(), l->currentColumn());
+ pushToken(State.Tokens);
+ tokenizeAssignment(State, Flags);
+ State.LeftHand = false;
+ }
+}
+void CScriptTokenizer::tokenizeExpression(ScriptTokenState &State, int Flags) {
+ tokenizeAssignment(State, Flags);
+ while(l->tk == ',') {
+ pushToken(State.Tokens);
+ tokenizeAssignment(State, Flags);
+ State.LeftHand = false;
+ }
+}
+void CScriptTokenizer::tokenizeBlock(ScriptTokenState &State, int Flags) {
+ bool addBlockStart = (Flags&TOKENIZE_FLAGS_noBlockStart)==0;
+ Flags&=~(TOKENIZE_FLAGS_noBlockStart);
+ State.Marks.push_back(pushToken(State.Tokens, '{')); // push Token & push BeginIdx
+ if(addBlockStart) pushForwarder(State);
+
+ while(l->tk != '}' && l->tk != LEX_EOF)
+ tokenizeStatement(State, Flags);
+ pushToken(State.Tokens, '}');
+
+ if(addBlockStart) removeEmptyForwarder(State); // clean-up BlockStarts
+
+ setTokenSkip(State);
+}
+
+void CScriptTokenizer::tokenizeStatementNoLet(ScriptTokenState &State, int Flags) {
+ if(l->tk == LEX_R_LET)
+ tokenizeLet(State, Flags | TOKENIZE_FLAGS_asStatement, true);
+ else if(l->tk==LEX_R_FUNCTION)
+ tokenizeFunction(State, Flags | TOKENIZE_FLAGS_asStatement, true);
+ else
+ tokenizeStatement(State, Flags);
+}
+void CScriptTokenizer::tokenizeStatement(ScriptTokenState &State, int Flags) {
+ int tk = l->tk;
+ switch(l->tk)
+ {
+ case '{': tokenizeBlock(State, Flags); break;
+ case ';': pushToken(State.Tokens); break;
+ case LEX_R_CONST:
+ case LEX_R_VAR: tokenizeVarAndConst(State, Flags); break;
+ case LEX_R_LET: tokenizeLet(State, Flags | TOKENIZE_FLAGS_asStatement); break;
+ case LEX_R_WITH: tokenizeWith(State, Flags); break;
+ case LEX_R_IF: tokenizeIf(State, Flags); break;
+ case LEX_R_SWITCH: tokenizeSwitch(State, Flags); break;
+ case LEX_R_DO:
+ case LEX_R_WHILE: tokenizeWhileAndDo(State, Flags); break;
+ case LEX_R_FOR: tokenizeFor(State, Flags); break;
+ case LEX_R_FUNCTION: tokenizeFunction(State, Flags | TOKENIZE_FLAGS_asStatement); break;
+ case LEX_R_TRY: tokenizeTry(State, Flags); break;
+ case LEX_R_RETURN:
+ if( (Flags & TOKENIZE_FLAGS_canReturn)==0)
+ throw new CScriptException(SyntaxError, "'return' statement, but not in a function.", l->currentFile, l->currentLine(), l->currentColumn());
+ case LEX_R_THROW:
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx
+ if(l->tk != ';' && l->tk != '}' && !l->lineBreakBeforeToken) {
+ if(tk==LEX_R_RETURN) State.HaveReturnValue = true;
+ tokenizeExpression(State, Flags);
+ }
+ pushToken(State.Tokens, ';'); // push ';'
+ setTokenSkip(State);
+ break;
+ case LEX_R_BREAK:
+ case LEX_R_CONTINUE:
+ {
+ bool isBreak = l->tk == LEX_R_BREAK;
+ State.Marks.push_back(pushToken(State.Tokens)); // push Token
+
+ if(l->tk != ';' && !l->lineBreakBeforeToken) {
+ l->check(LEX_ID);
+ STRING_VECTOR_t &L = isBreak ? State.Labels : State.LoopLabels;
+ if(find(L.begin(), L.end(), l->tkStr) == L.end())
+ throw new CScriptException(SyntaxError, "label '"+l->tkStr+"' not found", l->currentFile, l->currentLine(), l->currentColumn());
+ pushToken(State.Tokens); // push 'Label'
+ } else if((Flags & (isBreak ? TOKENIZE_FLAGS_canBreak : TOKENIZE_FLAGS_canContinue) )==0)
+ throw new CScriptException(SyntaxError,
+ isBreak ? "'break' must be inside loop, switch or labeled statement" : "'continue' must be inside loop",
+ l->currentFile, l->currentLine(), l->currentColumn());
+ pushToken(State.Tokens, ';'); // push ';'
+ setTokenSkip(State);
+ }
+ break;
+ case LEX_ID:
+ {
+ State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx
+ STRING_VECTOR_t::size_type label_count = State.Labels.size();
+ tokenizeExpression(State, Flags | TOKENIZE_FLAGS_canLabel);
+ if(label_count < State.Labels.size() && l->tk == ':') {
+ State.Tokens.erase(State.Tokens.begin()+State.Marks.back()); // remove skip
+ State.Marks.pop_back();
+ pushToken(State.Tokens); // push ':'
+ tokenizeStatement(State, Flags);
+ State.Labels.pop_back();
+ } else {
+ pushToken(State.Tokens, ';');
+ setTokenSkip(State);
+ }
+ }
+ break;
+ case 0:
+ break;
+ default:
+ State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx
+ tokenizeExpression(State, Flags);
+ pushToken(State.Tokens, ';');
+ setTokenSkip(State);
+ break;
+ }
+
+}
+
+int CScriptTokenizer::pushToken(TOKEN_VECT &Tokens, int Match, int Alternate) {
+ if(Match == ';' && l->tk != ';' && (l->lineBreakBeforeToken || l->tk=='}' || l->tk==LEX_EOF))
+ Tokens.push_back(CScriptToken(';')); // inject ';'
+ else
+ Tokens.push_back(CScriptToken(l, Match, Alternate));
+ return Tokens.size()-1;
+}
+int CScriptTokenizer::pushToken(TOKEN_VECT &Tokens, const CScriptToken &Token) {
+ int ret = Tokens.size();
+ Tokens.push_back(Token);
+ return ret;
+}
+void CScriptTokenizer::pushForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, vector &Marks) {
+ Marks.push_back(Tokens.size());
+ CScriptToken token(LEX_T_FORWARD);
+ Tokens.push_back(token);
+ Forwarders.push_back(token.Forwarder());
+}
+void CScriptTokenizer::pushForwarder(ScriptTokenState &State, bool noMarks/*=false*/) {
+ if(!noMarks) State.Marks.push_back(State.Tokens.size());
+ CScriptToken token(LEX_T_FORWARD);
+ State.Tokens.push_back(token);
+ State.Forwarders.push_back(token.Forwarder());
+}
+void CScriptTokenizer::removeEmptyForwarder(ScriptTokenState &State)
+{
+ CScriptTokenDataForwardsPtr &forwarder = State.Forwarders.back();
+ forwarder->vars_in_letscope.clear();
+ if(forwarder->empty())
+ State.Tokens.erase(State.Tokens.begin()+State.Marks.back());
+ State.Forwarders.pop_back();
+ State.Marks.pop_back();
+}
+
+void CScriptTokenizer::removeEmptyForwarder( TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, vector &Marks )
+{
+ CScriptTokenDataForwardsPtr &forwarder = Forwarders.back();
+ forwarder->vars_in_letscope.clear();
+ if(forwarder->empty())
+ Tokens.erase(Tokens.begin()+Marks.back());
+ Forwarders.pop_back();
+ Marks.pop_back();
+}
+void CScriptTokenizer::throwTokenNotExpected() {
+ throw new CScriptException(SyntaxError, "'"+CScriptToken::getTokenStr(l->tk)+"' was not expected", l->currentFile, l->currentLine(), l->currentColumn());
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVar
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVar::CScriptVar(CTinyJS *Context, const CScriptVarPtr &Prototype) {
+ extensible = true;
+ context = Context;
+ memset(temporaryMark, 0, sizeof(temporaryMark));
+ if(context->first) {
+ next = context->first;
+ next->prev = this;
+ } else {
+ next = 0;
+ }
+ context->first = this;
+ prev = 0;
+ refs = 0;
+ if(Prototype)
+ addChild(TINYJS___PROTO___VAR, Prototype, SCRIPTVARLINK_WRITABLE);
+#if DEBUG_MEMORY
+ mark_allocated(this);
+#endif
+}
+CScriptVar::CScriptVar(const CScriptVar &Copy) {
+ extensible = Copy.extensible;
+ context = Copy.context;
+ memset(temporaryMark, 0, sizeof(temporaryMark));
+ if(context->first) {
+ next = context->first;
+ next->prev = this;
+ } else {
+ next = 0;
+ }
+ context->first = this;
+ prev = 0;
+ refs = 0;
+ SCRIPTVAR_CHILDS_cit it;
+ for(it = Copy.Childs.begin(); it!= Copy.Childs.end(); ++it) {
+ addChild((*it)->getName(), (*it)->getVarPtr(), (*it)->getFlags());
+ }
+
+#if DEBUG_MEMORY
+ mark_allocated(this);
+#endif
+}
+CScriptVar::~CScriptVar(void) {
+#if DEBUG_MEMORY
+ mark_deallocated(this);
+#endif
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it)
+ (*it)->setOwner(0);
+ removeAllChildren();
+ if(prev)
+ prev->next = next;
+ else
+ context->first = next;
+ if(next)
+ next->prev = prev;
+}
+
+/// Type
+
+bool CScriptVar::isObject() {return false;}
+bool CScriptVar::isError() {return false;}
+bool CScriptVar::isArray() {return false;}
+bool CScriptVar::isRegExp() {return false;}
+bool CScriptVar::isAccessor() {return false;}
+bool CScriptVar::isNull() {return false;}
+bool CScriptVar::isUndefined() {return false;}
+bool CScriptVar::isNaN() {return false;}
+bool CScriptVar::isString() {return false;}
+bool CScriptVar::isInt() {return false;}
+bool CScriptVar::isBool() {return false;}
+int CScriptVar::isInfinity() { return 0; } ///< +1==POSITIVE_INFINITY, -1==NEGATIVE_INFINITY, 0==is not an InfinityVar
+bool CScriptVar::isDouble() {return false;}
+bool CScriptVar::isRealNumber() {return false;}
+bool CScriptVar::isNumber() {return false;}
+bool CScriptVar::isPrimitive() {return false;}
+bool CScriptVar::isFunction() {return false;}
+bool CScriptVar::isNative() {return false;}
+bool CScriptVar::isBounded() {return false;}
+bool CScriptVar::isIterator() {return false;}
+bool CScriptVar::isGenerator() {return false;}
+
+//////////////////////////////////////////////////////////////////////////
+/// Value
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarPrimitivePtr CScriptVar::getRawPrimitive() {
+ return CScriptVarPrimitivePtr(); // default NULL-Ptr
+}
+CScriptVarPrimitivePtr CScriptVar::toPrimitive() {
+ return toPrimitive_hintNumber();
+}
+
+CScriptVarPrimitivePtr CScriptVar::toPrimitive(CScriptResult &execute) {
+ return toPrimitive_hintNumber(execute);
+}
+
+CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintString(int32_t radix) {
+ CScriptResult execute;
+ CScriptVarPrimitivePtr var = toPrimitive_hintString(execute, radix);
+ execute.cThrow();
+ return var;
+}
+CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintString(CScriptResult &execute, int32_t radix) {
+ if(execute) {
+ if(!isPrimitive()) {
+ CScriptVarPtr ret = callJS_toString(execute, radix);
+ if(execute && !ret->isPrimitive()) {
+ ret = callJS_valueOf(execute);
+ if(execute && !ret->isPrimitive())
+ context->throwError(execute, TypeError, "can't convert to primitive type");
+ }
+ return ret;
+ }
+ return this;
+ }
+ return constScriptVar(Undefined);
+}
+CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintNumber() {
+ CScriptResult execute;
+ CScriptVarPrimitivePtr var = toPrimitive_hintNumber(execute);
+ execute.cThrow();
+ return var;
+}
+CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintNumber(CScriptResult &execute) {
+ if(execute) {
+ if(!isPrimitive()) {
+ CScriptVarPtr ret = callJS_valueOf(execute);
+ if(execute && !ret->isPrimitive()) {
+ ret = callJS_toString(execute);
+ if(execute && !ret->isPrimitive())
+ context->throwError(execute, TypeError, "can't convert to primitive type");
+ }
+ return ret;
+ }
+ return this;
+ }
+ return constScriptVar(Undefined);
+}
+
+CScriptVarPtr CScriptVar::callJS_valueOf(CScriptResult &execute) {
+ if(execute) {
+ CScriptVarPtr FncValueOf = findChildWithPrototypeChain("valueOf").getter(execute);
+ if(FncValueOf && FncValueOf != context->objectPrototype_valueOf) { // custom valueOf in JavaScript
+ if(FncValueOf->isFunction()) { // no Error if toString not callable
+ vector Params;
+ return context->callFunction(execute, FncValueOf, Params, this);
+ }
+ } else
+ return valueOf_CallBack();
+ }
+ return this;
+}
+CScriptVarPtr CScriptVar::valueOf_CallBack() {
+ return this;
+}
+
+CScriptVarPtr CScriptVar::callJS_toString(CScriptResult &execute, int radix/*=0*/) {
+ if(execute) {
+ CScriptVarPtr FncToString = findChildWithPrototypeChain("toString").getter(execute);
+ if(FncToString && FncToString != context->objectPrototype_toString) { // custom valueOf in JavaScript
+ if(FncToString->isFunction()) { // no Error if toString not callable
+ vector Params;
+ Params.push_back(newScriptVar(radix));
+ return context->callFunction(execute, FncToString, Params, this);
+ }
+ } else
+ return toString_CallBack(execute, radix);
+ }
+ return this;
+}
+CScriptVarPtr CScriptVar::toString_CallBack(CScriptResult &execute, int radix/*=0*/) {
+ return this;
+}
+
+CNumber CScriptVar::toNumber() { return toPrimitive_hintNumber()->toNumber_Callback(); };
+CNumber CScriptVar::toNumber(CScriptResult &execute) { return toPrimitive_hintNumber(execute)->toNumber_Callback(); };
+bool CScriptVar::toBoolean() { return true; }
+string CScriptVar::toString(int32_t radix) { return toPrimitive_hintString(radix)->toCString(radix); }
+string CScriptVar::toString(CScriptResult &execute, int32_t radix/*=0*/) { return toPrimitive_hintString(execute, radix)->toCString(radix); }
+
+int CScriptVar::getInt() { return toNumber().toInt32(); }
+double CScriptVar::getDouble() { return toNumber().toDouble(); }
+bool CScriptVar::getBool() { return toBoolean(); }
+string CScriptVar::getString() { return toPrimitive_hintString()->toCString(); }
+
+CScriptTokenDataFnc *CScriptVar::getFunctionData() { return 0; }
+
+CScriptVarPtr CScriptVar::toIterator(int Mode/*=3*/) {
+ CScriptResult execute;
+ CScriptVarPtr var = toIterator(execute, Mode);
+ execute.cThrow();
+ return var;
+}
+CScriptVarPtr CScriptVar::toIterator(CScriptResult &execute, int Mode/*=3*/) {
+ if(!execute) return constScriptVar(Undefined);
+ if(isIterator()) return this;
+ CScriptVarFunctionPtr Generator(findChildWithPrototypeChain("__iterator__").getter(execute));
+ vector args;
+ if(Generator) return context->callFunction(execute, Generator, args, this);
+ return newScriptVarDefaultIterator(context, this, Mode);
+}
+
+string CScriptVar::getParsableString() {
+ uint32_t UniqueID = context->allocUniqueID();
+ bool hasRecursion=false;
+ string ret = getParsableString("", " ", UniqueID, hasRecursion);
+ context->freeUniqueID();
+ return ret;
+}
+
+string CScriptVar::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) {
+ getParsableStringRecursionsCheck();
+ return indentString+toString();
+}
+
+CScriptVarPtr CScriptVar::getNumericVar() { return newScriptVar(toNumber()); }
+
+////// Flags
+
+void CScriptVar::seal() {
+ preventExtensions();
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it)
+ (*it)->setConfigurable(false);
+}
+bool CScriptVar::isSealed() const {
+ if(isExtensible()) return false;
+ for(SCRIPTVAR_CHILDS_cit it = Childs.begin(); it != Childs.end(); ++it)
+ if((*it)->isConfigurable()) return false;
+ return true;
+}
+void CScriptVar::freeze() {
+ preventExtensions();
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it)
+ (*it)->setConfigurable(false), (*it)->setWritable(false);
+}
+bool CScriptVar::isFrozen() const {
+ if(isExtensible()) return false;
+ for(SCRIPTVAR_CHILDS_cit it = Childs.begin(); it != Childs.end(); ++it)
+ if((*it)->isConfigurable() || (*it)->isWritable()) return false;
+ return true;
+}
+
+////// Childs
+
+CScriptVarPtr CScriptVar::getOwnPropertyDescriptor(const string &Name) {
+ CScriptVarLinkPtr child = findChild(Name);
+ if(!child) {
+ CScriptVarStringPtr strVar = getRawPrimitive();
+ uint32_t Idx;
+ if (strVar && (Idx=isArrayIndex(Name))!=uint32_t(-1) && IdxstringLength()) {
+ int Char = strVar->getChar(Idx);
+ CScriptVarPtr ret = newScriptVar(Object);
+ ret->addChild("value", newScriptVar(string(1, (char)Char)));
+ ret->addChild("writable", constScriptVar(false));
+ ret->addChild("enumerable", constScriptVar(true));
+ ret->addChild("configurable", constScriptVar(false));
+ return ret;
+ }
+ }
+
+ if(!child || child->getVarPtr()->isUndefined()) return constScriptVar(Undefined);
+ CScriptVarPtr ret = newScriptVar(Object);
+ if(child->getVarPtr()->isAccessor()) {
+ CScriptVarLinkPtr value = child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR);
+ ret->addChild("get", value ? value->getVarPtr() : constScriptVar(Undefined));
+ value = child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR);
+ ret->addChild("set", value ? value->getVarPtr() : constScriptVar(Undefined));
+ } else {
+ ret->addChild("value", child->getVarPtr()->valueOf_CallBack());
+ ret->addChild("writable", constScriptVar(child->isWritable()));
+ }
+ ret->addChild("enumerable", constScriptVar(child->isEnumerable()));
+ ret->addChild("configurable", constScriptVar(child->isConfigurable()));
+ return ret;
+}
+const char *CScriptVar::defineProperty(const string &Name, CScriptVarPtr Attributes) {
+ CScriptVarPtr attr;
+ CScriptVarLinkPtr child = findChildWithStringChars(Name);
+
+ CScriptVarPtr attr_value = Attributes->findChild("value");
+ CScriptVarPtr attr_writable = Attributes->findChild("writable");
+ CScriptVarPtr attr_get = Attributes->findChild("get");
+ CScriptVarPtr attr_set = Attributes->findChild("set");
+ CScriptVarPtr attr_enumerable = Attributes->findChild("enumerable");
+ CScriptVarPtr attr_configurable = Attributes->findChild("configurable");
+ bool attr_isDataDescriptor = !attr_get && !attr_set;
+ if(!attr_isDataDescriptor && (attr_value || attr_writable)) return "property descriptors must not specify a value or be writable when a getter or setter has been specified";
+ if(attr_isDataDescriptor) {
+ if(attr_get && (!attr_get->isUndefined() || !attr_get->isFunction())) return "property descriptor's getter field is neither undefined nor a function";
+ if(attr_set && (!attr_set->isUndefined() || !attr_set->isFunction())) return "property descriptor's setter field is neither undefined nor a function";
+ }
+ if(!child) {
+ if(!isExtensible()) return "is not extensible";
+ if(attr_isDataDescriptor) {
+ child = addChild(Name, attr_value?attr_value:constScriptVar(Undefined), 0);
+ if(attr_writable) child->setWritable(attr_writable->toBoolean());
+ } else {
+ child = addChild(Name, newScriptVarAccessor(context, attr_get, attr_set), SCRIPTVARLINK_WRITABLE);
+ }
+ } else {
+ if(!child->isConfigurable()) {
+ if(attr_configurable && attr_configurable->toBoolean()) goto cant_redefine;
+ if(attr_enumerable && attr_enumerable->toBoolean() != child->isEnumerable()) goto cant_redefine;
+ if(child->getVarPtr()->isAccessor()) {
+ if(attr_isDataDescriptor) goto cant_redefine;
+ if(attr_get && attr_get != child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR)) goto cant_redefine;
+ if(attr_set && attr_set != child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR)) goto cant_redefine;
+ } else if(!attr_isDataDescriptor) goto cant_redefine;
+ else if(!child->isWritable()) {
+ if(attr_writable && attr_writable->toBoolean()) goto cant_redefine;
+ if(attr_value && !attr_value->mathsOp(child, LEX_EQUAL)->toBoolean()) goto cant_redefine;
+ }
+ }
+ if(attr_isDataDescriptor) {
+ if(child->getVarPtr()->isAccessor()) child->setWritable(false);
+ child->setVarPtr(attr_value?attr_value:constScriptVar(Undefined));
+ if(attr_writable) child->setWritable(attr_writable->toBoolean());
+ } else {
+ if(child->getVarPtr()->isAccessor()) {
+ if(!attr_get) attr_get = child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR);
+ if(!attr_set) attr_set = child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR);
+ }
+ child->setVarPtr(newScriptVarAccessor(context, attr_get, attr_set));
+ child->setWritable(true);
+ }
+ }
+ if(attr_enumerable) child->setEnumerable(attr_enumerable->toBoolean());
+ if(attr_configurable) child->setConfigurable(attr_configurable->toBoolean());
+ return 0;
+cant_redefine:
+ return "can't redefine non-configurable property";
+}
+
+CScriptVarLinkPtr CScriptVar::findChild(const string &childName) {
+ if(Childs.empty()) return 0;
+ SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName);
+ if(it != Childs.end() && (*it)->getName() == childName)
+ return *it;
+ return 0;
+}
+
+CScriptVarLinkWorkPtr CScriptVar::findChildWithStringChars(const string &childName) {
+ CScriptVarLinkWorkPtr child = findChild(childName);
+ if(child) return child;
+ CScriptVarStringPtr strVar = getRawPrimitive();
+ uint32_t Idx;
+ if (strVar && (Idx=isArrayIndex(childName))!=uint32_t(-1)) {
+ if (IdxstringLength()) {
+ int Char = strVar->getChar(Idx);
+ child(newScriptVar(string(1, (char)Char)), childName, SCRIPTVARLINK_ENUMERABLE);
+ } else {
+ child(constScriptVar(Undefined), childName, SCRIPTVARLINK_ENUMERABLE);
+ }
+ child.setReferencedOwner(this); // fake referenced Owner
+ return child;
+ }
+ return 0;
+}
+
+CScriptVarLinkPtr CScriptVar::findChildInPrototypeChain(const string &childName) {
+ unsigned int uniqueID = context->allocUniqueID();
+ // Look for links to actual parent classes
+ CScriptVarPtr object = this;
+ CScriptVarLinkPtr __proto__;
+ while( object->getTemporaryMark() != uniqueID && (__proto__ = object->findChild(TINYJS___PROTO___VAR)) ) {
+ CScriptVarLinkPtr implementation = __proto__->getVarPtr()->findChild(childName);
+ if (implementation) {
+ context->freeUniqueID();
+ return implementation;
+ }
+ object->setTemporaryMark(uniqueID); // prevents recursions
+ object = __proto__;
+ }
+ context->freeUniqueID();
+ return 0;
+}
+
+CScriptVarLinkWorkPtr CScriptVar::findChildWithPrototypeChain(const string &childName) {
+ CScriptVarLinkWorkPtr child = findChildWithStringChars(childName);
+ if(child) return child;
+ child = findChildInPrototypeChain(childName);
+ if(child) {
+ child(child->getVarPtr(), child->getName(), child->getFlags()); // recreate implementation
+ child.setReferencedOwner(this); // fake referenced Owner
+ }
+ return child;
+}
+CScriptVarLinkPtr CScriptVar::findChildByPath(const string &path) {
+ string::size_type p = path.find('.');
+ CScriptVarLinkPtr child;
+ if (p == string::npos)
+ return findChild(path);
+ if( (child = findChild(path.substr(0,p))) )
+ return child->getVarPtr()->findChildByPath(path.substr(p+1));
+ return 0;
+}
+
+CScriptVarLinkPtr CScriptVar::findChildOrCreate(const string &childName/*, int varFlags*/) {
+ CScriptVarLinkPtr l = findChild(childName);
+ if (l) return l;
+ return addChild(childName, constScriptVar(Undefined));
+ // return addChild(childName, new CScriptVar(context, TINYJS_BLANK_DATA, varFlags));
+}
+
+CScriptVarLinkPtr CScriptVar::findChildOrCreateByPath(const string &path) {
+ string::size_type p = path.find('.');
+ if (p == string::npos)
+ return findChildOrCreate(path);
+ string childName(path, 0, p);
+ CScriptVarLinkPtr l = findChild(childName);
+ if (!l) l = addChild(childName, newScriptVar(Object));
+ return l->getVarPtr()->findChildOrCreateByPath(path.substr(p+1));
+}
+
+void CScriptVar::keys(set &Keys, bool OnlyEnumerable/*=true*/, uint32_t ID/*=0*/)
+{
+ if(ID) setTemporaryMark(ID);
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) {
+ if(!OnlyEnumerable || (*it)->isEnumerable())
+ Keys.insert((*it)->getName());
+ }
+ CScriptVarStringPtr isStringObj = this->getRawPrimitive();
+ if(isStringObj) {
+ uint32_t length = isStringObj->stringLength();
+ for(uint32_t i=0; igetVarPtr()->getTemporaryMark() != ID )
+ __proto__->getVarPtr()->keys(Keys, OnlyEnumerable, ID);
+}
+
+/// add & remove
+CScriptVarLinkPtr CScriptVar::addChild(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) {
+ CScriptVarLinkPtr link;
+ SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName);
+ if(it == Childs.end() || (*it)->getName() != childName) {
+ link = CScriptVarLinkPtr(child?child:constScriptVar(Undefined), childName, linkFlags);
+ link->setOwner(this);
+
+ Childs.insert(it, 1, link);
+#ifdef _DEBUG
+ } else {
+ ASSERT(0); // addChild - the child exists
+#endif
+ }
+ return link;
+}
+CScriptVarLinkPtr CScriptVar::addChildNoDup(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) {
+ return addChildOrReplace(childName, child, linkFlags);
+}
+CScriptVarLinkPtr CScriptVar::addChildOrReplace(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) {
+ SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName);
+ if(it == Childs.end() || (*it)->getName() != childName) {
+ CScriptVarLinkPtr link(child, childName, linkFlags);
+ link->setOwner(this);
+ Childs.insert(it, 1, link);
+ return link;
+ } else {
+ (*it)->setVarPtr(child);
+ return (*it);
+ }
+}
+
+bool CScriptVar::removeLink(CScriptVarLinkPtr &link) {
+ if (!link) return false;
+ SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), link->getName());
+ if(it != Childs.end() && (*it) == link) {
+ Childs.erase(it);
+#ifdef _DEBUG
+ } else {
+ ASSERT(0); // removeLink - the link is not atached to this var
+#endif
+ }
+ link.clear();
+ return true;
+}
+void CScriptVar::removeAllChildren() {
+ Childs.clear();
+}
+
+CScriptVarPtr CScriptVar::getArrayIndex(uint32_t idx) {
+ CScriptVarLinkPtr link = findChild(int2string(idx));
+ if (link) return link;
+ else return constScriptVar(Undefined); // undefined
+}
+
+void CScriptVar::setArrayIndex(uint32_t idx, const CScriptVarPtr &value) {
+ string sIdx = int2string(idx);
+ CScriptVarLinkPtr link = findChild(sIdx);
+
+ if (link) {
+ link->setVarPtr(value);
+ } else {
+ addChild(sIdx, value);
+ }
+}
+
+uint32_t CScriptVar::getArrayLength() {
+ if (!isArray() || Childs.size()==0) return 0;
+ return isArrayIndex(Childs.back()->getName())+1;
+}
+
+CScriptVarPtr CScriptVar::mathsOp(const CScriptVarPtr &b, int op) {
+ CScriptResult execute;
+ return context->mathsOp(execute, this, b, op);
+}
+
+void CScriptVar::trace(const string &name) {
+ string indentStr;
+ uint32_t uniqueID = context->allocUniqueID();
+ trace(indentStr, uniqueID, name);
+ context->freeUniqueID();
+}
+void CScriptVar::trace(string &indentStr, uint32_t uniqueID, const string &name) {
+ string indent = " ";
+ const char *extra="";
+ if(getTemporaryMark() == uniqueID)
+ extra = " recursion detected";
+ TRACE("%s'%s' = '%s' %s%s\n",
+ indentStr.c_str(),
+ name.c_str(),
+ toString().c_str(),
+ getFlagsAsString().c_str(),
+ extra);
+ if(getTemporaryMark() != uniqueID) {
+ setTemporaryMark(uniqueID);
+ indentStr+=indent;
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) {
+ if((*it)->isEnumerable())
+ (*it)->getVarPtr()->trace(indentStr, uniqueID, (*it)->getName());
+ }
+ indentStr = indentStr.substr(0, indentStr.length()-2);
+ }
+}
+
+string CScriptVar::getFlagsAsString() {
+ string flagstr = "";
+ if (isFunction()) flagstr = flagstr + "FUNCTION ";
+ if (isObject()) flagstr = flagstr + "OBJECT ";
+ if (isArray()) flagstr = flagstr + "ARRAY ";
+ if (isNative()) flagstr = flagstr + "NATIVE ";
+ if (isDouble()) flagstr = flagstr + "DOUBLE ";
+ if (isInt()) flagstr = flagstr + "INTEGER ";
+ if (isBool()) flagstr = flagstr + "BOOLEAN ";
+ if (isString()) flagstr = flagstr + "STRING ";
+ if (isRegExp()) flagstr = flagstr + "REGEXP ";
+ if (isNaN()) flagstr = flagstr + "NaN ";
+ if (isInfinity()) flagstr = flagstr + "INFINITY ";
+ return flagstr;
+}
+
+CScriptVar *CScriptVar::ref() {
+ refs++;
+ return this;
+}
+void CScriptVar::unref() {
+ refs--;
+ ASSERT(refs>=0); // printf("OMFG, we have unreffed too far!\n");
+ if (refs==0)
+ delete this;
+}
+
+int CScriptVar::getRefs() {
+ return refs;
+}
+
+void CScriptVar::setTemporaryMark_recursive(uint32_t ID)
+{
+ if(getTemporaryMark() != ID) {
+ setTemporaryMark(ID);
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) {
+ (*it)->getVarPtr()->setTemporaryMark_recursive(ID);
+ }
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarLink
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarLink::CScriptVarLink(const CScriptVarPtr &Var, const string &Name /*=TINYJS_TEMP_NAME*/, int Flags /*=SCRIPTVARLINK_DEFAULT*/)
+ : name(Name), owner(0), flags(Flags), refs(0) {
+#if DEBUG_MEMORY
+ mark_allocated(this);
+#endif
+ var = Var;
+}
+
+CScriptVarLink::~CScriptVarLink() {
+#if DEBUG_MEMORY
+ mark_deallocated(this);
+#endif
+}
+
+CScriptVarLink *CScriptVarLink::ref() {
+ refs++;
+ return this;
+}
+void CScriptVarLink::unref() {
+ refs--;
+ ASSERT(refs>=0); // printf("OMFG, we have unreffed too far!\n");
+ if (refs==0)
+ delete this;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarLinkPtr
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarLinkPtr & CScriptVarLinkPtr::operator()( const CScriptVarPtr &var, const std::string &name /*= TINYJS_TEMP_NAME*/, int flags /*= SCRIPTVARLINK_DEFAULT*/ ) {
+ if(link && link->refs == 1) { // the link is only refered by this
+ link->name = name;
+ link->owner = 0;
+ link->flags = flags;
+ link->var = var;
+ } else {
+ if(link) link->unref();
+ link = (new CScriptVarLink(var, name, flags))->ref();
+ }
+ return *this;
+}
+
+CScriptVarLinkWorkPtr CScriptVarLinkPtr::getter() {
+ return CScriptVarLinkWorkPtr(*this).getter();
+}
+
+CScriptVarLinkWorkPtr CScriptVarLinkPtr::getter( CScriptResult &execute ) {
+ return CScriptVarLinkWorkPtr(*this).getter(execute);
+}
+
+CScriptVarLinkWorkPtr CScriptVarLinkPtr::setter( const CScriptVarPtr &Var ) {
+ return CScriptVarLinkWorkPtr(*this).setter(Var);
+}
+
+CScriptVarLinkWorkPtr CScriptVarLinkPtr::setter( CScriptResult &execute, const CScriptVarPtr &Var ) {
+ return CScriptVarLinkWorkPtr(*this).setter(execute, Var);
+}
+
+bool CScriptVarLinkPtr::operator <(const string &rhs) const {
+ uint32_t lhs_int = isArrayIndex(link->getName());
+ uint32_t rhs_int = isArrayIndex(rhs);
+ if(lhs_int==uint32_t(-1)) {
+ if(rhs_int==uint32_t(-1))
+ return link->getName() < rhs;
+ else
+ return true;
+ } else if(rhs_int==uint32_t(-1))
+ return false;
+ return lhs_int < rhs_int;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarLinkWorkPtr
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::getter() {
+ if(link && link->getVarPtr()) {
+ CScriptResult execute;
+ CScriptVarPtr ret = getter(execute);
+ execute.cThrow();
+ return ret;
+ }
+ return *this;
+}
+CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::getter(CScriptResult &execute) {
+ if(execute && link && link->getVarPtr() && link->getVarPtr()->isAccessor()) {
+ const CScriptVarPtr &var = link->getVarPtr();
+ CScriptVarLinkPtr getter = var->findChild(TINYJS_ACCESSOR_GET_VAR);
+ if(getter) {
+ vector Params;
+ ASSERT(getReferencedOwner());
+ return getter->getVarPtr()->getContext()->callFunction(execute, getter->getVarPtr(), Params, getReferencedOwner());
+ } else
+ return var->constScriptVar(Undefined);
+ } else
+ return *this;
+}
+CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::setter( const CScriptVarPtr &Var ) {
+ if(link && link->getVarPtr()) {
+ CScriptResult execute;
+ CScriptVarPtr ret = setter(execute, Var);
+ execute.cThrow();
+ return ret;
+ }
+ return *this;
+}
+
+CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::setter( CScriptResult &execute, const CScriptVarPtr &Var ) {
+ if(execute) {
+ if(link) {
+ if(link->getVarPtr() && link->getVarPtr()->isAccessor()) {
+ const CScriptVarPtr &var = link->getVarPtr();
+ CScriptVarLinkPtr setter = var->findChild(TINYJS_ACCESSOR_SET_VAR);
+ if(setter) {
+ vector Params;
+ Params.push_back(Var);
+ ASSERT(getReferencedOwner());
+ setter->getVarPtr()->getContext()->callFunction(execute, setter->getVarPtr(), Params, getReferencedOwner());
+ }
+ } else
+ link->setVarPtr(Var);
+ }
+ }
+ return *this;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarPrimitive
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarPrimitive::~CScriptVarPrimitive(){}
+
+bool CScriptVarPrimitive::isPrimitive() { return true; }
+CScriptVarPrimitivePtr CScriptVarPrimitive::getRawPrimitive() { return this; }
+bool CScriptVarPrimitive::toBoolean() { return false; }
+CScriptVarPtr CScriptVarPrimitive::toObject() { return this; }
+CScriptVarPtr CScriptVarPrimitive::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) {
+ return newScriptVar(toCString(radix));
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptVarUndefined
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(Undefined);
+CScriptVarUndefined::CScriptVarUndefined(CTinyJS *Context) : CScriptVarPrimitive(Context, Context->objectPrototype) { }
+CScriptVarUndefined::~CScriptVarUndefined() {}
+CScriptVarPtr CScriptVarUndefined::clone() { return new CScriptVarUndefined(*this); }
+bool CScriptVarUndefined::isUndefined() { return true; }
+
+CNumber CScriptVarUndefined::toNumber_Callback() { return NaN; }
+string CScriptVarUndefined::toCString(int radix/*=0*/) { return "undefined"; }
+string CScriptVarUndefined::getVarType() { return "undefined"; }
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptVarNull
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(Null);
+CScriptVarNull::CScriptVarNull(CTinyJS *Context) : CScriptVarPrimitive(Context, Context->objectPrototype) { }
+CScriptVarNull::~CScriptVarNull() {}
+CScriptVarPtr CScriptVarNull::clone() { return new CScriptVarNull(*this); }
+bool CScriptVarNull::isNull() { return true; }
+
+CNumber CScriptVarNull::toNumber_Callback() { return 0; }
+string CScriptVarNull::toCString(int radix/*=0*/) { return "null"; }
+string CScriptVarNull::getVarType() { return "null"; }
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarString
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarString::CScriptVarString(CTinyJS *Context, const string &Data) : CScriptVarPrimitive(Context, Context->stringPrototype), data(Data) {
+ addChild("length", newScriptVar(data.size()), SCRIPTVARLINK_CONSTANT);
+/*
+ CScriptVarLinkPtr acc = addChild("length", newScriptVar(Accessor), 0);
+ CScriptVarFunctionPtr getter(::newScriptVar(Context, this, &CScriptVarString::native_Length, 0));
+ getter->setFunctionData(new CScriptTokenDataFnc);
+ acc->getVarPtr()->addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0);
+*/
+}
+CScriptVarString::~CScriptVarString() {}
+CScriptVarPtr CScriptVarString::clone() { return new CScriptVarString(*this); }
+bool CScriptVarString::isString() { return true; }
+
+bool CScriptVarString::toBoolean() { return data.length()!=0; }
+CNumber CScriptVarString::toNumber_Callback() { return data.c_str(); }
+string CScriptVarString::toCString(int radix/*=0*/) { return data; }
+
+string CScriptVarString::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return indentString+getJSString(data); }
+string CScriptVarString::getVarType() { return "string"; }
+
+CScriptVarPtr CScriptVarString::toObject() {
+ CScriptVarPtr ret = newScriptVar(CScriptVarPrimitivePtr(this), context->stringPrototype);
+ ret->addChild("length", newScriptVar(data.size()), SCRIPTVARLINK_CONSTANT);
+ return ret;
+}
+
+CScriptVarPtr CScriptVarString::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) {
+ return this;
+}
+
+int CScriptVarString::getChar(uint32_t Idx) {
+ if((string::size_type)Idx >= data.length())
+ return -1;
+ else
+ return (unsigned char)data[Idx];
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CNumber
+//////////////////////////////////////////////////////////////////////////
+
+NegativeZero_t NegativeZero;
+declare_dummy_t(NaN);
+Infinity InfinityPositive(1);
+Infinity InfinityNegative(-1);
+#if 1
+static inline bool _isNaN(volatile double *Value1, volatile double *Value2) {
+ return !(*Value1==*Value2);
+}
+
+static inline bool isNaN(double Value) {
+ return _isNaN(&Value, &Value);
+}
+inline bool isNegZero(double d) {
+ double x=-0.0;
+ return memcmp(&d, &x, sizeof(double))==0;
+}
+
+CNumber &CNumber::operator=(double Value) {
+ double integral;
+ if(isNegZero(Value))
+ type=tnNULL, Int32=0;
+ else if(numeric_limits::has_infinity && Value == numeric_limits::infinity())
+ type=tInfinity, Int32=1;
+ else if(numeric_limits::has_infinity && Value == -numeric_limits::infinity())
+ type=tInfinity, Int32=-1;
+ else if(::isNaN(Value) || Value == numeric_limits::quiet_NaN() || Value == std::numeric_limits::signaling_NaN())
+ type=tNaN, Int32=0;
+ else if(modf(Value, &integral)==0.0 && numeric_limits::min()<=integral && integral<=numeric_limits::max())
+ type=tInt32, Int32=int32_t(integral);
+ else
+ type=tDouble, Double=Value;
+ return *this;
+}
+CNumber &CNumber::operator=(const char *str) {
+
+ while(isWhitespace(*str)) str++;
+ const char *start = str, *endptr;
+ if(*str == '-' || *str == '+') str++;
+ if(*str == '0' && ( str[1]=='x' || str[1]=='X'))
+ parseInt(start, 16, &endptr);
+ else if(*str == '0' && str[1]>='0' && str[1]<='7')
+ parseInt(start, 8, &endptr);
+ else
+ parseFloat(start, &endptr);
+ while(isWhitespace(*endptr)) endptr++;
+ if(*endptr != '\0')
+ type=tNaN, Int32=0;
+ return *this;
+}
+int32_t CNumber::parseInt(const char * str, int32_t radix/*=0*/, const char **endptr/*=0*/) {
+ type=tInt32, Int32=0;
+ if(endptr) *endptr = str;
+ bool stripPrefix = false; //< is true if radix==0 or radix==16
+ if(radix == 0) {
+ radix=10;
+ stripPrefix = true;
+ } else if(radix < 2 || radix > 36) {
+ type = tNaN;
+ return 0;
+ } else
+ stripPrefix = radix == 16;
+ while(isWhitespace(*str)) str++;
+ int sign=1;
+ if(*str=='-') sign=-1,str++;
+ else if(*str=='+') str++;
+ if(stripPrefix && *str=='0' && (str[1]=='x' || str[1]=='X')) str+=2, radix=16;
+ else if(stripPrefix && *str=='0' && str[1]>='0' && str[1]<='7') str+=1, radix=8;
+ int32_t max = 0x7fffffff/radix;
+ const char *start = str;
+ for( ; *str; str++) {
+ if(*str >= '0' && *str <= '0'-1+radix) Int32 = Int32*radix+*str-'0';
+ else if(*str>='a' && *str<='a'-11+radix) Int32 = Int32*radix+*str-'a'+10;
+ else if(*str>='A' && *str<='A'-11+radix) Int32 = Int32*radix+*str-'A'+10;
+ else break;
+ if(Int32 >= max) {
+ type=tDouble, Double=double(Int32);
+ for(str++ ; *str; str++) {
+ if(*str >= '0' && *str <= '0'-1+radix) Double = Double *radix+*str-'0';
+ else if(*str>='a' && *str<='a'-11+radix) Double = Double *radix+*str-'a'+10;
+ else if(*str>='A' && *str<='A'-11+radix) Double = Double *radix+*str-'A'+10;
+ else break;
+ }
+ break;
+ }
+ }
+ if(str == start) {
+ type= tNaN;
+ return 0;
+ }
+ if(sign<0 && ((type==tInt32 && Int32==0) || (type==tDouble && Double==0.0))) { type=tnNULL,Int32=0; return radix; }
+ if(type==tInt32) operator=(sign<0 ? -Int32 : Int32);
+ else operator=(sign<0 ? -Double : Double);
+ if(endptr) *endptr = (char*)str;
+ return radix;
+}
+
+void CNumber::parseFloat(const char * str, const char **endptr/*=0*/) {
+ type=tInt32, Int32=0;
+ if(endptr) *endptr = str;
+ while(isWhitespace(*str)) str++;
+ int sign=1;
+ if(*str=='-') sign=-1,str++;
+ else if(*str=='+') str++;
+ if(strncmp(str, "Infinity", 8) == 0) { type=tInfinity, Int32=sign; return; }
+ double d = strtod(str, (char**)endptr);
+ operator=(sign>0 ? d : -d);
+ return;
+}
+
+CNumber CNumber::add(const CNumber &Value) const {
+ if(type==tNaN || Value.type==tNaN)
+ return CNumber(tNaN);
+ else if(type==tInfinity || Value.type==tInfinity) {
+ if(type!=tInfinity)
+ return Value;
+ else if(Value.type!=tInfinity || sign()==Value.sign())
+ return *this;
+ else
+ return CNumber(tNaN);
+ } else if(type==tnNULL)
+ return Value;
+ else if(Value.type==tnNULL)
+ return *this;
+ else if(type==tDouble || Value.type==tDouble)
+ return CNumber(toDouble()+Value.toDouble());
+ else {
+ int32_t range_max = numeric_limits::max();
+ int32_t range_min = numeric_limits::min();
+ if(Int32>0) range_max-=Int32;
+ else if(Int32<0) range_min-=Int32;
+ if(range_min<=Value.Int32 && Value.Int32<=range_max)
+ return CNumber(Int32+Value.Int32);
+ else
+ return CNumber(double(Int32)+double(Value.Int32));
+ }
+}
+
+CNumber CNumber::operator-() const {
+ switch(type) {
+ case tInt32:
+ if(Int32==0)
+ return CNumber(NegativeZero);
+ case tnNULL:
+ return CNumber(-Int32);
+ case tDouble:
+ return CNumber(-Double);
+ case tInfinity:
+ return CNumber(tInfinity, -Int32);
+ default:
+ return CNumber(tNaN);
+ }
+}
+
+static inline int bits(uint32_t Value) {
+ uint32_t b=0, mask=0xFFFF0000UL;
+ for(int shift=16; shift>0 && Value!=0; shift>>=1, mask>>=shift) {
+ if(Value & mask) {
+ b += shift;
+ Value>>=shift;
+ }
+ }
+ return b;
+}
+static inline int bits(int32_t Value) {
+ return bits(uint32_t(Value<0?-Value:Value));
+}
+
+CNumber CNumber::multi(const CNumber &Value) const {
+ if(type==tNaN || Value.type==tNaN)
+ return CNumber(tNaN);
+ else if(type==tInfinity || Value.type==tInfinity) {
+ if(isZero() || Value.isZero())
+ return CNumber(tNaN);
+ else
+ return CNumber(tInfinity, sign()==Value.sign()?1:-1);
+ } else if(isZero() || Value.isZero()) {
+ if(sign()==Value.sign())
+ return CNumber(0);
+ else
+ return CNumber(NegativeZero);
+ } else if(type==tDouble || Value.type==tDouble)
+ return CNumber(toDouble()*Value.toDouble());
+ else {
+ // Int32*Int32
+ if(bits(Int32)+bits(Value.Int32) <= 29)
+ return CNumber(Int32*Value.Int32);
+ else
+ return CNumber(double(Int32)*double(Value.Int32));
+ }
+}
+
+
+CNumber CNumber::div( const CNumber &Value ) const {
+ if(type==tNaN || Value.type==tNaN) return CNumber(tNaN);
+ int Sign = sign()*Value.sign();
+ if(type==tInfinity) {
+ if(Value.type==tInfinity) return CNumber(tNaN);
+ else return CNumber(tInfinity, Sign);
+ }
+ if(Value.type==tInfinity) {
+ if(Sign<0) return CNumber(NegativeZero);
+ else return CNumber(0);
+ } else if(Value.isZero()) {
+ if(isZero()) return CNumber(tNaN);
+ else return CNumber(tInfinity, Sign);
+ } else
+ return CNumber(toDouble() / Value.toDouble());
+}
+
+CNumber CNumber::modulo( const CNumber &Value ) const {
+ if(type==tNaN || type==tInfinity || Value.type==tNaN || Value.isZero()) return CNumber(tNaN);
+ if(Value.type==tInfinity) return CNumber(*this);
+ if(isZero()) return CNumber(0);
+ if(type==tDouble || Value.type==tDouble || (Int32==numeric_limits::min() && Value == -1) /* use double to prevent integer overflow */ ) {
+ double n = toDouble(), d = Value.toDouble(), q;
+ modf(n/d, &q);
+ return CNumber(n - (d * q));
+ } else
+ return CNumber(Int32 % Value.Int32);
+}
+
+CNumber CNumber::round() const {
+ if(type != tDouble) return CNumber(*this);
+ if(Double < 0.0 && Double >= -0.5)
+ return CNumber(NegativeZero);
+ return CNumber(::floor(Double+0.5));
+}
+
+CNumber CNumber::floor() const {
+ if(type != tDouble) return CNumber(*this);
+ return CNumber(::floor(Double));
+}
+
+CNumber CNumber::ceil() const {
+ if(type != tDouble) return CNumber(*this);
+ return CNumber(::ceil(Double));
+}
+
+CNumber CNumber::abs() const {
+ if(sign()<0) return -CNumber(*this);
+ else return CNumber(*this);
+}
+
+CNumber CNumber::shift(const CNumber &Value, bool Right) const {
+ int32_t lhs = toInt32();
+ uint32_t rhs = Value.toUInt32() & 0x1F;
+ return CNumber(Right ? lhs>>rhs : lhs<>rhs : lhs< 9 ? ('a'-10) : '0'));
+ if(p==buf_end) {
+ char *new_buf = (char *)realloc(buf, buf_end-buf+16+1); // for '\0'
+ if(!new_buf) { free(buf); return 0; }
+ p = new_buf + (buf_end - buf);
+ buf_end = p + 16;
+ buf = new_buf;
+ }
+ } while (val > 0);
+
+ // We now have the digit of the number in the buffer, but in reverse
+ // order. Thus we reverse them now.
+ *p-- = '\0';
+ firstdig = buf;
+ if(*firstdig=='-') firstdig++;
+ do {
+ temp = *p;
+ *p = *firstdig;
+ *firstdig = temp;
+ p--;
+ firstdig++;
+ } while (firstdig < p);
+ return buf;
+}
+
+static char *tiny_dtoa(double val, unsigned radix) {
+ char *buf, *buf_end, *p, temp;
+ unsigned digval;
+
+ buf = (char*)malloc(64);
+ if(!buf) return 0;
+ buf_end = buf+64-2; // -1 for '.' , -1 for '\0'
+
+ p = buf;
+ if (val < 0.0) {
+ *p++ = '-';
+ val = -val;
+ }
+
+ double val_1 = floor(val);
+ double val_2 = val - val_1;
+
+
+ do {
+ double tmp = val_1 / radix;
+ val_1 = floor(tmp);
+ digval = (unsigned)((tmp - val_1) * radix);
+
+ *p++ = (char) (digval + (digval > 9 ? ('a'-10) : '0'));
+ if(p==buf_end) {
+ char *new_buf = (char *)realloc(buf, buf_end-buf+16+2); // +2 for '.' + '\0'
+ if(!new_buf) { free(buf); return 0; }
+ p = new_buf + (buf_end - buf);
+ buf_end = p + 16;
+ buf = new_buf;
+ }
+ } while (val_1 > 0.0);
+
+ // We now have the digit of the number in the buffer, but in reverse
+ // order. Thus we reverse them now.
+ char *p1 = buf;
+ char *p2 = p-1;
+ do {
+ temp = *p2;
+ *p2-- = *p1;
+ *p1++ = temp;
+ } while (p1 < p2);
+
+ if(val_2) {
+ *p++ = '.';
+ do {
+ val_2 *= radix;
+ digval = (unsigned)(val_2);
+ val_2 -= digval;
+
+ *p++ = (char) (digval + (digval > 9 ? ('a'-10) : '0'));
+ if(p==buf_end) {
+ char *new_buf = (char *)realloc(buf, buf_end-buf+16);
+ if(!new_buf) { free(buf); return 0; }
+ p = new_buf + (buf_end - buf);
+ buf_end = p + 16;
+ buf = new_buf;
+ }
+ } while (val_2 > 0.0);
+
+ }
+ *p = '\0';
+ return buf;
+}
+std::string CNumber::toString( uint32_t Radix/*=10*/ ) const {
+ char *str;
+ if(2 > Radix || Radix > 36)
+ Radix = 10; // todo error;
+ switch(type) {
+ case tInt32:
+ if( (str = tiny_ltoa(Int32, Radix)) ) {
+ string ret(str); free(str);
+ return ret;
+ }
+ break;
+ case tnNULL:
+ return "0";
+ case tDouble:
+ if(Radix==10) {
+ ostringstream str;
+ str.unsetf(ios::floatfield);
+#if (defined(_MSC_VER) && _MSC_VER >= 1600) || __cplusplus >= 201103L
+ str.precision(numeric_limits::max_digits10);
+#else
+ str.precision(numeric_limits::digits10+2);
+#endif
+ str << Double;
+ return str.str();
+ } else if( (str = tiny_dtoa(Double, Radix)) ) {
+ string ret(str); free(str);
+ return ret;
+ }
+ break;
+ case tInfinity:
+ return Int32<0?"-Infinity":"Infinity";
+ case tNaN:
+ return "NaN";
+ }
+ return "";
+}
+
+double CNumber::toDouble() const
+{
+ switch(type) {
+ case tnNULL:
+ return -0.0;
+ case tInt32:
+ return double(Int32);
+ case tDouble:
+ return Double;
+ case tNaN:
+ return std::numeric_limits::quiet_NaN();
+ case tInfinity:
+ return Int32<0 ? -std::numeric_limits::infinity():std::numeric_limits::infinity();
+ }
+ return 0.0;
+}
+
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarNumber
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarNumber::CScriptVarNumber(CTinyJS *Context, const CNumber &Data) : CScriptVarPrimitive(Context, Context->numberPrototype), data(Data) {}
+CScriptVarNumber::~CScriptVarNumber() {}
+CScriptVarPtr CScriptVarNumber::clone() { return new CScriptVarNumber(*this); }
+bool CScriptVarNumber::isNumber() { return true; }
+bool CScriptVarNumber::isInt() { return data.isInt32(); }
+bool CScriptVarNumber::isDouble() { return data.isDouble(); }
+bool CScriptVarNumber::isRealNumber() { return isInt() || isDouble(); }
+bool CScriptVarNumber::isNaN() { return data.isNaN(); }
+int CScriptVarNumber::isInfinity() { return data.isInfinity(); }
+
+bool CScriptVarNumber::toBoolean() { return data.toBoolean(); }
+CNumber CScriptVarNumber::toNumber_Callback() { return data; }
+string CScriptVarNumber::toCString(int radix/*=0*/) { return data.toString(radix); }
+
+string CScriptVarNumber::getVarType() { return "number"; }
+
+CScriptVarPtr CScriptVarNumber::toObject() { return newScriptVar(CScriptVarPrimitivePtr(this), context->numberPrototype); }
+inline define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &Obj) {
+ if(!Obj.isInt32() && !Obj.isDouble()) {
+ if(Obj.isNaN()) return Context->constScriptVar(NaN);
+ if(Obj.isInfinity()) return Context->constScriptVar(Infinity(Obj.sign()));
+ if(Obj.isNegativeZero()) return Context->constScriptVar(NegativeZero);
+ }
+ return new CScriptVarNumber(Context, Obj);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptVarBool
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarBool::CScriptVarBool(CTinyJS *Context, bool Data) : CScriptVarPrimitive(Context, Context->booleanPrototype), data(Data) {}
+CScriptVarBool::~CScriptVarBool() {}
+CScriptVarPtr CScriptVarBool::clone() { return new CScriptVarBool(*this); }
+bool CScriptVarBool::isBool() { return true; }
+
+bool CScriptVarBool::toBoolean() { return data; }
+CNumber CScriptVarBool::toNumber_Callback() { return data?1:0; }
+string CScriptVarBool::toCString(int radix/*=0*/) { return data ? "true" : "false"; }
+
+string CScriptVarBool::getVarType() { return "boolean"; }
+
+CScriptVarPtr CScriptVarBool::toObject() { return newScriptVar(CScriptVarPrimitivePtr(this), context->booleanPrototype); }
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarObject
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(Object);
+CScriptVarObject::CScriptVarObject(CTinyJS *Context) : CScriptVar(Context, Context->objectPrototype) { }
+CScriptVarObject::~CScriptVarObject() {}
+CScriptVarPtr CScriptVarObject::clone() { return new CScriptVarObject(*this); }
+
+void CScriptVarObject::removeAllChildren()
+{
+ CScriptVar::removeAllChildren();
+ value.clear();
+}
+
+CScriptVarPrimitivePtr CScriptVarObject::getRawPrimitive() { return value; }
+bool CScriptVarObject::isObject() { return true; }
+
+string CScriptVarObject::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) {
+ getParsableStringRecursionsCheck();
+ string destination;
+ const char *nl = indent.size() ? "\n" : " ";
+ const char *comma = "";
+ destination.append("{");
+ if(Childs.size()) {
+ string new_indentString = indentString + indent;
+ for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) {
+ if((*it)->isEnumerable()) {
+ destination.append(comma); comma=",";
+ destination.append(nl).append(new_indentString).append(getIDString((*it)->getName()));
+ destination.append(" : ");
+ destination.append((*it)->getVarPtr()->getParsableString(new_indentString, indent, uniqueID, hasRecursion));
+ }
+ }
+ destination.append(nl).append(indentString);
+ }
+ destination.append("}");
+ return destination;
+}
+string CScriptVarObject::getVarType() { return "object"; }
+string CScriptVarObject::getVarTypeTagName() { return "Object"; }
+
+CScriptVarPtr CScriptVarObject::toObject() { return this; }
+
+CScriptVarPtr CScriptVarObject::valueOf_CallBack() {
+ if(value)
+ return value->valueOf_CallBack();
+ return CScriptVar::valueOf_CallBack();
+}
+CScriptVarPtr CScriptVarObject::toString_CallBack(CScriptResult &execute, int radix) {
+ if(value)
+ return value->toString_CallBack(execute, radix);
+ return newScriptVar("[object "+getVarTypeTagName()+"]");
+};
+
+void CScriptVarObject::setTemporaryMark_recursive( uint32_t ID) {
+ CScriptVar::setTemporaryMark_recursive(ID);
+ if(value) value->setTemporaryMark_recursive(ID);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarObjectTyped (simple Object with Typename
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(StopIteration);
+CScriptVarObjectTypeTagged::~CScriptVarObjectTypeTagged() {}
+CScriptVarPtr CScriptVarObjectTypeTagged::clone() { return new CScriptVarObjectTypeTagged(*this); }
+std::string CScriptVarObjectTypeTagged::getVarTypeTagName() { return typeTagName; }
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarError
+//////////////////////////////////////////////////////////////////////////
+
+const char *ERROR_NAME[] = {"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError"};
+
+CScriptVarError::CScriptVarError(CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column) : CScriptVarObject(Context, Context->getErrorPrototype(type)) {
+ if(message && *message) addChild("message", newScriptVar(message));
+ if(file && *file) addChild("fileName", newScriptVar(file));
+ if(line>=0) addChild("lineNumber", newScriptVar(line+1));
+ if(column>=0) addChild("column", newScriptVar(column+1));
+}
+
+CScriptVarError::~CScriptVarError() {}
+CScriptVarPtr CScriptVarError::clone() { return new CScriptVarError(*this); }
+bool CScriptVarError::isError() { return true; }
+
+CScriptVarPtr CScriptVarError::toString_CallBack(CScriptResult &execute, int radix) {
+ CScriptVarLinkPtr link;
+ string name = ERROR_NAME[Error];
+ link = findChildWithPrototypeChain("name"); if(link) name = link->toString(execute);
+ string message; link = findChildWithPrototypeChain("message"); if(link) message = link->toString(execute);
+ string fileName; link = findChildWithPrototypeChain("fileName"); if(link) fileName = link->toString(execute);
+ int lineNumber=-1; link = findChildWithPrototypeChain("lineNumber"); if(link) lineNumber = link->toNumber().toInt32();
+ int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32();
+ ostringstream msg;
+ msg << name << ": " << message;
+ if(lineNumber >= 0) msg << " at Line:" << lineNumber+1;
+ if(column >=0) msg << " Column:" << column+1;
+ if(fileName.length()) msg << " in " << fileName;
+ return newScriptVar(msg.str());
+}
+
+CScriptException *CScriptVarError::toCScriptException()
+{
+ CScriptVarLinkPtr link;
+ string name = ERROR_NAME[Error];
+ link = findChildWithPrototypeChain("name"); if(link) name = link->toString();
+ int ErrorCode;
+ for(ErrorCode=(sizeof(ERROR_NAME)/sizeof(ERROR_NAME[0]))-1; ErrorCode>0; ErrorCode--) {
+ if(name == ERROR_NAME[ErrorCode]) break;
+ }
+ string message; link = findChildWithPrototypeChain("message"); if(link) message = link->toString();
+ string fileName; link = findChildWithPrototypeChain("fileName"); if(link) fileName = link->toString();
+ int lineNumber=-1; link = findChildWithPrototypeChain("lineNumber"); if(link) lineNumber = link->toNumber().toInt32()-1;
+ int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32()-1;
+ return new CScriptException((enum ERROR_TYPES)ErrorCode, message, fileName, lineNumber, column);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptVarArray
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(Array);
+CScriptVarArray::CScriptVarArray(CTinyJS *Context) : CScriptVarObject(Context, Context->arrayPrototype), toStringRecursion(false) {
+ CScriptVarLinkPtr acc = addChild("length", newScriptVar(Accessor), 0);
+ CScriptVarFunctionPtr getter(::newScriptVar(Context, this, &CScriptVarArray::native_Length, 0));
+ getter->setFunctionData(new CScriptTokenDataFnc);
+ acc->getVarPtr()->addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0);
+}
+
+CScriptVarArray::~CScriptVarArray() {}
+CScriptVarPtr CScriptVarArray::clone() { return new CScriptVarArray(*this); }
+bool CScriptVarArray::isArray() { return true; }
+string CScriptVarArray::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) {
+ getParsableStringRecursionsCheck();
+ string destination;
+ const char *nl = indent.size() ? "\n" : " ";
+ const char *comma = "";
+ destination.append("[");
+ int len = getArrayLength();
+ if(len) {
+ string new_indentString = indentString + indent;
+ for (int i=0;igetParsableString(new_indentString, indent, uniqueID, hasRecursion));
+ }
+ destination.append(nl).append(indentString);
+ }
+ destination.append("]");
+ return destination;
+}
+CScriptVarPtr CScriptVarArray::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) {
+ ostringstream destination;
+ if(toStringRecursion) {
+ return newScriptVar("");
+ }
+ toStringRecursion = true;
+ try {
+ int len = getArrayLength();
+ for (int i=0;itoString(execute);
+ if (isetReturnVar(newScriptVar(c->getArgument("this")->getArrayLength()));
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarRegExp
+//////////////////////////////////////////////////////////////////////////
+
+#ifndef NO_REGEXP
+
+CScriptVarRegExp::CScriptVarRegExp(CTinyJS *Context, const string &Regexp, const string &Flags) : CScriptVarObject(Context, Context->regexpPrototype), regexp(Regexp), flags(Flags) {
+ addChild("global", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Global, 0, 0, 0), 0);
+ addChild("ignoreCase", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_IgnoreCase, 0, 0, 0), 0);
+ addChild("multiline", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Multiline, 0, 0, 0), 0);
+ addChild("sticky", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Sticky, 0, 0, 0), 0);
+ addChild("regexp", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Source, 0, 0, 0), 0);
+ addChild("lastIndex", newScriptVar(0));
+}
+CScriptVarRegExp::~CScriptVarRegExp() {}
+CScriptVarPtr CScriptVarRegExp::clone() { return new CScriptVarRegExp(*this); }
+bool CScriptVarRegExp::isRegExp() { return true; }
+//int CScriptVarRegExp::getInt() {return strtol(regexp.c_str(),0,0); }
+//bool CScriptVarRegExp::getBool() {return regexp.length()!=0;}
+//double CScriptVarRegExp::getDouble() {return strtod(regexp.c_str(),0);}
+//string CScriptVarRegExp::getString() { return "/"+regexp+"/"+flags; }
+//string CScriptVarRegExp::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return getString(); }
+CScriptVarPtr CScriptVarRegExp::toString_CallBack(CScriptResult &execute, int radix) {
+ return newScriptVar("/"+regexp+"/"+flags);
+}
+void CScriptVarRegExp::native_Global(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(constScriptVar(Global()));
+}
+void CScriptVarRegExp::native_IgnoreCase(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(constScriptVar(IgnoreCase()));
+}
+void CScriptVarRegExp::native_Multiline(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(constScriptVar(Multiline()));
+}
+void CScriptVarRegExp::native_Sticky(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(constScriptVar(Sticky()));
+}
+void CScriptVarRegExp::native_Source(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(newScriptVar(regexp));
+}
+unsigned int CScriptVarRegExp::LastIndex() {
+ CScriptVarPtr lastIndex = findChild("lastIndex");
+ if(lastIndex) return lastIndex->toNumber().toInt32();
+ return 0;
+}
+void CScriptVarRegExp::LastIndex(unsigned int Idx) {
+ addChildOrReplace("lastIndex", newScriptVar((int)Idx));
+}
+
+CScriptVarPtr CScriptVarRegExp::exec( const string &Input, bool Test /*= false*/ )
+{
+ regex::flag_type flags = regex_constants::ECMAScript;
+ if(IgnoreCase()) flags |= regex_constants::icase;
+ bool global = Global(), sticky = Sticky();
+ unsigned int lastIndex = LastIndex();
+ int offset = 0;
+ if(global || sticky) {
+ if(lastIndex > Input.length()) goto failed;
+ offset=lastIndex;
+ }
+ {
+ regex_constants::match_flag_type mflag = sticky?regex_constants::match_continuous:regex_constants::match_default;
+ if(offset) mflag |= regex_constants::match_prev_avail;
+ smatch match;
+ if(regex_search(Input.begin()+offset, Input.end(), match, regex(regexp, flags), mflag) ) {
+ LastIndex(offset+match.position()+match.str().length());
+ if(Test) return constScriptVar(true);
+
+ CScriptVarArrayPtr retVar = newScriptVar(Array);
+ retVar->addChild("input", newScriptVar(Input));
+ retVar->addChild("index", newScriptVar(match.position()));
+ for(smatch::size_type idx=0; idxaddChild(int2string(idx), newScriptVar(match[idx].str()));
+ return retVar;
+ }
+ }
+failed:
+ if(global || sticky)
+ LastIndex(0);
+ if(Test) return constScriptVar(false);
+ return constScriptVar(Null);
+}
+
+const char * CScriptVarRegExp::ErrorStr( int Error )
+{
+ switch(Error) {
+ case regex_constants::error_badbrace: return "the expression contained an invalid count in a { } expression";
+ case regex_constants::error_badrepeat: return "a repeat expression (one of '*', '?', '+', '{' in most contexts) was not preceded by an expression";
+ case regex_constants::error_brace: return "the expression contained an unmatched '{' or '}'";
+ case regex_constants::error_brack: return "the expression contained an unmatched '[' or ']'";
+ case regex_constants::error_collate: return "the expression contained an invalid collating element name";
+ case regex_constants::error_complexity: return "an attempted match failed because it was too complex";
+ case regex_constants::error_ctype: return "the expression contained an invalid character class name";
+ case regex_constants::error_escape: return "the expression contained an invalid escape sequence";
+ case regex_constants::error_paren: return "the expression contained an unmatched '(' or ')'";
+ case regex_constants::error_range: return "the expression contained an invalid character range specifier";
+ case regex_constants::error_space: return "parsing a regular expression failed because there were not enough resources available";
+ case regex_constants::error_stack: return "an attempted match failed because there was not enough memory available";
+ case regex_constants::error_backref: return "the expression contained an invalid back reference";
+ default: return "";
+ }
+}
+
+#endif /* NO_REGEXP */
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarDefaultIterator
+//////////////////////////////////////////////////////////////////////////
+
+//declare_dummy_t(DefaultIterator);
+CScriptVarDefaultIterator::CScriptVarDefaultIterator(CTinyJS *Context, const CScriptVarPtr &Object, int Mode)
+ : CScriptVarObject(Context, Context->iteratorPrototype), mode(Mode), object(Object) {
+ object->keys(keys, true);
+ pos = keys.begin();
+ addChild("next", ::newScriptVar(context, this, &CScriptVarDefaultIterator::native_next, 0));
+}
+CScriptVarDefaultIterator::~CScriptVarDefaultIterator() {}
+CScriptVarPtr CScriptVarDefaultIterator::clone() { return new CScriptVarDefaultIterator(*this); }
+bool CScriptVarDefaultIterator::isIterator() {return true;}
+void CScriptVarDefaultIterator::native_next(const CFunctionsScopePtr &c, void *data) {
+ if(pos==keys.end()) throw constScriptVar(StopIteration);
+ CScriptVarPtr ret, ret0, ret1;
+ if(mode&1) ret0 = newScriptVar(*pos);
+ if(mode&2) ret1 = object->findChildWithStringChars(*pos);
+ pos++;
+ if(mode==3) {
+ ret = newScriptVar(Array);
+ ret->setArrayIndex(0, ret0);
+ ret->setArrayIndex(1, ret1);
+ } else if(mode==1)
+ ret = ret0;
+ else
+ ret = ret1;
+ c->setReturnVar(ret);
+}
+
+
+#ifndef NO_GENERATORS
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarGenerator
+//////////////////////////////////////////////////////////////////////////
+
+//declare_dummy_t(Generator);
+CScriptVarGenerator::CScriptVarGenerator(CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function)
+ : CScriptVarObject(Context, Context->generatorPrototype), functionRoot(FunctionRoot), function(Function),
+ closed(false), yieldVarIsException(false), coroutine(this) {
+// addChild("next", ::newScriptVar(context, this, &CScriptVarGenerator::native_send, 0, "Generator.next"));
+ // addChild("send", ::newScriptVar(context, this, &CScriptVarGenerator::native_send, (void*)1, "Generator.send"));
+ //addChild("close", ::newScriptVar(context, this, &CScriptVarGenerator::native_throw, (void*)0, "Generator.close"));
+ //addChild("throw", ::newScriptVar(context, this, &CScriptVarGenerator::native_throw, (void*)1, "Generator.throw"));
+}
+CScriptVarGenerator::~CScriptVarGenerator() {
+ if(coroutine.isStarted() && coroutine.isRunning()) {
+ coroutine.Stop(false);
+ coroutine.next();
+ coroutine.Stop();
+ }
+}
+CScriptVarPtr CScriptVarGenerator::clone() { return new CScriptVarGenerator(*this); }
+bool CScriptVarGenerator::isIterator() {return true;}
+bool CScriptVarGenerator::isGenerator() {return true;}
+
+string CScriptVarGenerator::getVarType() { return "generator"; }
+string CScriptVarGenerator::getVarTypeTagName() { return "Generator"; }
+
+void CScriptVarGenerator::setTemporaryMark_recursive( uint32_t ID ) {
+ CScriptVarObject::setTemporaryMark_recursive(ID);
+ functionRoot->setTemporaryMark_recursive(ID);
+ function->setTemporaryMark_recursive(ID);
+ if(yieldVar) yieldVar->setTemporaryMark_recursive(ID);
+ for(std::vector::iterator it=generatorScopes.begin(); it != generatorScopes.end(); ++it)
+ (*it)->setTemporaryMark_recursive(ID);
+}
+void CScriptVarGenerator::native_send(const CFunctionsScopePtr &c, void *data) {
+ // data == 0 ==> next()
+ // data != 0 ==> send(...)
+ if(closed)
+ throw constScriptVar(StopIteration);
+
+ yieldVar = data ? c->getArgument(0) : constScriptVar(Undefined);
+ yieldVarIsException = false;
+
+ if(!coroutine.isStarted() && data && !yieldVar->isUndefined())
+ c->throwError(TypeError, "attempt to send value to newborn generator");
+ if(coroutine.next()) {
+ c->setReturnVar(yieldVar);
+ return;
+ }
+ closed = true;
+ throw yieldVar;
+}
+void CScriptVarGenerator::native_throw(const CFunctionsScopePtr &c, void *data) {
+ // data == 0 ==> close()
+ // data != 0 ==> throw(...)
+ if(closed || !coroutine.isStarted()) {
+ closed = true;
+ if(data)
+ throw c->getArgument(0);
+ else
+ return;
+ }
+ yieldVar = data ? c->getArgument(0) : CScriptVarPtr();
+ yieldVarIsException = true;
+ closed = data==0;
+ if(coroutine.next()) {
+ c->setReturnVar(yieldVar);
+ return;
+ }
+ closed = true;
+ /*
+ * from http://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
+ * Generators have a close() method that forces the generator to close itself. The effects of closing a generator are:
+ * - Any finally clauses active in the generator function are run.
+ * - If a finally clause throws any exception other than StopIteration, the exception is propagated to the caller of the close() method.
+ * - The generator terminates.
+ *
+ * but in Firefox is also "StopIteration" propagated to the caller
+ * define GENERATOR_CLOSE_LIKE_IN_FIREFOX to enable this behavior
+ */
+//#define GENERATOR_CLOSE_LIKE_IN_FIREFOX
+#ifdef GENERATOR_CLOSE_LIKE_IN_FIREFOX
+ if(data || yieldVar)
+ throw yieldVar;
+#else
+ if(data || (yieldVar && yieldVar != constScriptVar(StopIteration)))
+ throw yieldVar;
+#endif
+}
+
+int CScriptVarGenerator::Coroutine()
+{
+ context->generator_start(this);
+ return 0;
+}
+
+CScriptVarPtr CScriptVarGenerator::yield( CScriptResult &execute, CScriptVar *YieldIn )
+{
+ yieldVar = YieldIn;
+ coroutine.yield();
+ if(yieldVarIsException) {
+ execute.set(CScriptResult::Throw, yieldVar);
+ return constScriptVar(Undefined);
+ }
+ return yieldVar;
+}
+
+#endif /*NO_GENERATORS*/
+
+//////////////////////////////////////////////////////////////////////////
+// CScriptVarFunction
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarFunction::CScriptVarFunction(CTinyJS *Context, CScriptTokenDataFnc *Data) : CScriptVarObject(Context, Context->functionPrototype), data(0) {
+ setFunctionData(Data);
+}
+CScriptVarFunction::~CScriptVarFunction() { setFunctionData(0); }
+CScriptVarPtr CScriptVarFunction::clone() { return new CScriptVarFunction(*this); }
+bool CScriptVarFunction::isObject() { return true; }
+bool CScriptVarFunction::isFunction() { return true; }
+bool CScriptVarFunction::isPrimitive() { return false; }
+
+//string CScriptVarFunction::getString() {return "[ Function ]";}
+string CScriptVarFunction::getVarType() { return "function"; }
+string CScriptVarFunction::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) {
+ getParsableStringRecursionsCheck();
+ string destination;
+ destination.append("function ").append(data->name);
+ // get list of arguments
+ destination.append(data->getArgumentsString());
+
+ if(isNative() || isBounded())
+ destination.append("{ [native code] }");
+ else {
+ destination.append(CScriptToken::getParsableString(data->body, indentString, indent));
+ if(!data->body.size() || data->body.front().token != '{')
+ destination.append(";");
+ }
+ return destination;
+}
+
+CScriptVarPtr CScriptVarFunction::toString_CallBack(CScriptResult &execute, int radix){
+ bool hasRecursion;
+ return newScriptVar(getParsableString("", " ", 0, hasRecursion));
+}
+
+CScriptTokenDataFnc *CScriptVarFunction::getFunctionData() { return data; }
+
+void CScriptVarFunction::setFunctionData(CScriptTokenDataFnc *Data) {
+ if(data) { data->unref(); data = 0; }
+ if(Data) {
+ data = Data; data->ref();
+ addChildOrReplace("length", newScriptVar((int)data->arguments.size()), 0);
+ // can not add "name" here because name is a StingVar with length as getter
+ // length-getter is a function with a function name -> endless recursion
+ //addChildNoDup("name", newScriptVar(data->name), 0);
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarFunctionBounded
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarFunctionBounded::CScriptVarFunctionBounded(CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments)
+ : CScriptVarFunction(BoundedFunction->getContext(), new CScriptTokenDataFnc) ,
+ boundedFunction(BoundedFunction),
+ boundedThis(BoundedThis),
+ boundedArguments(BoundedArguments) {
+ getFunctionData()->name = BoundedFunction->getFunctionData()->name;
+}
+CScriptVarFunctionBounded::~CScriptVarFunctionBounded(){}
+CScriptVarPtr CScriptVarFunctionBounded::clone() { return new CScriptVarFunctionBounded(*this); }
+bool CScriptVarFunctionBounded::isBounded() { return true; }
+void CScriptVarFunctionBounded::setTemporaryMark_recursive(uint32_t ID) {
+ CScriptVarFunction::setTemporaryMark_recursive(ID);
+ boundedThis->setTemporaryMark_recursive(ID);
+ for(vector::iterator it=boundedArguments.begin(); it!=boundedArguments.end(); ++it)
+ (*it)->setTemporaryMark_recursive(ID);
+}
+
+CScriptVarPtr CScriptVarFunctionBounded::callFunction( CScriptResult &execute, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis/*=0*/ )
+{
+ vector newArgs=boundedArguments;
+ newArgs.insert(newArgs.end(), Arguments.begin(), Arguments.end());
+ return context->callFunction(execute, boundedFunction, newArgs, newThis ? This : boundedThis, newThis);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarFunctionNative
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarFunctionNative::~CScriptVarFunctionNative() {}
+bool CScriptVarFunctionNative::isNative() { return true; }
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarFunctionNativeCallback
+//////////////////////////////////////////////////////////////////////////
+
+CScriptVarFunctionNativeCallback::~CScriptVarFunctionNativeCallback() {}
+CScriptVarPtr CScriptVarFunctionNativeCallback::clone() { return new CScriptVarFunctionNativeCallback(*this); }
+void CScriptVarFunctionNativeCallback::callFunction(const CFunctionsScopePtr &c) { jsCallback(c, jsUserData); }
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarAccessor
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(Accessor);
+CScriptVarAccessor::CScriptVarAccessor(CTinyJS *Context) : CScriptVarObject(Context, Context->objectPrototype) { }
+CScriptVarAccessor::CScriptVarAccessor(CTinyJS *Context, JSCallback getterFnc, void *getterData, JSCallback setterFnc, void *setterData)
+ : CScriptVarObject(Context)
+{
+ if(getterFnc)
+ addChild(TINYJS_ACCESSOR_GET_VAR, ::newScriptVar(Context, getterFnc, getterData), 0);
+ if(setterFnc)
+ addChild(TINYJS_ACCESSOR_SET_VAR, ::newScriptVar(Context, setterFnc, setterData), 0);
+}
+
+CScriptVarAccessor::CScriptVarAccessor( CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter) : CScriptVarObject(Context, Context->objectPrototype) {
+ if(getter)
+ addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0);
+ if(setter)
+ addChild(TINYJS_ACCESSOR_SET_VAR, setter, 0);
+}
+
+CScriptVarAccessor::~CScriptVarAccessor() {}
+CScriptVarPtr CScriptVarAccessor::clone() { return new CScriptVarAccessor(*this); }
+bool CScriptVarAccessor::isAccessor() { return true; }
+bool CScriptVarAccessor::isPrimitive() { return false; }
+string CScriptVarAccessor::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) {
+ return "";
+}
+string CScriptVarAccessor::getVarType() { return "accessor"; }
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarScope
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(Scope);
+CScriptVarScope::~CScriptVarScope() {}
+CScriptVarPtr CScriptVarScope::clone() { return CScriptVarPtr(); }
+bool CScriptVarScope::isObject() { return false; }
+CScriptVarPtr CScriptVarScope::scopeVar() { return this; } ///< to create var like: var a = ...
+CScriptVarPtr CScriptVarScope::scopeLet() { return this; } ///< to create var like: let a = ...
+CScriptVarLinkWorkPtr CScriptVarScope::findInScopes(const string &childName) {
+ return CScriptVar::findChild(childName);
+}
+CScriptVarScopePtr CScriptVarScope::getParent() { return CScriptVarScopePtr(); } ///< no Parent
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarScopeFnc
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(ScopeFnc);
+CScriptVarScopeFnc::~CScriptVarScopeFnc() {}
+CScriptVarLinkWorkPtr CScriptVarScopeFnc::findInScopes(const string &childName) {
+ CScriptVarLinkWorkPtr ret = findChild(childName);
+ if( !ret ) {
+ if(closure) ret = CScriptVarScopePtr(closure)->findInScopes(childName);
+ else ret = context->getRoot()->findChild(childName);
+ }
+ return ret;
+}
+
+void CScriptVarScopeFnc::setReturnVar(const CScriptVarPtr &var) {
+ addChildOrReplace(TINYJS_RETURN_VAR, var);
+}
+
+CScriptVarPtr CScriptVarScopeFnc::getParameter(const string &name) {
+ return getArgument(name);
+}
+
+CScriptVarPtr CScriptVarScopeFnc::getParameter(int Idx) {
+ return getArgument(Idx);
+}
+CScriptVarPtr CScriptVarScopeFnc::getArgument(const string &name) {
+ return findChildOrCreate(name);
+}
+CScriptVarPtr CScriptVarScopeFnc::getArgument(int Idx) {
+ CScriptVarLinkPtr arguments = findChildOrCreate(TINYJS_ARGUMENTS_VAR);
+ if(arguments) arguments = arguments->getVarPtr()->findChild(int2string(Idx));
+ return arguments ? arguments->getVarPtr() : constScriptVar(Undefined);
+}
+int CScriptVarScopeFnc::getParameterLength() {
+ return getArgumentsLength();
+}
+int CScriptVarScopeFnc::getArgumentsLength() {
+ CScriptVarLinkPtr arguments = findChild(TINYJS_ARGUMENTS_VAR);
+ if(arguments) arguments = arguments->getVarPtr()->findChild("length");
+ return arguments ? arguments.getter()->toNumber().toInt32() : 0;
+}
+
+void CScriptVarScopeFnc::throwError( ERROR_TYPES ErrorType, const string &message ) {
+ throw newScriptVarError(context, ErrorType, message.c_str());
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarScopeLet
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(ScopeLet);
+CScriptVarScopeLet::CScriptVarScopeLet(const CScriptVarScopePtr &Parent) // constructor for LetScope
+ : CScriptVarScope(Parent->getContext()), parent(addChild(TINYJS_SCOPE_PARENT_VAR, Parent, 0))
+ , letExpressionInitMode(false) {}
+
+CScriptVarScopeLet::~CScriptVarScopeLet() {}
+CScriptVarPtr CScriptVarScopeLet::scopeVar() { // to create var like: var a = ...
+ return getParent()->scopeVar();
+}
+CScriptVarScopePtr CScriptVarScopeLet::getParent() { return (CScriptVarPtr)parent; }
+CScriptVarLinkWorkPtr CScriptVarScopeLet::findInScopes(const string &childName) {
+ CScriptVarLinkWorkPtr ret;
+ if(letExpressionInitMode) {
+ return getParent()->findInScopes(childName);
+ } else {
+ ret = findChild(childName);
+ if( !ret ) ret = getParent()->findInScopes(childName);
+ }
+ return ret;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CScriptVarScopeWith
+//////////////////////////////////////////////////////////////////////////
+
+declare_dummy_t(ScopeWith);
+CScriptVarScopeWith::~CScriptVarScopeWith() {}
+CScriptVarPtr CScriptVarScopeWith::scopeLet() { // to create var like: let a = ...
+ return getParent()->scopeLet();
+}
+CScriptVarLinkWorkPtr CScriptVarScopeWith::findInScopes(const string &childName) {
+ if(childName == "this") return with;
+ CScriptVarLinkWorkPtr ret = with->getVarPtr()->findChild(childName);
+ if( !ret ) {
+ ret = with->getVarPtr()->findChildInPrototypeChain(childName);
+ if(ret) {
+ ret(ret->getVarPtr(), ret->getName()); // recreate ret
+ ret.setReferencedOwner(with->getVarPtr()); // fake referenced Owner
+ }
+ }
+ if( !ret ) ret = getParent()->findInScopes(childName);
+ return ret;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// CTinyJS
+//////////////////////////////////////////////////////////////////////////
+
+extern "C" void _registerFunctions(CTinyJS *tinyJS);
+extern "C" void _registerStringFunctions(CTinyJS *tinyJS);
+extern "C" void _registerMathFunctions(CTinyJS *tinyJS);
+
+CTinyJS::CTinyJS() {
+ CScriptVarPtr var;
+ t = 0;
+ haveTry = false;
+ first = 0;
+ uniqueID = 0;
+ currentMarkSlot = -1;
+ stackBase = 0;
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Object-Prototype
+ // must be created as first object because this prototype is the base of all objects
+ objectPrototype = newScriptVar(Object);
+
+ // all objects have a prototype. Also the prototype of prototypes
+ objectPrototype->addChild(TINYJS___PROTO___VAR, objectPrototype, 0);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Function-Prototype
+ // must be created as second object because this is the base of all functions (also constructors)
+ functionPrototype = newScriptVar(Object);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Scopes
+ root = ::newScriptVar(this, Scope);
+ scopes.push_back(root);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Add built-in classes
+ //////////////////////////////////////////////////////////////////////////
+ // Object
+ var = addNative("function Object()", this, &CTinyJS::native_Object, 0, SCRIPTVARLINK_CONSTANT);
+ objectPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ objectPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ addNative("function Object.getPrototypeOf(obj)", this, &CTinyJS::native_Object_getPrototypeOf);
+ addNative("function Object.preventExtensions(obj)", this, &CTinyJS::native_Object_setObjectSecure);
+ addNative("function Object.isExtensible(obj)", this, &CTinyJS::native_Object_isSecureObject);
+ addNative("function Object.seel(obj)", this, &CTinyJS::native_Object_setObjectSecure, (void*)1);
+ addNative("function Object.isSealed(obj)", this, &CTinyJS::native_Object_isSecureObject, (void*)1);
+ addNative("function Object.freeze(obj)", this, &CTinyJS::native_Object_setObjectSecure, (void*)2);
+ addNative("function Object.isFrozen(obj)", this, &CTinyJS::native_Object_isSecureObject, (void*)2);
+ addNative("function Object.keys(obj)", this, &CTinyJS::native_Object_keys);
+ addNative("function Object.getOwnPropertyNames(obj)", this, &CTinyJS::native_Object_keys, (void*)1);
+ addNative("function Object.getOwnPropertyDescriptor(obj,name)", this, &CTinyJS::native_Object_getOwnPropertyDescriptor);
+ addNative("function Object.defineProperty(obj,name,attributes)", this, &CTinyJS::native_Object_defineProperty);
+ addNative("function Object.defineProperties(obj,properties)", this, &CTinyJS::native_Object_defineProperties);
+ addNative("function Object.create(obj,properties)", this, &CTinyJS::native_Object_defineProperties, (void*)1);
+
+ addNative("function Object.prototype.hasOwnProperty(prop)", this, &CTinyJS::native_Object_prototype_hasOwnProperty);
+ objectPrototype_valueOf = addNative("function Object.prototype.valueOf()", this, &CTinyJS::native_Object_prototype_valueOf);
+ objectPrototype_toString = addNative("function Object.prototype.toString(radix)", this, &CTinyJS::native_Object_prototype_toString);
+ pseudo_refered.push_back(&objectPrototype);
+ pseudo_refered.push_back(&objectPrototype_valueOf);
+ pseudo_refered.push_back(&objectPrototype_toString);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Array
+ var = addNative("function Array()", this, &CTinyJS::native_Array, 0, SCRIPTVARLINK_CONSTANT);
+ arrayPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ arrayPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ arrayPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
+ arrayPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&arrayPrototype);
+ var = addNative("function Array.__constructor__()", this, &CTinyJS::native_Array, (void*)1, SCRIPTVARLINK_CONSTANT);
+ var->getFunctionData()->name = "Array";
+
+ //////////////////////////////////////////////////////////////////////////
+ // String
+ var = addNative("function String()", this, &CTinyJS::native_String, 0, SCRIPTVARLINK_CONSTANT);
+ stringPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ stringPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ stringPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
+ stringPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&stringPrototype);
+ var = addNative("function String.__constructor__()", this, &CTinyJS::native_String, (void*)1, SCRIPTVARLINK_CONSTANT);
+ var->getFunctionData()->name = "String";
+
+ //////////////////////////////////////////////////////////////////////////
+ // RegExp
+#ifndef NO_REGEXP
+ var = addNative("function RegExp()", this, &CTinyJS::native_RegExp, 0, SCRIPTVARLINK_CONSTANT);
+ regexpPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ regexpPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ regexpPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
+ regexpPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(®expPrototype);
+#endif /* NO_REGEXP */
+
+ //////////////////////////////////////////////////////////////////////////
+ // Number
+ var = addNative("function Number()", this, &CTinyJS::native_Number, 0, SCRIPTVARLINK_CONSTANT);
+ var->addChild("NaN", constNaN = newScriptVarNumber(this, NaN), SCRIPTVARLINK_CONSTANT);
+ var->addChild("MAX_VALUE", newScriptVarNumber(this, numeric_limits::max()), SCRIPTVARLINK_CONSTANT);
+ var->addChild("MIN_VALUE", newScriptVarNumber(this, numeric_limits::min()), SCRIPTVARLINK_CONSTANT);
+ var->addChild("POSITIVE_INFINITY", constInfinityPositive = newScriptVarNumber(this, InfinityPositive), SCRIPTVARLINK_CONSTANT);
+ var->addChild("NEGATIVE_INFINITY", constInfinityNegative = newScriptVarNumber(this, InfinityNegative), SCRIPTVARLINK_CONSTANT);
+ numberPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ numberPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ numberPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
+ numberPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&numberPrototype);
+ pseudo_refered.push_back(&constNaN);
+ pseudo_refered.push_back(&constInfinityPositive);
+ pseudo_refered.push_back(&constInfinityNegative);
+ var = addNative("function Number.__constructor__()", this, &CTinyJS::native_Number, (void*)1, SCRIPTVARLINK_CONSTANT);
+ var->getFunctionData()->name = "Number";
+
+ //////////////////////////////////////////////////////////////////////////
+ // Boolean
+ var = addNative("function Boolean()", this, &CTinyJS::native_Boolean, 0, SCRIPTVARLINK_CONSTANT);
+ booleanPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ booleanPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ booleanPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
+ booleanPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&booleanPrototype);
+ var = addNative("function Boolean.__constructor__()", this, &CTinyJS::native_Boolean, (void*)1, SCRIPTVARLINK_CONSTANT);
+ var->getFunctionData()->name = "Boolean";
+
+ //////////////////////////////////////////////////////////////////////////
+ // Iterator
+ var = addNative("function Iterator(obj,mode)", this, &CTinyJS::native_Iterator, 0, SCRIPTVARLINK_CONSTANT);
+ iteratorPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ iteratorPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&iteratorPrototype);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Generator
+// var = addNative("function Iterator(obj,mode)", this, &CTinyJS::native_Iterator, 0, SCRIPTVARLINK_CONSTANT);
+#ifndef NO_GENERATORS
+ generatorPrototype = newScriptVar(Object);
+ generatorPrototype->addChild("next", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)0, "Generator.next"), SCRIPTVARLINK_BUILDINDEFAULT);
+ generatorPrototype->addChild("send", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)1, "Generator.send"), SCRIPTVARLINK_BUILDINDEFAULT);
+ generatorPrototype->addChild("close", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)2, "Generator.close"), SCRIPTVARLINK_BUILDINDEFAULT);
+ generatorPrototype->addChild("throw", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)3, "Generator.throw"), SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&generatorPrototype);
+#endif /*NO_GENERATORS*/
+
+ //////////////////////////////////////////////////////////////////////////
+ // Function
+ var = addNative("function Function(params, body)", this, &CTinyJS::native_Function, 0, SCRIPTVARLINK_CONSTANT);
+ var->addChildOrReplace(TINYJS_PROTOTYPE_CLASS, functionPrototype);
+ functionPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ addNative("function Function.prototype.call(objc)", this, &CTinyJS::native_Function_prototype_call);
+ addNative("function Function.prototype.apply(objc, args)", this, &CTinyJS::native_Function_prototype_apply);
+ addNative("function Function.prototype.bind(objc, args)", this, &CTinyJS::native_Function_prototype_bind);
+ functionPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
+ functionPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
+ pseudo_refered.push_back(&functionPrototype);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Error
+ var = addNative("function Error(message, fileName, lineNumber, column)", this, &CTinyJS::native_Error, 0, SCRIPTVARLINK_CONSTANT);
+ errorPrototypes[Error] = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ errorPrototypes[Error]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ errorPrototypes[Error]->addChild("message", newScriptVar(""));
+ errorPrototypes[Error]->addChild("name", newScriptVar("Error"));
+ errorPrototypes[Error]->addChild("fileName", newScriptVar(""));
+ errorPrototypes[Error]->addChild("lineNumber", newScriptVar(-1)); // -1 means not viable
+ errorPrototypes[Error]->addChild("column", newScriptVar(-1)); // -1 means not viable
+
+ var = addNative("function EvalError(message, fileName, lineNumber, column)", this, &CTinyJS::native_EvalError, 0, SCRIPTVARLINK_CONSTANT);
+ errorPrototypes[EvalError] = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ errorPrototypes[EvalError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ errorPrototypes[EvalError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE);
+ errorPrototypes[EvalError]->addChild("name", newScriptVar("EvalError"));
+
+ var = addNative("function RangeError(message, fileName, lineNumber, column)", this, &CTinyJS::native_RangeError, 0, SCRIPTVARLINK_CONSTANT);
+ errorPrototypes[RangeError] = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ errorPrototypes[RangeError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ errorPrototypes[RangeError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE);
+ errorPrototypes[RangeError]->addChild("name", newScriptVar("RangeError"));
+
+ var = addNative("function ReferenceError(message, fileName, lineNumber, column)", this, &CTinyJS::native_ReferenceError, 0, SCRIPTVARLINK_CONSTANT);
+ errorPrototypes[ReferenceError] = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ errorPrototypes[ReferenceError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ errorPrototypes[ReferenceError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE);
+ errorPrototypes[ReferenceError]->addChild("name", newScriptVar("ReferenceError"));
+
+ var = addNative("function SyntaxError(message, fileName, lineNumber, column)", this, &CTinyJS::native_SyntaxError, 0, SCRIPTVARLINK_CONSTANT);
+ errorPrototypes[SyntaxError] = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ errorPrototypes[SyntaxError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ errorPrototypes[SyntaxError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE);
+ errorPrototypes[SyntaxError]->addChild("name", newScriptVar("SyntaxError"));
+
+ var = addNative("function TypeError(message, fileName, lineNumber, column)", this, &CTinyJS::native_TypeError, 0, SCRIPTVARLINK_CONSTANT);
+ errorPrototypes[TypeError] = var->findChild(TINYJS_PROTOTYPE_CLASS);
+ errorPrototypes[TypeError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
+ errorPrototypes[TypeError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE);
+ errorPrototypes[TypeError]->addChild("name", newScriptVar("TypeError"));
+
+
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // add global built-in vars & constants
+ root->addChild("undefined", constUndefined = newScriptVarUndefined(this), SCRIPTVARLINK_CONSTANT);
+ pseudo_refered.push_back(&constUndefined);
+ constNull = newScriptVarNull(this); pseudo_refered.push_back(&constNull);
+ root->addChild("NaN", constNaN, SCRIPTVARLINK_CONSTANT);
+ root->addChild("Infinity", constInfinityPositive, SCRIPTVARLINK_CONSTANT);
+ root->addChild("StopIteration", constStopIteration=newScriptVar(Object, var=newScriptVar(Object), "StopIteration"), SCRIPTVARLINK_CONSTANT);
+ constStopIteration->addChild(TINYJS_PROTOTYPE_CLASS, var, SCRIPTVARLINK_CONSTANT); pseudo_refered.push_back(&constStopIteration);
+ constNegativZero = newScriptVarNumber(this, NegativeZero); pseudo_refered.push_back(&constNegativZero);
+ constFalse = newScriptVarBool(this, false); pseudo_refered.push_back(&constFalse);
+ constTrue = newScriptVarBool(this, true); pseudo_refered.push_back(&constTrue);
+
+ //////////////////////////////////////////////////////////////////////////
+ // add global functions
+ addNative("function eval(jsCode)", this, &CTinyJS::native_eval);
+ native_require_read = 0;
+ addNative("function require(jsFile)", this, &CTinyJS::native_require);
+ addNative("function isNaN(objc)", this, &CTinyJS::native_isNAN);
+ addNative("function isFinite(objc)", this, &CTinyJS::native_isFinite);
+ addNative("function parseInt(string, radix)", this, &CTinyJS::native_parseInt);
+ addNative("function parseFloat(string)", this, &CTinyJS::native_parseFloat);
+
+ root->addChild("JSON", newScriptVar(Object), SCRIPTVARLINK_BUILDINDEFAULT);
+ addNative("function JSON.parse(text, reviver)", this, &CTinyJS::native_JSON_parse);
+
+ _registerFunctions(this);
+ _registerStringFunctions(this);
+ _registerMathFunctions(this);
+}
+
+CTinyJS::~CTinyJS() {
+ ASSERT(!t);
+ for(vector::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it)
+ **it = CScriptVarPtr();
+ for(int i=Error; iremoveAllChildren();
+ scopes.clear();
+ ClearUnreferedVars();
+ root = CScriptVarPtr();
+#ifdef _DEBUG
+ for(CScriptVar *p = first; p; p=p->next)
+ printf("%p\n", p);
+#endif
+#if DEBUG_MEMORY
+ show_allocated();
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// throws an Error & Exception
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const string &message ) {
+ if(execute && haveTry) {
+ execute.set(CScriptResult::Throw, newScriptVarError(this, ErrorType, message.c_str(), t->currentFile.c_str(), t->currentLine(), t->currentColumn()));
+ return;
+ }
+ throw new CScriptException(ErrorType, message, t->currentFile, t->currentLine(), t->currentColumn());
+}
+void CTinyJS::throwException(ERROR_TYPES ErrorType, const string &message ) {
+ throw new CScriptException(ErrorType, message, t->currentFile, t->currentLine(), t->currentColumn());
+}
+
+void CTinyJS::throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const string &message, CScriptTokenizer::ScriptTokenPosition &Pos ){
+ if(execute && haveTry) {
+ execute.set(CScriptResult::Throw, newScriptVarError(this, ErrorType, message.c_str(), t->currentFile.c_str(), Pos.currentLine(), Pos.currentColumn()));
+ return;
+ }
+ throw new CScriptException(ErrorType, message, t->currentFile, Pos.currentLine(), Pos.currentColumn());
+}
+void CTinyJS::throwException(ERROR_TYPES ErrorType, const string &message, CScriptTokenizer::ScriptTokenPosition &Pos ){
+ throw new CScriptException(ErrorType, message, t->currentFile, Pos.currentLine(), Pos.currentColumn());
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::trace() {
+ root->trace();
+}
+
+void CTinyJS::execute(CScriptTokenizer &Tokenizer) {
+ evaluateComplex(Tokenizer);
+}
+
+void CTinyJS::execute(const char *Code, const string &File, int Line, int Column) {
+ evaluateComplex(Code, File, Line, Column);
+}
+
+void CTinyJS::execute(const string &Code, const string &File, int Line, int Column) {
+ evaluateComplex(Code, File, Line, Column);
+}
+
+CScriptVarLinkPtr CTinyJS::evaluateComplex(CScriptTokenizer &Tokenizer) {
+ t = &Tokenizer;
+ CScriptResult execute;
+ try {
+ do {
+ execute_statement(execute);
+ while (t->tk==';') t->match(';'); // skip empty statements
+ } while (t->tk!=LEX_EOF);
+ } catch (...) {
+ haveTry = false;
+ t=0; // clean up Tokenizer
+ throw; //
+ }
+ t=0;
+ ClearUnreferedVars(execute.value);
+
+ uint32_t UniqueID = allocUniqueID();
+ setTemporaryID_recursive(UniqueID);
+ if(execute.value) execute.value->setTemporaryMark_recursive(UniqueID);
+ for(CScriptVar *p = first; p; p=p->next)
+ {
+ if(p->getTemporaryMark() != UniqueID)
+ printf("%s %p\n", p->getVarType().c_str(), p);
+ }
+ freeUniqueID();
+
+ if (execute.value)
+ return CScriptVarLinkPtr(execute.value);
+ // return undefined...
+ return CScriptVarLinkPtr(constScriptVar(Undefined));
+}
+CScriptVarLinkPtr CTinyJS::evaluateComplex(const char *Code, const string &File, int Line, int Column) {
+ CScriptTokenizer Tokenizer(Code, File, Line, Column);
+ return evaluateComplex(Tokenizer);
+}
+CScriptVarLinkPtr CTinyJS::evaluateComplex(const string &Code, const string &File, int Line, int Column) {
+ CScriptTokenizer Tokenizer(Code.c_str(), File, Line, Column);
+ return evaluateComplex(Tokenizer);
+}
+
+string CTinyJS::evaluate(CScriptTokenizer &Tokenizer) {
+ return evaluateComplex(Tokenizer)->toString();
+}
+string CTinyJS::evaluate(const char *Code, const string &File, int Line, int Column) {
+ return evaluateComplex(Code, File, Line, Column)->toString();
+}
+string CTinyJS::evaluate(const string &Code, const string &File, int Line, int Column) {
+ return evaluate(Code.c_str(), File, Line, Column);
+}
+
+CScriptVarFunctionNativePtr CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata, int LinkFlags) {
+ return addNative(funcDesc, ::newScriptVar(this, ptr, userdata), LinkFlags);
+}
+
+CScriptVarFunctionNativePtr CTinyJS::addNative(const string &funcDesc, CScriptVarFunctionNativePtr Var, int LinkFlags) {
+ CScriptLex lex(funcDesc.c_str());
+ CScriptVarPtr base = root;
+
+ lex.match(LEX_R_FUNCTION);
+ string funcName = lex.tkStr;
+ lex.match(LEX_ID);
+ /* Check for dots, we might want to do something like function String.substring ... */
+ while (lex.tk == '.') {
+ lex.match('.');
+ CScriptVarLinkPtr link = base->findChild(funcName);
+ // if it doesn't exist, make an object class
+ if (!link) link = base->addChild(funcName, newScriptVar(Object));
+ base = link->getVarPtr();
+ funcName = lex.tkStr;
+ lex.match(LEX_ID);
+ }
+
+ auto_ptr pFunctionData(new CScriptTokenDataFnc);
+ pFunctionData->name = funcName;
+ lex.match('(');
+ while (lex.tk!=')') {
+ pFunctionData->arguments.push_back(CScriptToken(LEX_ID, lex.tkStr));
+ lex.match(LEX_ID);
+ if (lex.tk!=')') lex.match(',',')');
+ }
+ lex.match(')');
+ Var->setFunctionData(pFunctionData.release());
+ Var->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE);
+
+ base->addChild(funcName, Var, LinkFlags);
+ return Var;
+
+}
+
+CScriptVarLinkWorkPtr CTinyJS::parseFunctionDefinition(const CScriptToken &FncToken) {
+ const CScriptTokenDataFnc &Fnc = FncToken.Fnc();
+// string fncName = (FncToken.token == LEX_T_FUNCTION_OPERATOR) ? TINYJS_TEMP_NAME : Fnc.name;
+ CScriptVarLinkWorkPtr funcVar(newScriptVar((CScriptTokenDataFnc*)&Fnc), Fnc.name);
+ if(scope() != root)
+ funcVar->getVarPtr()->addChild(TINYJS_FUNCTION_CLOSURE_VAR, scope(), 0);
+ funcVar->getVarPtr()->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE)->getVarPtr()->addChild(TINYJS_CONSTRUCTOR_VAR, funcVar->getVarPtr(), SCRIPTVARLINK_WRITABLE);
+ return funcVar;
+}
+
+CScriptVarLinkWorkPtr CTinyJS::parseFunctionsBodyFromString(const string &ArgumentList, const string &FncBody) {
+ string Fnc = "function ("+ArgumentList+"){"+FncBody+"}";
+ CScriptTokenizer tokenizer(Fnc.c_str());
+ return parseFunctionDefinition(tokenizer.getToken());
+}
+CScriptVarPtr CTinyJS::callFunction(const CScriptVarFunctionPtr &Function, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis) {
+ CScriptResult execute;
+ CScriptVarPtr retVar = callFunction(execute, Function, Arguments, This, newThis);
+ execute.cThrow();
+ return retVar;
+}
+
+CScriptVarPtr CTinyJS::callFunction(CScriptResult &execute, const CScriptVarFunctionPtr &Function, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis) {
+ ASSERT(Function && Function->isFunction());
+
+ if(Function->isBounded()) return CScriptVarFunctionBoundedPtr(Function)->callFunction(execute, Arguments, This, newThis);
+
+ CScriptTokenDataFnc *Fnc = Function->getFunctionData();
+ CScriptVarScopeFncPtr functionRoot(::newScriptVar(this, ScopeFnc, CScriptVarPtr(Function->findChild(TINYJS_FUNCTION_CLOSURE_VAR))));
+ if(Fnc->name.size()) functionRoot->addChild(Fnc->name, Function);
+ functionRoot->addChild("this", This);
+ CScriptVarPtr arguments = functionRoot->addChild(TINYJS_ARGUMENTS_VAR, newScriptVar(Object));
+
+ CScriptResult function_execute;
+ int length_proto = Fnc->arguments.size();
+ int length_arguments = Arguments.size();
+ int length = max(length_proto, length_arguments);
+ for(int arguments_idx = 0; arguments_idxaddChild(arguments_idx_str, Arguments[arguments_idx]);
+ } else {
+ value = constScriptVar(Undefined);
+ }
+ if(arguments_idx < length_proto) {
+ CScriptToken &FncArguments = Fnc->arguments[arguments_idx];
+ if(FncArguments.token == LEX_ID)
+ functionRoot->addChildOrReplace(FncArguments.String(), value);
+ else
+ assign_destructuring_var(functionRoot, FncArguments.DestructuringVar(), value, function_execute);
+ }
+ }
+ arguments->addChild("length", newScriptVar(length_arguments));
+
+#ifndef NO_GENERATORS
+ if(Fnc->isGenerator) {
+ return ::newScriptVarCScriptVarGenerator(this, functionRoot, Function);
+ }
+#endif /*NO_GENERATORS*/
+ // execute function!
+ // add the function's execute space to the symbol table so we can recurse
+ CScopeControl ScopeControl(this);
+ ScopeControl.addFncScope(functionRoot);
+ if (Function->isNative()) {
+ try {
+ CScriptVarFunctionNativePtr(Function)->callFunction(functionRoot);
+ CScriptVarLinkPtr ret = functionRoot->findChild(TINYJS_RETURN_VAR);
+ function_execute.set(CScriptResult::Return, ret ? CScriptVarPtr(ret) : constUndefined);
+ } catch (CScriptVarPtr v) {
+ if(haveTry) {
+ function_execute.setThrow(v, "native function '"+Fnc->name+"'");
+ } else if(v->isError()) {
+ CScriptException *err = CScriptVarErrorPtr(v)->toCScriptException();
+ if(err->fileName.empty()) err->fileName = "native function '"+Fnc->name+"'";
+ throw err;
+ }
+ else
+ throw new CScriptException(Error, "uncaught exception: '"+v->toString(function_execute)+"' in native function '"+Fnc->name+"'");
+ }
+ } else {
+ /* we just want to execute the block, but something could
+ * have messed up and left us with the wrong ScriptLex, so
+ * we want to be careful here... */
+ string oldFile = t->currentFile;
+ t->currentFile = Fnc->file;
+ t->pushTokenScope(Fnc->body);
+ if(Fnc->body.front().token == '{')
+ execute_block(function_execute);
+ else {
+ CScriptVarPtr ret = execute_base(function_execute);
+ if(function_execute) function_execute.set(CScriptResult::Return, ret);
+ }
+ t->currentFile = oldFile;
+
+ // because return will probably have called this, and set execute to false
+ }
+ if(function_execute.isReturnNormal()) {
+ if(newThis) *newThis = functionRoot->findChild("this");
+ if(function_execute.isReturn()) {
+ CScriptVarPtr ret = function_execute.value;
+ return ret;
+ }
+ } else
+ execute = function_execute;
+ return constScriptVar(Undefined);
+}
+#ifndef NO_GENERATORS
+void CTinyJS::generator_start(CScriptVarGenerator *Generator)
+{
+ // push current Generator
+ generatorStack.push_back(Generator);
+
+ // safe callers stackBase & set generators one
+ Generator->callersStackBase = stackBase;
+ stackBase = 0;
+
+ // safe callers ScopeSize
+ Generator->callersScopeSize = scopes.size();
+
+ // safe callers Tokenizer & set generators one
+ Generator->callersTokenizer = t;
+ CScriptTokenizer generatorTokenizer;
+ t = &generatorTokenizer;
+
+ // safe callers haveTry
+ Generator->callersHaveTry = haveTry;
+ haveTry = true;
+
+ // push generator's FunctionRoot
+ CScopeControl ScopeControl(this);
+ ScopeControl.addFncScope(Generator->getFunctionRoot());
+
+ // call generator-function
+ CScriptTokenDataFnc *Fnc = Generator->getFunction()->getFunctionData();
+ CScriptResult function_execute;
+ TOKEN_VECT eof(1, CScriptToken());
+ t->pushTokenScope(eof);
+ t->pushTokenScope(Fnc->body);
+ t->currentFile = Fnc->file;
+ try {
+ if(Fnc->body.front().token == '{')
+ execute_block(function_execute);
+ else {
+ execute_base(function_execute);
+ }
+ if(function_execute.isThrow())
+ Generator->setException(function_execute.value);
+ else
+ Generator->setException(constStopIteration);
+// } catch(CScriptVarPtr &e) {
+// Generator->setException(e);
+ } catch(CScriptCoroutine::StopIteration_t &) {
+ Generator->setException(CScriptVarPtr());
+// } catch(CScriptException *e) {
+// Generator->setException(newScriptVarError(this, *e));
+ } catch(...) {
+ // pop current Generator
+ generatorStack.pop_back();
+
+ // restore callers stackBase
+ stackBase = Generator->callersStackBase;
+
+ // restore callers Scope (restored by ScopeControl
+ // scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end());
+
+ // restore callers Tokenizer
+ t = Generator->callersTokenizer;
+
+ // restore callers haveTry
+ haveTry = Generator->callersHaveTry;
+
+ Generator->setException(constStopIteration);
+ // re-throw
+ throw;
+ }
+ // pop current Generator
+ generatorStack.pop_back();
+
+ // restore callers stackBase
+ stackBase = Generator->callersStackBase;
+
+ // restore callers Scope (restored by ScopeControl
+ // scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end());
+
+ // restore callers Tokenizer
+ t = Generator->callersTokenizer;
+
+ // restore callers haveTry
+ haveTry = Generator->callersHaveTry;
+}
+CScriptVarPtr CTinyJS::generator_yield(CScriptResult &execute, CScriptVar *YieldIn)
+{
+ if(!execute) return constUndefined;
+ CScriptVarGenerator *Generator = generatorStack.back();
+ if(Generator->isClosed()) {
+ throwError(execute, TypeError, "yield from closing generator function");
+ return constUndefined;
+ }
+
+ // pop current Generator
+ generatorStack.pop_back();
+
+ // safe generators and restore callers stackBase
+ void *generatorStckBase = stackBase;
+ stackBase = Generator->callersStackBase;
+
+ // safe generators and restore callers scopes
+ Generator->generatorScopes.assign(scopes.begin()+Generator->callersScopeSize, scopes.end());
+ scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end());
+
+ // safe generators and restore callers Tokenizer
+ CScriptTokenizer *generatorTokenizer = t;
+ t = Generator->callersTokenizer;
+
+ // safe generators and restore callers haveTry
+ bool generatorsHaveTry = haveTry;
+ haveTry = Generator->callersHaveTry;
+
+ CScriptVarPtr ret;
+ try {
+ ret = Generator->yield(execute, YieldIn);
+ } catch(...) {
+ // normaly catch(CScriptCoroutine::CScriptCoroutineFinish_t &)
+ // force StopIteration with call CScriptCoroutine::Stop() before CScriptCoroutine::next()
+ // but catch(...) is for paranoia
+
+ // push current Generator
+ generatorStack.push_back(Generator);
+
+ // safe callers and restore generators stackBase
+ Generator->callersStackBase = stackBase;
+ stackBase = generatorStckBase;
+
+ // safe callers and restore generator Scopes
+ Generator->callersScopeSize = scopes.size();
+ scopes.insert(scopes.end(), Generator->generatorScopes.begin(), Generator->generatorScopes.end());
+ Generator->generatorScopes.clear();
+
+ // safe callers and restore generator Tokenizer
+ Generator->callersTokenizer = t;
+ t = generatorTokenizer;
+
+ // safe callers and restore generator haveTry
+ Generator->callersHaveTry = haveTry;
+ haveTry = generatorsHaveTry;
+
+ // re-throw
+ throw;
+ }
+ // push current Generator
+ generatorStack.push_back(Generator);
+
+ // safe callers and restore generators stackBase
+ Generator->callersStackBase = stackBase;
+ stackBase = generatorStckBase;
+
+ // safe callers and restore generator Scopes
+ Generator->callersScopeSize = scopes.size();
+ scopes.insert(scopes.end(), Generator->generatorScopes.begin(), Generator->generatorScopes.end());
+ Generator->generatorScopes.clear();
+
+ // safe callers and restore generator Tokenizer
+ Generator->callersTokenizer = t;
+ t = generatorTokenizer;
+
+ // safe callers and restore generator haveTry
+ Generator->callersHaveTry = haveTry;
+ haveTry = generatorsHaveTry;
+
+ return ret;
+}
+#endif /*NO_GENERATORS*/
+
+
+
+CScriptVarPtr CTinyJS::mathsOp(CScriptResult &execute, const CScriptVarPtr &A, const CScriptVarPtr &B, int op) {
+ if(!execute) return constUndefined;
+ if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) {
+ // check type first
+ if( (A->getVarType() == B->getVarType()) ^ (op == LEX_TYPEEQUAL)) return constFalse;
+ // check value second
+ return mathsOp(execute, A, B, op == LEX_TYPEEQUAL ? LEX_EQUAL : LEX_NEQUAL);
+ }
+ if (!A->isPrimitive() && !B->isPrimitive()) { // Objects both
+ // check pointers
+ switch (op) {
+ case LEX_EQUAL: return constScriptVar(A==B);
+ case LEX_NEQUAL: return constScriptVar(A!=B);
+ }
+ }
+
+ CScriptVarPtr a = A->toPrimitive_hintNumber(execute);
+ CScriptVarPtr b = B->toPrimitive_hintNumber(execute);
+ if(!execute) return constUndefined;
+ // do maths...
+ bool a_isString = a->isString();
+ bool b_isString = b->isString();
+ // both a String or one a String and op='+'
+ if( (a_isString && b_isString) || ((a_isString || b_isString) && op == '+')) {
+ string da = a->isNull() ? "" : a->toString(execute);
+ string db = b->isNull() ? "" : b->toString(execute);
+ switch (op) {
+ case '+':
+ try{
+ return newScriptVar(da+db);
+ } catch(exception& e) {
+ throwError(execute, Error, e.what());
+ return constUndefined;
+ }
+ case LEX_EQUAL: return constScriptVar(da==db);
+ case LEX_NEQUAL: return constScriptVar(da!=db);
+ case '<': return constScriptVar(da': return constScriptVar(da>db);
+ case LEX_GEQUAL: return constScriptVar(da>=db);
+ }
+ }
+ // special for undefined and null --> every true: undefined==undefined, undefined==null, null==undefined and null=null
+ else if( (a->isUndefined() || a->isNull()) && (b->isUndefined() || b->isNull()) ) {
+ switch (op) {
+ case LEX_EQUAL: return constScriptVar(true);
+ case LEX_NEQUAL:
+ case LEX_GEQUAL:
+ case LEX_LEQUAL:
+ case '<':
+ case '>': return constScriptVar(false);
+ }
+ }
+ CNumber da = a->toNumber();
+ CNumber db = b->toNumber();
+ switch (op) {
+ case '+': return a->newScriptVar(da+db);
+ case '-': return a->newScriptVar(da-db);
+ case '*': return a->newScriptVar(da*db);
+ case '/': return a->newScriptVar(da/db);
+ case '%': return a->newScriptVar(da%db);
+ case '&': return a->newScriptVar(da.toInt32()&db.toInt32());
+ case '|': return a->newScriptVar(da.toInt32()|db.toInt32());
+ case '^': return a->newScriptVar(da.toInt32()^db.toInt32());
+ case '~': return a->newScriptVar(~da);
+ case LEX_LSHIFT: return a->newScriptVar(da<newScriptVar(da>>db);
+ case LEX_RSHIFTU: return a->newScriptVar(da.ushift(db));
+ case LEX_EQUAL: return a->constScriptVar(da==db);
+ case LEX_NEQUAL: return a->constScriptVar(da!=db);
+ case '<': return a->constScriptVar(daconstScriptVar(da<=db);
+ case '>': return a->constScriptVar(da>db);
+ case LEX_GEQUAL: return a->constScriptVar(da>=db);
+ default: throw new CScriptException("This operation not supported on the int datatype");
+ }
+}
+
+void CTinyJS::assign_destructuring_var(const CScriptVarPtr &Scope, const CScriptTokenDataDestructuringVar &Objc, const CScriptVarPtr &Val, CScriptResult &execute) {
+ if(!execute) return;
+ if(Objc.vars.size() == 1) {
+ if(Scope)
+ Scope->addChildOrReplace(Objc.vars.front().second, Val);
+ else {
+ CScriptVarLinkWorkPtr v(findInScopes(Objc.vars.front().second));
+ ASSERT(v==true);
+ if(v) v->setVarPtr(Val);
+ }
+ } else {
+ vector Path(1, Val);
+ for(DESTRUCTURING_VARS_cit it=Objc.vars.begin()+1; it!=Objc.vars.end(); ++it) {
+ if(it->second == "}" || it->second == "]")
+ Path.pop_back();
+ else {
+ if(it->second.empty()) continue; // skip empty entries
+ CScriptVarLinkWorkPtr var = Path.back()->findChildWithStringChars(it->first);
+ if(var) var = var.getter(execute); else var = constUndefined;
+ if(!execute) return;
+ if(it->second == "{" || it->second == "[") {
+ Path.push_back(var);
+ } else if(Scope)
+ Scope->addChildOrReplace(it->second, var);
+ else {
+ CScriptVarLinkWorkPtr v(findInScopes(it->second));
+ ASSERT(v==true);
+ if(v) v->setVarPtr(var);
+ }
+ }
+ }
+ }
+}
+
+void CTinyJS::execute_var_init( bool hideLetScope, CScriptResult &execute )
+{
+ for(;;) {
+ t->check(LEX_T_DESTRUCTURING_VAR);
+ CScriptTokenDataDestructuringVar &Objc = t->getToken().DestructuringVar();
+ t->match(LEX_T_DESTRUCTURING_VAR);
+ if(t->tk == '=') {
+ t->match('=');
+ if(hideLetScope) CScriptVarScopeLetPtr(scope())->setletExpressionInitMode(true);
+ CScriptVarPtr Val = execute_assignment(execute);
+ if(hideLetScope) CScriptVarScopeLetPtr(scope())->setletExpressionInitMode(false);
+ assign_destructuring_var(0, Objc, Val, execute);
+ }
+ if (t->tk == ',')
+ t->match(',');
+ else
+ break;
+ }
+}
+void CTinyJS::execute_destructuring(CScriptTokenDataObjectLiteral &Objc, const CScriptVarPtr &Val, CScriptResult &execute) {
+ for(vector::iterator it=Objc.elements.begin(); execute && it!=Objc.elements.end(); ++it) {
+ if(it->value.empty()) continue;
+ CScriptVarPtr rhs = Val->findChildWithStringChars(it->id).getter(execute);
+ if(!rhs) rhs=constUndefined;
+ if(it->value.front().token == LEX_T_OBJECT_LITERAL && it->value.front().Object().destructuring) {
+ execute_destructuring(it->value.front().Object(), rhs, execute);
+ } else {
+ t->pushTokenScope(it->value);
+ CScriptVarLinkWorkPtr lhs = execute_condition(execute);
+ if(lhs->isWritable()) {
+ if (!lhs->isOwned()) {
+ CScriptVarPtr fakedOwner = lhs.getReferencedOwner();
+ if(fakedOwner) {
+ if(!fakedOwner->isExtensible())
+ continue;
+ lhs = fakedOwner->addChildOrReplace(lhs->getName(), lhs);
+ } else
+ lhs = root->addChildOrReplace(lhs->getName(), lhs);
+ }
+ lhs.setter(execute, rhs);
+ }
+ }
+ }
+}
+
+CScriptVarLinkWorkPtr CTinyJS::execute_literals(CScriptResult &execute) {
+ switch(t->tk) {
+ case LEX_ID:
+ if(execute) {
+ CScriptVarLinkWorkPtr a(findInScopes(t->tkStr()));
+ if (!a) {
+ /* Variable doesn't exist! JavaScript says we should create it
+ * (we won't add it here. This is done in the assignment operator)*/
+ if(t->tkStr() == "this")
+ a = root; // fake this
+ else
+ a = CScriptVarLinkPtr(constScriptVar(Undefined), t->tkStr());
+ }
+/*
+ prvention for assignment to this is now done by the tokenizer
+ else if(t->tkStr() == "this")
+ a(a->getVarPtr()); // prevent assign to this
+*/
+ t->match(LEX_ID);
+ return a;
+ }
+ t->match(LEX_ID);
+ break;
+ case LEX_INT:
+ {
+ CScriptVarPtr a = newScriptVar(t->getToken().Int());
+ a->setExtensible(false);
+ t->match(LEX_INT);
+ return a;
+ }
+ break;
+ case LEX_FLOAT:
+ {
+ CScriptVarPtr a = newScriptVar(t->getToken().Float());
+ t->match(LEX_FLOAT);
+ return a;
+ }
+ break;
+ case LEX_STR:
+ {
+ CScriptVarPtr a = newScriptVar(t->getToken().String());
+ t->match(LEX_STR);
+ return a;
+ }
+ break;
+#ifndef NO_REGEXP
+ case LEX_REGEXP:
+ {
+ string::size_type pos = t->getToken().String().find_last_of('/');
+ string source = t->getToken().String().substr(1, pos-1);
+ string flags = t->getToken().String().substr(pos+1);
+ CScriptVarPtr a = newScriptVar(source, flags);
+ t->match(LEX_REGEXP);
+ return a;
+ }
+ break;
+#endif /* NO_REGEXP */
+ case LEX_T_OBJECT_LITERAL:
+ if(execute) {
+ CScriptTokenDataObjectLiteral &Objc = t->getToken().Object();
+ t->match(LEX_T_OBJECT_LITERAL);
+ if(Objc.destructuring) {
+ t->match('=');
+ CScriptVarPtr a = execute_assignment(execute);
+ if(execute) execute_destructuring(Objc, a, execute);
+ return a;
+ } else {
+ CScriptVarPtr a = Objc.type==CScriptTokenDataObjectLiteral::OBJECT ? newScriptVar(Object) : newScriptVar(Array);
+ for(vector::iterator it=Objc.elements.begin(); execute && it!=Objc.elements.end(); ++it) {
+ if(it->value.empty()) continue;
+ CScriptToken &tk = it->value.front();
+ if(tk.token==LEX_T_GET || tk.token==LEX_T_SET) {
+ CScriptTokenDataFnc &Fnc = tk.Fnc();
+ if((tk.token == LEX_T_GET && Fnc.arguments.size()==0) || (tk.token == LEX_T_SET && Fnc.arguments.size()==1)) {
+ CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(tk);
+ CScriptVarLinkWorkPtr child = a->findChild(Fnc.name);
+ if(child && !child->getVarPtr()->isAccessor()) child.clear();
+ if(!child) child = a->addChildOrReplace(Fnc.name, newScriptVar(Accessor));
+ child->getVarPtr()->addChildOrReplace((tk.token==LEX_T_GET?TINYJS_ACCESSOR_GET_VAR:TINYJS_ACCESSOR_SET_VAR), funcVar->getVarPtr());
+ }
+ } else {
+ t->pushTokenScope(it->value);
+ a->addChildOrReplace(it->id, execute_assignment(execute));
+ while(0);
+ }
+ }
+ return a;
+ }
+ } else
+ t->match(LEX_T_OBJECT_LITERAL);
+ break;
+ case LEX_R_LET: // let as expression
+ if(execute) {
+ CScopeControl ScopeControl(this);
+ t->match(LEX_R_LET);
+ t->match('(');
+ t->check(LEX_T_FORWARD);
+ ScopeControl.addLetScope();
+ execute_statement(execute); // execute forwarder
+ execute_var_init(true, execute);
+ t->match(')');
+ return execute_assignment(execute);
+ } else {
+ t->skip(t->getToken().Int());
+ }
+ break;
+ case LEX_T_FUNCTION_OPERATOR:
+ if(execute) {
+ CScriptVarLinkWorkPtr a = parseFunctionDefinition(t->getToken());
+ t->match(LEX_T_FUNCTION_OPERATOR);
+ return a;
+ }
+ t->match(LEX_T_FUNCTION_OPERATOR);
+ break;
+#ifndef NO_GENERATORS
+ case LEX_R_YIELD:
+ if (execute) {
+ t->match(LEX_R_YIELD);
+ CScriptVarPtr result = constUndefined;
+ if (t->tk != ';')
+ result = execute_base(execute);
+ if(execute)
+ return generator_yield(execute, result.getVar());
+ else
+ return constUndefined;
+ } else
+ t->skip(t->getToken().Int());
+ break;
+#endif /*NO_GENERATORS*/
+ case LEX_R_NEW: // new -> create a new object
+ if (execute) {
+ t->match(LEX_R_NEW);
+ CScriptVarLinkWorkPtr parent = execute_literals(execute);
+ CScriptVarLinkWorkPtr objClass = execute_member(parent, execute).getter(execute);
+ if (execute) {
+ CScriptVarPtr Constructor = objClass->getVarPtr();
+ if(Constructor->isFunction()) {
+ CScriptVarPtr obj(newScriptVar(Object));
+ CScriptVarLinkPtr prototype = Constructor->findChild(TINYJS_PROTOTYPE_CLASS);
+ if(!prototype || prototype->getVarPtr()->isUndefined() || prototype->getVarPtr()->isNull()) {
+ prototype = Constructor->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE);
+ obj->addChildOrReplace(TINYJS___PROTO___VAR, prototype, SCRIPTVARLINK_WRITABLE);
+ }
+ CScriptVarLinkPtr __constructor__ = Constructor->findChild("__constructor__");
+ if(__constructor__ && __constructor__->getVarPtr()->isFunction())
+ Constructor = __constructor__;
+ if (stackBase) {
+ int dummy;
+ if(&dummy < stackBase)
+ throwError(execute, Error, "too much recursion");
+ }
+ vector arguments;
+ if (t->tk == '(') {
+ t->match('(');
+ while(t->tk!=')') {
+ CScriptVarPtr value = execute_assignment(execute).getter(execute);
+ if (execute)
+ arguments.push_back(value);
+ if (t->tk!=')') t->match(',', ')');
+ }
+ t->match(')');
+ }
+ if(execute) {
+ CScriptVarPtr returnVar = callFunction(execute, Constructor, arguments, obj, &obj);
+ if(returnVar->isObject())
+ return CScriptVarLinkWorkPtr(returnVar);
+ return CScriptVarLinkWorkPtr(obj);
+ }
+ } else
+ throwError(execute, TypeError, objClass->getName() + " is not a constructor");
+ } else
+ if (t->tk == '(') t->skip(t->getToken().Int());
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_R_TRUE:
+ t->match(LEX_R_TRUE);
+ return constScriptVar(true);
+ case LEX_R_FALSE:
+ t->match(LEX_R_FALSE);
+ return constScriptVar(false);
+ case LEX_R_NULL:
+ t->match(LEX_R_NULL);
+ return constScriptVar(Null);
+ case '(':
+ if(execute) {
+ t->match('(');
+ CScriptVarLinkWorkPtr a = execute_base(execute).getter(execute);
+ t->match(')');
+ return a;
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_T_EXCEPTION_VAR:
+ t->match(LEX_T_EXCEPTION_VAR);
+ if(execute.value) return execute.value;
+ break;
+ default:
+ t->match(LEX_EOF);
+ break;
+ }
+ return constScriptVar(Undefined);
+
+}
+CScriptVarLinkWorkPtr CTinyJS::execute_member(CScriptVarLinkWorkPtr &parent, CScriptResult &execute) {
+ CScriptVarLinkWorkPtr a;
+ parent.swap(a);
+ if(t->tk == '.' || t->tk == '[') {
+ while(t->tk == '.' || t->tk == '[') {
+ parent.swap(a);
+ a = parent.getter(execute); // a is now the "getted" var
+ if(execute && (a->getVarPtr()->isUndefined() || a->getVarPtr()->isNull())) {
+ throwError(execute, ReferenceError, a->getName() + " is " + a->toString(execute));
+ }
+ string name;
+ if(t->tk == '.') {
+ t->match('.');
+ name = t->tkStr();
+ t->match(LEX_ID);
+ } else {
+ if(execute) {
+ t->match('[');
+ name = execute_base(execute)->toString(execute);
+ t->match(']');
+ } else
+ t->skip(t->getToken().Int());
+ }
+ if (execute) {
+ CScriptVarPtr aVar = a;
+ a = aVar->findChildWithPrototypeChain(name);
+ if(!a) {
+ a(constScriptVar(Undefined), name);
+ a.setReferencedOwner(aVar);
+ }
+ }
+ }
+ }
+ return a;
+}
+
+CScriptVarLinkWorkPtr CTinyJS::execute_function_call(CScriptResult &execute) {
+ CScriptVarLinkWorkPtr parent = execute_literals(execute);
+ CScriptVarLinkWorkPtr a = execute_member(parent, execute);
+ while (t->tk == '(') {
+ if (execute) {
+ if(a->getVarPtr()->isUndefined() || a->getVarPtr()->isNull())
+ throwError(execute, ReferenceError, a->getName() + " is " + a->toString(execute));
+ CScriptVarPtr fnc = a.getter(execute)->getVarPtr();
+ if (!fnc->isFunction())
+ throwError(execute, TypeError, a->getName() + " is not a function");
+ if (stackBase) {
+ int dummy;
+ if(&dummy < stackBase)
+ throwError(execute, Error, "too much recursion");
+ }
+
+ t->match('('); // path += '(';
+
+ // grab in all parameters
+ vector arguments;
+ while(t->tk!=')') {
+ CScriptVarLinkWorkPtr value = execute_assignment(execute).getter(execute);
+// path += (*value)->getString();
+ if (execute) {
+ arguments.push_back(value);
+ }
+ if (t->tk!=')') { t->match(','); /*path+=',';*/ }
+ }
+ t->match(')'); //path+=')';
+ // setup a return variable
+ CScriptVarLinkWorkPtr returnVar;
+ if(execute) {
+ if (!parent)
+ parent = findInScopes("this");
+ // if no parent use the root-scope
+ CScriptVarPtr This(parent ? parent->getVarPtr() : (CScriptVarPtr )root);
+ a = callFunction(execute, fnc, arguments, This);
+ }
+ } else {
+ // function, but not executing - just parse args and be done
+ t->match('(');
+ while (t->tk != ')') {
+ CScriptVarLinkWorkPtr value = execute_base(execute);
+ // if (t->tk!=')') t->match(',');
+ }
+ t->match(')');
+ }
+ a = execute_member(parent = a, execute);
+ }
+ return a;
+}
+// R->L: Precedence 3 (in-/decrement) ++ --
+// R<-L: Precedence 4 (unary) ! ~ + - typeof void delete
+bool CTinyJS::execute_unary_rhs(CScriptResult &execute, CScriptVarLinkWorkPtr& a) {
+ t->match(t->tk);
+ a = execute_unary(execute).getter(execute);
+ if(execute) CheckRightHandVar(execute, a);
+ return execute;
+};
+CScriptVarLinkWorkPtr CTinyJS::execute_unary(CScriptResult &execute) {
+ CScriptVarLinkWorkPtr a;
+ switch(t->tk) {
+ case '-':
+ if(execute_unary_rhs(execute, a))
+ a(newScriptVar(-a->getVarPtr()->toNumber(execute)));
+ break;
+ case '+':
+ if(execute_unary_rhs(execute, a))
+ a = newScriptVar(a->getVarPtr()->toNumber(execute));
+ break;
+ case '!':
+ if(execute_unary_rhs(execute, a))
+ a = constScriptVar(!a->getVarPtr()->toBoolean());
+ break;
+ case '~':
+ if(execute_unary_rhs(execute, a))
+ a = newScriptVar(~a->getVarPtr()->toNumber(execute));
+ break;
+ case LEX_R_TYPEOF:
+ if(execute_unary_rhs(execute, a))
+ a = newScriptVar(a->getVarPtr()->getVarType());
+ break;
+ case LEX_R_VOID:
+ if(execute_unary_rhs(execute, a))
+ a = constScriptVar(Undefined);
+ break;
+ case LEX_R_DELETE:
+ t->match(LEX_R_DELETE); // delete
+ a = execute_unary(execute); // no getter - delete can remove the accessor
+ if (execute) {
+ // !!! no right-hand-check by delete
+ if(a->isOwned() && a->isConfigurable() && a->getName() != "this") {
+ a->getOwner()->removeLink(a); // removes the link from owner
+ a = constScriptVar(true);
+ }
+ else
+ a = constScriptVar(false);
+ }
+ break;
+ case LEX_PLUSPLUS:
+ case LEX_MINUSMINUS:
+ {
+ int op = t->tk;
+ t->match(op); // pre increment/decrement
+ CScriptTokenizer::ScriptTokenPosition ErrorPos = t->getPos();
+ a = execute_function_call(execute);
+ if (execute) {
+ if(a->getName().empty())
+ throwError(execute, SyntaxError, string("invalid ")+(op==LEX_PLUSPLUS ? "increment" : "decrement")+" operand", ErrorPos);
+ else if(!a->isOwned() && !a.hasReferencedOwner() && !a->getName().empty())
+ throwError(execute, ReferenceError, a->getName() + " is not defined", ErrorPos);
+ CScriptVarPtr res = newScriptVar(a.getter(execute)->getVarPtr()->toNumber(execute).add(op==LEX_PLUSPLUS ? 1 : -1));
+ if(a->isWritable()) {
+ if(!a->isOwned() && a.hasReferencedOwner() && a.getReferencedOwner()->isExtensible())
+ a.getReferencedOwner()->addChildOrReplace(a->getName(), res);
+ else
+ a.setter(execute, res);
+ }
+ a = res;
+ }
+ }
+ break;
+ default:
+ a = execute_function_call(execute);
+ break;
+ }
+ // post increment/decrement
+ if (t->tk==LEX_PLUSPLUS || t->tk==LEX_MINUSMINUS) {
+ int op = t->tk;
+ t->match(op);
+ if (execute) {
+ if(a->getName().empty())
+ throwError(execute, SyntaxError, string("invalid ")+(op==LEX_PLUSPLUS ? "increment" : "decrement")+" operand", t->getPrevPos());
+ else if(!a->isOwned() && !a.hasReferencedOwner() && !a->getName().empty())
+ throwError(execute, ReferenceError, a->getName() + " is not defined", t->getPrevPos());
+ CNumber num = a.getter(execute)->getVarPtr()->toNumber(execute);
+ CScriptVarPtr res = newScriptVar(num.add(op==LEX_PLUSPLUS ? 1 : -1));
+ if(a->isWritable()) {
+ if(!a->isOwned() && a.hasReferencedOwner() && a.getReferencedOwner()->isExtensible())
+ a.getReferencedOwner()->addChildOrReplace(a->getName(), res);
+ else
+ a.setter(execute, res);
+ }
+ a = newScriptVar(num);
+ }
+ }
+ return a;
+}
+
+// L->R: Precedence 5 (term) * / %
+CScriptVarLinkWorkPtr CTinyJS::execute_term(CScriptResult &execute) {
+ CScriptVarLinkWorkPtr a = execute_unary(execute);
+ if (t->tk=='*' || t->tk=='/' || t->tk=='%') {
+ CheckRightHandVar(execute, a);
+ while (t->tk=='*' || t->tk=='/' || t->tk=='%') {
+ int op = t->tk;
+ t->match(t->tk);
+ CScriptVarLinkWorkPtr b = execute_unary(execute); // L->R
+ if (execute) {
+ CheckRightHandVar(execute, b);
+ a = mathsOp(execute, a.getter(execute), b.getter(execute), op);
+ }
+ }
+ }
+ return a;
+}
+
+// L->R: Precedence 6 (addition/subtraction) + -
+CScriptVarLinkWorkPtr CTinyJS::execute_expression(CScriptResult &execute) {
+ CScriptVarLinkWorkPtr a = execute_term(execute);
+ if (t->tk=='+' || t->tk=='-') {
+ CheckRightHandVar(execute, a);
+ while (t->tk=='+' || t->tk=='-') {
+ int op = t->tk;
+ t->match(t->tk);
+ CScriptVarLinkWorkPtr b = execute_term(execute); // L->R
+ if (execute) {
+ CheckRightHandVar(execute, b);
+ a = mathsOp(execute, a.getter(execute), b.getter(execute), op);
+ }
+ }
+ }
+ return a;
+}
+
+// L->R: Precedence 7 (bitwise shift) << >> >>>
+CScriptVarLinkWorkPtr CTinyJS::execute_binary_shift(CScriptResult &execute) {
+ CScriptVarLinkWorkPtr a = execute_expression(execute);
+ if (t->tk==LEX_LSHIFT || t->tk==LEX_RSHIFT || t->tk==LEX_RSHIFTU) {
+ CheckRightHandVar(execute, a);
+ while (t->tk>=LEX_SHIFTS_BEGIN && t->tk<=LEX_SHIFTS_END) {
+ int op = t->tk;
+ t->match(t->tk);
+
+ CScriptVarLinkWorkPtr b = execute_expression(execute); // L->R
+ if (execute) {
+ CheckRightHandVar(execute, a);
+ // not in-place, so just replace
+ a = mathsOp(execute, a.getter(execute), b.getter(execute), op);
+ }
+ }
+ }
+ return a;
+}
+// L->R: Precedence 8 (relational) < <= > <= in instanceof
+// L->R: Precedence 9 (equality) == != === !===
+CScriptVarLinkWorkPtr CTinyJS::execute_relation(CScriptResult &execute, int set, int set_n) {
+ CScriptVarLinkWorkPtr a = set_n ? execute_relation(execute, set_n, 0) : execute_binary_shift(execute);
+ if ((set==LEX_EQUAL && t->tk>=LEX_RELATIONS_1_BEGIN && t->tk<=LEX_RELATIONS_1_END)
+ || (set=='<' && (t->tk==LEX_LEQUAL || t->tk==LEX_GEQUAL || t->tk=='<' || t->tk=='>' || t->tk == LEX_R_IN || t->tk == LEX_R_INSTANCEOF))) {
+ CheckRightHandVar(execute, a);
+ a = a.getter(execute);
+ while ((set==LEX_EQUAL && t->tk>=LEX_RELATIONS_1_BEGIN && t->tk<=LEX_RELATIONS_1_END)
+ || (set=='<' && (t->tk==LEX_LEQUAL || t->tk==LEX_GEQUAL || t->tk=='<' || t->tk=='>' || t->tk == LEX_R_IN || t->tk == LEX_R_INSTANCEOF))) {
+ int op = t->tk;
+ t->match(t->tk);
+ CScriptVarLinkWorkPtr b = set_n ? execute_relation(execute, set_n, 0) : execute_binary_shift(execute); // L->R
+ if (execute) {
+ CheckRightHandVar(execute, b);
+ string nameOf_b = b->getName();
+ b = b.getter(execute);
+ if(op == LEX_R_IN) {
+ if(!b->getVarPtr()->isObject())
+ throwError(execute, TypeError, "invalid 'in' operand "+nameOf_b);
+ a(constScriptVar( (bool)b->getVarPtr()->findChildWithPrototypeChain(a->toString(execute))));
+ } else if(op == LEX_R_INSTANCEOF) {
+ CScriptVarLinkPtr prototype = b->getVarPtr()->findChild(TINYJS_PROTOTYPE_CLASS);
+ if(!prototype)
+ throwError(execute, TypeError, "invalid 'instanceof' operand "+nameOf_b);
+ else {
+ unsigned int uniqueID = allocUniqueID();
+ CScriptVarPtr object = a->getVarPtr()->findChild(TINYJS___PROTO___VAR);
+ while( object && object!=prototype->getVarPtr() && object->getTemporaryMark() != uniqueID) {
+ object->setTemporaryMark(uniqueID); // prevents recursions
+ object = object->findChild(TINYJS___PROTO___VAR);
+ }
+ freeUniqueID();
+ a(constScriptVar(object && object==prototype->getVarPtr()));
+ }
+ } else
+ a = mathsOp(execute, a, b, op);
+ }
+ }
+ }
+ return a;
+}
+
+// L->R: Precedence 10 (bitwise-and) &
+// L->R: Precedence 11 (bitwise-xor) ^
+// L->R: Precedence 12 (bitwise-or) |
+CScriptVarLinkWorkPtr CTinyJS::execute_binary_logic(CScriptResult &execute, int op, int op_n1, int op_n2) {
+ CScriptVarLinkWorkPtr a = op_n1 ? execute_binary_logic(execute, op_n1, op_n2, 0) : execute_relation(execute);
+ if (t->tk==op) {
+ CheckRightHandVar(execute, a);
+ a = a.getter(execute);
+ while (t->tk==op) {
+ t->match(t->tk);
+ CScriptVarLinkWorkPtr b = op_n1 ? execute_binary_logic(execute, op_n1, op_n2, 0) : execute_relation(execute); // L->R
+ if (execute) {
+ CheckRightHandVar(execute, b);
+ a = mathsOp(execute, a, b.getter(execute), op);
+ }
+ }
+ }
+ return a;
+}
+// L->R: Precedence 13 ==> (logical-and) &&
+// L->R: Precedence 14 ==> (logical-or) ||
+CScriptVarLinkWorkPtr CTinyJS::execute_logic(CScriptResult &execute, int op /*= LEX_OROR*/, int op_n /*= LEX_ANDAND*/) {
+ CScriptVarLinkWorkPtr a = op_n ? execute_logic(execute, op_n, 0) : execute_binary_logic(execute);
+ if (t->tk==op) {
+ if(execute) {
+ CScriptVarLinkWorkPtr b;
+ CheckRightHandVar(execute, a);
+ a(a.getter(execute)); // rebuild a
+ do {
+ if(execute && (op==LEX_ANDAND ? a->toBoolean() : !a->toBoolean())) {
+ t->match(t->tk);
+ b = op_n ? execute_logic(execute, op_n, 0) : execute_binary_logic(execute);
+ CheckRightHandVar(execute, b); a(b.getter(execute)); // rebuild a
+ } else
+ t->skip(t->getToken().Int());
+ } while(t->tk==op);
+ } else
+ t->skip(t->getToken().Int());
+ }
+ return a;
+}
+
+// L<-R: Precedence 15 (condition) ?:
+CScriptVarLinkWorkPtr CTinyJS::execute_condition(CScriptResult &execute) {
+ CScriptVarLinkWorkPtr a = execute_logic(execute);
+ if (t->tk=='?') {
+ CheckRightHandVar(execute, a);
+ bool cond = execute && a.getter(execute)->toBoolean();
+ if(execute) {
+ if(cond) {
+ t->match('?');
+// a = execute_condition(execute);
+ a = execute_assignment(execute);
+ t->check(':');
+ t->skip(t->getToken().Int());
+ } else {
+ CScriptVarLinkWorkPtr b;
+ t->skip(t->getToken().Int());
+ t->match(':');
+ return execute_assignment(execute);
+// return execute_condition(execute);
+ }
+ } else {
+ t->skip(t->getToken().Int());
+ t->skip(t->getToken().Int());
+ }
+ }
+ return a;
+}
+
+// L<-R: Precedence 16 (assignment) = += -= *= /= %= <<= >>= >>>= &= |= ^=
+// now we can return CScriptVarLinkPtr execute_assignment returns always no setters/getters
+// force life of the Owner is no more needed
+CScriptVarLinkPtr CTinyJS::execute_assignment(CScriptResult &execute) {
+ return execute_assignment(execute_condition(execute), execute);
+}
+CScriptVarLinkPtr CTinyJS::execute_assignment(CScriptVarLinkWorkPtr lhs, CScriptResult &execute) {
+ if (t->tk=='=' || (t->tk>=LEX_ASSIGNMENTS_BEGIN && t->tk<=LEX_ASSIGNMENTS_END) ) {
+ int op = t->tk;
+ CScriptTokenizer::ScriptTokenPosition leftHandPos = t->getPos();
+ t->match(t->tk);
+ CScriptVarLinkWorkPtr rhs = execute_assignment(execute).getter(execute); // L<-R
+ if (execute) {
+ if (!lhs->isOwned() && !lhs.hasReferencedOwner() && lhs->getName().empty()) {
+ throw new CScriptException(ReferenceError, "invalid assignment left-hand side (at runtime)", t->currentFile, leftHandPos.currentLine(), leftHandPos.currentColumn());
+ } else if (op != '=' && !lhs->isOwned()) {
+ throwError(execute, ReferenceError, lhs->getName() + " is not defined");
+ }
+ else if(lhs->isWritable()) {
+ if (op=='=') {
+ if (!lhs->isOwned()) {
+ CScriptVarPtr fakedOwner = lhs.getReferencedOwner();
+ if(fakedOwner) {
+ if(!fakedOwner->isExtensible())
+ return rhs->getVarPtr();
+ lhs = fakedOwner->addChildOrReplace(lhs->getName(), lhs);
+ } else
+ lhs = root->addChildOrReplace(lhs->getName(), lhs);
+ }
+ lhs.setter(execute, rhs);
+ return rhs->getVarPtr();
+ } else {
+ CScriptVarPtr result;
+ static int assignments[] = {'+', '-', '*', '/', '%', LEX_LSHIFT, LEX_RSHIFT, LEX_RSHIFTU, '&', '|', '^'};
+ result = mathsOp(execute, lhs, rhs, assignments[op-LEX_PLUSEQUAL]);
+ lhs.setter(execute, result);
+ return result;
+ }
+ } else {
+ // lhs is not writable we ignore lhs & use rhs
+ return rhs->getVarPtr();
+ }
+ }
+ }
+ else
+ CheckRightHandVar(execute, lhs);
+ return lhs.getter(execute);
+}
+// L->R: Precedence 17 (comma) ,
+CScriptVarLinkPtr CTinyJS::execute_base(CScriptResult &execute) {
+ CScriptVarLinkPtr a;
+ for(;;)
+ {
+ a = execute_assignment(execute); // L->R
+ if (t->tk == ',') {
+ t->match(',');
+ } else
+ break;
+ }
+ return a;
+}
+void CTinyJS::execute_block(CScriptResult &execute) {
+ if(execute) {
+ t->match('{');
+ CScopeControl ScopeControl(this);
+ if(t->tk==LEX_T_FORWARD) // add a LetScope only if needed
+ ScopeControl.addLetScope();
+ while (t->tk && t->tk!='}')
+ execute_statement(execute);
+ t->match('}');
+ // scopes.pop_back();
+ }
+ else
+ t->skip(t->getToken().Int());
+}
+void CTinyJS::execute_statement(CScriptResult &execute) {
+ switch(t->tk) {
+ case '{': /* A block of code */
+ execute_block(execute);
+ break;
+ case ';': /* Empty statement - to allow things like ;;; */
+ t->match(';');
+ break;
+ case LEX_T_FORWARD:
+ {
+ CScriptVarPtr in_scope = scope()->scopeLet();
+ STRING_SET_t *varNames = t->getToken().Forwarder().varNames;
+ for(int i=0; ifindChild(*it);
+ if(!a) in_scope->addChild(*it, constScriptVar(Undefined), i==CScriptTokenDataForwards::CONSTS ? SCRIPTVARLINK_CONSTDEFAULT : SCRIPTVARLINK_VARDEFAULT);
+ }
+ in_scope = scope()->scopeVar();
+ }
+ CScriptTokenDataForwards::FNC_SET_t &functions = t->getToken().Forwarder().functions;
+ for(CScriptTokenDataForwards::FNC_SET_it it=functions.begin(); it!=functions.end(); ++it) {
+ CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(*it);
+ in_scope->addChildOrReplace(funcVar->getName(), funcVar, SCRIPTVARLINK_VARDEFAULT);
+ }
+ t->match(LEX_T_FORWARD);
+ }
+ break;
+ case LEX_R_VAR:
+ case LEX_R_LET:
+ case LEX_R_CONST:
+ if(execute)
+ {
+ CScopeControl ScopeControl(this);
+ bool isLet = t->tk==LEX_R_LET, let_ext=false;
+ t->match(t->tk);
+ if(isLet && t->tk=='(') {
+ let_ext = true;
+ t->match('(');
+ t->check(LEX_T_FORWARD);
+ ScopeControl.addLetScope();
+ execute_statement(execute); // forwarder
+ }
+ execute_var_init(let_ext, execute);
+ if(let_ext) {
+ t->match(')');
+ execute_statement(execute);
+ } else
+ t->match(';');
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_R_WITH:
+ if(execute) {
+ t->match(LEX_R_WITH);
+ t->match('(');
+ CScriptVarLinkPtr var = execute_base(execute);
+ t->match(')');
+ CScopeControl ScopeControl(this);
+ ScopeControl.addWithScope(var);
+ execute_statement(execute);
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_R_IF:
+ if(execute) {
+ t->match(LEX_R_IF);
+ t->match('(');
+ bool cond = execute_base(execute)->toBoolean();
+ t->match(')');
+ if(cond && execute) {
+ t->match(LEX_T_SKIP);
+ execute_statement(execute);
+ } else {
+ t->check(LEX_T_SKIP);
+ t->skip(t->getToken().Int());
+ }
+ if (t->tk==LEX_R_ELSE) {
+ if(!cond && execute) {
+ t->match(LEX_R_ELSE);
+ execute_statement(execute);
+ }
+ else
+ t->skip(t->getToken().Int());
+ }
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_T_FOR_IN:
+ {
+ CScriptTokenDataLoop &LoopData = t->getToken().Loop();
+ t->match(LEX_T_FOR_IN);
+ if(!execute) break;
+
+ CScopeControl ScopeControl(this);
+ if(LoopData.init.size()) {
+ t->pushTokenScope(LoopData.init);
+ ScopeControl.addLetScope();
+ execute_statement(execute); // forwarder
+ }
+ if(!execute) break;
+
+ t->pushTokenScope(LoopData.iter);
+ CScriptVarPtr for_in_var = execute_base(execute);
+
+ if(!execute) break;
+
+ CScriptVarPtr Iterator(for_in_var->toIterator(execute, LoopData.type!=CScriptTokenDataLoop::FOR_IN ? 2:1));
+ CScriptVarFunctionPtr Iterator_next(Iterator->findChildWithPrototypeChain("next").getter(execute));
+ if(execute && !Iterator_next) throwError(execute, TypeError, "'" + for_in_var->toString(execute) + "' is not iterable", t->getPrevPos());
+ if(!execute) break;
+ CScriptResult tmp_execute;
+ for(;;) {
+ bool old_haveTry = haveTry;
+ haveTry = true;
+ tmp_execute.set(CScriptResult::Normal, Iterator);
+ t->pushTokenScope(LoopData.condition);
+ execute_statement(tmp_execute);
+ haveTry = old_haveTry;
+ if(tmp_execute.isThrow()){
+ if(tmp_execute.value != constStopIteration) {
+ if(!haveTry)
+ throw new CScriptException("uncaught exception: '"+tmp_execute.value->toString(CScriptResult())+"'", t->currentFile, t->currentLine(), t->currentColumn());
+ else
+ execute = tmp_execute;
+ }
+ break;
+ }
+ t->pushTokenScope(LoopData.body);
+ execute_statement(execute);
+ if(!execute) {
+ bool Continue = false;
+ if(execute.isBreakContinue()
+ &&
+ (execute.target.empty() || find(LoopData.labels.begin(), LoopData.labels.end(), execute.target) != LoopData.labels.end())) {
+ Continue = execute.isContinue();
+ execute.set(CScriptResult::Normal, false);
+ }
+ if(!Continue) break;
+ }
+ }
+ }
+ break;
+ case LEX_T_LOOP:
+ {
+ CScriptTokenDataLoop &LoopData = t->getToken().Loop();
+ t->match(LEX_T_LOOP);
+ if(!execute) break;
+
+ CScopeControl ScopeControl(this);
+ if(LoopData.type == CScriptTokenDataLoop::FOR) {
+ CScriptResult tmp_execute;
+ t->pushTokenScope(LoopData.init);
+ if(t->tk == LEX_T_FORWARD) {
+ ScopeControl.addLetScope();
+ execute_statement(tmp_execute); // forwarder
+ }
+ if(t->tk==LEX_R_VAR || t->tk==LEX_R_LET)
+ execute_statement(tmp_execute); // initialisation
+ else if (t->tk != ';')
+ execute_base(tmp_execute); // initialisation
+ if(!execute(tmp_execute)) break;
+ }
+
+ bool loopCond = true; // Empty Condition -->always true
+ if(LoopData.type != CScriptTokenDataLoop::DO && LoopData.condition.size()) {
+ t->pushTokenScope(LoopData.condition);
+ loopCond = execute_base(execute)->toBoolean();
+ if(!execute) break;
+ }
+ while (loopCond && execute) {
+ t->pushTokenScope(LoopData.body);
+ execute_statement(execute);
+ if(!execute) {
+ bool Continue = false;
+ if(execute.isBreakContinue()
+ &&
+ (execute.target.empty() || find(LoopData.labels.begin(), LoopData.labels.end(), execute.target) != LoopData.labels.end())) {
+ Continue = execute.isContinue();
+ execute.set(CScriptResult::Normal, false);
+ }
+ if(!Continue) break;
+ }
+ if(LoopData.type == CScriptTokenDataLoop::FOR && execute && LoopData.iter.size()) {
+ t->pushTokenScope(LoopData.iter);
+ execute_base(execute);
+ }
+ if(execute && LoopData.condition.size()) {
+ t->pushTokenScope(LoopData.condition);
+ loopCond = execute_base(execute)->toBoolean();
+ }
+ }
+ }
+ break;
+ case LEX_R_BREAK:
+ case LEX_R_CONTINUE:
+ if (execute)
+ {
+ CScriptResult::TYPE type = t->tk==LEX_R_BREAK ? CScriptResult::Break : CScriptResult::Continue;
+ string label;
+ t->match(t->tk);
+ if(t->tk == LEX_ID) {
+ label = t->tkStr();
+ t->match(LEX_ID);
+ }
+ t->match(';');
+ execute.set(type, label);
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_R_RETURN:
+ if (execute) {
+ t->match(LEX_R_RETURN);
+ CScriptVarPtr result = constUndefined;
+ if (t->tk != ';')
+ result = execute_base(execute);
+ t->match(';');
+ execute.set(CScriptResult::Return, result);
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_R_FUNCTION:
+ if(execute) {
+ CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(t->getToken());
+ scope()->scopeVar()->addChildOrReplace(funcVar->getName(), funcVar, SCRIPTVARLINK_VARDEFAULT);
+ }
+ case LEX_T_FUNCTION_PLACEHOLDER:
+ t->match(t->tk);
+ break;
+ case LEX_T_TRY:
+ if(execute) {
+ CScriptTokenDataTry &TryData = t->getToken().Try();
+
+ bool old_haveTry = haveTry;
+ haveTry = true;
+
+ // execute try-block
+ t->pushTokenScope(TryData.tryBlock);
+ execute_block(execute);
+
+ bool isThrow = execute.isThrow();
+
+ if(isThrow && execute.value) {
+ // execute catch-blocks only if value set (spezial case Generator.close() -> only finally-blocks are executed)
+ for(CScriptTokenDataTry::CatchBlock_it catchBlock = TryData.catchBlocks.begin(); catchBlock!=TryData.catchBlocks.end(); catchBlock++) {
+ CScriptResult catch_execute;
+ CScopeControl ScopeControl(this);
+ ScopeControl.addLetScope();
+ t->pushTokenScope(catchBlock->condition); // condition;
+ execute_statement(catch_execute); // forwarder
+ assign_destructuring_var(0, *catchBlock->indentifiers, execute.value, catch_execute);
+ bool condition = true;
+ if(catchBlock->condition.size()>1)
+ condition = execute_base(catch_execute)->toBoolean();
+ if(!catch_execute) {
+ execute = catch_execute;
+ break;
+ } else if(condition) {
+ t->pushTokenScope(catchBlock->block); // condition;
+ execute_block(catch_execute);
+ execute = catch_execute;
+ break;
+ }
+ }
+ }
+ if(TryData.finallyBlock.size()) {
+ CScriptResult finally_execute; // alway execute finally-block
+ t->pushTokenScope(TryData.finallyBlock); // finally;
+ execute_block(finally_execute);
+ execute(finally_execute);
+ }
+ // restore haveTry
+ haveTry = old_haveTry;
+ if(execute.isThrow() && !haveTry) { // (exception in catch or finally or no catch-clause found) and no parent try-block
+ if(execute.value->isError())
+ throw CScriptVarErrorPtr(execute.value)->toCScriptException();
+ throw new CScriptException("uncaught exception: '"+execute.value->toString()+"'", execute.throw_at_file, execute.throw_at_line, execute.throw_at_column);
+ }
+
+ }
+ t->match(LEX_T_TRY);
+ break;
+ case LEX_R_THROW:
+ if(execute) {
+ CScriptTokenizer::ScriptTokenPosition tokenPos = t->getPos();
+ // int tokenStart = t->getToken().pos;
+ t->match(LEX_R_THROW);
+ CScriptVarPtr a = execute_base(execute);
+ if(execute) {
+ if(haveTry)
+ execute.setThrow(a, t->currentFile, tokenPos.currentLine(), tokenPos.currentColumn());
+ else
+ throw new CScriptException("uncaught exception: '"+a->toString(execute)+"'", t->currentFile, tokenPos.currentLine(), tokenPos.currentColumn());
+ }
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_R_SWITCH:
+ if(execute) {
+ t->match(LEX_R_SWITCH);
+ t->match('(');
+ CScriptVarPtr SwitchValue = execute_base(execute);
+ t->match(')');
+ if(execute) {
+ t->match('{');
+ CScopeControl ScopeControl(this);
+ if(t->tk == LEX_T_FORWARD) {
+ ScopeControl.addLetScope(); // add let-scope only if needed
+ execute_statement(execute); // execute forwarder
+ }
+ CScriptTokenizer::ScriptTokenPosition defaultStart = t->getPos();
+ bool hasDefault = false, found = false;
+ while (t->tk) {
+ switch(t->tk) {
+ case LEX_R_CASE:
+ if(!execute)
+ t->skip(t->getToken().Int()); // skip up to'}'
+ else if(found) { // execute && found
+ t->match(LEX_R_CASE);
+ t->skip(t->getToken().Int()); // skip up to ':'
+ t->match(':'); // skip ':' and execute all after ':'
+ } else { // execute && !found
+ t->match(LEX_R_CASE);
+ t->match(LEX_T_SKIP); // skip 'L_T_SKIP'
+ CScriptVarLinkPtr CaseValue = execute_base(execute);
+ CaseValue = mathsOp(execute, CaseValue, SwitchValue, LEX_TYPEEQUAL);
+ if(execute) {
+ found = CaseValue->toBoolean();
+ if(found) t->match(':'); // skip ':' and execute all after ':'
+ else t->skip(t->getToken().Int()); // skip up to next 'case'/'default' or '}'
+ } else
+ t->skip(t->getToken().Int()); // skip up to next 'case'/'default' or '}'
+ }
+ break;
+ case LEX_R_DEFAULT:
+ if(!execute)
+ t->skip(t->getToken().Int()); // skip up to'}' NOTE: no extra 'L_T_SKIP' for skipping tp ':'
+ else {
+ t->match(LEX_R_DEFAULT);
+ if(found)
+ t->match(':'); // skip ':' and execute all after ':'
+ else {
+ hasDefault = true; // in fist pass: skip default-area
+ defaultStart = t->getPos(); // remember pos of default
+ t->skip(t->getToken().Int()); // skip up to next 'case' or '}'
+ }
+ }
+ break;
+ case '}':
+ if(execute && !found && hasDefault) { // if not found & have default -> execute default
+ found = true;
+ t->setPos(defaultStart);
+ t->match(':');
+ } else
+ goto end_while; // goto isn't fine but C supports no "break lable;"
+ break;
+ default:
+ ASSERT(found);
+ execute_statement(execute);
+ break;
+ }
+ }
+end_while:
+ t->match('}');
+ if(execute.isBreak() && execute.target.empty()) {
+ execute.set(CScriptResult::Normal);
+ }
+ } else
+ t->skip(t->getToken().Int());
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ case LEX_T_DUMMY_LABEL:
+ t->match(LEX_T_DUMMY_LABEL);
+ t->match(':');
+ break;
+ case LEX_T_LABEL:
+ {
+ STRING_VECTOR_t Labels;
+ while(t->tk == LEX_T_LABEL) {
+ Labels.push_back(t->tkStr());
+ t->match(LEX_T_LABEL);
+ t->match(':');
+ }
+ if(execute) {
+ execute_statement(execute);
+ if(execute.isBreak() && find(Labels.begin(), Labels.end(), execute.target) != Labels.end()) { // break this label
+ execute.set(CScriptResult::Normal, false);
+ }
+ }
+ else
+ execute_statement(execute);
+ }
+ break;
+ case LEX_EOF:
+ t->match(LEX_EOF);
+ break;
+ default:
+ if(t->tk!=LEX_T_SKIP || execute) {
+ if(t->tk==LEX_T_SKIP) t->match(LEX_T_SKIP);
+ /* Execute a simple statement that only contains basic arithmetic... */
+ CScriptVarPtr ret = execute_base(execute);
+ if(execute) execute.set(CScriptResult::Normal, CScriptVarPtr(ret));
+ t->match(';');
+ } else
+ t->skip(t->getToken().Int());
+ break;
+ }
+}
+
+
+/// Finds a child, looking recursively up the scopes
+CScriptVarLinkPtr CTinyJS::findInScopes(const string &childName) {
+ return scope()->findInScopes(childName);
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// Object
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Object(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(c->getArgument(0)->toObject());
+}
+void CTinyJS::native_Object_getPrototypeOf(const CFunctionsScopePtr &c, void *data) {
+ if(c->getArgumentsLength()>=1) {
+ CScriptVarPtr obj = c->getArgument(0);
+ if(obj->isObject()) {
+ c->setReturnVar(obj->findChild(TINYJS___PROTO___VAR));
+ return;
+ }
+ }
+ c->throwError(TypeError, "argument is not an object");
+}
+
+void CTinyJS::native_Object_setObjectSecure(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr obj = c->getArgument(0);
+ if(!obj->isObject()) c->throwError(TypeError, "argument is not an object");
+ if(data==(void*)2)
+ obj->freeze();
+ else if(data==(void*)1)
+ obj->seal();
+ else
+ obj->preventExtensions();
+ c->setReturnVar(obj);
+}
+
+void CTinyJS::native_Object_isSecureObject(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr obj = c->getArgument(0);
+ if(!obj->isObject()) c->throwError(TypeError, "argument is not an object");
+ bool ret;
+ if(data==(void*)2)
+ ret = obj->isFrozen();
+ else if(data==(void*)1)
+ ret = obj->isSealed();
+ else
+ ret = obj->isExtensible();
+ c->setReturnVar(constScriptVar(ret));
+}
+
+void CTinyJS::native_Object_keys(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr obj = c->getArgument(0);
+ if(!obj->isObject()) c->throwError(TypeError, "argument is not an object");
+ CScriptVarPtr returnVar = c->newScriptVar(Array);
+ c->setReturnVar(returnVar);
+
+ STRING_SET_t keys;
+ obj->keys(keys, data==0);
+
+ uint32_t idx=0;
+ for(STRING_SET_it it=keys.begin(); it!=keys.end(); ++it)
+ returnVar->setArrayIndex(idx++, newScriptVar(*it));
+}
+
+void CTinyJS::native_Object_getOwnPropertyDescriptor(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr obj = c->getArgument(0);
+ if(!obj->isObject()) c->throwError(TypeError, "argument is not an object");
+ c->setReturnVar(obj->getOwnPropertyDescriptor(c->getArgument(1)->toString()));
+}
+
+void CTinyJS::native_Object_defineProperty(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr obj = c->getArgument(0);
+ if(!obj->isObject()) c->throwError(TypeError, "argument is not an object");
+ string name = c->getArgument(1)->toString();
+ CScriptVarPtr attributes = c->getArgument(2);
+ if(!attributes->isObject()) c->throwError(TypeError, "attributes is not an object");
+ const char *err = obj->defineProperty(name, attributes);
+ if(err) c->throwError(TypeError, err);
+ c->setReturnVar(obj);
+}
+
+void CTinyJS::native_Object_defineProperties(const CFunctionsScopePtr &c, void *data) {
+ bool ObjectCreate = data!=0;
+ CScriptVarPtr obj = c->getArgument(0);
+ if(ObjectCreate) {
+ if(!obj->isObject() && !obj->isNull()) c->throwError(TypeError, "argument is not an object or null");
+ obj = newScriptVar(Object, obj);
+ } else
+ if(!obj->isObject()) c->throwError(TypeError, "argument is not an object");
+ c->setReturnVar(obj);
+ if(c->getArrayLength()<2) {
+ if(ObjectCreate) return;
+ c->throwError(TypeError, "Object.defineProperties requires 2 arguments");
+ }
+
+ CScriptVarPtr properties = c->getArgument(1);
+
+ STRING_SET_t names;
+ properties->keys(names, true);
+
+ for(STRING_SET_it it=names.begin(); it!=names.end(); ++it) {
+ CScriptVarPtr attributes = properties->findChildWithStringChars(*it).getter();
+ if(!attributes->isObject()) c->throwError(TypeError, "descriptor for "+*it+" is not an object");
+ const char *err = obj->defineProperty(*it, attributes);
+ if(err) c->throwError(TypeError, err);
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// Object.prototype
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Object_prototype_hasOwnProperty(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr This = c->getArgument("this");
+ string PropStr = c->getArgument("prop")->toString();
+ CScriptVarLinkPtr Prop = This->findChild(PropStr);
+ bool res = Prop && !Prop->getVarPtr()->isUndefined();
+ if(!res) {
+ CScriptVarStringPtr This_asString = This->getRawPrimitive();
+ if(This_asString) {
+ uint32_t Idx = isArrayIndex(PropStr);
+ res = Idx!=uint32_t(-1) && IdxstringLength();
+ }
+ }
+ c->setReturnVar(c->constScriptVar(res));
+}
+void CTinyJS::native_Object_prototype_valueOf(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(c->getArgument("this")->valueOf_CallBack());
+}
+void CTinyJS::native_Object_prototype_toString(const CFunctionsScopePtr &c, void *data) {
+ CScriptResult execute;
+ int radix = 10;
+ if(c->getArgumentsLength()>=1) radix = c->getArgument("radix")->toNumber().toInt32();
+ c->setReturnVar(c->getArgument("this")->toString_CallBack(execute, radix));
+ if(!execute) {
+ // TODO
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// Array
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Array(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr returnVar = c->newScriptVar(Array);
+ c->setReturnVar(returnVar);
+ int length = c->getArgumentsLength();
+ CScriptVarPtr Argument_0_Var = c->getArgument(0);
+ if(data!=0 && length == 1 && Argument_0_Var->isNumber()) {
+ CNumber Argument_0 = Argument_0_Var->toNumber();
+ uint32_t new_size = Argument_0.toUInt32();
+ if(Argument_0.isFinite() && Argument_0 == new_size)
+ returnVar->setArrayIndex(new_size-1, constScriptVar(Undefined));
+ else
+ c->throwError(RangeError, "invalid array length");
+ } else for(int i=0; isetArrayIndex(i, c->getArgument(i));
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// String
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_String(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr arg;
+ if(c->getArgumentsLength()==0)
+ arg = newScriptVar("");
+ else
+ arg = newScriptVar(c->getArgument(0)->toString());
+ if(data)
+ c->setReturnVar(arg->toObject());
+ else
+ c->setReturnVar(arg);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// RegExp
+//////////////////////////////////////////////////////////////////////////
+#ifndef NO_REGEXP
+
+void CTinyJS::native_RegExp(const CFunctionsScopePtr &c, void *data) {
+ int arglen = c->getArgumentsLength();
+ string RegExp, Flags;
+ if(arglen>=1) {
+ RegExp = c->getArgument(0)->toString();
+ try { regex(RegExp, regex_constants::ECMAScript); } catch(regex_error e) {
+ c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code()));
+ }
+ if(arglen>=2) {
+ Flags = c->getArgument(1)->toString();
+ string::size_type pos = Flags.find_first_not_of("gimy");
+ if(pos != string::npos) {
+ c->throwError(SyntaxError, string("invalid regular expression flag ")+Flags[pos]);
+ }
+ }
+ }
+ c->setReturnVar(newScriptVar(RegExp, Flags));
+}
+#endif /* NO_REGEXP */
+
+//////////////////////////////////////////////////////////////////////////
+/// Number
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Number(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr arg;
+ if(c->getArgumentsLength()==0)
+ arg = newScriptVar(0);
+ else
+ arg = newScriptVar(c->getArgument(0)->toNumber());
+ if(data)
+ c->setReturnVar(arg->toObject());
+ else
+ c->setReturnVar(arg);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// Boolean
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Boolean(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarPtr arg;
+ if(c->getArgumentsLength()==0)
+ arg = constScriptVar(false);
+ else
+ arg = constScriptVar(c->getArgument(0)->toBoolean());
+ if(data)
+ c->setReturnVar(arg->toObject());
+ else
+ c->setReturnVar(arg);
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// Iterator
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Iterator(const CFunctionsScopePtr &c, void *data) {
+ if(c->getArgumentsLength()<1) c->throwError(TypeError, "missing argument 0 when calling function Iterator");
+ c->setReturnVar(c->getArgument(0)->toIterator(c->getArgument(1)->toBoolean()?1:3));
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// Generator
+//////////////////////////////////////////////////////////////////////////
+
+#ifndef NO_GENERATORS
+void CTinyJS::native_Generator_prototype_next(const CFunctionsScopePtr &c, void *data) {
+ CScriptVarGeneratorPtr Generator(c->getArgument("this"));
+ if(!Generator) {
+ static const char *fnc[] = {"next","send","close","throw"};
+ c->throwError(TypeError, string(fnc[(int)data])+" method called on incompatible Object");
+ }
+ if((int)data >=2)
+ Generator->native_throw(c, (void*)(((int)data)-2));
+ else
+ Generator->native_send(c, data);
+}
+#endif /*NO_GENERATORS*/
+
+
+//////////////////////////////////////////////////////////////////////////
+/// Function
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_Function(const CFunctionsScopePtr &c, void *data) {
+ int length = c->getArgumentsLength();
+ string params, body;
+ if(length>=1)
+ body = c->getArgument(length-1)->toString();
+ if(length>=2) {
+ params = c->getArgument(0)->toString();
+ for(int i=1; igetArgument(i)->toString());
+ }
+ }
+ c->setReturnVar(parseFunctionsBodyFromString(params,body));
+}
+
+void CTinyJS::native_Function_prototype_call(const CFunctionsScopePtr &c, void *data) {
+ int length = c->getArgumentsLength();
+ CScriptVarPtr Fnc = c->getArgument("this");
+ if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.call called on incompatible Object");
+ CScriptVarPtr This = c->getArgument(0);
+ vector Args;
+ for(int i=1; igetArgument(i));
+ c->setReturnVar(callFunction(Fnc, Args, This));
+}
+void CTinyJS::native_Function_prototype_apply(const CFunctionsScopePtr &c, void *data) {
+ int length=0;
+ CScriptVarPtr Fnc = c->getArgument("this");
+ if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.apply called on incompatible Object");
+ // Argument_0
+ CScriptVarPtr This = c->getArgument(0)->toObject();
+ if(This->isNull() || This->isUndefined()) This=root;
+ // Argument_1
+ CScriptVarPtr Array = c->getArgument(1);
+ if(!Array->isNull() && !Array->isUndefined()) {
+ CScriptVarLinkWorkPtr Length = Array->findChild("length");
+ if(!Length) c->throwError(TypeError, "second argument to Function.prototype.apply must be an array or an array like object");
+ length = Length.getter()->toNumber().toInt32();
+ }
+ vector Args;
+ for(int i=0; ifindChild(int2string(i));
+ if(value) Args.push_back(value);
+ else Args.push_back(constScriptVar(Undefined));
+ }
+ c->setReturnVar(callFunction(Fnc, Args, This));
+}
+void CTinyJS::native_Function_prototype_bind(const CFunctionsScopePtr &c, void *data) {
+ int length = c->getArgumentsLength();
+ CScriptVarPtr Fnc = c->getArgument("this");
+ if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.bind called on incompatible Object");
+ CScriptVarPtr This = c->getArgument(0);
+ if(This->isUndefined() || This->isNull()) This = root;
+ vector Args;
+ for(int i=1; igetArgument(i));
+ c->setReturnVar(newScriptVarFunctionBounded(Fnc, This, Args));
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/// Error
+//////////////////////////////////////////////////////////////////////////
+
+static CScriptVarPtr _newError(CTinyJS *context, ERROR_TYPES type, const CFunctionsScopePtr &c) {
+ int i = c->getArgumentsLength();
+ string message, fileName;
+ int line=-1, column=-1;
+ if(i>0) message = c->getArgument(0)->toString();
+ if(i>1) fileName = c->getArgument(1)->toString();
+ if(i>2) line = c->getArgument(2)->toNumber().toInt32();
+ if(i>3) column = c->getArgument(3)->toNumber().toInt32();
+ return ::newScriptVarError(context, type, message.c_str(), fileName.c_str(), line, column);
+}
+void CTinyJS::native_Error(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, Error,c)); }
+void CTinyJS::native_EvalError(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, EvalError,c)); }
+void CTinyJS::native_RangeError(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, RangeError,c)); }
+void CTinyJS::native_ReferenceError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, ReferenceError,c)); }
+void CTinyJS::native_SyntaxError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, SyntaxError,c)); }
+void CTinyJS::native_TypeError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, TypeError,c)); }
+
+//////////////////////////////////////////////////////////////////////////
+/// global functions
+//////////////////////////////////////////////////////////////////////////
+
+void CTinyJS::native_eval(const CFunctionsScopePtr &c, void *data) {
+ string Code = c->getArgument("jsCode")->toString();
+ CScriptVarScopePtr scEvalScope = scopes.back(); // save scope
+ scopes.pop_back(); // go back to the callers scope
+ CScriptResult execute;
+ CScriptTokenizer *oldTokenizer = t; t=0;
+ try {
+ CScriptTokenizer Tokenizer(Code.c_str(), "eval");
+ t = &Tokenizer;
+ do {
+ execute_statement(execute);
+ while (t->tk==';') t->match(';'); // skip empty statements
+ } while (t->tk!=LEX_EOF);
+ } catch (CScriptException *e) { // script exceptions
+ t = oldTokenizer; // restore tokenizer
+ scopes.push_back(scEvalScope); // restore Scopes;
+ if(haveTry) { // an Error in eval is always catchable
+ CScriptVarPtr E = newScriptVarError(this, e->errorType, e->message.c_str(), e->fileName.c_str(), e->lineNumber, e->column);
+ delete e;
+ throw E;
+ } else
+ throw e;
+ } catch (...) { // all other exceptions
+ t = oldTokenizer; // restore tokenizer
+ scopes.push_back(scEvalScope); // restore Scopes;
+ throw; // re-throw
+ }
+ t = oldTokenizer; // restore tokenizer
+ scopes.push_back(scEvalScope); // restore Scopes;
+ if(execute.value)
+ c->setReturnVar(execute.value);
+}
+
+static int _native_require_read(const string &Fname, std::string &Data) {
+ std::ifstream in(Fname.c_str(), std::ios::in | std::ios::binary);
+ if (in) {
+ in.seekg(0, std::ios::end);
+ Data.resize((string::size_type)in.tellg());
+ in.seekg(0, std::ios::beg);
+ in.read(&Data[0], Data.size());
+ in.close();
+ return(0);
+ }
+ return errno;
+}
+
+void CTinyJS::native_require(const CFunctionsScopePtr &c, void *data) {
+ string File = c->getArgument("jsFile")->toString();
+ string Code;
+ int ErrorNo;
+ if(!native_require_read)
+ native_require_read = _native_require_read; // use builtin if no callback
+
+ if((ErrorNo = native_require_read(File, Code))) {
+ ostringstream msg;
+ msg << "can't read \"" << File << "\" (Error=" << ErrorNo << ")";
+ c->throwError(Error, msg.str());
+ }
+ c->addChildOrReplace("jsCode", c->newScriptVar(Code));
+ native_eval(c, data);
+}
+
+void CTinyJS::native_isNAN(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(constScriptVar(c->getArgument("objc")->toNumber().isNaN()));
+}
+
+void CTinyJS::native_isFinite(const CFunctionsScopePtr &c, void *data) {
+ c->setReturnVar(constScriptVar(c->getArgument("objc")->toNumber().isFinite()));
+}
+
+void CTinyJS::native_parseInt(const CFunctionsScopePtr &c, void *) {
+ CNumber result;
+ result.parseInt(c->getArgument("string")->toString(), c->getArgument("radix")->toNumber().toInt32());
+ c->setReturnVar(c->newScriptVar(result));
+}
+
+void CTinyJS::native_parseFloat(const CFunctionsScopePtr &c, void *) {
+ CNumber result;
+ result.parseFloat(c->getArgument("string")->toString());
+ c->setReturnVar(c->newScriptVar(result));
+}
+
+
+
+void CTinyJS::native_JSON_parse(const CFunctionsScopePtr &c, void *data) {
+ string Code = "�" + c->getArgument("text")->toString();
+ // "�" is a spezal-token - it's for the tokenizer and means the code begins not in Statement-level
+ CScriptVarLinkWorkPtr returnVar;
+ CScriptTokenizer *oldTokenizer = t; t=0;
+ try {
+ CScriptTokenizer Tokenizer(Code.c_str(), "JSON.parse", 0, -1);
+ t = &Tokenizer;
+ CScriptResult execute;
+ returnVar = execute_literals(execute);
+ t->match(LEX_EOF);
+ } catch (CScriptException *e) {
+ t = oldTokenizer;
+ throw e;
+ }
+ t = oldTokenizer;
+
+ if(returnVar)
+ c->setReturnVar(returnVar);
+}
+
+void CTinyJS::setTemporaryID_recursive(uint32_t ID) {
+ for(vector::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it)
+ if(**it) (**it)->setTemporaryMark_recursive(ID);
+ for(int i=Error; isetTemporaryMark_recursive(ID);
+ root->setTemporaryMark_recursive(ID);
+}
+
+void CTinyJS::ClearUnreferedVars(const CScriptVarPtr &extra/*=CScriptVarPtr()*/) {
+ uint32_t UniqueID = allocUniqueID();
+ setTemporaryID_recursive(UniqueID);
+ if(extra) extra->setTemporaryMark_recursive(UniqueID);
+ CScriptVar *p = first;
+
+ while(p)
+ {
+ if(p->getTemporaryMark() != UniqueID)
+ {
+ CScriptVarPtr var = p;
+ var->removeAllChildren();
+ p = var->next;
+ }
+ else
+ p = p->next;
+ }
+ freeUniqueID();
+}
+
diff --git a/TinyJS.h b/TinyJS.h
index 2cfc166..f03181a 100755
--- a/TinyJS.h
+++ b/TinyJS.h
@@ -1,337 +1,2302 @@
-/*
- * TinyJS
- *
- * A single-file Javascript-alike engine
- *
- * Authored By Gordon Williams
- *
- * Copyright (C) 2009 Pur3 Ltd
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#ifndef TINYJS_H
-#define TINYJS_H
-
-#ifdef _WIN32
+/*
+ * TinyJS
+ *
+ * A single-file Javascript-alike engine
+ *
+ * Authored By Gordon Williams
+ *
+ * Copyright (C) 2009 Pur3 Ltd
+ *
+
+ * 42TinyJS
+ *
+ * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine
+ *
+ * Authored / Changed By Armin Diedering
+ *
+ * Copyright (C) 2010-2014 ardisoft
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TINYJS_H
+#define TINYJS_H
+
+#include
+#include
+#include