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 +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef NO_POOL_ALLOCATOR + template + class fixed_size_object {}; +#else +# include "pool_allocator.h" +#endif + +#ifdef _MSC_VER +# if defined(_DEBUG) && defined(_DEBUG_NEW) +# define _AFXDLL +# include // MFC-Kern- und -Standardkomponenten +# define new DEBUG_NEW +# endif +# define DEPRECATED(_Text) __declspec(deprecated(_Text)) +#elif defined(__GNUC__) +# define DEPRECATED(_Text) __attribute__ ((deprecated)) +#else +# define DEPRECATED(_Text) +#endif + +#ifndef ASSERT +# define ASSERT(X) assert(X) +#endif + +#undef TRACE +#ifndef TRACE +#define TRACE printf +#endif // TRACE + +enum LEX_TYPES { + LEX_EOF = 0, +#define LEX_RELATIONS_1_BEGIN LEX_EQUAL + LEX_EQUAL = 256, + LEX_TYPEEQUAL, + LEX_NEQUAL, + LEX_NTYPEEQUAL, +#define LEX_RELATIONS_1_END LEX_NTYPEEQUAL + LEX_LEQUAL, + LEX_GEQUAL, +#define LEX_SHIFTS_BEGIN LEX_LSHIFT + LEX_LSHIFT, + LEX_RSHIFT, + LEX_RSHIFTU, // unsigned +#define LEX_SHIFTS_END LEX_RSHIFTU + LEX_PLUSPLUS, + LEX_MINUSMINUS, + LEX_ANDAND, + LEX_OROR, + LEX_INT, + +#define LEX_ASSIGNMENTS_BEGIN LEX_PLUSEQUAL + LEX_PLUSEQUAL, + LEX_MINUSEQUAL, + LEX_ASTERISKEQUAL, + LEX_SLASHEQUAL, + LEX_PERCENTEQUAL, + LEX_LSHIFTEQUAL, + LEX_RSHIFTEQUAL, + LEX_RSHIFTUEQUAL, // unsigned + LEX_ANDEQUAL, + LEX_OREQUAL, + LEX_XOREQUAL, +#define LEX_ASSIGNMENTS_END LEX_XOREQUAL + +#define LEX_TOKEN_NONSIMPLE_1_BEGIN LEX_TOKEN_STRING_BEGIN +#define LEX_TOKEN_STRING_BEGIN LEX_ID + LEX_ID, + LEX_STR, + LEX_REGEXP, + LEX_T_LABEL, + LEX_T_DUMMY_LABEL, +#define LEX_TOKEN_STRING_END LEX_T_DUMMY_LABEL + + LEX_FLOAT, +#define LEX_TOKEN_NONSIMPLE_1_END LEX_FLOAT + + // reserved words + LEX_R_IF, + LEX_R_ELSE, + LEX_R_DO, + LEX_R_WHILE, + LEX_R_FOR, + LEX_R_IN, + LEX_T_OF, + LEX_R_BREAK, + LEX_R_CONTINUE, + LEX_R_RETURN, + LEX_R_VAR, + LEX_R_LET, + LEX_R_CONST, + LEX_R_WITH, + LEX_R_TRUE, + LEX_R_FALSE, + LEX_R_NULL, + LEX_R_NEW, + LEX_R_TRY, + LEX_R_CATCH, + LEX_R_FINALLY, + LEX_R_THROW, + LEX_R_TYPEOF, + LEX_R_VOID, + LEX_R_DELETE, + LEX_R_INSTANCEOF, + LEX_R_SWITCH, + LEX_R_CASE, + LEX_R_DEFAULT, + + // special token +// LEX_T_FILE, +#define LEX_TOKEN_NONSIMPLE_2_BEGIN LEX_TOKEN_FOR_BEGIN +#define LEX_TOKEN_FOR_BEGIN LEX_T_LOOP + LEX_T_LOOP, + LEX_T_FOR_IN, +#define LEX_TOKEN_FOR_END LEX_T_FOR_IN +#define LEX_TOKEN_FUNCTION_BEGIN LEX_R_FUNCTION + LEX_R_FUNCTION, + LEX_T_FUNCTION_PLACEHOLDER, + LEX_T_FUNCTION_OPERATOR, + LEX_T_GET, + LEX_T_SET, +#define LEX_TOKEN_FUNCTION_END LEX_T_SET + LEX_T_TRY, + LEX_T_OBJECT_LITERAL, + LEX_T_DESTRUCTURING_VAR, + LEX_T_FORWARD, +#define LEX_TOKEN_NONSIMPLE_2_END LEX_T_FORWARD + + LEX_T_EXCEPTION_VAR, + LEX_T_SKIP, + + LEX_R_YIELD, + +}; +#define LEX_TOKEN_DATA_STRING(tk) ((LEX_TOKEN_STRING_BEGIN<= tk && tk <= LEX_TOKEN_STRING_END)) +#define LEX_TOKEN_DATA_FLOAT(tk) (tk==LEX_FLOAT) +#define LEX_TOKEN_DATA_LOOP(tk) (LEX_TOKEN_FOR_BEGIN <= tk && tk <= LEX_TOKEN_FOR_END) +#define LEX_TOKEN_DATA_FUNCTION(tk) (LEX_TOKEN_FUNCTION_BEGIN <= tk && tk <= LEX_TOKEN_FUNCTION_END) +#define LEX_TOKEN_DATA_TRY(tk) (tk == LEX_T_TRY) +#define LEX_TOKEN_DATA_OBJECT_LITERAL(tk) (tk==LEX_T_OBJECT_LITERAL) +#define LEX_TOKEN_DATA_DESTRUCTURING_VAR(tk) (tk==LEX_T_DESTRUCTURING_VAR) +#define LEX_TOKEN_DATA_FORWARDER(tk) (tk==LEX_T_FORWARD) + +#define LEX_TOKEN_DATA_SIMPLE(tk) (!((LEX_TOKEN_NONSIMPLE_1_BEGIN <= tk && tk <= LEX_TOKEN_NONSIMPLE_1_END) || (LEX_TOKEN_NONSIMPLE_2_BEGIN <= tk && tk <= LEX_TOKEN_NONSIMPLE_2_END))) + +enum SCRIPTVARLINK_FLAGS { + SCRIPTVARLINK_WRITABLE = 1<<0, + SCRIPTVARLINK_CONFIGURABLE = 1<<1, + SCRIPTVARLINK_ENUMERABLE = 1<<2, + SCRIPTVARLINK_DEFAULT = SCRIPTVARLINK_WRITABLE | SCRIPTVARLINK_CONFIGURABLE | SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_VARDEFAULT = SCRIPTVARLINK_WRITABLE | SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_CONSTDEFAULT = SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_BUILDINDEFAULT = SCRIPTVARLINK_WRITABLE | SCRIPTVARLINK_CONFIGURABLE, + SCRIPTVARLINK_READONLY = SCRIPTVARLINK_CONFIGURABLE, + SCRIPTVARLINK_READONLY_ENUM = SCRIPTVARLINK_CONFIGURABLE | SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_CONSTANT = 0, +}; + +enum ERROR_TYPES { + Error = 0, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError +}; +#define ERROR_MAX TypeError +#define ERROR_COUNT (ERROR_MAX+1) +extern const char *ERROR_NAME[]; + +#define TEMPORARY_MARK_SLOTS 5 + +#define TINYJS_RETURN_VAR "return" +#define TINYJS_LOKALE_VAR "__locale__" +#define TINYJS_ANONYMOUS_VAR "__anonymous__" +#define TINYJS_ARGUMENTS_VAR "arguments" +#define TINYJS___PROTO___VAR "__proto__" +#define TINYJS_PROTOTYPE_CLASS "prototype" +#define TINYJS_FUNCTION_CLOSURE_VAR "__function_closure__" +#define TINYJS_SCOPE_PARENT_VAR "__scope_parent__" +#define TINYJS_SCOPE_WITH_VAR "__scope_with__" +#define TINYJS_ACCESSOR_GET_VAR "__accessor_get__" +#define TINYJS_ACCESSOR_SET_VAR "__accessor_set__" +#define TINYJS_CONSTRUCTOR_VAR "constructor" +#define TINYJS_TEMP_NAME "" +#define TINYJS_BLANK_DATA "" + +typedef std::vector STRING_VECTOR_t; +typedef STRING_VECTOR_t::iterator STRING_VECTOR_it; + +typedef std::set STRING_SET_t; +typedef STRING_SET_t::iterator STRING_SET_it; + +/// convert the given string into a quoted string suitable for javascript +std::string getJSString(const std::string &str); +/// convert the given int into a string +std::string int2string(int32_t intData); +std::string int2string(uint32_t intData); +/// convert the given double into a string +std::string float2string(const double &floatData); + + +////////////////////////////////////////////////////////////////////////// +/// CScriptException +////////////////////////////////////////////////////////////////////////// + +class CScriptException { +public: + ERROR_TYPES errorType; + std::string message; + std::string fileName; + int lineNumber; + int column; + CScriptException(const std::string &Message, const std::string &File, int Line=-1, int Column=-1) : + errorType(Error), message(Message), fileName(File), lineNumber(Line), column(Column){} + CScriptException(ERROR_TYPES ErrorType, const std::string &Message, const std::string &File, int Line=-1, int Column=-1) : + errorType(ErrorType), message(Message), fileName(File), lineNumber(Line), column(Column){} + CScriptException(const std::string &Message, const char *File="", int Line=-1, int Column=-1) : + errorType(Error), message(Message), fileName(File), lineNumber(Line), column(Column){} + CScriptException(ERROR_TYPES ErrorType, const std::string &Message, const char *File="", int Line=-1, int Column=-1) : + errorType(ErrorType), message(Message), fileName(File), lineNumber(Line), column(Column){} + std::string toString(); +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptLex +////////////////////////////////////////////////////////////////////////// + +class CScriptLex +{ +public: + CScriptLex(const char *Code, const std::string &File="", int Line=0, int Column=0); + struct POS; + int tk; ///< The type of the token that we have + int last_tk; ///< The type of the last token that we have + std::string tkStr; ///< Data contained in the token we have here + + void check(int expected_tk, int alternate_tk=-1); ///< Lexical check wotsit + void match(int expected_tk, int alternate_tk=-1); ///< Lexical match wotsit + void reset(const POS &toPos); ///< Reset this lex so we can start again + + std::string currentFile; + struct POS { + const char *tokenStart; + int currentLine; + const char *currentLineStart; + int currentColumn() { return tokenStart-currentLineStart; } + } pos; + int currentLine() { return pos.currentLine; } + int currentColumn() { return pos.currentColumn(); } + bool lineBreakBeforeToken; +private: + const char *data; + const char *dataPos; + char currCh, nextCh; + + void getNextCh(); + void getNextToken(); ///< Get the text token from our text string +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptTokenData +////////////////////////////////////////////////////////////////////////// + +class CScriptToken; +typedef std::vector TOKEN_VECT; +typedef std::vector::iterator TOKEN_VECT_it; +typedef std::vector::const_iterator TOKEN_VECT_cit; +class CScriptTokenData +{ +protected: + CScriptTokenData() : refs(0){} + virtual ~CScriptTokenData() {} +private: +// CScriptTokenData(const CScriptTokenData &noCopy); +// CScriptTokenData &operator=(const CScriptTokenData &noCopy); +public: + void ref() { refs++; } + void unref() { if(--refs == 0) delete this; } +private: + int refs; +}; +template +class CScriptTokenDataPtr { +public: + CScriptTokenDataPtr() : ptr(0) {} + CScriptTokenDataPtr(const CScriptTokenDataPtr &Copy) : ptr(0) { *this=Copy; } + CScriptTokenDataPtr &operator=(const CScriptTokenDataPtr &Copy) { + if(ptr != Copy.ptr) { + if(ptr) ptr->unref(); + if((ptr = Copy.ptr)) ptr->ref(); + } + return *this; + } + CScriptTokenDataPtr(C &Init) { (ptr=&Init)->ref(); } + ~CScriptTokenDataPtr() { if(ptr) ptr->unref(); } + C *operator->() { return ptr; } + C &operator*() { return *ptr; } + operator bool() { return ptr!=0; } + bool operator==(const CScriptTokenDataPtr& rhs) { return ptr==rhs.ptr; } +private: + C *ptr; +}; + +class CScriptTokenDataString : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataString(const std::string &String) : tokenStr(String) {} + std::string tokenStr; +private: +}; + +class CScriptTokenDataFnc : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataFnc() : line(0),isGenerator(false) {} + std::string file; + int line; + std::string name; + TOKEN_VECT arguments; + TOKEN_VECT body; + std::string getArgumentsString(); + bool isGenerator; +}; + +class CScriptTokenDataForwards : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataForwards() {} + enum { + LETS = 0, + VARS, + CONSTS, + END + }; + STRING_SET_t varNames[END]; + STRING_SET_t vars_in_letscope; + class compare_fnc_token_by_name { + public: + bool operator()(const CScriptToken& lhs, const CScriptToken& rhs) const; + }; + typedef std::set FNC_SET_t; + typedef FNC_SET_t::iterator FNC_SET_it; + FNC_SET_t functions; + bool checkRedefinition(const std::string &Str, bool checkVars); + void addVars( STRING_VECTOR_t &Vars ); + void addConsts( STRING_VECTOR_t &Vars ); + std::string addVarsInLetscope(STRING_VECTOR_t &Vars); + std::string addLets(STRING_VECTOR_t &Lets); + bool empty() { return varNames[LETS].empty() && varNames[VARS].empty() && varNames[CONSTS].empty() && functions.empty(); } +private: +}; +class CScriptTokenDataForwardsPtr { +public: + CScriptTokenDataForwardsPtr() : ptr(0) {} + CScriptTokenDataForwardsPtr(const CScriptTokenDataForwardsPtr &Copy) : ptr(0) { *this=Copy; } + CScriptTokenDataForwardsPtr &operator=(const CScriptTokenDataForwardsPtr &Copy) { + if(ptr != Copy.ptr) { + if(ptr) ptr->unref(); + if((ptr = Copy.ptr)) ptr->ref(); + } + return *this; + } + CScriptTokenDataForwardsPtr(CScriptTokenDataForwards &Init) { (ptr=&Init)->ref(); } + ~CScriptTokenDataForwardsPtr() { if(ptr) ptr->unref(); } + CScriptTokenDataForwards *operator->() { return ptr; } + operator bool() { return ptr!=0; } + bool operator==(const CScriptTokenDataForwardsPtr& rhs) { return ptr==rhs.ptr; } +private: + CScriptTokenDataForwards *ptr; +}; +typedef std::vector FORWARDER_VECTOR_t; + +class CScriptTokenDataLoop : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataLoop() { type=FOR; } + enum {FOR_EACH=0, FOR_IN, FOR_OF, FOR, WHILE, DO} type; // do not change the order + STRING_VECTOR_t labels; + TOKEN_VECT init; + TOKEN_VECT condition; + TOKEN_VECT iter; + TOKEN_VECT body; + std::string getParsableString(const std::string &IndentString="", const std::string &Indent=""); +}; + +typedef std::pair DESTRUCTURING_VAR_t; +typedef std::vector DESTRUCTURING_VARS_t; +typedef DESTRUCTURING_VARS_t::iterator DESTRUCTURING_VARS_it; +typedef DESTRUCTURING_VARS_t::const_iterator DESTRUCTURING_VARS_cit; +class CScriptTokenDataDestructuringVar : public fixed_size_object, public CScriptTokenData { +public: + DESTRUCTURING_VARS_t vars; + void getVarNames(STRING_VECTOR_t Name); + std::string getParsableString(); +private: +}; + +class CScriptTokenDataObjectLiteral : public fixed_size_object, public CScriptTokenData { +public: + enum {ARRAY, OBJECT} type; + int flags; + struct ELEMENT { + std::string id; + TOKEN_VECT value; + }; + bool destructuring; + bool structuring; + std::vector elements; + void setMode(bool Destructuring); + std::string getParsableString(); +private: +}; + +class CScriptTokenDataTry : public fixed_size_object, public CScriptTokenData { +public: + TOKEN_VECT tryBlock; + struct CatchBlock { + CScriptTokenDataPtr indentifiers; + TOKEN_VECT condition; + TOKEN_VECT block; + }; + std::vector catchBlocks; + typedef std::vector::iterator CatchBlock_it; + TOKEN_VECT finallyBlock; + std::string getParsableString(const std::string &IndentString="", const std::string &Indent=""); +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptToken +////////////////////////////////////////////////////////////////////////// + +class CScriptTokenizer; +/* + a Token needs 8 Byte + 2 Bytes for the Row-Position of the Token + 2 Bytes for the Token self + and + 4 Bytes for special Datas in an union + e.g. an int for interger-literals + or pointer for double-literals, + for string-literals or for functions +*/ +class CScriptToken : public fixed_size_object +{ +public: + CScriptToken() : line(0), column(0), token(0), intData(0) {} + CScriptToken(CScriptLex *l, int Match=-1, int Alternate=-1); + CScriptToken(uint16_t Tk, int IntData=0); + CScriptToken(uint16_t Tk, const std::string &TkStr); + CScriptToken(const CScriptToken &Copy) : token(0) { *this = Copy; } + CScriptToken &operator =(const CScriptToken &Copy); + ~CScriptToken() { clear(); } + + int &Int() { ASSERT(LEX_TOKEN_DATA_SIMPLE(token)); return intData; } + std::string &String() { ASSERT(LEX_TOKEN_DATA_STRING(token)); return dynamic_cast(tokenData)->tokenStr; } + double &Float() { ASSERT(LEX_TOKEN_DATA_FLOAT(token)); return *floatData; } + CScriptTokenDataFnc &Fnc() { ASSERT(LEX_TOKEN_DATA_FUNCTION(token)); return *dynamic_cast(tokenData); } + const CScriptTokenDataFnc &Fnc() const { ASSERT(LEX_TOKEN_DATA_FUNCTION(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataObjectLiteral &Object() { ASSERT(LEX_TOKEN_DATA_OBJECT_LITERAL(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataDestructuringVar &DestructuringVar() { ASSERT(LEX_TOKEN_DATA_DESTRUCTURING_VAR(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataLoop &Loop() { ASSERT(LEX_TOKEN_DATA_LOOP(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataTry &Try() { ASSERT(LEX_TOKEN_DATA_TRY(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataForwards &Forwarder() { ASSERT(LEX_TOKEN_DATA_FORWARDER(token)); return *dynamic_cast(tokenData); } +#ifdef _DEBUG + std::string token_str; +#endif + uint16_t line; + uint16_t column; + uint16_t token; + + static std::string getParsableString(TOKEN_VECT &Tokens, const std::string &IndentString="", const std::string &Indent=""); + static std::string getParsableString(TOKEN_VECT_it Begin, TOKEN_VECT_it End, const std::string &IndentString="", const std::string &Indent=""); + static std::string getTokenStr( int token, bool *need_space=0 ); + static const char *isReservedWord(int Token); + static int isReservedWord(const std::string &Str); +private: + + void clear(); + union { + int intData; + double *floatData; + CScriptTokenData *tokenData; + }; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptTokenizer - converts the code in a vector with tokens +////////////////////////////////////////////////////////////////////////// + +class CScriptTokenizer +{ +public: + struct ScriptTokenPosition { + ScriptTokenPosition(TOKEN_VECT *Tokens) : tokens(Tokens), pos(tokens->begin())/*, currentLine(0)*//*, currentColumn(0)*/ {} + bool operator ==(const ScriptTokenPosition &eq) { return pos == eq.pos; } + ScriptTokenPosition &operator =(const ScriptTokenPosition ©) { + tokens=copy.tokens; pos=copy.pos; + return *this; + } + TOKEN_VECT *tokens; + TOKEN_VECT_it pos; + int currentLine() { return pos->line; } + int currentColumn() { return pos->column; } + }; + struct ScriptTokenState { + TOKEN_VECT Tokens; + FORWARDER_VECTOR_t Forwarders; + std::vector Marks; + STRING_VECTOR_t Labels; + STRING_VECTOR_t LoopLabels; + bool LeftHand; + void pushLeftHandState() { States.push_back(LeftHand); } + void popLeftHandeState() { LeftHand = States.back(); States.pop_back(); } + std::vector States; + bool FunctionIsGenerator; + bool HaveReturnValue; + }; + CScriptTokenizer(); + CScriptTokenizer(CScriptLex &Lexer); + CScriptTokenizer(const char *Code, const std::string &File="", int Line=0, int Column=0); + void tokenizeCode(CScriptLex &Lexer); + + CScriptToken &getToken() { return *(tokenScopeStack.back().pos); } + void getNextToken(); + bool check(int ExpectedToken, int AlternateToken=-1); + void match(int ExpectedToken, int AlternateToken=-1); + void pushTokenScope(TOKEN_VECT &Tokens); + ScriptTokenPosition &getPos() { return tokenScopeStack.back(); } + void setPos(ScriptTokenPosition &TokenPos); + ScriptTokenPosition &getPrevPos() { return prevPos; } + void skip(int Tokens); + int tk; // current Token + std::string currentFile; + int currentLine() { return getPos().currentLine();} + int currentColumn() { return getPos().currentColumn();} + const std::string &tkStr() { static std::string empty; return LEX_TOKEN_DATA_STRING(getToken().token)?getToken().String():empty; } +private: + void tokenizeTry(ScriptTokenState &State, int Flags); + void tokenizeSwitch(ScriptTokenState &State, int Flags); + void tokenizeWith(ScriptTokenState &State, int Flags); + void tokenizeWhileAndDo(ScriptTokenState &State, int Flags); + void tokenizeIf(ScriptTokenState &State, int Flags); + void tokenizeFor(ScriptTokenState &State, int Flags); + CScriptToken tokenizeVarIdentifier(STRING_VECTOR_t *VarNames=0, bool *NeedAssignment=0); + void tokenizeFunction(ScriptTokenState &State, int Flags, bool noLetDef=false); + void tokenizeLet(ScriptTokenState &State, int Flags, bool noLetDef=false); + void tokenizeVarNoConst(ScriptTokenState &State, int Flags); + void tokenizeVarAndConst(ScriptTokenState &State, int Flags); + void _tokenizeLiteralObject(ScriptTokenState &State, int Flags); + void _tokenizeLiteralArray(ScriptTokenState &State, int Flags); + + void tokenizeLiteral(ScriptTokenState &State, int Flags); + void tokenizeMember(ScriptTokenState &State, int Flags); + void tokenizeFunctionCall(ScriptTokenState &State, int Flags); + void tokenizeSubExpression(ScriptTokenState &State, int Flags); + void tokenizeLogic(ScriptTokenState &State, int Flags, int op= LEX_OROR, int op_n=LEX_ANDAND); + void tokenizeCondition(ScriptTokenState &State, int Flags); + void tokenizeAssignment(ScriptTokenState &State, int Flags); + void tokenizeExpression(ScriptTokenState &State, int Flags); + void tokenizeBlock(ScriptTokenState &State, int Flags); + void tokenizeStatementNoLet(ScriptTokenState &State, int Flags); + void tokenizeStatement(ScriptTokenState &State, int Flags); + + int pushToken(TOKEN_VECT &Tokens, int Match=-1, int Alternate=-1); + int pushToken(TOKEN_VECT &Tokens, const CScriptToken &Token); + void pushForwarder(ScriptTokenState &State, bool noMarks=false); + void removeEmptyForwarder(ScriptTokenState &State); + void pushForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, std::vector &Marks); + void removeEmptyForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, std::vector &Marks); + void throwTokenNotExpected(); + CScriptLex *l; + TOKEN_VECT tokens; + ScriptTokenPosition prevPos; + std::vector tokenScopeStack; +}; + + +////////////////////////////////////////////////////////////////////////// +/// forward-declaration +////////////////////////////////////////////////////////////////////////// + +class CNumber; +class CScriptVar; +class CScriptVarPtr; +template class CScriptVarPointer; +class CScriptVarLink; +class CScriptVarLinkPtr; +class CScriptVarLinkWorkPtr; + +class CScriptVarPrimitive; +typedef CScriptVarPointer CScriptVarPrimitivePtr; + +class CScriptVarScopeFnc; +typedef CScriptVarPointer CFunctionsScopePtr; +typedef void (*JSCallback)(const CFunctionsScopePtr &var, void *userdata); + +class CTinyJS; +class CScriptResult; + +////////////////////////////////////////////////////////////////////////// +/// CScriptVar +////////////////////////////////////////////////////////////////////////// + +typedef std::vector SCRIPTVAR_CHILDS_t; +typedef SCRIPTVAR_CHILDS_t::iterator SCRIPTVAR_CHILDS_it; +typedef SCRIPTVAR_CHILDS_t::const_iterator SCRIPTVAR_CHILDS_cit; + +class CScriptVar : public fixed_size_object { +protected: + CScriptVar(CTinyJS *Context, const CScriptVarPtr &Prototype); ///< Create + CScriptVar(const CScriptVar &Copy); ///< Copy protected -> use clone for public +private: + CScriptVar & operator=(const CScriptVar &Copy) MEMBER_DELETE; ///< private -> no assignment-Copy +public: + virtual ~CScriptVar(); + virtual CScriptVarPtr clone()=0; + + /// Type + virtual bool isObject(); ///< is an Object + virtual bool isArray(); ///< is an Array + virtual bool isError(); ///< is an ErrorObject + virtual bool isRegExp(); ///< is a RegExpObject + virtual bool isAccessor(); ///< is an Accessor + virtual bool isNull(); ///< is Null + virtual bool isUndefined();///< is Undefined + virtual bool isNaN(); ///< is NaN + virtual bool isString(); ///< is String + virtual bool isInt(); ///< is Integer + virtual bool isBool(); ///< is Bool + virtual int isInfinity(); ///< is Infinity ///< +1==POSITIVE_INFINITY, -1==NEGATIVE_INFINITY, 0==is not an InfinityVar + virtual bool isDouble(); ///< is Double + + virtual bool isRealNumber(); ///< is isInt | isDouble + virtual bool isNumber(); ///< is isNaN | isInt | isDouble | isInfinity + virtual bool isPrimitive();///< isNull | isUndefined | isNaN | isString | isInt | isDouble | isInfinity + + virtual bool isFunction(); ///< is CScriptVarFunction / CScriptVarFunctionNativeCallback / CScriptVarFunctionNativeClass + virtual bool isNative(); ///< is CScriptVarFunctionNativeCallback / CScriptVarFunctionNativeClass + virtual bool isBounded(); ///< is CScriptVarFunctionBounded + + virtual bool isIterator(); + virtual bool isGenerator(); + + bool isBasic() { return Childs.empty(); } ///< Is this *not* an array/object/etc + + + ////////////////////////////////////////////////////////////////////////// + /// Value + ////////////////////////////////////////////////////////////////////////// + + virtual CScriptVarPrimitivePtr getRawPrimitive()=0; ///< is Var==Primitive -> return this isObject return Value + CScriptVarPrimitivePtr toPrimitive(); ///< by default call getDefaultValue_hintNumber by a Date-object calls getDefaultValue_hintString + virtual CScriptVarPrimitivePtr toPrimitive(CScriptResult &execute); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintString(int32_t radix=0); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintString(CScriptResult &execute, int32_t radix=0); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintNumber(); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintNumber(CScriptResult &execute); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + + CScriptVarPtr callJS_toString(CScriptResult &execute, int radix=0); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + CScriptVarPtr callJS_valueOf(CScriptResult &execute); + virtual CScriptVarPtr valueOf_CallBack(); + + CNumber toNumber(); + CNumber toNumber(CScriptResult &execute); + virtual bool toBoolean(); + std::string toString(int32_t radix=0); ///< shortcut for this->toPrimitive_hintString()->toCString(); + std::string toString(CScriptResult &execute, int32_t radix=0); ///< shortcut for this->toPrimitive_hintString(execute)->toCString(); +#define WARN_DEPRECATED +#ifdef WARN_DEPRECATED + int DEPRECATED("getInt() is deprecated use toNumber().toInt32 instead") getInt(); + bool DEPRECATED("getBool() is deprecated use toBoolean() instead") getBool(); + double DEPRECATED("getDouble() is deprecated use toNumber().toDouble() instead") getDouble(); + std::string DEPRECATED("getString() is deprecated use toString() instead") getString(); +#else + int getInt(); + bool getBool(); + double getDouble(); + std::string getString(); +#endif + virtual CScriptTokenDataFnc *getFunctionData(); ///< { return 0; } + + virtual CScriptVarPtr toObject()=0; + + CScriptVarPtr toIterator(int Mode=3); + CScriptVarPtr toIterator(CScriptResult &execute, int Mode=3); + + +// virtual std::string getParsableString(const std::string &indentString, const std::string &indent, bool &hasRecursion); ///< get Data as a parsable javascript string +#define getParsableStringRecursionsCheck() do{ \ + if(uniqueID) { \ + if(uniqueID==getTemporaryMark()) { hasRecursion=true; return "recursion"; } \ + setTemporaryMark(uniqueID); \ + } \ + } while(0) + + std::string getParsableString(); ///< get Data as a parsable javascript string + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); ///< get Data as a parsable javascript string + virtual std::string getVarType()=0; + +#ifdef WARN_DEPRECATED + CScriptVarPtr DEPRECATED("getNumericVar() is deprecated use toNumber() instead") getNumericVar(); ///< returns an Integer, a Double, an Infinity or a NaN +#else + CScriptVarPtr getNumericVar(); ///< returns an Integer, a Double, an Infinity or a NaN +#endif + + ////////////////////////////////////////////////////////////////////////// + /// Childs + ////////////////////////////////////////////////////////////////////////// + + + CScriptVarPtr getOwnPropertyDescriptor(const std::string &Name); + const char *defineProperty(const std::string &Name, CScriptVarPtr Attributes); + + /// flags + void setExtensible(bool On=true) { extensible=On; } + void preventExtensions() { extensible=false; } + bool isExtensible() const { return extensible; } + void seal(); + bool isSealed() const; + void freeze(); + bool isFrozen() const; + + /// find + CScriptVarLinkPtr findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 + CScriptVarLinkWorkPtr findChildWithStringChars(const std::string &childName); + CScriptVarLinkPtr findChildInPrototypeChain(const std::string &childName); + CScriptVarLinkWorkPtr findChildWithPrototypeChain(const std::string &childName); + CScriptVarLinkPtr findChildByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) + CScriptVarLinkPtr findChildOrCreate(const std::string &childName/*, int varFlags=SCRIPTVAR_UNDEFINED*/); ///< Tries to find a child with the given name, or will create it with the given flags + CScriptVarLinkPtr findChildOrCreateByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) + void keys(STRING_SET_t &Keys, bool OnlyEnumerable=true, uint32_t ID=0); + /// add & remove + CScriptVarLinkPtr addChild(const std::string &childName, const CScriptVarPtr &child, int linkFlags = SCRIPTVARLINK_DEFAULT); + CScriptVarLinkPtr DEPRECATED("addChildNoDup is deprecated use addChildOrReplace instead!") addChildNoDup(const std::string &childName, const CScriptVarPtr &child, int linkFlags = SCRIPTVARLINK_DEFAULT); + CScriptVarLinkPtr addChildOrReplace(const std::string &childName, const CScriptVarPtr &child, int linkFlags = SCRIPTVARLINK_DEFAULT); ///< add a child overwriting any with the same name + bool removeLink(CScriptVarLinkPtr &link); ///< Remove a specific link (this is faster than finding via a child) + virtual void removeAllChildren(); + + /// ARRAY + CScriptVarPtr getArrayIndex(uint32_t idx); ///< The the value at an array index + void setArrayIndex(uint32_t idx, const CScriptVarPtr &value); ///< Set the value at an array index + uint32_t getArrayLength(); ///< If this is an array, return the number of items in it (else 0) + + ////////////////////////////////////////////////////////////////////////// + int getChildren() { return Childs.size(); } ///< Get the number of children + CTinyJS *getContext() { return context; } + CScriptVarPtr mathsOp(const CScriptVarPtr &b, int op); ///< do a maths op with another script variable + + void trace(const std::string &name = ""); ///< Dump out the contents of this using trace + void trace(std::string &indentStr, uint32_t uniqueID, const std::string &name = ""); ///< Dump out the contents of this using trace + std::string getFlagsAsString(); ///< For debugging - just dump a string version of the flags +// void getJSON(std::ostringstream &destination, const std::string linePrefix=""); ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) + + SCRIPTVAR_CHILDS_t Childs; + + /// For memory management/garbage collection +private: + CScriptVar *ref(); ///< Add reference to this variable + void unref(); ///< Remove a reference, and delete this variable if required +public: + int getRefs(); ///< Get the number of references to this script variable + template + operator T *(){ T *ret = dynamic_cast(this); ASSERT(ret!=0); return ret; } + template + T *get(){ T *ret = dynamic_cast(this); ASSERT(ret!=0); return ret; } + + //CScriptVarPtr newScriptVar(const CNumber &t); // { return ::newScriptVar(context, t); } + template CScriptVarPtr newScriptVar(T t); // { return ::newScriptVar(context, t); } + template CScriptVarPtr newScriptVar(T1 t1, T2 t2); // { return ::newScriptVar(context, t); } + template const CScriptVarPtr &constScriptVar(T t); // { return ::newScriptVar(context, t); } + void setTemporaryMark(uint32_t ID); // defined as inline at end of this file { temporaryMark[context->getCurrentMarkSlot()] = ID; } + virtual void setTemporaryMark_recursive(uint32_t ID); + uint32_t getTemporaryMark(); // defined as inline at end of this file { return temporaryMark[context->getCurrentMarkSlot()]; } +protected: + bool extensible; + CTinyJS *context; + int refs; ///< The number of references held to this - used for garbage collection + CScriptVar *prev; +public: + CScriptVar *next; + uint32_t temporaryMark[TEMPORARY_MARK_SLOTS]; + + friend class CScriptVarPtr; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPtr +////////////////////////////////////////////////////////////////////////// + +class CScriptVarPtr { +public: + // construct + CScriptVarPtr() : var(0) {} ///< 0-Pointer + CScriptVarPtr(CScriptVar *Var) : var(Var) { if(var) var->ref(); } // creates a new CScriptVar (from new); + + // copy + CScriptVarPtr(const CScriptVarPtr &Copy) : var(Copy.var) { if(var) var->ref(); } + CScriptVarPtr& operator=(const CScriptVarPtr &Copy) { + if(var != Copy.var) { + if(var) var->unref(); + var = Copy.var; if(var) var->ref(); + } + return *this; + } + // deconstruct + ~CScriptVarPtr() { if(var) var->unref(); } + + // if + operator bool() const { return var!=0; } + + bool operator ==(const CScriptVarPtr &Other) const { return var == Other.var; } + bool operator !=(const CScriptVarPtr &Other) const { return var != Other.var; } + + // access + CScriptVar * operator ->() const { return var; } + CScriptVar *getVar() const { return var; } + + void clear() { if(var) var->unref(); var=0; } +protected: + CScriptVar *var; +}; + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPointer - template +////////////////////////////////////////////////////////////////////////// + +template +class CScriptVarPointer : public CScriptVarPtr { +public: + CScriptVarPointer() {} + CScriptVarPointer(CScriptVar *Var) : CScriptVarPtr(dynamic_cast(Var)) {} + CScriptVarPointer(const CScriptVarPtr &Copy) : CScriptVarPtr(dynamic_cast(Copy.getVar())) {} + CScriptVarPointer &operator=(const CScriptVarPtr &Copy) { CScriptVarPtr::operator=(dynamic_cast(Copy.getVar())); return *this; } + C * operator ->() const { C *Var = dynamic_cast(var); ASSERT(var && Var); return Var; } +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLink +////////////////////////////////////////////////////////////////////////// +class CScriptVarLink : public fixed_size_object +{ +private: // prevent gloabal creating + CScriptVarLink(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT); +private: // prevent Copy + CScriptVarLink(const CScriptVarLink &link) MEMBER_DELETE; ///< Copy constructor +public: + ~CScriptVarLink(); + + const std::string &getName() const { return name; } + + int getFlags() { return flags; } + const CScriptVarPtr &getVarPtr() const { return var; } + const CScriptVarPtr &setVarPtr(const CScriptVarPtr &Var) { return var = Var; } ///< simple Replace the Variable pointed to + + + bool isOwned() const { return owner!=0; } + + bool isWritable() const { return (flags & SCRIPTVARLINK_WRITABLE) != 0; } + void setWritable(bool On) { On ? (flags |= SCRIPTVARLINK_WRITABLE) : (flags &= ~SCRIPTVARLINK_WRITABLE); } + bool isConfigurable() const { return (flags & SCRIPTVARLINK_CONFIGURABLE) != 0; } + void setConfigurable(bool On) { On ? (flags |= SCRIPTVARLINK_CONFIGURABLE) : (flags &= ~SCRIPTVARLINK_CONFIGURABLE); } + bool isEnumerable() const { return (flags & SCRIPTVARLINK_ENUMERABLE) != 0; } + void setEnumerable(bool On) { On ? (flags |= SCRIPTVARLINK_ENUMERABLE) : (flags &= ~SCRIPTVARLINK_ENUMERABLE); } + + CScriptVar *getOwner() { return owner; }; + void setOwner(CScriptVar *Owner) { owner = Owner; } + + /// forward to ScriptVar + + CScriptVarPrimitivePtr toPrimitive() { ///< by default call getDefaultValue_hintNumber by a Date-object calls getDefaultValue_hintString + return var->toPrimitive(); } + CScriptVarPrimitivePtr toPrimitive(CScriptResult &execute) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive(execute); } + CScriptVarPrimitivePtr toPrimitive_hintString(int32_t radix=0) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintString(radix); } + CScriptVarPrimitivePtr toPrimitive_hintString(CScriptResult &execute, int32_t radix=0) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintString(execute, radix); } + CScriptVarPrimitivePtr toPrimitive_hintNumber() { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintNumber(); } + CScriptVarPrimitivePtr toPrimitive_hintNumber(CScriptResult &execute) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintNumber(execute); } + + CNumber toNumber(); // { return var->toNumber(); } + CNumber toNumber(CScriptResult &execute); // { return var->toNumber(execute); } + bool toBoolean() { return var->toBoolean(); } + std::string toString(int32_t radix=0) { ///< shortcut for this->toPrimitive_hintString()->toCString(); + return var->toString(radix); } + std::string toString(CScriptResult &execute, int32_t radix=0) { ///< shortcut for this->toPrimitive_hintString(execute)->toCString(); + return var->toString(execute, radix); } + CScriptVarPtr toObject() { return var->toObject(); }; + +private: + std::string name; + CScriptVar *owner; // pointer to the owner CScriptVar + uint32_t flags; + CScriptVarPtr var; #ifdef _DEBUG -#define _CRTDBG_MAP_ALLOC -#include -#include + char dummy[24]; +#endif + CScriptVarLink *ref(); + void unref(); +private: + int refs; + friend class CScriptVarLinkPtr; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLinkPtr +////////////////////////////////////////////////////////////////////////// + +class CScriptVarLinkPtr { +public: + // construct + CScriptVarLinkPtr() : link(0) {} ///< 0-Pointer + CScriptVarLinkPtr(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT) { link=(new CScriptVarLink(var, name, flags))->ref(); } + CScriptVarLinkPtr(CScriptVarLink *Link) : link(Link) { if(link) link->ref(); } // creates a new CScriptVarLink (from new); + + // reconstruct + CScriptVarLinkPtr &operator()(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT); + CScriptVarLinkPtr &operator=(const CScriptVarPtr &var) { return operator()(var); } + // deconstruct + ~CScriptVarLinkPtr() { if(link) link->unref(); } + + // copy + CScriptVarLinkPtr(const CScriptVarLinkPtr &Copy) : link(Copy.link) { if(link) link->ref(); } + CScriptVarLinkPtr &operator=(const CScriptVarLinkPtr &Copy) { + if(link != Copy.link) { + if(link) link->unref(); + link = Copy.link; if(link) link->ref(); + } + return *this; + } + + // getter & setter + CScriptVarLinkWorkPtr getter(); + CScriptVarLinkWorkPtr getter(CScriptResult &execute); + CScriptVarLinkWorkPtr setter(const CScriptVarPtr &Var); + CScriptVarLinkWorkPtr setter(CScriptResult &execute, const CScriptVarPtr &Var); + + // if + operator bool() const { return link!=0; } + + // for sorting in child-list + bool operator <(const std::string &rhs) const; + bool operator ==(const CScriptVarLinkPtr &rhs) const { return link==rhs.link; } + // access to CScriptVarLink + CScriptVarLink *operator ->() const { return link; } + + operator const CScriptVarPtr &() const { static CScriptVarPtr NullPtr; return link?link->getVarPtr():NullPtr; } + + void clear() { if(link) link->unref(); link=0; } +protected: + CScriptVarLink *link; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLinkWorkPtr +////////////////////////////////////////////////////////////////////////// + +class CScriptVarLinkWorkPtr : public CScriptVarLinkPtr { +public: + // construct + CScriptVarLinkWorkPtr() {} + CScriptVarLinkWorkPtr(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT) : CScriptVarLinkPtr(var, name, flags) {} + CScriptVarLinkWorkPtr(CScriptVarLink *Link) : CScriptVarLinkPtr(Link) { if(link) referencedOwner = link->getOwner(); } // creates a new CScriptVarLink (from new); + CScriptVarLinkWorkPtr(const CScriptVarLinkPtr &Copy) : CScriptVarLinkPtr(Copy) { if(link) referencedOwner = link->getOwner(); } + + // reconstruct + CScriptVarLinkWorkPtr &operator()(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT) {CScriptVarLinkPtr::operator()(var, name, flags); referencedOwner.clear(); return *this; } + + // copy + CScriptVarLinkWorkPtr(const CScriptVarLinkWorkPtr &Copy) : CScriptVarLinkPtr(Copy), referencedOwner(Copy.referencedOwner) {} + CScriptVarLinkWorkPtr &operator=(const CScriptVarLinkWorkPtr &Copy) { CScriptVarLinkPtr::operator=(Copy); referencedOwner = Copy.referencedOwner; return *this; } + + // getter & setter + CScriptVarLinkWorkPtr getter(); + CScriptVarLinkWorkPtr getter(CScriptResult &execute); + CScriptVarLinkWorkPtr setter(const CScriptVarPtr &Var); + CScriptVarLinkWorkPtr setter(CScriptResult &execute, const CScriptVarPtr &Var); + + + void swap(CScriptVarLinkWorkPtr &Link) { + CScriptVarPtr _referencedOwner = referencedOwner; referencedOwner = Link.referencedOwner; Link.referencedOwner = _referencedOwner; + CScriptVarLink *_link=link; link=Link.link; Link.link=_link; + } + + void clear() { CScriptVarLinkPtr::clear(); referencedOwner.clear(); } + void setReferencedOwner(const CScriptVarPtr &Owner) { referencedOwner = Owner; } + const CScriptVarPtr &getReferencedOwner() const { return referencedOwner; } + bool hasReferencedOwner() const { return referencedOwner; } +private: + CScriptVarPtr referencedOwner; +}; + + +////////////////////////////////////////////////////////////////////////// +#define define_dummy_t(t1) struct t1##_t{}; extern t1##_t t1 +#define declare_dummy_t(t1) t1##_t t1 +#define define_newScriptVar_Fnc(t1, ...) CScriptVarPtr newScriptVar(__VA_ARGS__) +#define define_newScriptVar_NamedFnc(t1, ...) CScriptVarPtr newScriptVar##t1(__VA_ARGS__) +#define define_ScriptVarPtr_Type(t1) class CScriptVar##t1; typedef CScriptVarPointer CScriptVar##t1##Ptr + +#define define_DEPRECATED_newScriptVar_Fnc(t1, ...) CScriptVarPtr DEPRECATED("newScriptVar("#__VA_ARGS__") is deprecated use constScriptVar("#__VA_ARGS__") instead") newScriptVar(__VA_ARGS__) + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPrimitive +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Primitive); +class CScriptVarPrimitive : public CScriptVar { +protected: + CScriptVarPrimitive(CTinyJS *Context, const CScriptVarPtr &Prototype) : CScriptVar(Context, Prototype) { setExtensible(false); } + CScriptVarPrimitive(const CScriptVarPrimitive &Copy) : CScriptVar(Copy) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarPrimitive(); + + virtual bool isPrimitive(); ///< return true; + + virtual CScriptVarPrimitivePtr getRawPrimitive(); + virtual bool toBoolean(); /// false by default + virtual CNumber toNumber_Callback()=0; + virtual std::string toCString(int radix=0)=0; + + virtual CScriptVarPtr toObject(); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); +protected: +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarUndefined +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Undefined); +define_ScriptVarPtr_Type(Undefined); +class CScriptVarUndefined : public CScriptVarPrimitive { +protected: + CScriptVarUndefined(CTinyJS *Context); + CScriptVarUndefined(const CScriptVarUndefined &Copy) : CScriptVarPrimitive(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarUndefined(); + virtual CScriptVarPtr clone(); + + virtual bool isUndefined(); // { return true; } + + virtual CNumber toNumber_Callback(); // { return NaN; } + virtual std::string toCString(int radix=0);// { return "undefined"; } + + virtual std::string getVarType(); // { return "undefined"; } + friend define_DEPRECATED_newScriptVar_Fnc(Undefined, CTinyJS *, Undefined_t); + friend define_newScriptVar_NamedFnc(Undefined, CTinyJS *Context); +}; +inline define_DEPRECATED_newScriptVar_Fnc(Undefined, CTinyJS *Context, Undefined_t) { return new CScriptVarUndefined(Context); } +inline define_newScriptVar_NamedFnc(Undefined, CTinyJS *Context) { return new CScriptVarUndefined(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarNull +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Null); +define_ScriptVarPtr_Type(Null); +class CScriptVarNull : public CScriptVarPrimitive { +protected: + CScriptVarNull(CTinyJS *Context); + CScriptVarNull(const CScriptVarNull &Copy) : CScriptVarPrimitive(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarNull(); + virtual CScriptVarPtr clone(); + + virtual bool isNull(); // { return true; } + + virtual CNumber toNumber_Callback(); // { return 0; } + virtual std::string toCString(int radix=0);// { return "null"; } + + virtual std::string getVarType(); // { return "null"; } + + friend define_DEPRECATED_newScriptVar_Fnc(Null, CTinyJS *Context, Null_t); + friend define_newScriptVar_NamedFnc(Null, CTinyJS *Context); +}; +inline define_DEPRECATED_newScriptVar_Fnc(Null, CTinyJS *Context, Null_t) { return new CScriptVarNull(Context); } +inline define_newScriptVar_NamedFnc(Null, CTinyJS *Context) { return new CScriptVarNull(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarString +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(String); +class CScriptVarString : public CScriptVarPrimitive { +protected: + CScriptVarString(CTinyJS *Context, const std::string &Data); + CScriptVarString(const CScriptVarString &Copy) : CScriptVarPrimitive(Copy), data(Copy.data) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarString(); + virtual CScriptVarPtr clone(); + virtual bool isString(); // { return true; } + + virtual bool toBoolean(); + virtual CNumber toNumber_Callback(); + virtual std::string toCString(int radix=0); + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); // { return getJSString(data); } + virtual std::string getVarType(); // { return "string"; } + + virtual CScriptVarPtr toObject(); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + + uint32_t stringLength() { return data.size(); } + int getChar(uint32_t Idx); +protected: + std::string data; +private: + friend define_newScriptVar_Fnc(String, CTinyJS *Context, const std::string &); + friend define_newScriptVar_Fnc(String, CTinyJS *Context, const char *); + friend define_newScriptVar_Fnc(String, CTinyJS *Context, char *); +}; +inline define_newScriptVar_Fnc(String, CTinyJS *Context, const std::string &Obj) { return new CScriptVarString(Context, Obj); } +inline define_newScriptVar_Fnc(String, CTinyJS *Context, const char *Obj) { return new CScriptVarString(Context, Obj); } +inline define_newScriptVar_Fnc(String, CTinyJS *Context, char *Obj) { return new CScriptVarString(Context, Obj); } + + +////////////////////////////////////////////////////////////////////////// +/// CNumber +////////////////////////////////////////////////////////////////////////// +define_dummy_t(NegativeZero); +define_dummy_t(NaN); +class Infinity{public:Infinity(int Sig=1):sig(Sig){} int Sig(){return sig;} private:int sig; } ; +extern Infinity InfinityPositive; +extern Infinity InfinityNegative; + +class CNumber { +private: + enum NType { + tnNULL, tInt32, tDouble, tNaN, tInfinity + }; + CNumber(NType Type, int32_t InfinitySign=0) : type(Type) { Int32 = InfinitySign; } +public: + + CNumber(const CNumber &Copy) { *this=Copy; } + + CNumber(int32_t Value=0) : type(tInt32) { Int32=Value; } +#if 1 + templateCNumber(T Value) { *this = Value; } +#else + CNumber(negativeZero_t Value) { *this = Value; } + CNumber(NaN_t Value) { *this = Value; } + CNumber(Infinity Value) { *this = Value; } + CNumber(uint32_t Value) { *this = Value; } + CNumber(double Value) { *this = Value; } + CNumber(unsigned char Value) { *this = Value; } + CNumber(const char *Value) { *this = Value; } + CNumber(const std::string &Value) { *this = Value; } +#endif + CNumber &operator=(NegativeZero_t) { type=tnNULL; Int32=0; return *this; } + CNumber &operator=(NaN_t) { type=tNaN; Int32=0; return *this; } + CNumber &operator=(Infinity v) { type=tInfinity; Int32=v.Sig(); return *this; } + CNumber &operator=(int32_t Value) { type=tInt32; Int32=Value; return *this; } + CNumber &operator=(uint32_t Value) { + if(Value<=(uint32_t)std::numeric_limits::max()) + type=tInt32, Int32=int32_t(Value); + else + type=tDouble, Double=Value; + return *this; + } + CNumber &operator=(double Value); + CNumber &operator=(unsigned char Value) { type=tInt32; Int32=Value; return *this; } + CNumber &operator=(const char *Value); + CNumber &operator=(const std::string &Value) { return operator=(Value.c_str());} + + int32_t parseInt(const char *str, int32_t radix=0, const char **endptr=0); + void parseInt(const std::string &str, int32_t radix=0) { parseInt(str.c_str(), radix); } + void parseFloat(const char *str, const char **endptr=0); + void parseFloat(const std::string &str) { parseFloat(str.c_str()); } + + CNumber add(const CNumber &Value) const; + CNumber operator-() const; + CNumber operator~() const { if(type==tNaN) return *this; else return ~toInt32(); } + bool operator!() const { return isZero(); } + CNumber multi(const CNumber &Value) const; + CNumber div(const CNumber &Value) const; + CNumber modulo(const CNumber &Value) const; + + CNumber round() const; + CNumber floor() const; + CNumber ceil() const; + CNumber abs() const; + + CNumber shift(const CNumber &Value, bool right) const; + CNumber ushift(const CNumber &Value, bool right=true) const; + + CNumber binary(const CNumber &Value, char Mode) const; + + + int less(const CNumber &Value) const; + bool equal(const CNumber &Value) const; + + + bool isInt32() const { return type == tInt32; } + bool isDouble() const { return type == tDouble; } + + bool isNaN() const { return type == tNaN; } + int isInfinity() const { return type == tInfinity ? Int32 : 0; } + bool isFinite() const { return type == tInt32 || type == tDouble || type == tnNULL; } + bool isNegativeZero() const { return type==tnNULL; } + bool isZero() const; ///< is 0, -0 + bool isInteger() const; + int sign() const; + + int32_t toInt32() const { return cast(); } + uint32_t toUInt32() const { return cast(); } + double toDouble() const; + bool toBoolean() const { return !isZero() && type!=tNaN; } + std::string toString(uint32_t Radix=10) const; +private: + template T cast() const { + switch(type) { + case tInt32: + return T(Int32); + case tDouble: + return T(Double); + default: + return T(0); + } + } + NType type; + union { + int32_t Int32; + double Double; + }; +}; +inline CNumber operator+(const CNumber &lhs, const CNumber &rhs) { return lhs.add(rhs); } +inline CNumber &operator+=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.add(rhs); } +inline CNumber operator-(const CNumber &lhs, const CNumber &rhs) { return lhs.add(-rhs); } +inline CNumber &operator-=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.add(-rhs); } +inline CNumber operator*(const CNumber &lhs, const CNumber &rhs) { return lhs.multi(rhs); } +inline CNumber &operator*=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.multi(rhs); } +inline CNumber operator/(const CNumber &lhs, const CNumber &rhs) { return lhs.div(rhs); } +inline CNumber &operator/=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.div(rhs); } +inline CNumber operator%(const CNumber &lhs, const CNumber &rhs) { return lhs.modulo(rhs); } +inline CNumber &operator%=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.modulo(rhs); } +inline CNumber operator>>(const CNumber &lhs, const CNumber &rhs) { return lhs.shift(rhs, true); } +inline CNumber &operator>>=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.shift(rhs, true); } +inline CNumber operator<<(const CNumber &lhs, const CNumber &rhs) { return lhs.shift(rhs, false); } +inline CNumber &operator<<=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.shift(rhs, false); } + +inline bool operator==(const CNumber &lhs, const CNumber &rhs) { return lhs.equal(rhs); } +inline bool operator!=(const CNumber &lhs, const CNumber &rhs) { return !lhs.equal(rhs); } +inline bool operator<(const CNumber &lhs, const CNumber &rhs) { return lhs.less(rhs)>0; } +inline bool operator<=(const CNumber &lhs, const CNumber &rhs) { return rhs.less(lhs)<0; } +inline bool operator>(const CNumber &lhs, const CNumber &rhs) { return rhs.less(lhs)>0; } +inline bool operator>=(const CNumber &lhs, const CNumber &rhs) { return lhs.less(rhs)<0; } + +inline CNumber round(const CNumber &lhs) { return lhs.round(); } +inline CNumber floor(const CNumber &lhs) { return lhs.floor(); } +inline CNumber ceil(const CNumber &lhs) { return lhs.ceil(); } +inline CNumber abs(const CNumber &lhs) { return lhs.abs(); } + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarNumber +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Number); +class CScriptVarNumber : public CScriptVarPrimitive { +protected: + CScriptVarNumber(CTinyJS *Context, const CNumber &Data); + CScriptVarNumber(const CScriptVarNumber &Copy) : CScriptVarPrimitive(Copy), data(Copy.data) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarNumber(); + virtual CScriptVarPtr clone(); + virtual bool isNumber(); // { return true; } + virtual bool isInt(); // { return true; } + virtual bool isDouble(); // { return true; } + virtual bool isRealNumber(); // { return true; } + virtual int isInfinity(); // { return data; } + virtual bool isNaN();// { return true; } + + virtual bool toBoolean(); + virtual CNumber toNumber_Callback(); + virtual std::string toCString(int radix=0); + + virtual std::string getVarType(); // { return "number"; } + + virtual CScriptVarPtr toObject(); +private: + CNumber data; + friend define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &); + friend define_newScriptVar_NamedFnc(Number, CTinyJS *Context, const CNumber &); +}; +define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &Obj); +inline define_newScriptVar_NamedFnc(Number, CTinyJS *Context, const CNumber &Obj) { return new CScriptVarNumber(Context, Obj); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, unsigned int Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, unsigned long Obj) { return newScriptVarNumber(Context, CNumber((uint32_t)Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, int Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, long Obj) { return newScriptVarNumber(Context, CNumber((int32_t)Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, double Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } +inline define_DEPRECATED_newScriptVar_Fnc(NaN, CTinyJS *Context, NaN_t) { return newScriptVarNumber(Context, CNumber(NaN)); } +inline define_DEPRECATED_newScriptVar_Fnc(Infinity, CTinyJS *Context, Infinity Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarBool +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Bool); +class CScriptVarBool : public CScriptVarPrimitive { +protected: + CScriptVarBool(CTinyJS *Context, bool Data); + CScriptVarBool(const CScriptVarBool &Copy) : CScriptVarPrimitive(Copy), data(Copy.data) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarBool(); + virtual CScriptVarPtr clone(); + virtual bool isBool(); // { return true; } + + virtual bool toBoolean(); + virtual CNumber toNumber_Callback(); + virtual std::string toCString(int radix=0); + + virtual std::string getVarType(); // { return "boolean"; } + + virtual CScriptVarPtr toObject(); +protected: + bool data; + + friend define_DEPRECATED_newScriptVar_Fnc(Bool, CTinyJS *, bool); + friend define_newScriptVar_NamedFnc(Bool, CTinyJS *Context, bool); +}; +inline define_DEPRECATED_newScriptVar_Fnc(Bool, CTinyJS *Context, bool Obj) { return new CScriptVarBool(Context, Obj); } +inline define_newScriptVar_NamedFnc(Bool, CTinyJS *Context, bool Obj) { return new CScriptVarBool(Context, Obj); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarObject +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Object); +define_ScriptVarPtr_Type(Object); + +class CScriptVarObject : public CScriptVar { +protected: + CScriptVarObject(CTinyJS *Context); + CScriptVarObject(CTinyJS *Context, const CScriptVarPtr &Prototype) : CScriptVar(Context, Prototype) {} + CScriptVarObject(CTinyJS *Context, const CScriptVarPrimitivePtr &Value, const CScriptVarPtr &Prototype) : CScriptVar(Context, Prototype), value(Value) {} + CScriptVarObject(const CScriptVarObject &Copy) : CScriptVar(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarObject(); + virtual CScriptVarPtr clone(); + + virtual void removeAllChildren(); + + virtual CScriptVarPrimitivePtr getRawPrimitive(); + virtual bool isObject(); // { return true; } + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + virtual std::string getVarType(); ///< always "object" + virtual std::string getVarTypeTagName(); ///< always "Object" + virtual CScriptVarPtr toObject(); + + virtual CScriptVarPtr valueOf_CallBack(); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + virtual void setTemporaryMark_recursive(uint32_t ID); +protected: +private: + CScriptVarPrimitivePtr value; + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPrimitivePtr &, const CScriptVarPtr &); +}; +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t) { return new CScriptVarObject(Context); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &Prototype) { return new CScriptVarObject(Context, Prototype); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &Prototype) { return new CScriptVarObject(Context, Prototype); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPrimitivePtr &Value, const CScriptVarPtr &Prototype) { return new CScriptVarObject(Context, Value, Prototype); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarObjectTyped (simple Object with TypeTagName) +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(StopIteration); + +class CScriptVarObjectTypeTagged : public CScriptVarObject { +protected: +// CScriptVarObjectTyped(CTinyJS *Context); + CScriptVarObjectTypeTagged(CTinyJS *Context, const CScriptVarPtr &Prototype, const std::string &TypeTagName) : CScriptVarObject(Context, Prototype), typeTagName(TypeTagName) {} + CScriptVarObjectTypeTagged(const CScriptVarObjectTypeTagged &Copy) : CScriptVarObject(Copy), typeTagName(Copy.typeTagName) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarObjectTypeTagged(); + virtual CScriptVarPtr clone(); + + virtual std::string getVarTypeTagName(); +protected: +private: + std::string typeTagName; + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &, const std::string &); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &, const std::string &); +}; +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &Prototype, const std::string &TypeTagName) { return new CScriptVarObjectTypeTagged(Context, Prototype, TypeTagName); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &Prototype, const std::string &TypeTagName) { return new CScriptVarObjectTypeTagged(Context, Prototype, TypeTagName); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarError +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Error); + +class CScriptVarError : public CScriptVarObject { +protected: + CScriptVarError(CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column);// : CScriptVarObject(Context), value(Value) {} + CScriptVarError(const CScriptVarError &Copy) : CScriptVarObject(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarError(); + virtual CScriptVarPtr clone(); + virtual bool isError(); // { return true; } + +// virtual std::string getParsableString(const std::string &indentString, const std::string &indent); ///< get Data as a parsable javascript string + + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + CScriptException *toCScriptException(); +private: + friend define_newScriptVar_NamedFnc(Error, CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column); + friend define_newScriptVar_NamedFnc(Error, CTinyJS *Context, const CScriptException &Exception); +}; +inline define_newScriptVar_NamedFnc(Error, CTinyJS *Context, ERROR_TYPES type, const char *message=0, const char *file=0, int line=-1, int column=-1) { return new CScriptVarError(Context, type, message, file, line, column); } +inline define_newScriptVar_NamedFnc(Error, CTinyJS *Context, const CScriptException &Exception) { return new CScriptVarError(Context, Exception.errorType, Exception.message.c_str(), Exception.fileName.c_str(), Exception.lineNumber, Exception.column); } + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarArray +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Array); +define_ScriptVarPtr_Type(Array); +class CScriptVarArray : public CScriptVarObject { +protected: + CScriptVarArray(CTinyJS *Context); + CScriptVarArray(const CScriptVarArray &Copy) : CScriptVarObject(Copy), toStringRecursion(Copy.toStringRecursion) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarArray(); + virtual CScriptVarPtr clone(); + virtual bool isArray(); // { return true; } + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + + friend define_newScriptVar_Fnc(Array, CTinyJS *Context, Array_t); +private: + void native_Length(const CFunctionsScopePtr &c, void *data); + bool toStringRecursion; +}; +inline define_newScriptVar_Fnc(Array, CTinyJS *Context, Array_t) { return new CScriptVarArray(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarRegExp +////////////////////////////////////////////////////////////////////////// +#ifndef NO_REGEXP + +define_ScriptVarPtr_Type(RegExp); +class CScriptVarRegExp : public CScriptVarObject { +protected: + CScriptVarRegExp(CTinyJS *Context, const std::string &Source, const std::string &Flags); + CScriptVarRegExp(const CScriptVarRegExp &Copy) : CScriptVarObject(Copy), regexp(Copy.regexp), flags(Copy.flags) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarRegExp(); + virtual CScriptVarPtr clone(); + virtual bool isRegExp(); // { return true; } + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + + CScriptVarPtr exec(const std::string &Input, bool Test=false); + + bool Global() { return flags.find('g')!=std::string::npos; } + bool IgnoreCase() { return flags.find('i')!=std::string::npos; } + bool Multiline() { return true; /* currently always true -- flags.find('m')!=std::string::npos;*/ } + bool Sticky() { return flags.find('y')!=std::string::npos; } + const std::string &Regexp() { return regexp; } + unsigned int LastIndex(); + void LastIndex(unsigned int Idx); + + static const char *ErrorStr(int Error); +protected: + std::string regexp; + std::string flags; +private: + void native_Global(const CFunctionsScopePtr &c, void *data); + void native_IgnoreCase(const CFunctionsScopePtr &c, void *data); + void native_Multiline(const CFunctionsScopePtr &c, void *data); + void native_Sticky(const CFunctionsScopePtr &c, void *data); + void native_Source(const CFunctionsScopePtr &c, void *data); + + friend define_newScriptVar_Fnc(RegExp, CTinyJS *Context, const std::string &, const std::string &); + +}; +inline define_newScriptVar_Fnc(RegExp, CTinyJS *Context, const std::string &Obj, const std::string &Flags) { return new CScriptVarRegExp(Context, Obj, Flags); } + +#endif /* NO_REGEXP */ + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunction +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Function); +class CScriptVarFunction : public CScriptVarObject { +protected: + CScriptVarFunction(CTinyJS *Context, CScriptTokenDataFnc *Data); + CScriptVarFunction(const CScriptVarFunction &Copy) : CScriptVarObject(Copy), data(Copy.data) { data->ref(); } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunction(); + virtual CScriptVarPtr clone(); + virtual bool isObject(); // { return true; } + virtual bool isFunction(); // { return true; } + virtual bool isPrimitive(); // { return false; } + + virtual std::string getVarType(); // { return "function"; } + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + virtual CScriptTokenDataFnc *getFunctionData(); + void setFunctionData(CScriptTokenDataFnc *Data); +private: + CScriptTokenDataFnc *data; + + friend define_newScriptVar_Fnc(Function, CTinyJS *Context, CScriptTokenDataFnc *); +}; +inline define_newScriptVar_Fnc(Function, CTinyJS *Context, CScriptTokenDataFnc *Obj) { return new CScriptVarFunction(Context, Obj); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionBounded +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(FunctionBounded); +class CScriptVarFunctionBounded : public CScriptVarFunction { +protected: + CScriptVarFunctionBounded(CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments); + CScriptVarFunctionBounded(const CScriptVarFunctionBounded &Copy) : CScriptVarFunction(Copy), boundedThis(Copy.boundedThis), boundedArguments(Copy.boundedArguments) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunctionBounded(); + virtual CScriptVarPtr clone(); + virtual bool isBounded(); ///< is CScriptVarFunctionBounded + virtual void setTemporaryMark_recursive(uint32_t ID); + CScriptVarPtr callFunction(CScriptResult &execute, std::vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis=0); +protected: +private: + CScriptVarFunctionPtr boundedFunction; + CScriptVarPtr boundedThis; + std::vector boundedArguments; + + friend define_newScriptVar_NamedFnc(FunctionBounded, CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments); +}; +inline define_newScriptVar_NamedFnc(FunctionBounded, CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments) { return new CScriptVarFunctionBounded(BoundedFunction, BoundedThis, BoundedArguments); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNative +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(FunctionNative); +class CScriptVarFunctionNative : public CScriptVarFunction { +protected: + CScriptVarFunctionNative(CTinyJS *Context, void *Userdata, const char *Name) : CScriptVarFunction(Context, new CScriptTokenDataFnc), jsUserData(Userdata) { + if(Name) getFunctionData()->name = Name; + } + CScriptVarFunctionNative(const CScriptVarFunctionNative &Copy) : CScriptVarFunction(Copy), jsUserData(Copy.jsUserData) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunctionNative(); + virtual CScriptVarPtr clone()=0; + virtual bool isNative(); // { return true; } + + virtual void callFunction(const CFunctionsScopePtr &c)=0;// { jsCallback(c, jsCallbackUserData); } +protected: + void *jsUserData; ///< user data passed as second argument to native functions +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNativeCallback +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(FunctionNativeCallback); +class CScriptVarFunctionNativeCallback : public CScriptVarFunctionNative { +protected: + CScriptVarFunctionNativeCallback(CTinyJS *Context, JSCallback Callback, void *Userdata, const char *Name) : CScriptVarFunctionNative(Context, Userdata, Name), jsCallback(Callback) { } + CScriptVarFunctionNativeCallback(const CScriptVarFunctionNativeCallback &Copy) : CScriptVarFunctionNative(Copy), jsCallback(Copy.jsCallback) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunctionNativeCallback(); + virtual CScriptVarPtr clone(); + virtual void callFunction(const CFunctionsScopePtr &c); +private: + JSCallback jsCallback; ///< Callback for native functions + friend define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS *Context, JSCallback Callback, void*, const char*); +}; +inline define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS *Context, JSCallback Callback, void *Userdata, const char *Name=0) { return new CScriptVarFunctionNativeCallback(Context, Callback, Userdata, Name); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNativeClass +////////////////////////////////////////////////////////////////////////// + +template +class CScriptVarFunctionNativeClass : public CScriptVarFunctionNative { +protected: + CScriptVarFunctionNativeClass(CTinyJS *Context, native *ClassPtr, void (native::*ClassFnc)(const CFunctionsScopePtr &, void *), void *Userdata, const char *Name) : CScriptVarFunctionNative(Context, Userdata, Name), classPtr(ClassPtr), classFnc(ClassFnc) { } + CScriptVarFunctionNativeClass(const CScriptVarFunctionNativeClass &Copy) : CScriptVarFunctionNative(Copy), classPtr(Copy.classPtr), classFnc(Copy.classFnc) { } ///< Copy protected -> use clone for public +public: + virtual CScriptVarPtr clone() { return new CScriptVarFunctionNativeClass(*this); } + + virtual void callFunction(const CFunctionsScopePtr &c) { (classPtr->*classFnc)(c, jsUserData); } +private: + native *classPtr; + void (native::*classFnc)(const CFunctionsScopePtr &c, void *userdata); + template + friend define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS*, native2 *, void (native2::*)(const CFunctionsScopePtr &, void *), void *, const char *); +}; +template +define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS *Context, native *ClassPtr, void (native::*ClassFnc)(const CFunctionsScopePtr &, void *), void *Userdata, const char *Name=0) { return new CScriptVarFunctionNativeClass(Context, ClassPtr, ClassFnc, Userdata, Name); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarAccessor +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Accessor); +define_ScriptVarPtr_Type(Accessor); + +class CScriptVarAccessor : public CScriptVarObject { +protected: + CScriptVarAccessor(CTinyJS *Context); + CScriptVarAccessor(CTinyJS *Context, JSCallback getter, void *getterdata, JSCallback setter, void *setterdata); + template CScriptVarAccessor(CTinyJS *Context, C *class_ptr, void(C::*getterFnc)(const CFunctionsScopePtr &, void *), void *getterData, void(C::*setterFnc)(const CFunctionsScopePtr &, void *), void *setterData) : CScriptVarObject(Context) { + if(getterFnc) + addChild(TINYJS_ACCESSOR_GET_VAR, ::newScriptVar(Context, class_ptr, getterFnc, getterData), 0); + if(setterFnc) + addChild(TINYJS_ACCESSOR_SET_VAR, ::newScriptVar(Context, class_ptr, setterFnc, setterData), 0); + } + CScriptVarAccessor(CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter); + + CScriptVarAccessor(const CScriptVarAccessor &Copy) : CScriptVarObject(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarAccessor(); + virtual CScriptVarPtr clone(); + virtual bool isAccessor(); // { return true; } + virtual bool isPrimitive(); // { return false; } + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + virtual std::string getVarType(); // { return "object"; } + + CScriptVarPtr getValue(); + + friend define_newScriptVar_Fnc(Accessor, CTinyJS *Context, Accessor_t); + friend define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, JSCallback getter, void *getterdata, JSCallback setter, void *setterdata); + template friend define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, C *class_ptr, void(C::*getterFnc)(const CFunctionsScopePtr &, void *), void *getterData, void(C::*setterFnc)(const CFunctionsScopePtr &, void *), void *setterData); + friend define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, const CScriptVarFunctionPtr &, const CScriptVarFunctionPtr &); +}; +inline define_newScriptVar_Fnc(Accessor, CTinyJS *Context, Accessor_t) { return new CScriptVarAccessor(Context); } +inline define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, JSCallback getter, void *getterdata, JSCallback setter, void *setterdata) { return new CScriptVarAccessor(Context, getter, getterdata, setter, setterdata); } +template define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, C *class_ptr, void(C::*getterFnc)(const CFunctionsScopePtr &, void *), void *getterData, void(C::*setterFnc)(const CFunctionsScopePtr &, void *), void *setterData) { return new CScriptVarAccessor(Context, class_ptr, getterFnc, getterData, setterFnc, setterData); } +inline define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter) { return new CScriptVarAccessor(Context, getter, setter); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarDestructuring +////////////////////////////////////////////////////////////////////////// + +class CScriptVarDestructuring : public CScriptVarObject { +protected: // only derived classes or friends can be created + CScriptVarDestructuring(CTinyJS *Context) // constructor for rootScope + : CScriptVarObject(Context) {} + virtual CScriptVarPtr clone(); +public: + virtual ~CScriptVarDestructuring(); +}; + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScope +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Scope); +define_ScriptVarPtr_Type(Scope); +class CScriptVarScope : public CScriptVarObject { +protected: // only derived classes or friends can be created + CScriptVarScope(CTinyJS *Context) // constructor for rootScope + : CScriptVarObject(Context) {} + virtual CScriptVarPtr clone(); + virtual bool isObject(); // { return false; } +public: + virtual ~CScriptVarScope(); + virtual CScriptVarPtr scopeVar(); ///< to create var like: var a = ... + virtual CScriptVarPtr scopeLet(); ///< to create var like: let a = ... + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); + virtual CScriptVarScopePtr getParent(); + friend define_newScriptVar_Fnc(Scope, CTinyJS *Context, Scope_t); +}; +inline define_newScriptVar_Fnc(Scope, CTinyJS *Context, Scope_t) { return new CScriptVarScope(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeFnc +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(ScopeFnc); +define_ScriptVarPtr_Type(ScopeFnc); +class CScriptVarScopeFnc : public CScriptVarScope { +protected: // only derived classes or friends can be created + CScriptVarScopeFnc(CTinyJS *Context, const CScriptVarScopePtr &Closure) // constructor for FncScope + : CScriptVarScope(Context), closure(Closure ? addChild(TINYJS_FUNCTION_CLOSURE_VAR, Closure, 0) : CScriptVarLinkPtr()) {} +public: + virtual ~CScriptVarScopeFnc(); + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); + + void setReturnVar(const CScriptVarPtr &var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy() + + #define DEPRECATED_getParameter DEPRECATED("getParameter is deprecated use getArgument instead") + DEPRECATED_getParameter CScriptVarPtr getParameter(const std::string &name); + DEPRECATED_getParameter CScriptVarPtr getParameter(int Idx); + CScriptVarPtr getArgument(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions) + CScriptVarPtr getArgument(int Idx); ///< If this is a function, get the parameter with the given index (for use by native functions) + DEPRECATED("getParameterLength is deprecated use getArgumentsLength instead") int getParameterLength(); ///< If this is a function, get the count of parameters + int getArgumentsLength(); ///< If this is a function, get the count of parameters + + void throwError(ERROR_TYPES ErrorType, const std::string &message); + +protected: + CScriptVarLinkPtr closure; + friend define_newScriptVar_Fnc(ScopeFnc, CTinyJS *Context, ScopeFnc_t, const CScriptVarScopePtr &Closure); +}; +inline define_newScriptVar_Fnc(ScopeFnc, CTinyJS *Context, ScopeFnc_t, const CScriptVarScopePtr &Closure) { return new CScriptVarScopeFnc(Context, Closure); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeLet +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(ScopeLet); +define_ScriptVarPtr_Type(ScopeLet); +class CScriptVarScopeLet : public CScriptVarScope { +protected: // only derived classes or friends can be created + CScriptVarScopeLet(const CScriptVarScopePtr &Parent); // constructor for LetScope +// : CScriptVarScope(Parent->getContext()), parent( context->getRoot() != Parent ? addChild(TINYJS_SCOPE_PARENT_VAR, Parent, 0) : 0) {} +public: + virtual ~CScriptVarScopeLet(); + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); + virtual CScriptVarPtr scopeVar(); ///< to create var like: var a = ... + virtual CScriptVarScopePtr getParent(); + void setletExpressionInitMode(bool Mode) { letExpressionInitMode = Mode; } +protected: + CScriptVarLinkPtr parent; + bool letExpressionInitMode; + friend define_newScriptVar_Fnc(ScopeLet, CTinyJS *Context, ScopeLet_t, const CScriptVarScopePtr &Parent); +}; +inline define_newScriptVar_Fnc(ScopeLet, CTinyJS *, ScopeLet_t, const CScriptVarScopePtr &Parent) { return new CScriptVarScopeLet(Parent); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeWith +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(ScopeWith); +define_ScriptVarPtr_Type(ScopeWith); +class CScriptVarScopeWith : public CScriptVarScopeLet { +protected: + CScriptVarScopeWith(const CScriptVarScopePtr &Parent, const CScriptVarPtr &With) + : CScriptVarScopeLet(Parent), with(addChild(TINYJS_SCOPE_WITH_VAR, With, 0)) {} + +public: + virtual ~CScriptVarScopeWith(); + virtual CScriptVarPtr scopeLet(); ///< to create var like: let a = ... + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); +private: + CScriptVarLinkPtr with; + friend define_newScriptVar_Fnc(ScopeWith, CTinyJS *Context, ScopeWith_t, const CScriptVarScopePtr &Parent, const CScriptVarPtr &With); +}; +inline define_newScriptVar_Fnc(ScopeWith, CTinyJS *, ScopeWith_t, const CScriptVarScopePtr &Parent, const CScriptVarPtr &With) { return new CScriptVarScopeWith(Parent, With); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarDefaultIterator +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(DefaultIterator); +define_ScriptVarPtr_Type(DefaultIterator); + +class CScriptVarDefaultIterator : public CScriptVarObject { +protected: + CScriptVarDefaultIterator(CTinyJS *Context, const CScriptVarPtr &Object, int Mode); + CScriptVarDefaultIterator(const CScriptVarDefaultIterator &Copy) + : + CScriptVarObject(Copy), mode(Copy.mode), object(Copy.object), + keys(Copy.keys), pos(keys.begin()){} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarDefaultIterator(); + virtual CScriptVarPtr clone(); + virtual bool isIterator(); + + void native_next(const CFunctionsScopePtr &c, void *data); +private: + int mode; + CScriptVarPtr object; + STRING_SET_t keys; + STRING_SET_it pos; + friend define_newScriptVar_NamedFnc(DefaultIterator, CTinyJS *, const CScriptVarPtr &, int); + +}; +inline define_newScriptVar_NamedFnc(DefaultIterator, CTinyJS *Context, const CScriptVarPtr &Object, int Mode) { return new CScriptVarDefaultIterator(Context, Object, Mode); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarGenerator +////////////////////////////////////////////////////////////////////////// + +#ifndef NO_GENERATORS + +define_dummy_t(Generator); +define_ScriptVarPtr_Type(Generator); + +class CScriptVarGenerator : public CScriptVarObject { +protected: + CScriptVarGenerator(CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function); + CScriptVarGenerator(const CScriptVarGenerator &Copy) + : + CScriptVarObject(Copy), functionRoot(Copy.functionRoot), function(Copy.function), coroutine(this) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarGenerator(); + virtual CScriptVarPtr clone(); + virtual bool isIterator(); + virtual bool isGenerator(); + virtual std::string getVarType(); // { return "generator"; } + virtual std::string getVarTypeTagName(); // { return "Generator"; } + + CScriptVarPtr getFunctionRoot() { return functionRoot; } + CScriptVarFunctionPtr getFunction() { return function; } + + virtual void setTemporaryMark_recursive(uint32_t ID); + + void native_send(const CFunctionsScopePtr &c, void *data); + void native_throw(const CFunctionsScopePtr &c, void *data); + int Coroutine(); + void setException(const CScriptVarPtr &YieldVar) { + yieldVar = YieldVar; + yieldVarIsException = true; + } + bool isClosed() { return closed; } + CScriptVarPtr yield(CScriptResult &execute, CScriptVar *YieldIn); +private: + CScriptVarPtr functionRoot; + CScriptVarFunctionPtr function; + bool closed; + CScriptVarPtr yieldVar; + bool yieldVarIsException; + class CCooroutine : public CScriptCoroutine { + CCooroutine(CScriptVarGenerator* Parent) : parent(Parent) {} + int Coroutine() { return parent->Coroutine(); } + void yield() { CScriptCoroutine::yield(); } + CScriptVarGenerator* parent; + friend class CScriptVarGenerator; + } coroutine; + friend class CCooroutine; + +public: + void *callersStackBase; + int callersScopeSize; + CScriptTokenizer *callersTokenizer; + bool callersHaveTry; + std::vector generatorScopes; + + friend define_newScriptVar_NamedFnc(CScriptVarGenerator, CTinyJS *, const CScriptVarPtr &, const CScriptVarFunctionPtr &); + +}; +inline define_newScriptVar_NamedFnc(CScriptVarGenerator, CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function) { return new CScriptVarGenerator(Context, FunctionRoot, Function); } + +#endif + +////////////////////////////////////////////////////////////////////////// +template +inline CScriptVarPtr CScriptVar::newScriptVar(T t) { return ::newScriptVar(context, t); } +template +inline CScriptVarPtr CScriptVar::newScriptVar(T1 t1, T2 t2) { return ::newScriptVar(context, t1, t2); } +//inline CScriptVarPtr newScriptVar(const CNumber &t) { return ::newScriptVar(context, t); } +////////////////////////////////////////////////////////////////////////// + + +class CScriptResult { +public: + enum TYPE { + Normal, + Break, + Continue, + Return, + Throw, + noncatchableThrow, + noExecute + }; + CScriptResult() : type(Normal), throw_at_line(-1), throw_at_column(-1) {} +// CScriptResult(TYPE Type) : type(Type), throw_at_line(-1), throw_at_column(-1) {} +// ~RESULT() { if(type==Throw) throw value; } + bool isNormal() { return type==Normal; } + bool isBreak() { return type==Break; } + bool isContinue() { return type==Continue; } + bool isBreakContinue() { return type==Break || type==Continue; } + bool isReturn() { return type==Return; } + bool isReturnNormal() { return type==Return || type==Normal; } + bool isThrow() { return type==Throw; } + + + operator bool() const { return type==Normal; } + void set(TYPE Type, bool Clear=true) { type=Type; if(Clear) value.clear(), target.clear(); } + void set(TYPE Type, const CScriptVarPtr &Value) { type=Type; value=Value; } + void set(TYPE Type, const std::string &Target) { type=Type; target=Target; } + void setThrow(const CScriptVarPtr &Value, const std::string &File, int Line=-1, int Column=-1) { type=Throw; value=Value; throw_at_file=File, throw_at_line=Line; throw_at_column=Column; } + + void cThrow() { if(type==Throw) throw value; } + + CScriptResult &operator()(const CScriptResult &rhs) { if(rhs.type!=Normal) *this=rhs; return *this; } + + enum TYPE type; + CScriptVarPtr value; + std::string target; + std::string throw_at_file; + int throw_at_line; + int throw_at_column; + +}; + + + +////////////////////////////////////////////////////////////////////////// +/// CTinyJS +////////////////////////////////////////////////////////////////////////// + +typedef int (*native_require_read_fnc)(const std::string &Fname, std::string &Data); + +class CTinyJS { +public: + CTinyJS(); + ~CTinyJS(); + + void execute(CScriptTokenizer &Tokenizer); + void execute(const char *Code, const std::string &File="", int Line=0, int Column=0); + void execute(const std::string &Code, const std::string &File="", int Line=0, int Column=0); + /** Evaluate the given code and return a link to a javascript object, + * useful for (dangerous) JSON parsing. If nothing to return, will return + * 'undefined' variable type. CScriptVarLink is returned as this will + * automatically unref the result as it goes out of scope. If you want to + * keep it, you must use ref() and unref() */ + CScriptVarLinkPtr evaluateComplex(CScriptTokenizer &Tokenizer); + /** Evaluate the given code and return a link to a javascript object, + * useful for (dangerous) JSON parsing. If nothing to return, will return + * 'undefined' variable type. CScriptVarLink is returned as this will + * automatically unref the result as it goes out of scope. If you want to + * keep it, you must use ref() and unref() */ + CScriptVarLinkPtr evaluateComplex(const char *code, const std::string &File="", int Line=0, int Column=0); + /** Evaluate the given code and return a link to a javascript object, + * useful for (dangerous) JSON parsing. If nothing to return, will return + * 'undefined' variable type. CScriptVarLink is returned as this will + * automatically unref the result as it goes out of scope. If you want to + * keep it, you must use ref() and unref() */ + CScriptVarLinkPtr evaluateComplex(const std::string &code, const std::string &File="", int Line=0, int Column=0); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ + std::string evaluate(CScriptTokenizer &Tokenizer); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ + std::string evaluate(const char *code, const std::string &File="", int Line=0, int Column=0); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ + std::string evaluate(const std::string &code, const std::string &File="", int Line=0, int Column=0); + + native_require_read_fnc setRequireReadFnc(native_require_read_fnc fnc) { + native_require_read_fnc old = native_require_read; + native_require_read = fnc; + return old; + } + + /// add a native function to be called from TinyJS + /** example: + \code + void scRandInt(const CFunctionsScopePtr &c, void *userdata) { ... } + tinyJS->addNative("function randInt(min, max)", scRandInt, 0); + \endcode + + or + + \code + void scSubstring(const CFunctionsScopePtr &c, void *userdata) { ... } + tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); + \endcode + or + + \code + class Class + { + public: + void scSubstring(const CFunctionsScopePtr &c, void *userdata) { ... } + }; + Class Instanz; + tinyJS->addNative("function String.substring(lo, hi)", &Instanz, &Class::*scSubstring, 0); + \endcode + */ + + CScriptVarFunctionNativePtr addNative(const std::string &funcDesc, JSCallback ptr, void *userdata=0, int LinkFlags=SCRIPTVARLINK_BUILDINDEFAULT); + template + CScriptVarFunctionNativePtr addNative(const std::string &funcDesc, C *class_ptr, void(C::*class_fnc)(const CFunctionsScopePtr &, void *), void *userdata=0, int LinkFlags=SCRIPTVARLINK_BUILDINDEFAULT) + { + return addNative(funcDesc, ::newScriptVar(this, class_ptr, class_fnc, userdata), LinkFlags); + } + + /// Send all variables to stdout + void trace(); + + const CScriptVarScopePtr &getRoot() { return root; }; /// gets the root of symbol table + // CScriptVar *root; /// root of symbol table + + /// newVars & constVars + //CScriptVarPtr newScriptVar(const CNumber &t) { return ::newScriptVar(this, t); } + template CScriptVarPtr newScriptVar(T t) { return ::newScriptVar(this, t); } + template CScriptVarPtr newScriptVar(T1 t1, T2 t2) { return ::newScriptVar(this, t1, t2); } + template CScriptVarPtr newScriptVar(T1 t1, T2 t2, T3 t3) { return ::newScriptVar(this, t1, t2, t3); } + const CScriptVarPtr &constScriptVar(Undefined_t) { return constUndefined; } + const CScriptVarPtr &constScriptVar(Null_t) { return constNull; } + const CScriptVarPtr &constScriptVar(NaN_t) { return constNaN; } + const CScriptVarPtr &constScriptVar(Infinity t) { return t.Sig()<0 ? constInfinityNegative : constInfinityPositive; } + const CScriptVarPtr &constScriptVar(bool Val) { return Val?constTrue:constFalse; } + const CScriptVarPtr &constScriptVar(NegativeZero_t) { return constNegativZero; } + const CScriptVarPtr &constScriptVar(StopIteration_t) { return constStopIteration; } + +private: + CScriptTokenizer *t; /// current tokenizer + bool haveTry; + std::vectorscopes; + CScriptVarScopePtr root; + const CScriptVarScopePtr &scope() { return scopes.back(); } + + class CScopeControl { // helper-class to manage scopes + private: + CScopeControl(const CScopeControl& Copy) MEMBER_DELETE; // no copy + CScopeControl& operator =(const CScopeControl& Copy) MEMBER_DELETE; + public: + CScopeControl(CTinyJS *Context) : context(Context), count(0) {} + ~CScopeControl() { while(count--) {CScriptVarScopePtr parent = context->scopes.back()->getParent(); if(parent) context->scopes.back() = parent; else context->scopes.pop_back() ;} } + void addFncScope(const CScriptVarScopePtr &Scope) { context->scopes.push_back(Scope); count++; } + CScriptVarScopeLetPtr addLetScope() { count++; return context->scopes.back() = ::newScriptVar(context, ScopeLet, context->scopes.back()); } + void addWithScope(const CScriptVarPtr &With) { context->scopes.back() = ::newScriptVar(context, ScopeWith, context->scopes.back(), With); count++; } + private: + CTinyJS *context; + int count; + }; + friend class CScopeControl; +public: + CScriptVarPtr objectPrototype; /// Built in object class + CScriptVarPtr objectPrototype_valueOf; /// Built in object class + CScriptVarPtr objectPrototype_toString; /// Built in object class + CScriptVarPtr arrayPrototype; /// Built in array class + CScriptVarPtr stringPrototype; /// Built in string class + CScriptVarPtr regexpPrototype; /// Built in string class + CScriptVarPtr numberPrototype; /// Built in number class + CScriptVarPtr booleanPrototype; /// Built in boolean class + CScriptVarPtr iteratorPrototype; /// Built in iterator class +#ifndef NO_GENERATORS + CScriptVarPtr generatorPrototype; /// Built in generator class +#endif /*NO_GENERATORS*/ + CScriptVarPtr functionPrototype; /// Built in function class + const CScriptVarPtr &getErrorPrototype(ERROR_TYPES Type) { return errorPrototypes[Type]; } +private: + CScriptVarPtr errorPrototypes[ERROR_COUNT]; /// Built in error class + CScriptVarPtr constUndefined; + CScriptVarPtr constNull; + CScriptVarPtr constNaN; + CScriptVarPtr constInfinityPositive; + CScriptVarPtr constInfinityNegative; + CScriptVarPtr constNegativZero; + CScriptVarPtr constTrue; + CScriptVarPtr constFalse; + CScriptVarPtr constStopIteration; + + std::vector pseudo_refered; + + void CheckRightHandVar(CScriptResult &execute, CScriptVarLinkWorkPtr &link) + { + if(execute && link && !link->isOwned() && !link.hasReferencedOwner() && !link->getName().empty()) + throwError(execute, ReferenceError, link->getName() + " is not defined", t->getPrevPos()); + } + + void CheckRightHandVar(CScriptResult &execute, CScriptVarLinkWorkPtr &link, CScriptTokenizer::ScriptTokenPosition &Pos) + { + if(execute && link && !link->isOwned() && !link.hasReferencedOwner() && !link->getName().empty()) + throwError(execute, ReferenceError, link->getName() + " is not defined", Pos); + } + +public: + // function call + CScriptVarPtr callFunction(const CScriptVarFunctionPtr &Function, std::vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis=0); + CScriptVarPtr callFunction(CScriptResult &execute, const CScriptVarFunctionPtr &Function, std::vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis=0); + ////////////////////////////////////////////////////////////////////////// +#ifndef NO_GENERATORS + std::vector generatorStack; + void generator_start(CScriptVarGenerator *Generator); + CScriptVarPtr generator_yield(CScriptResult &execute, CScriptVar *YieldIn); #endif + ////////////////////////////////////////////////////////////////////////// + + // parsing - in order of precedence + CScriptVarPtr mathsOp(CScriptResult &execute, const CScriptVarPtr &a, const CScriptVarPtr &b, int op); +private: + void assign_destructuring_var(const CScriptVarPtr &Scope, const CScriptTokenDataDestructuringVar &Objc, const CScriptVarPtr &Val, CScriptResult &execute); + void execute_var_init(bool hideLetScope, CScriptResult &execute); + void execute_destructuring(CScriptTokenDataObjectLiteral &Objc, const CScriptVarPtr &Val, CScriptResult &execute); + CScriptVarLinkWorkPtr execute_literals(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_member(CScriptVarLinkWorkPtr &parent, CScriptResult &execute); + CScriptVarLinkWorkPtr execute_function_call(CScriptResult &execute); + bool execute_unary_rhs(CScriptResult &execute, CScriptVarLinkWorkPtr& a); + CScriptVarLinkWorkPtr execute_unary(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_term(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_expression(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_binary_shift(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_relation(CScriptResult &execute, int set=LEX_EQUAL, int set_n='<'); + CScriptVarLinkWorkPtr execute_binary_logic(CScriptResult &execute, int op='|', int op_n1='^', int op_n2='&'); + CScriptVarLinkWorkPtr execute_logic(CScriptResult &execute, int op=LEX_OROR, int op_n=LEX_ANDAND); + CScriptVarLinkWorkPtr execute_condition(CScriptResult &execute); + CScriptVarLinkPtr execute_assignment(CScriptVarLinkWorkPtr Lhs, CScriptResult &execute); + CScriptVarLinkPtr execute_assignment(CScriptResult &execute); + CScriptVarLinkPtr execute_base(CScriptResult &execute); + void execute_block(CScriptResult &execute); + void execute_statement(CScriptResult &execute); + // parsing utility functions + CScriptVarLinkWorkPtr parseFunctionDefinition(const CScriptToken &FncToken); + CScriptVarLinkWorkPtr parseFunctionsBodyFromString(const std::string &ArgumentList, const std::string &FncBody); +public: + CScriptVarLinkPtr findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes +private: + ////////////////////////////////////////////////////////////////////////// + /// addNative-helper + CScriptVarFunctionNativePtr addNative(const std::string &funcDesc, CScriptVarFunctionNativePtr Var, int LinkFlags); + + ////////////////////////////////////////////////////////////////////////// + /// throws an Error & Exception +public: + void throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const std::string &message); + void throwException(ERROR_TYPES ErrorType, const std::string &message); + void throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const std::string &message, CScriptTokenizer::ScriptTokenPosition &Pos); + void throwException(ERROR_TYPES ErrorType, const std::string &message, CScriptTokenizer::ScriptTokenPosition &Pos); +private: + ////////////////////////////////////////////////////////////////////////// + /// native Object-Constructors & prototype-functions + + void native_Object(const CFunctionsScopePtr &c, void *data); + void native_Object_getPrototypeOf(const CFunctionsScopePtr &c, void *data); + /* Userdate for set-/isObjectState + * 0 - preventExtensions / isExtensible + * 1 - seal / isSealed + * 2 - freeze / isFrozen + */ + void native_Object_setObjectSecure(const CFunctionsScopePtr &c, void *data); + void native_Object_isSecureObject(const CFunctionsScopePtr &c, void *data); + void native_Object_keys(const CFunctionsScopePtr &c, void *data); + void native_Object_getOwnPropertyDescriptor(const CFunctionsScopePtr &c, void *data); + void native_Object_defineProperty(const CFunctionsScopePtr &c, void *data); + void native_Object_defineProperties(const CFunctionsScopePtr &c, void *data); + + void native_Object_prototype_hasOwnProperty(const CFunctionsScopePtr &c, void *data); + void native_Object_prototype_valueOf(const CFunctionsScopePtr &c, void *data); + void native_Object_prototype_toString(const CFunctionsScopePtr &c, void *data); + + void native_Array(const CFunctionsScopePtr &c, void *data); + + void native_String(const CFunctionsScopePtr &c, void *data); + + void native_RegExp(const CFunctionsScopePtr &c, void *data); + + void native_Number(const CFunctionsScopePtr &c, void *data); + + void native_Boolean(const CFunctionsScopePtr &c, void *data); + + void native_Iterator(const CFunctionsScopePtr &c, void *data); + +// void native_Generator(const CFunctionsScopePtr &c, void *data); + void native_Generator_prototype_next(const CFunctionsScopePtr &c, void *data); + + void native_Function(const CFunctionsScopePtr &c, void *data); + void native_Function_prototype_call(const CFunctionsScopePtr &c, void *data); + void native_Function_prototype_apply(const CFunctionsScopePtr &c, void *data); + void native_Function_prototype_bind(const CFunctionsScopePtr &c, void *data); + + void native_Error(const CFunctionsScopePtr &c, void *data); + void native_EvalError(const CFunctionsScopePtr &c, void *data); + void native_RangeError(const CFunctionsScopePtr &c, void *data); + void native_ReferenceError(const CFunctionsScopePtr &c, void *data); + void native_SyntaxError(const CFunctionsScopePtr &c, void *data); + void native_TypeError(const CFunctionsScopePtr &c, void *data); + + + ////////////////////////////////////////////////////////////////////////// + /// global function + + void native_eval(const CFunctionsScopePtr &c, void *data); + void native_require(const CFunctionsScopePtr &c, void *data); + native_require_read_fnc native_require_read; + void native_isNAN(const CFunctionsScopePtr &c, void *data); + void native_isFinite(const CFunctionsScopePtr &c, void *data); + void native_parseInt(const CFunctionsScopePtr &c, void *data); + void native_parseFloat(const CFunctionsScopePtr &c, void *data); + + void native_JSON_parse(const CFunctionsScopePtr &c, void *data); + + + uint32_t uniqueID; + int32_t currentMarkSlot; + void *stackBase; +public: + int32_t getCurrentMarkSlot() { + ASSERT(currentMarkSlot >= 0); // UniqueID not allocated + return currentMarkSlot; + } + uint32_t allocUniqueID() { + ++currentMarkSlot; + ASSERT(currentMarkSlot= 0); // freeUniqueID without allocUniqueID + --currentMarkSlot; + } + CScriptVar *first; + void setTemporaryID_recursive(uint32_t ID); + void ClearUnreferedVars(const CScriptVarPtr &extra=CScriptVarPtr()); + void setStackBase(void * StackBase) { stackBase = StackBase; } + void setStackBase(uint32_t StackSize) { char dummy; stackBase = StackSize ? &dummy-StackSize : 0; } +}; + + +////////////////////////////////////////////////////////////////////////// +template +inline const CScriptVarPtr &CScriptVar::constScriptVar(T t) { return context->constScriptVar(t); } +////////////////////////////////////////////////////////////////////////// +inline CNumber CScriptVarLink::toNumber() { return var->toNumber(); } +inline CNumber CScriptVarLink::toNumber(CScriptResult &execute) { return var->toNumber(execute); } + +inline void CScriptVar::setTemporaryMark(uint32_t ID) { temporaryMark[context->getCurrentMarkSlot()] = ID; } +inline uint32_t CScriptVar::getTemporaryMark() { return temporaryMark[context->getCurrentMarkSlot()]; } + + #endif -#include -#include - -#ifndef TRACE -#define TRACE printf -#endif // TRACE - -const int TINYJS_LOOP_MAX_ITERATIONS = 8192; - -enum LEX_TYPES { - LEX_EOF = 0, - LEX_ID = 256, - LEX_INT, - LEX_FLOAT, - LEX_STR, - - LEX_EQUAL, - LEX_TYPEEQUAL, - LEX_NEQUAL, - LEX_NTYPEEQUAL, - LEX_LEQUAL, - LEX_LSHIFT, - LEX_LSHIFTEQUAL, - LEX_GEQUAL, - LEX_RSHIFT, - LEX_RSHIFTEQUAL, - LEX_PLUSEQUAL, - LEX_MINUSEQUAL, - LEX_PLUSPLUS, - LEX_MINUSMINUS, - LEX_ANDEQUAL, - LEX_ANDAND, - LEX_OREQUAL, - LEX_OROR, - LEX_XOREQUAL, - // reserved words -#define LEX_R_LIST_START LEX_R_IF - LEX_R_IF, - LEX_R_ELSE, - LEX_R_DO, - LEX_R_WHILE, - LEX_R_FOR, - LEX_R_BREAK, - LEX_R_CONTINUE, - LEX_R_FUNCTION, - LEX_R_RETURN, - LEX_R_VAR, - LEX_R_TRUE, - LEX_R_FALSE, - LEX_R_NULL, - LEX_R_UNDEFINED, - LEX_R_NEW, - - LEX_R_LIST_END /* always the last entry */ -}; - -enum SCRIPTVAR_FLAGS { - SCRIPTVAR_UNDEFINED = 0, - SCRIPTVAR_FUNCTION = 1, - SCRIPTVAR_OBJECT = 2, - SCRIPTVAR_ARRAY = 4, - SCRIPTVAR_DOUBLE = 8, // floating point double - SCRIPTVAR_INTEGER = 16, // integer number - SCRIPTVAR_STRING = 32, // string - SCRIPTVAR_NULL = 64, // it seems null is its own data type - - SCRIPTVAR_NATIVE = 128, // to specify this is a native function - SCRIPTVAR_NUMERICMASK = SCRIPTVAR_NULL | - SCRIPTVAR_DOUBLE | - SCRIPTVAR_INTEGER, - SCRIPTVAR_VARTYPEMASK = SCRIPTVAR_DOUBLE | - SCRIPTVAR_INTEGER | - SCRIPTVAR_STRING | - SCRIPTVAR_FUNCTION | - SCRIPTVAR_OBJECT | - SCRIPTVAR_ARRAY | - SCRIPTVAR_NULL, - -}; - -#define TINYJS_RETURN_VAR "return" -#define TINYJS_PROTOTYPE_CLASS "prototype" -#define TINYJS_TEMP_NAME "" -#define TINYJS_BLANK_DATA "" - -/// convert the given string into a quoted string suitable for javascript -std::string getJSString(const std::string &str); - -class CScriptException { -public: - std::string text; - CScriptException(const std::string &exceptionText); -}; - -class CScriptLex -{ -public: - CScriptLex(const std::string &input); - CScriptLex(CScriptLex *owner, int startChar, int endChar); - ~CScriptLex(void); - - char currCh, nextCh; - int tk; ///< The type of the token that we have - int tokenStart; ///< Position in the data at the beginning of the token we have here - int tokenEnd; ///< Position in the data at the last character of the token we have here - int tokenLastEnd; ///< Position in the data at the last character of the last token - std::string tkStr; ///< Data contained in the token we have here - - void match(int expected_tk); ///< Lexical match wotsit - static std::string getTokenStr(int token); ///< Get the string representation of the given token - void reset(); ///< Reset this lex so we can start again - - std::string getSubString(int pos); ///< Return a sub-string from the given position up until right now - CScriptLex *getSubLex(int lastPosition); ///< Return a sub-lexer from the given position up until right now - - std::string getPosition(int pos=-1); ///< Return a string representing the position in lines and columns of the character pos given - -protected: - /* When we go into a loop, we use getSubLex to get a lexer for just the sub-part of the - relevant string. This doesn't re-allocate and copy the string, but instead copies - the data pointer and sets dataOwned to false, and dataStart/dataEnd to the relevant things. */ - char *data; ///< Data string to get tokens from - int dataStart, dataEnd; ///< Start and end position in data string - bool dataOwned; ///< Do we own this data string? - - int dataPos; ///< Position in data (we CAN go past the end of the string here) - - void getNextCh(); - void getNextToken(); ///< Get the text token from our text string -}; - -class CScriptVar; - -typedef void (*JSCallback)(CScriptVar *var, void *userdata); - -class CScriptVarLink -{ -public: - std::string name; - CScriptVarLink *nextSibling; - CScriptVarLink *prevSibling; - CScriptVar *var; - bool owned; - - CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME); - CScriptVarLink(const CScriptVarLink &link); ///< Copy constructor - ~CScriptVarLink(); - void replaceWith(CScriptVar *newVar); ///< Replace the Variable pointed to - void replaceWith(CScriptVarLink *newVar); ///< Replace the Variable pointed to (just dereferences) -}; - -/// Variable class (containing a doubly-linked list of children) -class CScriptVar -{ -public: - CScriptVar(); ///< Create undefined - CScriptVar(const std::string &varData, int varFlags); ///< User defined - CScriptVar(const std::string &str); ///< Create a string - CScriptVar(double varData); - CScriptVar(int val); - ~CScriptVar(void); - - CScriptVar *getReturnVar(); ///< If this is a function, get the result value (for use by native functions) - void setReturnVar(CScriptVar *var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy() - CScriptVar *getParameter(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions) - - CScriptVarLink *findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 - CScriptVarLink *findChildOrCreate(const std::string &childName, int varFlags=SCRIPTVAR_UNDEFINED); ///< Tries to find a child with the given name, or will create it with the given flags - CScriptVarLink *findChildOrCreateByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) - CScriptVarLink *addChild(const std::string &childName, CScriptVar *child=NULL); - CScriptVarLink *addChildNoDup(const std::string &childName, CScriptVar *child=NULL); ///< add a child overwriting any with the same name - void removeChild(CScriptVar *child); - void removeLink(CScriptVarLink *link); ///< Remove a specific link (this is faster than finding via a child) - void removeAllChildren(); - CScriptVar *getArrayIndex(int idx); ///< The the value at an array index - void setArrayIndex(int idx, CScriptVar *value); ///< Set the value at an array index - int getArrayLength(); ///< If this is an array, return the number of items in it (else 0) - int getChildren(); ///< Get the number of children - - int getInt(); - bool getBool() { return getInt() != 0; } - double getDouble(); - const std::string &getString(); - std::string getParsableString(); ///< get Data as a parsable javascript string - void setInt(int num); - void setDouble(double val); - void setString(const std::string &str); - void setUndefined(); - - bool isInt() { return (flags&SCRIPTVAR_INTEGER)!=0; } - bool isDouble() { return (flags&SCRIPTVAR_DOUBLE)!=0; } - bool isString() { return (flags&SCRIPTVAR_STRING)!=0; } - bool isNumeric() { return (flags&SCRIPTVAR_NUMERICMASK)!=0; } - bool isFunction() { return (flags&SCRIPTVAR_FUNCTION)!=0; } - bool isObject() { return (flags&SCRIPTVAR_OBJECT)!=0; } - bool isArray() { return (flags&SCRIPTVAR_ARRAY)!=0; } - bool isNative() { return (flags&SCRIPTVAR_NATIVE)!=0; } - bool isUndefined() { return (flags & SCRIPTVAR_VARTYPEMASK) == SCRIPTVAR_UNDEFINED; } - bool isNull() { return (flags & SCRIPTVAR_NULL)!=0; } - bool isBasic() { return firstChild==0; } ///< Is this *not* an array/object/etc - - CScriptVar *mathsOp(CScriptVar *b, int op); ///< do a maths op with another script variable - void copyValue(CScriptVar *val); ///< copy the value from the value given - CScriptVar *deepCopy(); ///< deep copy this node and return the result - - void trace(std::string indentStr = "", const std::string &name = ""); ///< Dump out the contents of this using trace - std::string getFlagsAsString(); ///< For debugging - just dump a string version of the flags - void getJSON(std::ostringstream &destination, const std::string linePrefix=""); ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) - void setCallback(JSCallback callback, void *userdata); ///< Set the callback for native functions - - CScriptVarLink *firstChild; - CScriptVarLink *lastChild; - - /// For memory management/garbage collection - CScriptVar *ref(); ///< Add reference to this variable - void unref(); ///< Remove a reference, and delete this variable if required - int getRefs(); ///< Get the number of references to this script variable -protected: - int refs; ///< The number of references held to this - used for garbage collection - - std::string data; ///< The contents of this variable if it is a string - long intData; ///< The contents of this variable if it is an int - double doubleData; ///< The contents of this variable if it is a double - int flags; ///< the flags determine the type of the variable - int/double/string/etc - JSCallback jsCallback; ///< Callback for native functions - void *jsCallbackUserData; ///< user data passed as second argument to native functions - - void init(); ///< initialisation of data members - - /** Copy the basic data and flags from the variable given, with no - * children. Should be used internally only - by copyValue and deepCopy */ - void copySimpleData(CScriptVar *val); - - friend class CTinyJS; -}; - -class CTinyJS { -public: - CTinyJS(); - ~CTinyJS(); - - void execute(const std::string &code); - /** Evaluate the given code and return a link to a javascript object, - * useful for (dangerous) JSON parsing. If nothing to return, will return - * 'undefined' variable type. CScriptVarLink is returned as this will - * automatically unref the result as it goes out of scope. If you want to - * keep it, you must use ref() and unref() */ - CScriptVarLink evaluateComplex(const std::string &code); - /** Evaluate the given code and return a string. If nothing to return, will return - * 'undefined' */ - std::string evaluate(const std::string &code); - - /// add a native function to be called from TinyJS - /** example: - \code - void scRandInt(CScriptVar *c, void *userdata) { ... } - tinyJS->addNative("function randInt(min, max)", scRandInt, 0); - \endcode - - or - - \code - void scSubstring(CScriptVar *c, void *userdata) { ... } - tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); - \endcode - */ - void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata); - - /// Get the value of the given variable, or return 0 - const std::string *getVariable(const std::string &path); - - /// Send all variables to stdout - void trace(); - - CScriptVar *root; /// root of symbol table -private: - CScriptLex *l; /// current lexer - std::vector scopes; /// stack of scopes when parsing - CScriptVar *stringClass; /// Built in string class - CScriptVar *objectClass; /// Built in object class - CScriptVar *arrayClass; /// Built in array class - - // parsing - in order of precedence - CScriptVarLink *factor(bool &execute); - CScriptVarLink *unary(bool &execute); - CScriptVarLink *term(bool &execute); - CScriptVarLink *expression(bool &execute); - CScriptVarLink *condition(bool &execute); - CScriptVarLink *logic(bool &execute); - CScriptVarLink *base(bool &execute); - void block(bool &execute); - void statement(bool &execute); - // parsing utility functions - CScriptVarLink *parseFunctionDefinition(); - void parseFunctionArguments(CScriptVar *funcVar); - - CScriptVarLink *findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes - /// Look up in any parent classes of the given object - CScriptVarLink *findInParentClasses(CScriptVar *object, const std::string &name); -}; - -#endif + + diff --git a/TinyJS_Functions.cpp b/TinyJS_Functions.cpp index 7fa9f47..df3d7c5 100755 --- a/TinyJS_Functions.cpp +++ b/TinyJS_Functions.cpp @@ -7,135 +7,156 @@ * * 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. */ -#include "TinyJS_Functions.h" + #include #include #include +#include +#include "TinyJS.h" using namespace std; // ----------------------------------------------- Actual Functions -void scTrace(CScriptVar *c, void *userdata) { - CTinyJS *js = (CTinyJS*)userdata; - js->root->trace(); -} - -void scObjectDump(CScriptVar *c, void *) { - c->getParameter("this")->trace("> "); -} - -void scObjectClone(CScriptVar *c, void *) { - CScriptVar *obj = c->getParameter("this"); - c->getReturnVar()->copyValue(obj); +static void scTrace(const CFunctionsScopePtr &c, void * userdata) { + CTinyJS *js = (CTinyJS*)userdata; + if(c->getArgumentsLength()) + c->getArgument(0)->trace(); + else + js->getRoot()->trace("root"); } -void scMathRand(CScriptVar *c, void *) { - c->getReturnVar()->setDouble((double)rand()/RAND_MAX); +static void scObjectDump(const CFunctionsScopePtr &c, void *) { + c->getArgument("this")->trace("> "); } -void scMathRandInt(CScriptVar *c, void *) { - int min = c->getParameter("min")->getInt(); - int max = c->getParameter("max")->getInt(); - int val = min + (int)((long)rand()*(1+max-min)/RAND_MAX); - if (val>max) val=max; - c->getReturnVar()->setInt(val); +static void scObjectClone(const CFunctionsScopePtr &c, void *) { + CScriptVarPtr obj = c->getArgument("this"); + c->setReturnVar(obj->clone()); } +/* +static void scIntegerValueOf(const CFunctionsScopePtr &c, void *) { + string str = c->getArgument("str")->toString(); -void scCharToInt(CScriptVar *c, void *) { - string str = c->getParameter("ch")->getString();; - int val = 0; - if (str.length()>0) - val = (int)str.c_str()[0]; - c->getReturnVar()->setInt(val); + int val = 0; + if (str.length()==1) + val = str.operator[](0); + c->setReturnVar(c->newScriptVar(val)); } - -void scStringIndexOf(CScriptVar *c, void *) { - string str = c->getParameter("this")->getString(); - string search = c->getParameter("search")->getString(); - size_t p = str.find(search); - int val = (p==string::npos) ? -1 : p; - c->getReturnVar()->setInt(val); +*/ +static void scJSONStringify(const CFunctionsScopePtr &c, void *) { + uint32_t UniqueID = c->getContext()->allocUniqueID(); + bool hasRecursion=false; + c->setReturnVar(c->newScriptVar(c->getArgument("obj")->getParsableString("", " ", UniqueID, hasRecursion))); + c->getContext()->freeUniqueID(); + if(hasRecursion) c->throwError(TypeError, "cyclic object value"); } -void scStringSubstring(CScriptVar *c, void *) { - string str = c->getParameter("this")->getString(); - int lo = c->getParameter("lo")->getInt(); - int hi = c->getParameter("hi")->getInt(); - - int l = hi-lo; - if (l>0 && lo>=0 && lo+l<=(int)str.length()) - c->getReturnVar()->setString(str.substr(lo, l)); - else - c->getReturnVar()->setString(""); +static void scArrayContains(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument("obj"); + CScriptVarPtr arr = c->getArgument("this"); + + int l = arr->getArrayLength(); + CScriptVarPtr equal = c->constScriptVar(Undefined); + for (int i=0;imathsOp(arr->getArrayIndex(i), LEX_EQUAL); + if(equal->toBoolean()) { + c->setReturnVar(c->constScriptVar(true)); + return; + } + } + c->setReturnVar(c->constScriptVar(false)); } -void scStringCharAt(CScriptVar *c, void *) { - string str = c->getParameter("this")->getString(); - int p = c->getParameter("pos")->getInt(); - if (p>=0 && p<(int)str.length()) - c->getReturnVar()->setString(str.substr(p, 1)); - else - c->getReturnVar()->setString(""); +static void scArrayRemove(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument("obj"); + CScriptVarPtr arr = c->getArgument("this"); + int i; + vector removedIndices; + + int l = arr->getArrayLength(); + CScriptVarPtr equal = c->constScriptVar(Undefined); + for (i=0;imathsOp(arr->getArrayIndex(i), LEX_EQUAL); + if(equal->toBoolean()) { + removedIndices.push_back(i); + } + } + if(removedIndices.size()) { + vector::iterator remove_it = removedIndices.begin(); + int next_remove = *remove_it; + int next_insert = *remove_it++; + for (i=next_remove;ifindChild(int2string(i)); + if(i == next_remove) { + if(link) arr->removeLink(link); + if(remove_it != removedIndices.end()) + next_remove = *remove_it++; + } else { + if(link) { + arr->setArrayIndex(next_insert++, link); + arr->removeLink(link); + } + } + } + } } -void scIntegerParseInt(CScriptVar *c, void *) { - string str = c->getParameter("str")->getString(); - int val = strtol(str.c_str(),0,0); - c->getReturnVar()->setInt(val); -} +static void scArrayJoin(const CFunctionsScopePtr &c, void *data) { + string sep = c->getArgument("separator")->toString(); + CScriptVarPtr arr = c->getArgument("this"); -void scIntegerValueOf(CScriptVar *c, void *) { - string str = c->getParameter("str")->getString(); + ostringstream sstr; + int l = arr->getArrayLength(); + for (int i=0;i0) sstr << sep; + sstr << arr->getArrayIndex(i)->toString(); + } - int val = 0; - if (str.length()==1) - val = str[0]; - c->getReturnVar()->setInt(val); -} - -void scJSONStringify(CScriptVar *c, void *) { - std::ostringstream result; - c->getParameter("obj")->getJSON(result); - c->getReturnVar()->setString(result.str()); -} - -void scEval(CScriptVar *c, void *data) { - CTinyJS *tinyJS = (CTinyJS *)data; - std::string str = c->getParameter("jsCode")->getString(); - c->setReturnVar(tinyJS->evaluateComplex(str).var); + c->setReturnVar(c->newScriptVar(sstr.str())); } // ----------------------------------------------- Register Functions void registerFunctions(CTinyJS *tinyJS) { - tinyJS->addNative("function eval(jsCode)", scEval, tinyJS); // execute the given string and return the result - tinyJS->addNative("function trace()", scTrace, tinyJS); - tinyJS->addNative("function Object.dump()", scObjectDump, 0); - tinyJS->addNative("function Object.clone()", scObjectClone, 0); - tinyJS->addNative("function Math.rand()", scMathRand, 0); - tinyJS->addNative("function Math.randInt(min, max)", scMathRandInt, 0); - tinyJS->addNative("function charToInt(ch)", scCharToInt, 0); // convert a character to an int - get its value - tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not - tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0); - tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); - tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int - tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character - tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); // convert to JSON. replacer is ignored at the moment - // JSON.parse is left out as you can (unsafely!) use eval instead +} +extern "C" void _registerFunctions(CTinyJS *tinyJS) { + tinyJS->addNative("function trace()", scTrace, tinyJS, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Object.prototype.dump()", scObjectDump, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Object.prototype.clone()", scObjectClone, 0, SCRIPTVARLINK_BUILDINDEFAULT); + +// tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0, SCRIPTVARLINK_BUILDINDEFAULT); // value of a single character + tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0, SCRIPTVARLINK_BUILDINDEFAULT); // convert to JSON. replacer is ignored at the moment + tinyJS->addNative("function Array.prototype.contains(obj)", scArrayContains, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Array.prototype.remove(obj)", scArrayRemove, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Array.prototype.join(separator)", scArrayJoin, 0, SCRIPTVARLINK_BUILDINDEFAULT); } diff --git a/TinyJS_Functions.h b/TinyJS_Functions.h index bffff37..9e1fa07 100755 --- a/TinyJS_Functions.h +++ b/TinyJS_Functions.h @@ -7,28 +7,43 @@ * * 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. + + * 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. */ +#pragma message("The include "__FILE__" is deprecated - Functions now registered by default") + #ifndef TINYJS_FUNCTIONS_H #define TINYJS_FUNCTIONS_H #include "TinyJS.h" /// Register useful functions with the TinyJS interpreter -extern void registerFunctions(CTinyJS *tinyJS); +extern void DEPRECATED("is deprecated - Functions now registered by default") registerFunctions(CTinyJS *tinyJS); #endif diff --git a/TinyJS_MathFunctions.cpp b/TinyJS_MathFunctions.cpp new file mode 100644 index 0000000..5f1ece9 --- /dev/null +++ b/TinyJS_MathFunctions.cpp @@ -0,0 +1,441 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * - Math and Trigonometry functions + * + * Authored By O.Z.L.B. + * + * Copyright (C) 2011 O.Z.L.B. + * + + * 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. + */ + +#include +#include +#include +#include +#include "TinyJS.h" + +using namespace std; + +#define k_E exp(1.0) +#define k_PI 3.1415926535897932384626433832795 +#define k_LN2 log((double)2) +#define k_LN10 log((double)10) +#define k_LOG2E (log(k_E)/log((double)2)) +#define k_LOG10E log10(k_E) +#define k_SQRT1_2 sqrt((double)0.5) +#define k_SQRT2 sqrt((double)2) + +#define F_ABS(a) ((a)>=0 ? (a) : (-(a))) +#define F_MIN(a,b) ((a)>(b) ? (b) : (a)) +#define F_MAX(a,b) ((a)>(b) ? (a) : (b)) +#define F_SGN(a) ((a)>0 ? 1 : ((a)<0 ? -1 : 0 )) +#define F_RNG(a,min,max) ((a)<(min) ? min : ((a)>(max) ? max : a )) + +#ifdef _MSC_VER +namespace +{ + double asinh( const double &value ) { + double returned; + + if(value>0) + returned = log(value + sqrt(value * value + 1)); + else + returned = -log(-value + sqrt(value * value + 1)); + + return(returned); + } + + double acosh( const double &value ) { + double returned; + + if(value>0) + returned = log(value + sqrt(value * value - 1)); + else + returned = -log(-value + sqrt(value * value - 1)); + + return(returned); + } + + double atanh( double value ) { + bool neg = value<0; + if(neg) value=-value; + double value_x2 = 2.0*value; + if(value>=0.5) + value = log(1.0+value_x2/(1.0-value))/2.0; + else + value = log(1.0+value_x2+value_x2*value/(1.0-value))/2.0; + return(neg ? -value : value); + } +} +#endif + +#define PARAMETER_TO_NUMBER(v,n) CNumber v = c->getArgument(n)->toNumber() +#define RETURN_NAN_IS_NAN(v) do{ if(v.isNaN()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN_NAN_IS_NAN_OR_INFINITY(v) do{ if(v.isNaN() || v.isInfinity()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN_INFINITY_IS_INFINITY(v) do{ if(v.isInfinity()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN_ZERO_IS_ZERO(v) do{ if(v.isZero()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN(a) do{ c->setReturnVar(c->newScriptVar(a)); return; }while(0) +#define RETURNconst(a) c->setReturnVar(c->constScriptVar(a)) + +//Math.abs(x) - returns absolute of given value +static void scMathAbs(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN(a.sign()<0?-a:a); +} + +//Math.round(a) - returns nearest round of given value +static void scMathRound(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN(a.round()); +} + +//Math.ceil(a) - returns nearest round of given value +static void scMathCeil(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_INFINITY_IS_INFINITY(a); + RETURN(a.ceil()); +} + +//Math.floor(a) - returns nearest round of given value +static void scMathFloor(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN(a.floor()); +} + +//Math.min(a,b) - returns minimum of two given values +static void scMathMin(const CFunctionsScopePtr &c, void *userdata) { + int length = c->getArgumentsLength(); + CNumber ret(InfinityPositive); + for(int i=0; ia) ret=a; + } + RETURN(ret); +} + +//Math.max(a,b) - returns maximum of two given values +static void scMathMax(const CFunctionsScopePtr &c, void *userdata) { + int length = c->getArgumentsLength(); + CNumber ret(InfinityNegative); + for(int i=0; ib) RETURNconst(NaN); + if(xb) RETURN(b); + RETURN(x); +} + +//Math.sign(a) - returns sign of given value (-1==negative,0=zero,1=positive) +static void scMathSign(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN(a.isZero() ? 0 : a.sign()); +} +static void scMathRandom(const CFunctionsScopePtr &c, void *) { + static int inited=0; + if(!inited) { + inited = 1; + srand((unsigned int)time(NULL)); + } + RETURN(double(rand())/RAND_MAX); +} + +//Math.toDegrees(a) - returns degree value of a given angle in radians +static void scMathToDegrees(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_INFINITY_IS_INFINITY(a); + RETURN( (180.0/k_PI)*a ); +} + +//Math.toRadians(a) - returns radians value of a given angle in degrees +static void scMathToRadians(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_INFINITY_IS_INFINITY(a); + RETURN( (k_PI/180.0)*a ); +} + +//Math.sin(a) - returns trig. sine of given angle in radians +static void scMathSin(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); RETURN_ZERO_IS_ZERO(a); + RETURN( sin(a.toDouble()) ); +} + +//Math.asin(a) - returns trig. arcsine of given angle in radians +static void scMathASin(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_ZERO_IS_ZERO(a); + if(abs(a)>1) RETURNconst(NaN); + RETURN( asin(a.toDouble()) ); +} + +//Math.cos(a) - returns trig. cosine of given angle in radians +static void scMathCos(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); + if(a.isZero()) RETURN(1); + RETURN( cos(a.toDouble()) ); +} + +//Math.acos(a) - returns trig. arccosine of given angle in radians +static void scMathACos(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); + if(abs(a)>1) RETURNconst(NaN); + else if(a==1) RETURN(0); + RETURN( acos(a.toDouble()) ); +} + +//Math.tan(a) - returns trig. tangent of given angle in radians +static void scMathTan(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); RETURN_ZERO_IS_ZERO(a); + RETURN( tan(a.toDouble()) ); +} + +//Math.atan(a) - returns trig. arctangent of given angle in radians +static void scMathATan(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_ZERO_IS_ZERO(a); + int infinity=a.isInfinity(); + if(infinity) RETURN(k_PI/(infinity*2)); + RETURN( atan(a.toDouble()) ); +} + +//Math.atan2(a,b) - returns trig. arctangent of given angle in radians +static void scMathATan2(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + PARAMETER_TO_NUMBER(b,"b"); RETURN_NAN_IS_NAN(b); + int sign_a = a.sign(); + int sign_b = b.sign(); + if(a.isZero()) + RETURN(sign_a>0 ? (sign_b>0 ? 0.0 : k_PI) : (sign_b>0 ? -0.0 : -k_PI)); + else if(b.isZero()) + RETURN((sign_a>0 ? k_PI : -k_PI)/2.0); + int infinity_a=a.isInfinity(); + int infinity_b=b.isInfinity(); + if(infinity_a) { + if(infinity_b>0) RETURN(k_PI/(infinity_a*4)); + else if(infinity_b<0) RETURN(3.0*k_PI/(infinity_a*4)); + else RETURN(k_PI/(infinity_a*2)); + } else if(infinity_b>0) + RETURN(sign_a>0 ? 0.0 : -0.0); + else if(infinity_b<0) + RETURN(sign_a>0 ? k_PI : -k_PI); + RETURN( atan2(a.toDouble(), b.toDouble()) ); +} + + +//Math.sinh(a) - returns trig. hyperbolic sine of given angle in radians +static void scMathSinh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + if(abs(a)>1) RETURNconst(NaN); + RETURN( sinh(a.toDouble()) ); +} + +//Math.asinh(a) - returns trig. hyperbolic arcsine of given angle in radians +static void scMathASinh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_INFINITY_IS_INFINITY(a); + RETURN_ZERO_IS_ZERO(a); + RETURN( asinh(a.toDouble()) ); +} + +//Math.cosh(a) - returns trig. hyperbolic cosine of given angle in radians +static void scMathCosh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isInfinity()) RETURNconst(InfinityPositive); + RETURN( cosh(a.toDouble()) ); +} + +//Math.acosh(a) - returns trig. hyperbolic arccosine of given angle in radians +static void scMathACosh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_INFINITY_IS_INFINITY(a); + if(abs(a)<1) RETURNconst(NaN); + RETURN( acosh(a.toDouble()) ); +} + +//Math.tanh(a) - returns trig. hyperbolic tangent of given angle in radians +static void scMathTanh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + if(a.isInfinity()) RETURN(a.sign()); + RETURN( tanh(a.toDouble()) ); +} + +//Math.atanh(a) - returns trig. hyperbolic arctangent of given angle in radians +static void scMathATanh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + CNumber abs_a = abs(a); + if(abs_a > 1) RETURNconst(NaN); + if(abs_a == 1) RETURNconst(Infinity(a.sign())); + RETURN( atanh(a.toDouble()) ); +} + +//Math.log(a) - returns natural logaritm (base E) of given value +static void scMathLog(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isZero()) RETURNconst(InfinityNegative); + if(a.sign()<0) RETURNconst(NaN); + if(a.isInfinity()) RETURNconst(InfinityPositive); + RETURN( log( a.toDouble()) ); +} + +//Math.log10(a) - returns logaritm(base 10) of given value +static void scMathLog10(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isZero()) RETURNconst(InfinityNegative); + if(a.sign()<0) RETURNconst(NaN); + if(a.isInfinity()) RETURNconst(InfinityPositive); + RETURN( log10( a.toDouble()) ); +} + +//Math.exp(a) - returns e raised to the power of a given number +static void scMathExp(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isZero()) RETURN(1); + int a_i = a.isInfinity(); + if(a_i>0) RETURNconst(InfinityPositive); + else if(a_i<0) RETURN(0); + RETURN( exp(a.toDouble()) ); +} + +//Math.pow(a,b) - returns the result of a number raised to a power (a)^(b) +static void scMathPow(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + PARAMETER_TO_NUMBER(b,"b"); RETURN_NAN_IS_NAN(b); + if(b.isZero()) RETURN(1); + RETURN_NAN_IS_NAN(a); + if(b==1) RETURN(a); + + int sign; + CNumber a_abs = abs(a); + if((sign = b.isInfinity())) { + if( a_abs==1 ) RETURNconst(NaN); + else if( (a_abs>1) ^ (sign<0) ) RETURN(b); else RETURN(0); + } else if((sign = a.isInfinity())) { + if(sign>0) { + if(b.sign()>0) RETURN(a); else RETURN(0); + } else { + bool b_is_odd_int = ((b+1)/2).isInteger(); + if(b.sign()>0) RETURNconst(b_is_odd_int?InfinityNegative:InfinityPositive); + else RETURN(b_is_odd_int?CNumber(NegativeZero):CNumber(0)); + } + } else if(a.isZero()) { + if(a.isNegativeZero()) { + bool b_is_odd_int = ((b+1)/2).isInteger(); + if(b.sign()>0) RETURN(b_is_odd_int?CNumber(NegativeZero):CNumber(0)); + else RETURNconst(b_is_odd_int?InfinityNegative:InfinityPositive); + } else + if(b.sign()>0) RETURN(a); else RETURNconst(InfinityPositive); + } + if(a.sign()<0 && !b.isInteger()) RETURNconst(NaN); + + RETURN( pow(a.toDouble(), b.toDouble()) ); +} + +//Math.sqr(a) - returns square of given value +static void scMathSqr(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN( a*a ); +} + +//Math.sqrt(a) - returns square root of given value +static void scMathSqrt(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + if(a.sign()<0) RETURNconst(NaN); + RETURN_INFINITY_IS_INFINITY(a); + RETURN( sqrt(a.toDouble()) ); +} + +// ----------------------------------------------- Register Functions +void registerMathFunctions(CTinyJS *tinyJS) {} +extern "C" void _registerMathFunctions(CTinyJS *tinyJS) { + + CScriptVarPtr Math = tinyJS->getRoot()->addChild("Math", tinyJS->newScriptVar(Object), SCRIPTVARLINK_CONSTANT); + + // --- Math and Trigonometry functions --- + tinyJS->addNative("function Math.abs(a)", scMathAbs, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.round(a)", scMathRound, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.ceil(a)", scMathCeil, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.floor(a)", scMathFloor, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.min()", scMathMin, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.max()", scMathMax, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.range(x,a,b)", scMathRange, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sign(a)", scMathSign, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.random(a)", scMathRandom, 0, SCRIPTVARLINK_BUILDINDEFAULT); + + +// atan2, ceil, floor, random, round, + + Math->addChild("LN2", tinyJS->newScriptVar(k_LN2), SCRIPTVARLINK_READONLY); + Math->addChild("LN10", tinyJS->newScriptVar(k_LN10), SCRIPTVARLINK_READONLY); + Math->addChild("LOG2E", tinyJS->newScriptVar(k_LOG2E), SCRIPTVARLINK_READONLY); + Math->addChild("LOG10E", tinyJS->newScriptVar(k_LOG10E), SCRIPTVARLINK_READONLY); + Math->addChild("SQRT1_2", tinyJS->newScriptVar(k_SQRT1_2), SCRIPTVARLINK_READONLY); + Math->addChild("SQRT2", tinyJS->newScriptVar(k_SQRT2), SCRIPTVARLINK_READONLY); + Math->addChild("PI", tinyJS->newScriptVar(k_PI), SCRIPTVARLINK_READONLY); +// tinyJS->addNative("function Math.PI()", scMathPI, 0); + tinyJS->addNative("function Math.toDegrees(a)", scMathToDegrees, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.toRadians(a)", scMathToRadians, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sin(a)", scMathSin, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.asin(a)", scMathASin, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.cos(a)", scMathCos, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.acos(a)", scMathACos, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.tan(a)", scMathTan, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.atan(a)", scMathATan, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.atan2(a,b)", scMathATan2, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sinh(a)", scMathSinh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.asinh(a)", scMathASinh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.cosh(a)", scMathCosh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.acosh(a)", scMathACosh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.tanh(a)", scMathTanh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.atanh(a)", scMathATanh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + + Math->addChild("E", tinyJS->newScriptVar(k_E), SCRIPTVARLINK_READONLY); + tinyJS->addNative("function Math.log(a)", scMathLog, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.log10(a)", scMathLog10, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.exp(a)", scMathExp, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.pow(a,b)", scMathPow, 0, SCRIPTVARLINK_BUILDINDEFAULT); + + tinyJS->addNative("function Math.sqr(a)", scMathSqr, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sqrt(a)", scMathSqrt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + +} diff --git a/TinyJS_MathFunctions.h b/TinyJS_MathFunctions.h new file mode 100644 index 0000000..ea87da6 --- /dev/null +++ b/TinyJS_MathFunctions.h @@ -0,0 +1,51 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * - Math and Trigonometry functions + * + * Authored By O.Z.L.B. + * + * Copyright (C) 2011 O.Z.L.B. + * + + * 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. + */ + +#pragma message("The include "__FILE__" is deprecated - MathFunctions now registered by default") + +#ifndef TINYJS_MATHFUNCTIONS_H +#define TINYJS_MATHFUNCTIONS_H + +#include "TinyJS.h" + +/// Register useful math. functions with the TinyJS interpreter +extern void DEPRECATED("is deprecated - MathFunctions now registered by default") registerMathFunctions(CTinyJS *tinyJS); + +#endif diff --git a/TinyJS_StringFunctions.cpp b/TinyJS_StringFunctions.cpp new file mode 100644 index 0000000..99fa725 --- /dev/null +++ b/TinyJS_StringFunctions.cpp @@ -0,0 +1,567 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored 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. + */ + +#include +#include "TinyJS.h" + +#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 +#endif +using namespace std; +// ----------------------------------------------- Actual Functions + +#define CheckObjectCoercible(var) do { \ + if(var->isUndefined() || var->isNull())\ + c->throwError(TypeError, "can't convert undefined to object");\ + }while(0) + +static string this2string(const CFunctionsScopePtr &c) { + CScriptVarPtr This = c->getArgument("this"); + CheckObjectCoercible(This); + return This->toString(); +} + +static void scStringCharAt(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + int p = c->getArgument("pos")->toNumber().toInt32(); + if (p>=0 && p<(int)str.length()) + c->setReturnVar(c->newScriptVar(str.substr(p, 1))); + else + c->setReturnVar(c->newScriptVar("")); +} + +static void scStringCharCodeAt(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + int p = c->getArgument("pos")->toNumber().toInt32(); + if (p>=0 && p<(int)str.length()) + c->setReturnVar(c->newScriptVar((unsigned char)str.at(p))); + else + c->setReturnVar(c->constScriptVar(NaN)); +} + +static void scStringConcat(const CFunctionsScopePtr &c, void *userdata) { + int length = c->getArgumentsLength(); + string str = this2string(c); + for(int i=(int)userdata; igetArgument(i)->toString()); + c->setReturnVar(c->newScriptVar(str)); +} + +static void scStringIndexOf(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + string search = c->getArgument("search")->toString(); + CNumber pos_n = c->getArgument("pos")->toNumber(); + string::size_type pos; + pos = (userdata) ? string::npos : 0; + if(pos_n.sign()<0) pos = 0; + else if(pos_n.isInfinity()) pos = string::npos; + else if(pos_n.isFinite()) pos = pos_n.toInt32(); + string::size_type p = (userdata==0) ? str.find(search, pos) : str.rfind(search, pos); + int val = (p==string::npos) ? -1 : p; + c->setReturnVar(c->newScriptVar(val)); +} + +static void scStringLocaleCompare(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + string compareString = c->getArgument("compareString")->toString(); + int val = 0; + if(strcompareString) val = 1; + c->setReturnVar(c->newScriptVar(val)); +} + +static void scStringQuote(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + c->setReturnVar(c->newScriptVar(getJSString(str))); +} + +#ifndef NO_REGEXP +// helper-function for replace search +static bool regex_search(const string &str, const string::const_iterator &search_begin, const string &substr, bool ignoreCase, bool sticky, string::const_iterator &match_begin, string::const_iterator &match_end, smatch &match) { + regex::flag_type flags = regex_constants::ECMAScript; + if(ignoreCase) flags |= regex_constants::icase; + regex_constants::match_flag_type mflag = sticky?regex_constants::match_continuous:regex_constants::format_default; + if(str.begin() != search_begin) mflag |= regex_constants::match_prev_avail; + if(regex_search(search_begin, str.end(), match, regex(substr, flags), mflag)) { + match_begin = match[0].first; + match_end = match[0].second; + return true; + } + return false; +} +static bool regex_search(const string &str, const string::const_iterator &search_begin, const string &substr, bool ignoreCase, bool sticky, string::const_iterator &match_begin, string::const_iterator &match_end) { + smatch match; + return regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end, match); +} +#endif /* NO_REGEXP */ + +static bool charcmp (char i, char j) { return (i==j); } +static bool charicmp (char i, char j) { return (toupper(i)==toupper(j)); } +// helper-function for replace search +static bool string_search(const string &str, const string::const_iterator &search_begin, const string &substr, bool ignoreCase, bool sticky, string::const_iterator &match_begin, string::const_iterator &match_end) { + bool (*cmp)(char,char) = ignoreCase ? charicmp : charcmp; + if(sticky) { + match_begin = match_end = search_begin; + string::const_iterator s1e=str.end(); + string::const_iterator s2=substr.begin(), s2e=substr.end(); + while(match_end!=s1e && s2!=s2e && cmp(*match_end++, *s2++)); + return s2==s2e; + } + match_begin = search(search_begin, str.end(), substr.begin(), substr.end(), cmp); + if(match_begin==str.end()) return false; + match_end = match_begin + substr.length(); + return true; +} +//************************************ +// Method: getRegExpData +// FullName: getRegExpData +// Access: public static +// Returns: bool true if regexp-param=RegExp-Object / other false +// Qualifier: +// Parameter: const CFunctionsScopePtr & c +// Parameter: const string & regexp - parameter name of the regexp +// Parameter: bool noUndefined - true an undefined regexp aims in "" else in "undefined" +// Parameter: const string & flags - parameter name of the flags +// Parameter: string & substr - rgexp.source +// Parameter: bool & global +// Parameter: bool & ignoreCase +// Parameter: bool & sticky +//************************************ +static CScriptVarPtr getRegExpData(const CFunctionsScopePtr &c, const string ®exp, bool noUndefined, const char *flags_argument, string &substr, bool &global, bool &ignoreCase, bool &sticky) { + CScriptVarPtr regexpVar = c->getArgument(regexp); + if(regexpVar->isRegExp()) { +#ifndef NO_REGEXP + CScriptVarRegExpPtr RegExp(regexpVar); + substr = RegExp->Regexp(); + ignoreCase = RegExp->IgnoreCase(); + global = RegExp->Global(); + sticky = RegExp->Sticky(); + return RegExp; +#endif /* NO_REGEXP */ + } else { + substr.clear(); + if(!noUndefined || !regexpVar->isUndefined()) substr = regexpVar->toString(); + CScriptVarPtr flagVar; + if(flags_argument && (flagVar = c->getArgument(flags_argument)) && !flagVar->isUndefined()) { + string flags = flagVar->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]); + } + global = flags.find_first_of('g')!=string::npos; + ignoreCase = flags.find_first_of('i')!=string::npos; + sticky = flags.find_first_of('y')!=string::npos; + } else + global = ignoreCase = sticky = false; + } + return CScriptVarPtr(); +} + +static void scStringReplace(const CFunctionsScopePtr &c, void *) { + const string str = this2string(c); + CScriptVarPtr newsubstrVar = c->getArgument("newsubstr"); + string substr, ret_str; + bool global, ignoreCase, sticky; + bool isRegExp = getRegExpData(c, "substr", false, "flags", substr, global, ignoreCase, sticky); + if(isRegExp && !newsubstrVar->isFunction()) { +#ifndef NO_REGEXP + regex::flag_type flags = regex_constants::ECMAScript; + if(ignoreCase) flags |= regex_constants::icase; + regex_constants::match_flag_type mflags = regex_constants::match_default; + if(!global) mflags |= regex_constants::format_first_only; + if(sticky) mflags |= regex_constants::match_continuous; + ret_str = regex_replace(str, regex(substr, flags), newsubstrVar->toString(), mflags); +#endif /* NO_REGEXP */ + } else { + bool (*search)(const string &, const string::const_iterator &, const string &, bool, bool, string::const_iterator &, string::const_iterator &); +#ifndef NO_REGEXP + if(isRegExp) + search = regex_search; + else +#endif /* NO_REGEXP */ + search = string_search; + string newsubstr; + vector arguments; + if(!newsubstrVar->isFunction()) + newsubstr = newsubstrVar->toString(); + global = global && substr.length(); + string::const_iterator search_begin=str.begin(), match_begin, match_end; + if(search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)) { + do { + ret_str.append(search_begin, match_begin); + if(newsubstrVar->isFunction()) { + arguments.push_back(c->newScriptVar(string(match_begin, match_end))); + newsubstr = c->getContext()->callFunction(newsubstrVar, arguments, c)->toString(); + arguments.pop_back(); + } + ret_str.append(newsubstr); +#if 1 /* Fix from "vcmpeq" (see Issue 14) currently untested */ + if (match_begin == match_end) { + if (search_begin != str.end()) + ++search_begin; + else + break; + } else { + search_begin = match_end; + } +#else + search_begin = match_end; +#endif + } while(global && search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)); + } + ret_str.append(search_begin, str.end()); + } + c->setReturnVar(c->newScriptVar(ret_str)); +} +#ifndef NO_REGEXP +static void scStringMatch(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + + string flags="flags", substr, newsubstr, match; + bool global, ignoreCase, sticky; + CScriptVarRegExpPtr RegExp = getRegExpData(c, "regexp", true, "flags", substr, global, ignoreCase, sticky); + if(!global) { + if(!RegExp) + RegExp = ::newScriptVar(c->getContext(), substr, flags); + if(RegExp) { + try { + c->setReturnVar(RegExp->exec(str)); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + } + } else { + try { + CScriptVarArrayPtr retVar = c->newScriptVar(Array); + int idx=0; + string::size_type offset=0; + global = global && substr.length(); + string::const_iterator search_begin=str.begin(), match_begin, match_end; + if(regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)) { + do { + offset = match_begin-str.begin(); + retVar->addChild(int2string(idx++), c->newScriptVar(string(match_begin, match_end))); +#if 1 /* Fix from "vcmpeq" (see Issue 14) currently untested */ + if (match_begin == match_end) { + if (search_begin != str.end()) + ++search_begin; + else + break; + } else { + search_begin = match_end; + } +#else + search_begin = match_end; +#endif + } while(global && regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)); + } + if(idx) { + retVar->addChild("input", c->newScriptVar(str)); + retVar->addChild("index", c->newScriptVar((int)offset)); + c->setReturnVar(retVar); + } else + c->setReturnVar(c->constScriptVar(Null)); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + } +} +#endif /* NO_REGEXP */ + +static void scStringSearch(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + + string substr; + bool global, ignoreCase, sticky; + getRegExpData(c, "regexp", true, "flags", substr, global, ignoreCase, sticky); + string::const_iterator search_begin=str.begin(), match_begin, match_end; +#ifndef NO_REGEXP + try { + c->setReturnVar(c->newScriptVar(regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)?match_begin-search_begin:-1)); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } +#else /* NO_REGEXP */ + c->setReturnVar(c->newScriptVar(string_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)?match_begin-search_begin:-1)); +#endif /* NO_REGEXP */ +} + +static void scStringSlice(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + int length = c->getArgumentsLength()-((int)userdata & 1); + bool slice = ((int)userdata & 2) == 0; + int start = c->getArgument("start")->toNumber().toInt32(); + int end = (int)str.size(); + if(slice && start<0) start = str.size()+start; + if(length>1) { + end = c->getArgument("end")->toNumber().toInt32(); + if(slice && end<0) end = str.size()+end; + } + if(!slice && end < start) { end^=start; start^=end; end^=start; } + if(start<0) start = 0; + if(start>=(int)str.size()) + c->setReturnVar(c->newScriptVar("")); + else if(end <= start) + c->setReturnVar(c->newScriptVar("")); + else + c->setReturnVar(c->newScriptVar(str.substr(start, end-start))); +} + +static void scStringSplit(const CFunctionsScopePtr &c, void *) { + const string str = this2string(c); + + string seperator; + bool global, ignoreCase, sticky; +#ifndef NO_REGEXP + CScriptVarRegExpPtr RegExp = getRegExpData(c, "separator", true, 0, seperator, global, ignoreCase, sticky); +#else + getRegExpData(c, "separator", true, 0, seperator, global, ignoreCase, sticky); +#endif + + CScriptVarPtr sep_var = c->getArgument("separator"); + CScriptVarPtr limit_var = c->getArgument("limit"); + int limit = limit_var->isUndefined() ? 0x7fffffff : limit_var->toNumber().toInt32(); + + CScriptVarPtr result(newScriptVar(c->getContext(), Array)); + c->setReturnVar(result); + if(limit == 0) + return; + else if(!str.size() || sep_var->isUndefined()) { + result->setArrayIndex(0, c->newScriptVar(str)); + return; + } + if(seperator.size() == 0) { + for(int i=0; isetArrayIndex(i, c->newScriptVar(str.substr(i,1))); + return; + } + int length = 0; + string::const_iterator search_begin=str.begin(), match_begin, match_end; +#ifndef NO_REGEXP + smatch match; +#endif + bool found=true; + while(found) { +#ifndef NO_REGEXP + if(RegExp) { + try { + found = regex_search(str, search_begin, seperator, ignoreCase, sticky, match_begin, match_end, match); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + } else /* NO_REGEXP */ +#endif + found = string_search(str, search_begin, seperator, ignoreCase, sticky, match_begin, match_end); + string f; + if(found) { + result->setArrayIndex(length++, c->newScriptVar(string(search_begin, match_begin))); + if(length>=limit) break; +#ifndef NO_REGEXP + for(uint32_t i=1; isetArrayIndex(length++, c->newScriptVar(string(match[i].first, match[i].second))); + else + result->setArrayIndex(length++, c->constScriptVar(Undefined)); + if(length>=limit) break; + } + if(length>=limit) break; +#endif + search_begin = match_end; + } else { + result->setArrayIndex(length++, c->newScriptVar(string(search_begin,str.end()))); + if(length>=limit) break; + } + } +} + +static void scStringSubstr(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + int length = c->getArgumentsLength()-(int)userdata; + int start = c->getArgument("start")->toNumber().toInt32(); + if(start<0 || start>=(int)str.size()) + c->setReturnVar(c->newScriptVar("")); + else if(length>1) { + int length = c->getArgument("length")->toNumber().toInt32(); + c->setReturnVar(c->newScriptVar(str.substr(start, length))); + } else + c->setReturnVar(c->newScriptVar(str.substr(start))); +} + +static void scStringToLowerCase(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + transform(str.begin(), str.end(), str.begin(), ::tolower); + c->setReturnVar(c->newScriptVar(str)); +} + +static void scStringToUpperCase(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + transform(str.begin(), str.end(), str.begin(), ::toupper); + c->setReturnVar(c->newScriptVar(str)); +} + +static void scStringTrim(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + string::size_type start = 0; + string::size_type end = string::npos; + if((((int)userdata) & 2) == 0) { + start = str.find_first_not_of(" \t\r\n"); + if(start == string::npos) start = 0; + } + if((((int)userdata) & 1) == 0) { + end = str.find_last_not_of(" \t\r\n"); + if(end != string::npos) end = 1+end-start; + } + c->setReturnVar(c->newScriptVar(str.substr(start, end))); +} + + + +static void scCharToInt(const CFunctionsScopePtr &c, void *) { + string str = c->getArgument("ch")->toString();; + int val = 0; + if (str.length()>0) + val = (int)str.c_str()[0]; + c->setReturnVar(c->newScriptVar(val)); +} + + +static void scStringFromCharCode(const CFunctionsScopePtr &c, void *) { + char str[2]; + str[0] = c->getArgument("char")->toNumber().toInt32(); + str[1] = 0; + c->setReturnVar(c->newScriptVar(str)); +} + +////////////////////////////////////////////////////////////////////////// +// RegExp-Stuff +////////////////////////////////////////////////////////////////////////// + +#ifndef NO_REGEXP + +static void scRegExpTest(const CFunctionsScopePtr &c, void *) { + CScriptVarRegExpPtr This = c->getArgument("this"); + if(This) + c->setReturnVar(This->exec(c->getArgument("str")->toString(), true)); + else + c->throwError(TypeError, "Object is not a RegExp-Object in test(str)"); +} +static void scRegExpExec(const CFunctionsScopePtr &c, void *) { + CScriptVarRegExpPtr This = c->getArgument("this"); + if(This) + c->setReturnVar(This->exec(c->getArgument("str")->toString())); + else + c->throwError(TypeError, "Object is not a RegExp-Object in exec(str)"); +} +#endif /* NO_REGEXP */ + +// ----------------------------------------------- Register Functions +void registerStringFunctions(CTinyJS *tinyJS) {} +extern "C" void _registerStringFunctions(CTinyJS *tinyJS) { + CScriptVarPtr fnc; + // charAt + tinyJS->addNative("function String.prototype.charAt(pos)", scStringCharAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.charAt(this,pos)", scStringCharAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // charCodeAt + tinyJS->addNative("function String.prototype.charCodeAt(pos)", scStringCharCodeAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.charCodeAt(this,pos)", scStringCharCodeAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // concat + tinyJS->addNative("function String.prototype.concat()", scStringConcat, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.concat(this)", scStringConcat, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + // indexOf + tinyJS->addNative("function String.prototype.indexOf(search,pos)", scStringIndexOf, 0, SCRIPTVARLINK_BUILDINDEFAULT); // find the position of a string in a string, -1 if not + tinyJS->addNative("function String.indexOf(this,search,pos)", scStringIndexOf, 0, SCRIPTVARLINK_BUILDINDEFAULT); // find the position of a string in a string, -1 if not + // lastIndexOf + tinyJS->addNative("function String.prototype.lastIndexOf(search,pos)", scStringIndexOf, (void*)-1, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + tinyJS->addNative("function String.lastIndexOf(this,search,pos)", scStringIndexOf, (void*)-1, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + // localeCompare + tinyJS->addNative("function String.prototype.localeCompare(compareString)", scStringLocaleCompare, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.localeCompare(this,compareString)", scStringLocaleCompare, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // quote + tinyJS->addNative("function String.prototype.quote()", scStringQuote, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.quote(this)", scStringQuote, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#ifndef NO_REGEXP + // match + tinyJS->addNative("function String.prototype.match(regexp, flags)", scStringMatch, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.match(this, regexp, flags)", scStringMatch, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#endif /* !REGEXP */ + // replace + tinyJS->addNative("function String.prototype.replace(substr, newsubstr, flags)", scStringReplace, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.replace(this, substr, newsubstr, flags)", scStringReplace, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // search + tinyJS->addNative("function String.prototype.search(regexp, flags)", scStringSearch, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.search(this, regexp, flags)", scStringSearch, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // slice + tinyJS->addNative("function String.prototype.slice(start,end)", scStringSlice, 0, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + tinyJS->addNative("function String.slice(this,start,end)", scStringSlice, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + // split + tinyJS->addNative("function String.prototype.split(separator,limit)", scStringSplit, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.split(this,separator,limit)", scStringSplit, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // substr + tinyJS->addNative("function String.prototype.substr(start,length)", scStringSubstr, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.substr(this,start,length)", scStringSubstr, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + // substring + tinyJS->addNative("function String.prototype.substring(start,end)", scStringSlice, (void*)2, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.substring(this,start,end)", scStringSlice, (void*)3, SCRIPTVARLINK_BUILDINDEFAULT); + // toLowerCase toLocaleLowerCase currently the same function + tinyJS->addNative("function String.prototype.toLowerCase()", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toLowerCase(this)", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.prototype.toLocaleLowerCase()", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toLocaleLowerCase(this)", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // toUpperCase toLocaleUpperCase currently the same function + tinyJS->addNative("function String.prototype.toUpperCase()", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toUpperCase(this)", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.prototype.toLocaleUpperCase()", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toLocaleUpperCase(this)", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // trim + tinyJS->addNative("function String.prototype.trim()", scStringTrim, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.trim(this)", scStringTrim, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // trimLeft + tinyJS->addNative("function String.prototype.trimLeft()", scStringTrim, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.trimLeft(this)", scStringTrim, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + // trimRight + tinyJS->addNative("function String.prototype.trimRight()", scStringTrim, (void*)2, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.trimRight(this)", scStringTrim, (void*)2, SCRIPTVARLINK_BUILDINDEFAULT); + + tinyJS->addNative("function charToInt(ch)", scCharToInt, 0, SCRIPTVARLINK_BUILDINDEFAULT); // convert a character to an int - get its value + + tinyJS->addNative("function String.prototype.fromCharCode(char)", scStringFromCharCode, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#ifndef NO_REGEXP + tinyJS->addNative("function RegExp.prototype.test(str)", scRegExpTest, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function RegExp.prototype.exec(str)", scRegExpExec, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#endif /* NO_REGEXP */ +} + diff --git a/TinyJS_StringFunctions.h b/TinyJS_StringFunctions.h new file mode 100644 index 0000000..840fddf --- /dev/null +++ b/TinyJS_StringFunctions.h @@ -0,0 +1,40 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored 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. + */ + +#pragma message("The include "__FILE__" is deprecated - StringFunctions now registered by default") + +#ifndef TINYJS_STRINGFUNCTIONS_H +#define TINYJS_STRINGFUNCTIONS_H + +#include "TinyJS.h" + +/// Register useful functions with the TinyJS interpreter +extern void DEPRECATED("is deprecated - StringFunctions now registered by default") registerStringFunctions(CTinyJS *tinyJS); + +#endif diff --git a/TinyJS_Threading.cpp b/TinyJS_Threading.cpp new file mode 100644 index 0000000..a802991 --- /dev/null +++ b/TinyJS_Threading.cpp @@ -0,0 +1,236 @@ +#include "TinyJS_Threading.h" + +#undef HAVE_THREADING +#if !defined(NO_THREADING) && !defined(HAVE_CUSTOM_THREADING_IMPL) +# define HAVE_THREADING +# ifdef HAVE_CXX_THREADS +# include +# else +# if defined(WIN32) && !defined(HAVE_PTHREAD) +# include +# else +# include +# ifndef HAVE_PTHREAD +# define HAVE_PTHREAD +# endif +# endif +# endif +#endif + +#ifdef HAVE_THREADING + +////////////////////////////////////////////////////////////////////////// +// Mutex +////////////////////////////////////////////////////////////////////////// +#ifndef HAVE_CXX_THREADS + +#ifndef HAVE_PTHREAD +// simple mutex 4 windows +//# define pthread_mutex_t HANDLE +//# define pthread_mutex_init(m, a) *(m) = CreateMutex(NULL, false, NULL) +//# define pthread_mutex_destroy(m) CloseHandle(*(m)); +//# define pthread_mutex_lock(m) WaitForSingleObject(*(m), INFINITE); +//# define pthread_mutex_unlock(m) ReleaseMutex(*(m)); +# define pthread_mutex_t CRITICAL_SECTION +# define pthread_mutex_init(m, a) { InitializeCriticalSection(m); 0; } +# define pthread_mutex_destroy(m) do {} while(0) +# define pthread_mutex_lock(m) EnterCriticalSection(m) +# define pthread_mutex_unlock(m) LeaveCriticalSection(m) + +#endif + +class CScriptMutex_impl : public CScriptMutex::CScriptMutex_t { +public: + CScriptMutex_impl() { + pthread_mutex_init(&mutex, NULL); + } + ~CScriptMutex_impl() { + pthread_mutex_destroy(&mutex); + } + void lock() { + pthread_mutex_lock(&mutex); + } + void unlock() { + pthread_mutex_unlock(&mutex); + } + void *getRealMutex() { return &mutex; } + pthread_mutex_t mutex; +}; + + +CScriptMutex::CScriptMutex() { + mutex = new CScriptMutex_impl; +} + +CScriptMutex::~CScriptMutex() { + delete mutex; +} + +#endif /*HAVE_CXX_THREADS*/ + +////////////////////////////////////////////////////////////////////////// +// CondVar +////////////////////////////////////////////////////////////////////////// + +#ifndef HAVE_CXX_THREADS + +#ifndef HAVE_PTHREAD +// simple conditional Variable 4 windows +# define pthread_cond_t CONDITION_VARIABLE +# define pthread_cond_init(c, a) InitializeConditionVariable(c) +# define pthread_cond_destroy(c) do {} while(0) +# define pthread_cond_wait(c, m) SleepConditionVariableCS(c, m, INFINITE) +# define pthread_cond_signal(c) WakeConditionVariable(c); +#endif + +class CScriptCondVar_impl : public CScriptCondVar::CScriptCondVar_t { +public: + CScriptCondVar_impl(CScriptCondVar *_this) : This(_this) { + pthread_cond_init(&cond, NULL); + } + ~CScriptCondVar_impl() { + pthread_cond_destroy(&cond); + } + CScriptCondVar *This; + void notify_one() { + pthread_cond_signal(&cond); + } + void wait(CScriptUniqueLock &Lock) { + pthread_cond_wait(&cond, (pthread_mutex_t *)Lock.mutex->getRealMutex()); + } + pthread_cond_t cond; +}; + +CScriptCondVar::CScriptCondVar() { + condVar = new CScriptCondVar_impl(this); +} +CScriptCondVar::~CScriptCondVar() { + delete condVar; +} + +#endif /*HAVE_CXX_THREADS*/ + + + +////////////////////////////////////////////////////////////////////////// +// Threading +////////////////////////////////////////////////////////////////////////// + + +#ifdef HAVE_CXX_THREADS +# define pthread_attr_t int +# define pthread_attr_init(a) do {} while(0) +# define pthread_attr_destroy(a) do {} while(0) +# define pthread_t std::thread +# define pthread_create(t, attr, fnc, a) *(t) = std::thread(fnc, this); +# define pthread_join(t, v) t.join(); +#elif !defined(HAVE_PTHREAD) +// simple pthreads 4 windows +# define pthread_attr_t SIZE_T +# define pthread_attr_init(attr) (*attr=0) +# define pthread_attr_destroy(a) do {} while(0) +# define pthread_attr_setstacksize(attr, stack) *attr=stack; + +# define pthread_t HANDLE +# define pthread_create(t, attr, fnc, a) *(t) = CreateThread(NULL, attr ? *((pthread_attr_t*)attr) : 0, (LPTHREAD_START_ROUTINE)fnc, a, 0, NULL) +# define pthread_join(t, v) WaitForSingleObject(t, INFINITE), GetExitCodeThread(t,(LPDWORD)v), CloseHandle(t) +#endif + +class CScriptThread_impl : public CScriptThread::CScriptThread_t { +public: + CScriptThread_impl(CScriptThread *_this) : retvar((void*)-1), activ(false), running(false), started(false), This(_this) {} + ~CScriptThread_impl() {} + void Run() { + if(started) return; + activ = true; +// pthread_attr_t attribute; +// pthread_attr_init(&attribute); +// pthread_attr_setstacksize(&attribute,1024); + pthread_create(&thread, NULL /*&attribute*/, (void*(*)(void*))ThreadFnc, this); +// pthread_attr_destroy(&attribute); + while(!started); + } + int Stop(bool Wait) { + if(!running) return started ? retValue() : -1; + activ = false; + if(Wait) { + pthread_join(thread, &retvar); + } + return (int)retvar; + } + int retValue() { return (int)retvar; } + bool isActiv() { return activ; } + bool isRunning() { return running; } + bool isStarted() { return started; } + static void *ThreadFnc(CScriptThread_impl *This) { + This->running = This->started = true; + This->retvar = (void*)This->This->ThreadFnc(); + This->running = false; + This->This->ThreadFncFinished(); + return This->retvar; + } + void *retvar; + bool activ; + bool running; + bool started; + CScriptThread *This; + pthread_t thread; +}; + +CScriptThread::CScriptThread() { + thread = new CScriptThread_impl(this); +} +CScriptThread::~CScriptThread() { + delete thread; +} +void CScriptThread::ThreadFncFinished() {} + +CScriptCoroutine::StopIteration_t CScriptCoroutine::StopIteration; + +bool CScriptCoroutine::next() +{ + if(!isStarted()) { + Run(); + wake_main.wait(); + } else if(isRunning()) { + wake_thread.post(); + wake_main.wait(); + } else + return false; + if(!isRunning()) return false; + return true; +} +bool CScriptCoroutine::yield_no_throw() { + wake_main.post(); + wake_thread.wait(); + return isActiv(); +} +void CScriptCoroutine::yield() { + wake_main.post(); + wake_thread.wait(); + if(!isActiv()) { + throw StopIteration; + } +} +int CScriptCoroutine::ThreadFnc() { + int ret=-1; + try { + ret = Coroutine(); + } catch(StopIteration_t &) { + return 0; + } catch(std::exception & e) { + printf("CScriptCoroutine has received an uncaught exception: %s\n", e.what()); + return -1; + } catch(...) { + printf("CScriptCoroutine has received an uncaught and unknown exception\n"); + return -1; + } + return ret; +} +void CScriptCoroutine::ThreadFncFinished() { + wake_main.post(); +} + + +#endif // HAVE_THREADING + diff --git a/TinyJS_Threading.h b/TinyJS_Threading.h new file mode 100644 index 0000000..9b36607 --- /dev/null +++ b/TinyJS_Threading.h @@ -0,0 +1,160 @@ +#ifndef TinyJS_Threading_h__ +#define TinyJS_Threading_h__ +#include "config.h" +#ifndef NO_THREADING +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored 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 HAVE_CXX_THREADS + +# include +# include +typedef std::mutex CScriptMutex; +typedef std::unique_lock CScriptUniqueLock; +typedef std::condition_variable CScriptCondVar; + +#else + +class CScriptMutex { +public: + CScriptMutex(); + ~CScriptMutex(); + void lock() { mutex->lock(); } + void unlock() { mutex->unlock(); } + void *getRealMutex() { return mutex->getRealMutex(); } + class CScriptMutex_t{ + public: +// virtual ~CScriptMutex_t()=0; + virtual void lock()=0; + virtual void unlock()=0; + virtual void *getRealMutex()=0; + }; +private: + CScriptMutex_t *mutex; + +}; + +class CScriptUniqueLock { +public: + CScriptUniqueLock(CScriptMutex &Mutex) : mutex(&Mutex) { mutex->lock(); } + ~CScriptUniqueLock() { mutex->unlock(); } + CScriptMutex *mutex; +}; + +class CScriptCondVar { +public: + CScriptCondVar(); + virtual ~CScriptCondVar(); + void notify_one() { condVar->notify_one(); } + void wait(CScriptUniqueLock &Lock) { condVar->wait(Lock); } + class CScriptCondVar_t{ + public: + virtual void notify_one()=0; + virtual void wait(CScriptUniqueLock &Lock)=0; + }; +private: + CScriptCondVar_t *condVar; +}; + +#endif /*HAVE_CXX_THREADS*/ + + +class CScriptSemaphore +{ +private: + CScriptCondVar cond; + CScriptMutex mutex; + unsigned int count; + unsigned int max_count; + +public: + CScriptSemaphore(unsigned int currentCount=1, unsigned int maxCount=1) : count(currentCount), max_count(maxCount) {} + void post() { + CScriptUniqueLock lock(mutex); + + ++count; + cond.notify_one(); + } + void wait() { + CScriptUniqueLock lock(mutex); + while(!count) cond.wait(lock); + --count; + } + unsigned int currentWaits() { + CScriptUniqueLock lock(mutex); + return count; + } +}; + +class CScriptThread { +public: + CScriptThread(); + virtual ~CScriptThread(); + void Run() { thread->Run(); } + int Stop(bool Wait=true) { return thread->Stop(Wait); } + int retValue() { return thread->retValue(); } + virtual int ThreadFnc()=0; + virtual void ThreadFncFinished(); + bool isActiv() { return thread->isActiv(); } + bool isRunning() { return thread->isRunning(); } + bool isStarted() { return thread->isStarted(); } + class CScriptThread_t{ + public: + virtual void Run()=0; + virtual int Stop(bool Wait)=0; + virtual bool isActiv()=0; + virtual bool isRunning()=0; + virtual bool isStarted()=0; + virtual int retValue()=0; + }; +private: + CScriptThread_t *thread; +}; + +class CScriptCoroutine : protected CScriptThread { +public: + CScriptCoroutine() : wake_thread(0), wake_main(0) {} + typedef struct{} StopIteration_t; + static StopIteration_t StopIteration; + bool next(); // returns true if coroutine is running +protected: + virtual int Coroutine()=0; + void yield(); + bool yield_no_throw(); +private: + virtual int ThreadFnc(); + virtual void ThreadFncFinished(); + CScriptSemaphore wake_thread; + CScriptSemaphore wake_main; +}; + + + +#endif // NO_THREADING +#endif // TinyJS_Threading_h__ diff --git a/config.h b/config.h new file mode 100644 index 0000000..0642030 --- /dev/null +++ b/config.h @@ -0,0 +1,177 @@ +#ifndef _42TinyJS_config_h__ +#define _42TinyJS_config_h__ + +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored 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. + */ + +////////////////////////////////////////////////////////////////////////// + +/* POOL-ALLOCATOR + * ============== + * To speed-up new & delete 42TinyJS adds an object-pool + * The pool is activated by default. + * To deactivate this stuff define NO_POOL_ALLOCATOR + */ +//#define NO_POOL_ALLOCATOR + +/* + * for debugging-stuff you can define DEBUG_POOL_ALLOCATOR + * if a memory-leak detected the allocator usage is printed to stderr + */ +//#define DEBUG_POOL_ALLOCATOR +/* + * with define LOG_POOL_ALLOCATOR_MEMORY_USAGE + * the allocator usage is always printed to stderr + */ +//#define LOG_POOL_ALLOCATOR_MEMORY_USAGE + +// NOTE: _DEBUG or LOG_POOL_ALLOCATOR_MEMORY_USAGE implies DEBUG_POOL_ALLOCATOR + +////////////////////////////////////////////////////////////////////////// + +/* REGEXP-SUPPORT + * ============== + * The RegExp-support needs boost-regex or TR1-regex + * To deactivate this stuff define NO_REGEXP + */ +//#define NO_REGEXP + +/* if NO_REGEXP not defined is included and std::regex is used + * you can define HAVE_BOOST_REGEX and is included and boost::regex is used + */ +//#define HAVE_BOOST_REGEX + +/* or you can define HAVE_TR1_REGEX and is included and std::tr1::regex is used + */ +//#define HAVE_TR1_REGEX + + +////////////////////////////////////////////////////////////////////////// + +/* LET-STUFF + * ========= + * Redeclaration of LET-vars is not allowed in block-scopes. + * But in the root- and functions-scopes it is currently allowed + * In future ECMAScript versions this will be also in root-and functions-scopes forbidden + * To enable the future behavior define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES + */ +//#define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES + +////////////////////////////////////////////////////////////////////////// + +/* GENERATOR's + * =========== + * functions with "yield" in it is detected as Generator. + * Generator-support needs threading-stuff + * To disable Generators define NO_GENERATORS + */ +//#define NO_GENERATORS + + +////////////////////////////////////////////////////////////////////////// + +/* MULTI-THREADING + * =============== + * 42TinyJS is basically thread-save. + * You can run different or the same JS-code simultaneously in different instances of class TinyJS. + * >>> NOTE: You can NOT run more threads on the SAME instance of class TinyJS <<< + * The threading-stuff is needed by the pool-allocator (locking) and the generator-/yield-stuff + * to deactivate threading define NO_THREADING + * NOTE: if NO_POOL_ALLOCATOR not defined you can not run JS-code simultaneously + */ +//#define NO_THREADING + +/* with C++2011 (or MS VisualC++ 2012 and above) the C++ 2011 STL-Threading-API is used. + * You can define NO_CXX_THREADS to use alternate API's + */ +//#define NO_CXX_THREADS + +/* if C++ 2011 STL-Threading-API not available + * on Windows the windows-threading-API is used by default. + * on non-Windows (WIN32 is not defined) it is tried to use the POSIX pthread-API + * to force the pthread-API define HAVE_PTHREAD (windows needs in this case + * a pthread-lib e.g http://http://sourceware.org/pthreads-win32/) + */ +//#define HAVE_PTHREAD + +/* you can implement your own custom thread-implementation. + * to prevent the using of the win- or pthread-API define HAVE_CUSTOM_THREADING_IMPL + */ +//#define HAVE_CUSTOM_THREADING_IMPL + +//////////////////////////////////////////////// +// DO NOT MAKE CHANGES OF THE FOLLOWING STUFF // +//////////////////////////////////////////////// + +#if defined(NO_THREADING) && !defined(NO_GENERATORS) +# define NO_GENERATORS +#pragma message("\n***********************************************************************\n\ +* You have defined NO_THREADING and not defined NO_GENERATORS\n\ +* NOTE: GENERATORS needs THREADING. Generators/Yield are deactivated\n\ +***********************************************************************\n") +#endif + +#if defined(NO_POOL_ALLOCATOR) && defined(NO_GENERATORS) && !defined(NO_THREADING) +# define NO_THREADING +#endif + +#if !defined(NO_POOL_ALLOCATOR) && defined(NO_THREADING) +#pragma message("\n***********************************************************************\n\ +* You have defined NO_THREADING and not defined NO_POOL_ALLOCATOR\n\ +* NOTE: you can not run JS-code simultaneously in different threads\n\ +***********************************************************************\n") +#endif + +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L + +# define HAVE_CXX11_RVALUE_REFERENCE 1 +# define MEMBER_DELETE =delete + +# if !defined(NO_CXX_THREADS) && !defined(NO_THREADING) +# define HAVE_CXX_THREADS 1 +# endif +#else +# if _MSC_VER >= 1600 +# define HAVE_CXX11_RVALUE_REFERENCE 1 +# endif +# if _MSC_VER >= 1700 +# if !defined(NO_CXX_THREADS) && !defined(NO_THREADING) +# define HAVE_CXX_THREADS 1 +# endif +# endif +# if _MSC_VER >= 1800 +# define define MEMBER_DELETE =delete +# endif +#endif + +#ifndef MEMBER_DELETE +# define MEMBER_DELETE +#endif + +#endif // _42TinyJS_config_h__ diff --git a/lib-tiny-js.2012.vcxproj b/lib-tiny-js.2012.vcxproj new file mode 100644 index 0000000..0f8607d --- /dev/null +++ b/lib-tiny-js.2012.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {9751C465-0294-43CD-A5D9-BD038ABA3961} + lib-tiny-js + lib-tiny-js + + + + StaticLibrary + false + MultiByte + true + false + v110 + + + StaticLibrary + false + false + MultiByte + v110 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + Build\$(ProjectName)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + Build\$(ProjectName)\$(Configuration)\ + E:\Eigene Dateien\Visual Studio 2010\Projects\42tiny-js\pthread\pthreads-w32-2-9-1-release;$(IncludePath) + + + + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Level3 + EditAndContinue + + + + + Full + true + WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreadedDLL + true + Level3 + ProgramDatabase + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib-tiny-js.2012.vcxproj.filters b/lib-tiny-js.2012.vcxproj.filters new file mode 100644 index 0000000..ec27f62 --- /dev/null +++ b/lib-tiny-js.2012.vcxproj.filters @@ -0,0 +1,63 @@ + + + + + {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 + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + \ No newline at end of file diff --git a/lib-tiny-js.vcproj b/lib-tiny-js.vcproj new file mode 100644 index 0000000..7e4e4f4 --- /dev/null +++ b/lib-tiny-js.vcproj @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib-tiny-js.vcxproj b/lib-tiny-js.vcxproj new file mode 100644 index 0000000..7223dad --- /dev/null +++ b/lib-tiny-js.vcxproj @@ -0,0 +1,92 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {9751C465-0294-43CD-A5D9-BD038ABA3961} + lib-tiny-js + + + + StaticLibrary + false + MultiByte + true + false + + + StaticLibrary + false + false + MultiByte + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + Build\$(ProjectName)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + Build\$(ProjectName)\$(Configuration)\ + E:\Eigene Dateien\Visual Studio 2010\Projects\42tiny-js\pthread\pthreads-w32-2-9-1-release;$(IncludePath) + + + + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Level3 + EditAndContinue + + + + + Full + true + WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreadedDLL + true + Level3 + ProgramDatabase + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib-tiny-js.vcxproj.filters b/lib-tiny-js.vcxproj.filters new file mode 100644 index 0000000..ec27f62 --- /dev/null +++ b/lib-tiny-js.vcxproj.filters @@ -0,0 +1,63 @@ + + + + + {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 + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + \ No newline at end of file diff --git a/pool_allocator.cpp b/pool_allocator.cpp new file mode 100644 index 0000000..1478501 --- /dev/null +++ b/pool_allocator.cpp @@ -0,0 +1,258 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored 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. + */ + +#include "pool_allocator.h" +#include +#include +#include + +struct block { + block* next; +}; +struct block_head { + block_head* next; +}; + +static void set_next(void* p, void* next) { + static_cast(p)->next = static_cast(next); +} + +static void* get_next(void* p) { + return static_cast(p)->next; +} + +fixed_size_allocator::fixed_size_allocator( size_t numObjects, size_t objectSize, const char *for_class ) +{ + num_objects = numObjects; + object_size = objectSize >= sizeof(block) ? objectSize : sizeof(block); + + head_of_free_list = head = 0; + +#ifdef DEBUG_POOL_ALLOCATOR + if(for_class) name = for_class; + allocs= + frees= + max = + current= + blocks = +#endif + refs = 0; +} + +fixed_size_allocator::~fixed_size_allocator() +{ + while(head) { + char *p = (char*)head; + head = head->next; + delete [] p; + } +#ifdef DEBUG_POOL_ALLOCATOR +# ifndef LOG_POOL_ALLOCATOR_MEMORY_USAGE + if(refs) { +# endif + fprintf(stderr, "allocator [%s](%d) destroyed\n", name.c_str(), object_size); + fprintf(stderr, " allocs:%i, ", allocs); + fprintf(stderr, "frees:%i, ", frees); + fprintf(stderr, "max:%i, ", max); + fprintf(stderr, "blocks:%i\n", blocks); + if(refs) fprintf(stderr, "************ %i x not freed ************\n", refs); + fprintf(stderr, "\n"); +# ifndef LOG_POOL_ALLOCATOR_MEMORY_USAGE + } +# endif +#endif +} + +void* fixed_size_allocator::_alloc( size_t ) { + refs++; +#ifdef DEBUG_POOL_ALLOCATOR + allocs++;current++; + if(current>max)max=current; +#endif + void* p = head_of_free_list; + if(p) { + head_of_free_list = get_next(p); + } else { + + char* new_block = new char[sizeof(block_head) + num_objects * object_size]; + ((block_head*)new_block)->next = head; + head = (block_head*)new_block; + new_block += sizeof(block_head); + for(std::size_t i = object_size; i < (num_objects - 1) * object_size; i += object_size) { + set_next(&new_block[i], &new_block[i + object_size]); + } + set_next(&new_block[(num_objects - 1) * object_size], 0); + p = new_block; + head_of_free_list = &new_block[object_size]; + +#ifdef DEBUG_POOL_ALLOCATOR + blocks++; +#endif + } + return p; +} +#include +#ifndef ASSERT +# define ASSERT(X) assert(X) +#endif + +bool fixed_size_allocator::_free( void* p, size_t ) { + if(p == 0) return refs==0; + refs--; +#ifdef DEBUG_POOL_ALLOCATOR + ASSERT(refs>=0); + frees++;current--; +#endif + block* dead_object = static_cast(p); + + dead_object->next = static_cast(head_of_free_list); + head_of_free_list = dead_object; + return refs==0; +} +typedef std::vector allocator_pool_t; +typedef allocator_pool_t::iterator allocator_pool_it; + +static bool compare_allocator_pool(fixed_size_allocator *allocator, size_t Size) { + return allocator->objectSize() < Size; +} + +static class _allocator_pool +{ +public: + _allocator_pool() : allocator_pool(0) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_ok = last_access = 0; +#endif + } + ~_allocator_pool() { + if(allocator_pool && !allocator_pool->empty()) + for(allocator_pool_it it = allocator_pool->begin(); it!=allocator_pool->end(); it++) + delete *it; + delete allocator_pool; +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + if(last_access) fprintf(stderr, "last_ok:%i(%i)=%i%%\n", last_ok, last_access, (last_ok*100)/last_access); +#endif + } + allocator_pool_it *findAllocator(size_t size, allocator_pool_it &it) { + if(!allocator_pool) return 0; + it = lower_bound(allocator_pool->begin(), allocator_pool->end(), size, compare_allocator_pool); + if(it != allocator_pool->end() && (*it)->objectSize() == size) + return ⁢ + return 0; + } + fixed_size_allocator *checkLastAllocator(size_t size) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_access++; +#endif + if(last_allocate_allocator && last_allocate_allocator->objectSize()==size) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_ok++; +#endif + return last_allocate_allocator; + } + else if(last_free_allocator && last_free_allocator->objectSize()==size) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_ok++; +#endif + return last_free_allocator; + } else + return 0; + } + void removeAllocator(allocator_pool_it it) { + if(last_allocate_allocator == *it) last_allocate_allocator = 0; + if(last_free_allocator == *it) last_free_allocator = 0; + delete *it; allocator_pool->erase(it); + if(allocator_pool->empty()) { + delete allocator_pool; allocator_pool=0; + } + } + allocator_pool_t *allocator_pool; + fixed_size_allocator *last_allocate_allocator; + fixed_size_allocator *last_free_allocator; +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + int last_ok; + int last_access; +#endif +}allocator_pool; + +//#define WITH_TIME_LOGGER +#include "time_logger.h" + +TimeLoggerCreate(alloc, false); +TimeLoggerCreate(free, false); +#ifdef NO_THREADING +# define LOCK do{}while(0) +#else +CScriptMutex fixed_size_allocator::locker; +class lock_help { +public: + lock_help() { fixed_size_allocator::locker.lock(); } + ~lock_help() { fixed_size_allocator::locker.unlock(); } +}; +#define LOCK lock_help lock +#endif +void* fixed_size_allocator::alloc(size_t size, const char *for_class) { + TimeLoggerHelper(alloc); + LOCK; + if(!allocator_pool.allocator_pool) { + allocator_pool.allocator_pool = new allocator_pool_t(); + allocator_pool.last_allocate_allocator = allocator_pool.last_free_allocator = 0; + } + fixed_size_allocator *last = allocator_pool.checkLastAllocator(size); + if(last) + return last->_alloc(size); + else { + allocator_pool_it it; if(allocator_pool.findAllocator(size, it)) + return (allocator_pool.last_allocate_allocator = *(it))->_alloc(size); + else { + return (allocator_pool.last_allocate_allocator = (*allocator_pool.allocator_pool->insert(it, new fixed_size_allocator(64, size, for_class))))->_alloc(size); + } + } +} +void fixed_size_allocator::free(void *p, size_t size) { + TimeLoggerHelper(free); + LOCK; + if(!allocator_pool.allocator_pool) { + ASSERT(0/* free called but not allocator defined*/); + return; + } + fixed_size_allocator *last = allocator_pool.checkLastAllocator(size); + if(last) { + if( last->_free(p, size) ) { + allocator_pool_it it; if(allocator_pool.findAllocator(size, it)) + allocator_pool.removeAllocator(it); + } + } else { + allocator_pool_it it; if(allocator_pool.findAllocator(size, it)) { + if( (allocator_pool.last_free_allocator = *it)->_free(p, size) ) + allocator_pool.removeAllocator(it); + } else + ASSERT(0/* free called but not allocator defined*/); + } +} diff --git a/pool_allocator.h b/pool_allocator.h new file mode 100644 index 0000000..6072e6f --- /dev/null +++ b/pool_allocator.h @@ -0,0 +1,162 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored 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 pool_allocator_h__ +#define pool_allocator_h__ + +#include +#include +#include +#include "config.h" +#include "TinyJS_Threading.h" + +/************************************************************************ + * TinyJS must many many times allocates and frees objects + * To prevent memory fragmentation and speed ups allocates & frees, i have + * added to 42TinyJS a pool_allocator. This allocator allocates every 64 objects + * as a pool of objects. Is an object needed it can faster allocated from this pool as + * from the heap. + ************************************************************************/ + +#if !defined(DEBUG_POOL_ALLOCATOR) && (defined(_DEBUG) || defined(LOG_POOL_ALLOCATOR_MEMORY_USAGE)) +# define DEBUG_POOL_ALLOCATOR +#endif + +struct block_head; +class fixed_size_allocator { +public: + ~fixed_size_allocator(); + static void *alloc(size_t,const char* for_class=0); + static void free(void *, size_t); + size_t objectSize() { return object_size; } +#ifndef NO_THREADING + static CScriptMutex locker; +#endif +private: + fixed_size_allocator(size_t num_objects, size_t object_size, const char* for_class); + fixed_size_allocator(const fixed_size_allocator&); + fixed_size_allocator& operator=(const fixed_size_allocator&); + void *_alloc(size_t); + bool _free(void* p, size_t); + size_t num_objects; + size_t object_size; + void *head_of_free_list; + block_head *head; + int refs; +#ifdef DEBUG_POOL_ALLOCATOR + // Debug + std::string name; + int allocs; + int frees; + int current; + int max; + int blocks; +#endif +}; +//************************************************************************************** +template +class fixed_size_object { +public: + static void* operator new(size_t size) { +#ifdef DEBUG_POOL_ALLOCATOR + return fixed_size_allocator::alloc(size, typeid(T).name()); +#else + return fixed_size_allocator::alloc(size); +#endif + } + static void* operator new(size_t size, void* p) { + return p; + } + static void operator delete(void* p, size_t size) { + fixed_size_allocator::free(p, size); + } +private: +}; +#if 0 // under construction +template +class block_allocator_stl { +public : + // typedefs + + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; +public : + // convert an allocator to allocator + + template + struct rebind { + typedef block_allocator_stl other; + }; + + inline explicit block_allocator_stl() {} + inline ~block_allocator_stl() {} + inline block_allocator_stl(block_allocator_stl const&) {} + template + inline block_allocator_stl(block_allocator_stl const&) {} + inline block_allocator_stl &operator=(block_allocator_stl const&) {} + template + inline block_allocator_stl &operator=(block_allocator_stl const&) {} + + // address + + inline pointer address(reference r) { return &r; } + inline const_pointer address(const_reference r) { return &r; } + + // memory allocation + inline pointer allocate(size_type cnt, const void*) { + return reinterpret_cast(fixed_size_allocator::get(cnt * sizeof (T), true, typeid(T).name())->alloc(cnt * sizeof (T))); + // return reinterpret_cast(::operator new(cnt * sizeof (T))); + } + inline void deallocate(pointer p, size_type cnt) { + fixed_size_allocator::get(cnt * sizeof (T), false)->free(p, cnt * sizeof (T)); + // ::operator delete(p); + } + + // size + + inline size_type max_size() const { + return SIZE_MAX / sizeof(T); + } + + inline void construct(pointer _Ptr, value_type& _Val) { + ::new ((void*)_Ptr) value_type(_Val); + } + inline void destroy(pointer _Ptr) { + _Ptr->~value_type(); + } +}; +#endif + +#endif // pool_allocator_h__ diff --git a/run_tests.2012.vcxproj b/run_tests.2012.vcxproj new file mode 100644 index 0000000..d6aad31 --- /dev/null +++ b/run_tests.2012.vcxproj @@ -0,0 +1,96 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C6C6CD82-1400-464C-9B80-E15450CFDD18} + Win32Proj + run_tests + run_tests + + + + Application + true + MultiByte + false + false + v110 + + + Application + false + true + MultiByte + false + false + v110 + + + + + + + + + + + + + true + Build\$(ProjectName)\$(Configuration)\ + + + false + Build\$(ProjectName)\$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;WITH_TIME_LOGGER;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;WITH_TIME_LOGGER;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + {9751c465-0294-43cd-a5d9-bd038aba3961} + + + + + + \ No newline at end of file diff --git a/run_tests.2012.vcxproj.filters b/run_tests.2012.vcxproj.filters new file mode 100644 index 0000000..8390eb7 --- /dev/null +++ b/run_tests.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;mfcribbon-ms + + + + + Quelldateien + + + \ No newline at end of file diff --git a/run_tests.2012.vcxproj.user b/run_tests.2012.vcxproj.user new file mode 100644 index 0000000..77e44a0 --- /dev/null +++ b/run_tests.2012.vcxproj.user @@ -0,0 +1,11 @@ + + + + -k + WindowsLocalDebugger + + + -k + WindowsLocalDebugger + + \ No newline at end of file diff --git a/run_tests.cpp b/run_tests.cpp index e79af69..385def2 100644 --- a/run_tests.cpp +++ b/run_tests.cpp @@ -7,37 +7,75 @@ * * 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 + * + * Copyright (C) 2010-2014 ardisoft * - * 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. + * 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 program to run all the tests in the tests folder... */ +#ifdef _DEBUG +# ifdef _MSC_VER +# ifdef USE_DEBUG_NEW +//# include "targetver.h" +//# include +//# define new DEBUG_NEW +# endif +# else +# define DEBUG_MEMORY 1 +# endif +#endif +#define _CRT_SECURE_NO_WARNINGS + +#ifdef WIN32 +#include +#endif + #include "TinyJS.h" -#include "TinyJS_Functions.h" +//#include "TinyJS_Functions.h" +//#include "TinyJS_MathFunctions.h" +//#include "TinyJS_StringFunctions.h" #include #include #include #include -#include - +#include +#include +//#define WITH_TIME_LOGGER //#define INSANE_MEMORY_DEBUG + + +#include "time_logger.h" + #ifdef INSANE_MEMORY_DEBUG // needs -rdynamic when compiling/linking #include @@ -182,8 +220,25 @@ void memtracing_kill() { } } #endif // INSANE_MEMORY_DEBUG - - +class end { // this is for VisualStudio debugging stuff. It's holds the console open up to ENTER is pressed +public: + end() : active(false) {} + ~end() + { + if(active) { +#ifdef WIN32 + system("pause"); +#else + printf("press Enter (end)"); + getchar(); +#endif + } + } + bool active; +} end; +void js_print(const CFunctionsScopePtr &v, void *) { + printf("> %s\n", v->getArgument("text")->toString().c_str()); +} bool run_test(const char *filename) { printf("TEST %s ", filename); struct stat results; @@ -205,29 +260,42 @@ bool run_test(const char *filename) { fclose(file); CTinyJS s; - registerFunctions(&s); - s.root->addChild("result", new CScriptVar("0",SCRIPTVAR_INTEGER)); + s.addNative("function print(text)", &js_print, 0); + +// registerFunctions(&s); +// registerMathFunctions(&s); +// registerStringFunctions(&s); + s.getRoot()->addChild("result", s.newScriptVar(0)); +#ifdef WITH_TIME_LOGGER + TimeLoggerCreate(Test, true, filename); +#endif try { - s.execute(buffer); + s.execute(buffer, filename); } catch (CScriptException *e) { - printf("ERROR: %s\n", e->text.c_str()); + printf("%s\n", e->toString().c_str()); + delete e; } - bool pass = s.root->getParameter("result")->getBool(); + bool pass = s.getRoot()->findChild("result")->toBoolean(); +#ifdef WITH_TIME_LOGGER + TimeLoggerLogprint(Test); +#endif if (pass) printf("PASS\n"); else { - char fn[64]; - sprintf(fn, "%s.fail.js", filename); + char fn[64]; + sprintf(fn, "%s.fail.txt", filename); + +// logging is currently deactivated because stack-overflow by recursive vars FILE *f = fopen(fn, "wt"); if (f) { - std::ostringstream symbols; - s.root->getJSON(symbols); - fprintf(f, "%s", symbols.str().c_str()); + + std::string symbols = s.getRoot()->CScriptVar::getParsableString(); + fprintf(f, "%s", symbols.c_str()); fclose(f); } - printf("FAIL - symbols written to %s\n", fn); + printf("FAIL - symbols written to %s\n", fn); } delete[] buffer; @@ -241,37 +309,65 @@ int main(int argc, char **argv) #endif printf("TinyJS test runner\n"); printf("USAGE:\n"); - printf(" ./run_tests test.js : run just one test\n"); - printf(" ./run_tests : run all tests\n"); - if (argc==2) { - return !run_test(argv[1]); + printf(" ./run_tests [-k] tests/test001.js [tests/42tests/test002.js] : run tests\n"); + printf(" ./run_tests [-k] : run all tests\n"); + printf(" -k needs press enter at the end of runs\n"); + int arg_num = 1; + bool runs = false; + for(; arg_num + + + + Debug + Win32 + + + Release + Win32 + + + + {C6C6CD82-1400-464C-9B80-E15450CFDD18} + Win32Proj + run_tests + + + + Application + true + MultiByte + false + false + + + Application + false + true + MultiByte + false + false + + + + + + + + + + + + + true + Build\$(ProjectName)\$(Configuration)\ + + + false + Build\$(ProjectName)\$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;WITH_TIME_LOGGER;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;WITH_TIME_LOGGER;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + {9751c465-0294-43cd-a5d9-bd038aba3961} + + + + + + \ No newline at end of file diff --git a/run_tests.vcxproj.filters b/run_tests.vcxproj.filters new file mode 100644 index 0000000..8390eb7 --- /dev/null +++ b/run_tests.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;mfcribbon-ms + + + + + Quelldateien + + + \ No newline at end of file diff --git a/run_tests.vcxproj.user b/run_tests.vcxproj.user new file mode 100644 index 0000000..77e44a0 --- /dev/null +++ b/run_tests.vcxproj.user @@ -0,0 +1,11 @@ + + + + -k + WindowsLocalDebugger + + + -k + WindowsLocalDebugger + + \ No newline at end of file diff --git a/tests/test001.js b/tests/test001.js deleted file mode 100644 index e0d95dc..0000000 --- a/tests/test001.js +++ /dev/null @@ -1,2 +0,0 @@ -// simply testing we can return the correct value -result = 1; diff --git a/tests/test002.js b/tests/test002.js deleted file mode 100644 index cebaa85..0000000 --- a/tests/test002.js +++ /dev/null @@ -1,3 +0,0 @@ -// comparison -var a = 42; -result = a==42; diff --git a/tests/test003.js b/tests/test003.js deleted file mode 100644 index 55126fa..0000000 --- a/tests/test003.js +++ /dev/null @@ -1,5 +0,0 @@ -// simple for loop -var a = 0; -var i; -for (i=1;i<10;i++) a = a + i; -result = a==45; diff --git a/tests/test004.js b/tests/test004.js deleted file mode 100644 index 2f07c8b..0000000 --- a/tests/test004.js +++ /dev/null @@ -1,4 +0,0 @@ -// simple if -var a = 42; -if (a < 43) - result = 1; diff --git a/tests/test005.js b/tests/test005.js deleted file mode 100644 index f944064..0000000 --- a/tests/test005.js +++ /dev/null @@ -1,4 +0,0 @@ -// simple for loop containing initialisation, using += -var a = 0; -for (var i=1;i<10;i++) a += i; -result = a==45; diff --git a/tests/test006.js b/tests/test006.js deleted file mode 100644 index 292ad5e..0000000 --- a/tests/test006.js +++ /dev/null @@ -1,3 +0,0 @@ -// simple function -function add(x,y) { return x+y; } -result = add(3,6)==9; diff --git a/tests/test007.js b/tests/test007.js deleted file mode 100644 index 9a43bc6..0000000 --- a/tests/test007.js +++ /dev/null @@ -1,4 +0,0 @@ -// simple function scoping test -var a = 7; -function add(x,y) { var a=x+y; return a; } -result = add(3,6)==9 && a==7; diff --git a/tests/test008.js b/tests/test008.js deleted file mode 100644 index 8596ee2..0000000 --- a/tests/test008.js +++ /dev/null @@ -1,5 +0,0 @@ -// functions in variables -var bob; -bob.add = function(x,y) { return x+y; }; - -result = bob.add(3,6)==9; diff --git a/tests/test009.js b/tests/test009.js deleted file mode 100644 index 08bf42e..0000000 --- a/tests/test009.js +++ /dev/null @@ -1,4 +0,0 @@ -// functions in variables using JSON-style initialisation -var bob = { add : function(x,y) { return x+y; } }; - -result = bob.add(3,6)==9; diff --git a/tests/test010.js b/tests/test010.js deleted file mode 100644 index 82e89da..0000000 --- a/tests/test010.js +++ /dev/null @@ -1,4 +0,0 @@ -// double function calls -function a(x) { return x+2; } -function b(x) { return a(x)+1; } -result = a(3)==5 && b(3)==6; diff --git a/tests/test011.js b/tests/test011.js deleted file mode 100644 index 4eaf641..0000000 --- a/tests/test011.js +++ /dev/null @@ -1,7 +0,0 @@ -// recursion -function a(x) { - if (x>1) - return x*a(x-1); - return 1; -} -result = a(5)==1*2*3*4*5; diff --git a/tests/test012.js b/tests/test012.js deleted file mode 100644 index 2c50344..0000000 --- a/tests/test012.js +++ /dev/null @@ -1,6 +0,0 @@ -// if .. else -var a = 42; -if (a != 42) - result = 0; -else - result = 1; diff --git a/tests/test013.js b/tests/test013.js deleted file mode 100644 index a2fb08f..0000000 --- a/tests/test013.js +++ /dev/null @@ -1,7 +0,0 @@ -// if .. else with blocks -var a = 42; -if (a != 42) { - result = 0; -} else { - result = 1; -} diff --git a/tests/test014.js b/tests/test014.js deleted file mode 100644 index 9c2bb30..0000000 --- a/tests/test014.js +++ /dev/null @@ -1,15 +0,0 @@ -// Variable creation and scope from http://en.wikipedia.org/wiki/JavaScript_syntax -x = 0; // A global variable -var y = 'Hello!'; // Another global variable - -function f(){ - var z = 'foxes'; // A local variable - twenty = 20; // Global because keyword var is not used - return x; // We can use x here because it is global -} -// The value of z is no longer available - - -// testing -blah = f(); -result = blah==0 && z!='foxes' && twenty==20; diff --git a/tests/test015.js b/tests/test015.js deleted file mode 100644 index 4a70a37..0000000 --- a/tests/test015.js +++ /dev/null @@ -1,8 +0,0 @@ -// Number definition from http://en.wikipedia.org/wiki/JavaScript_syntax -a = 345; // an "integer", although there is only one numeric type in JavaScript -b = 34.5; // a floating-point number -c = 3.45e2; // another floating-point, equivalent to 345 -d = 0377; // an octal integer equal to 255 -e = 0xFF; // a hexadecimal integer equal to 255, digits represented by the letters A-F may be upper or lowercase - -result = a==345 && b*10==345 && c==345 && d==255 && e==255; diff --git a/tests/test016.js b/tests/test016.js deleted file mode 100644 index 4dcc4f6..0000000 --- a/tests/test016.js +++ /dev/null @@ -1,15 +0,0 @@ -// Undefined/null from http://en.wikipedia.org/wiki/JavaScript_syntax -var testUndefined; // variable declared but not defined, set to value of undefined -var testObj = {}; - -result = 1; -if ((""+test) != "undefined") result = 0; // test variable exists but value not defined, displays undefined -if ((""+testObj.myProp) != "undefined") result = 0; // testObj exists, property does not, displays undefined -if (!(undefined == null)) result = 0; // unenforced type during check, displays true -if (undefined === null) result = 0;// enforce type during check, displays false - - -if (null != undefined) result = 0; // unenforced type during check, displays true -if (null === undefined) result = 0; // enforce type during check, displays false - - diff --git a/tests/test017.js b/tests/test017.js deleted file mode 100644 index 0f6be1b..0000000 --- a/tests/test017.js +++ /dev/null @@ -1,11 +0,0 @@ -// references for arrays - -var a; -a[0] = 10; -a[1] = 22; - -b = a; - -b[0] = 5; - -result = a[0]==5 && a[1]==22 && b[1]==22; diff --git a/tests/test018.js b/tests/test018.js deleted file mode 100644 index 52da3d1..0000000 --- a/tests/test018.js +++ /dev/null @@ -1,18 +0,0 @@ -// references with functions - -var a = 42; -var b; -b[0] = 43; - -function foo(myarray) { - myarray[0]++; -} - -function bar(myvalue) { - myvalue++; -} - -foo(b); -bar(a); - -result = a==42 && b[0]==44; diff --git a/tests/test019.js b/tests/test019.js deleted file mode 100644 index 366f1d4..0000000 --- a/tests/test019.js +++ /dev/null @@ -1,19 +0,0 @@ -// built-in functions - -foo = "foo bar stuff"; -r = Math.rand(); - -parsed = Integer.parseInt("42"); - -aStr = "ABCD"; -aChar = aStr.charAt(0); - -obj1 = new Object(); -obj1.food = "cake"; -obj1.desert = "pie"; - -obj2 = obj1.clone(); -obj2.food = "kittens"; - -result = foo.length==13 && foo.indexOf("bar")==4 && foo.substring(8,13)=="stuff" && parsed==42 && - Integer.valueOf(aChar)==65 && obj1.food=="cake" && obj2.desert=="pie"; diff --git a/tests/test020.js b/tests/test020.js deleted file mode 100644 index 076b5c8..0000000 --- a/tests/test020.js +++ /dev/null @@ -1,27 +0,0 @@ -// Test reported by sterowang, Variable attribute defines conflict with function. -/* -What steps will reproduce the problem? -1. function a (){}; -2. b = {}; -3. b.a = {}; -4. a(); - -What is the expected output? What do you see instead? -Function "a" should be called. But the error message "Error Expecting 'a' -to be a function at (line: 1, col: 1)" received. - -What version of the product are you using? On what operating system? -Version 1.6 is used on Cent OS 5.4 - - -Please provide any additional information below. -When using dump() to show symbols, found the function "a" is reassigned to -"{}" by "b.a = {};" call. -*/ - -function a (){}; -b = {}; -b.a = {}; -a(); - -result = 1; diff --git a/tests/test021.js b/tests/test021.js deleted file mode 100644 index d94e231..0000000 --- a/tests/test021.js +++ /dev/null @@ -1,5 +0,0 @@ -/* Javascript eval */ - -myfoo = eval("{ foo: 42 }"); - -result = eval("4*10+2")==42 && myfoo.foo==42; diff --git a/tests/test022.js b/tests/test022.js deleted file mode 100644 index fc3f427..0000000 --- a/tests/test022.js +++ /dev/null @@ -1,9 +0,0 @@ -/* Javascript eval */ - -mystructure = { a:39, b:3, addStuff : function(c,d) { return c+d; } }; - -mystring = JSON.stringify(mystructure, undefined); - -mynewstructure = eval(mystring); - -result = mynewstructure.addStuff(mynewstructure.a, mynewstructure.b); diff --git a/tests/test023.js b/tests/test023.js deleted file mode 100644 index 619c486..0000000 --- a/tests/test023.js +++ /dev/null @@ -1,5 +0,0 @@ -// mikael.kindborg@mobilesorcery.com - Function symbol is evaluated in bracket-less body of false if-statement - -if (foo !== undefined) foo(); - -result = 1; diff --git a/tests/test024.js b/tests/test024.js deleted file mode 100644 index 66ecae6..0000000 --- a/tests/test024.js +++ /dev/null @@ -1,66 +0,0 @@ -/* Mandelbrot! */ - -X1 = -2.0; -Y1 = -2.0; -X2 = 2.0; -Y2 = 2.0; -PX = 32; -PY = 32; - - -lines = []; -for (y=0;y + * + * 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 time_logger_h__ +#define time_logger_h__ +#if defined(WITH_TIME_LOGGER) + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +class TimeLogger { +public: + TimeLogger(const char *Name, bool Started=false, const char *extName=0) + : + name(Name), start_time(gettime()), sum_time(0), calls(0), started(Started) { + if(extName) { + name+="["; + name+=extName; + name+="]"; + } + } + TimeLogger(const char *Name, const char *extName, bool Started=false) + : + name(Name), start_time(gettime()), sum_time(0), calls(0), started(Started) { + if(extName) { + name+="["; + name+=extName; + name+="]"; + } + } + ~TimeLogger() { + printLog(); +// getchar(); + } + void startTimer() { + start_time = gettime(); + started = true; + } + void stopTimer() { + if(!started) return; + sum_time += gettime()-start_time; + calls++; + started = false; + } + void printLog() { + if(started) stopTimer(); + if(calls == 1) + printf("Timer( %s ) = %d,%06d sec \n", + name.c_str(), (int)(sum_time / 1000000LL), (int)(sum_time % 1000000LL)); + else if(calls>1) + printf("Timer( %s ) = %d,%06d sec (called %d times) -> %.d microsec per call\n", + name.c_str(), (int)(sum_time / 1000000LL), (int)(sum_time % 1000000LL), + calls, (int)(sum_time/calls)); + calls = 0; sum_time = 0; + } +private: +// static int64_t frequenzy = 0; + std::string name; + int64_t start_time, sum_time; + uint32_t calls; + bool started; + int64_t gettime() { // set out to time in millisec +#ifdef _WIN32 + static LARGE_INTEGER fr = {0}; + LARGE_INTEGER li; + if(fr.QuadPart == 0) QueryPerformanceFrequency(&fr); + QueryPerformanceCounter(&li); + return (li.QuadPart * 1000000LL) / fr.QuadPart; +#else + return (clock() * 1000000LL) / CLOCKS_PER_SEC; +#endif + }; +}; +class _TimeLoggerHelper { +public: + _TimeLoggerHelper(TimeLogger &Tl) : tl(Tl) { tl.startTimer(); } + ~_TimeLoggerHelper() { tl.stopTimer(); } +private: + TimeLogger &tl; +}; +# define TimeLoggerCreate(a, ...) TimeLogger a##_TimeLogger(#a,##__VA_ARGS__) +# define TimeLoggerStart(a) a##_TimeLogger.startTimer() +# define TimeLoggerStop(a) a##_TimeLogger.stopTimer() +# define TimeLoggerLogprint(a) a##_TimeLogger.printLog() +# define TimeLoggerHelper(a) _TimeLoggerHelper a##_helper(a##_TimeLogger) +#else /* _DEBUG */ +# define TimeLoggerCreate(...) +# define TimeLoggerStart(...) do{}while(0) +# define TimeLoggerStop(...) do{}while(0) +# define TimeLoggerLogprint(a) do{}while(0) +# define TimeLoggerHelper(a) do{}while(0) +#endif /* _DEBUG */ + + + +#endif // time_logger_h__ diff --git a/tiny-js.2010.sln b/tiny-js.2010.sln new file mode 100644 index 0000000..5c40b0e --- /dev/null +++ b/tiny-js.2010.sln @@ -0,0 +1,34 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Script", "Script.vcxproj", "{70D538BA-867B-4564-8DEE-F2C78C5AD4C8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lib-tiny-js", "lib-tiny-js.vcxproj", "{9751C465-0294-43CD-A5D9-BD038ABA3961}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "run_tests", "run_tests.vcxproj", "{C6C6CD82-1400-464C-9B80-E15450CFDD18}" + ProjectSection(ProjectDependencies) = postProject + {9751C465-0294-43CD-A5D9-BD038ABA3961} = {9751C465-0294-43CD-A5D9-BD038ABA3961} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Debug|Win32.ActiveCfg = Debug|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Debug|Win32.Build.0 = Debug|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Release|Win32.ActiveCfg = Release|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Release|Win32.Build.0 = Release|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Debug|Win32.ActiveCfg = Debug|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Debug|Win32.Build.0 = Debug|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Release|Win32.ActiveCfg = Release|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Release|Win32.Build.0 = Release|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Debug|Win32.ActiveCfg = Debug|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Debug|Win32.Build.0 = Debug|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Release|Win32.ActiveCfg = Release|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tiny-js.2012.sln b/tiny-js.2012.sln new file mode 100644 index 0000000..61a590e --- /dev/null +++ b/tiny-js.2012.sln @@ -0,0 +1,34 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Script", "Script.2012.vcxproj", "{70D538BA-867B-4564-8DEE-F2C78C5AD4C8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lib-tiny-js", "lib-tiny-js.2012.vcxproj", "{9751C465-0294-43CD-A5D9-BD038ABA3961}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "run_tests", "run_tests.2012.vcxproj", "{C6C6CD82-1400-464C-9B80-E15450CFDD18}" + ProjectSection(ProjectDependencies) = postProject + {9751C465-0294-43CD-A5D9-BD038ABA3961} = {9751C465-0294-43CD-A5D9-BD038ABA3961} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Debug|Win32.ActiveCfg = Debug|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Debug|Win32.Build.0 = Debug|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Release|Win32.ActiveCfg = Release|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Release|Win32.Build.0 = Release|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Debug|Win32.ActiveCfg = Debug|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Debug|Win32.Build.0 = Debug|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Release|Win32.ActiveCfg = Release|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Release|Win32.Build.0 = Release|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Debug|Win32.ActiveCfg = Debug|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Debug|Win32.Build.0 = Debug|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Release|Win32.ActiveCfg = Release|Win32 + {C6C6CD82-1400-464C-9B80-E15450CFDD18}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tiny-js.sln b/tiny-js.sln new file mode 100644 index 0000000..c060999 --- /dev/null +++ b/tiny-js.sln @@ -0,0 +1,28 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Script", "Script.vcproj", "{70D538BA-867B-4564-8DEE-F2C78C5AD4C8}" + ProjectSection(ProjectDependencies) = postProject + {9751C465-0294-43CD-A5D9-BD038ABA3961} = {9751C465-0294-43CD-A5D9-BD038ABA3961} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lib-tiny-js", "lib-tiny-js.vcproj", "{9751C465-0294-43CD-A5D9-BD038ABA3961}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Debug|Win32.ActiveCfg = Debug|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Debug|Win32.Build.0 = Debug|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Release|Win32.ActiveCfg = Release|Win32 + {70D538BA-867B-4564-8DEE-F2C78C5AD4C8}.Release|Win32.Build.0 = Release|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Debug|Win32.ActiveCfg = Debug|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Debug|Win32.Build.0 = Debug|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Release|Win32.ActiveCfg = Release|Win32 + {9751C465-0294-43CD-A5D9-BD038ABA3961}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal