├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── Script.cpp ├── TinyJS.cpp ├── TinyJS.h ├── TinyJS_Functions.cpp ├── TinyJS_Functions.h ├── TinyJS_MathFunctions.cpp ├── TinyJS_MathFunctions.h ├── run_tests.cpp └── tests ├── 42tests ├── test001.js ├── test002.js ├── test003.js └── test004.js ├── test001.js ├── test002.js ├── test003.js ├── test004.js ├── test005.js ├── test006.js ├── test007.js ├── test008.js ├── test009.js ├── test010.js ├── test011.js ├── test012.js ├── test013.js ├── test014.js ├── test015.js ├── test016.js ├── test017.js ├── test018.js ├── test019.42.js ├── test019.js ├── test020.js ├── test021.42.js ├── test021.js ├── test022.42.js ├── test022.js ├── test023.js ├── test024.js ├── test025.js ├── test026.js ├── test027.js ├── test028.js ├── test029.js ├── test030.js ├── test031.js ├── test032.42.js ├── test032.js ├── test033.js ├── test034.js ├── test035.js └── test036.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | build.*/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (tiny-js) 2 | cmake_minimum_required (VERSION 2.6) 3 | 4 | set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 5 | 6 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") 7 | 8 | if (NOT CMAKE_BUILD_TYPE) 9 | set (CMAKE_BUILD_TYPE "Debug") 10 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") 11 | else() 12 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") 13 | endif(NOT CMAKE_BUILD_TYPE) 14 | 15 | if (NOT WIN32) 16 | include(CheckCXXCompilerFlag) 17 | CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14) 18 | if(COMPILER_SUPPORTS_CXX14) 19 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall") 20 | else() 21 | message(FATAL_ERROR "Compiler ${CMAKE_CXX_COMPILER} has no C++14 support.") 22 | endif() 23 | endif(NOT WIN32) 24 | 25 | FILE(GLOB TINY_JS_HEADER_FILES 26 | ${CMAKE_CURRENT_LIST_DIR}/TinyJS.h 27 | ) 28 | 29 | FILE(GLOB TINY_JS_SOURCE_FILES 30 | ${CMAKE_CURRENT_LIST_DIR}/TinyJS.cpp 31 | ${CMAKE_CURRENT_LIST_DIR}/TinyJS_Functions.cpp 32 | ${CMAKE_CURRENT_LIST_DIR}/TinyJS_MathFunctions.cpp 33 | ) 34 | 35 | add_library(tiny-js STATIC ${TINY_JS_HEADER_FILES} ${TINY_JS_SOURCE_FILES}) 36 | 37 | ADD_EXECUTABLE(tiny-js-cli Script.cpp ${TINY_JS_SOURCE_FILES}) 38 | 39 | ADD_EXECUTABLE(tiny-js-tests run_tests.cpp ${TINY_JS_SOURCE_FILES}) 40 | 41 | add_custom_command( 42 | TARGET tiny-js-tests POST_BUILD 43 | COMMAND ${CMAKE_COMMAND} -E copy_directory 44 | ${CMAKE_SOURCE_DIR}/tests 45 | ${CMAKE_CURRENT_BINARY_DIR}/tests) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gordon Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=g++ 2 | CFLAGS=-c -g -Wall -rdynamic -D_DEBUG 3 | LDFLAGS=-g -rdynamic 4 | 5 | SOURCES= \ 6 | TinyJS.cpp \ 7 | TinyJS_Functions.cpp \ 8 | TinyJS_MathFunctions.cpp 9 | 10 | OBJECTS=$(SOURCES:.cpp=.o) 11 | 12 | all: run_tests Script 13 | 14 | run_tests: run_tests.o $(OBJECTS) 15 | $(CC) $(LDFLAGS) run_tests.o $(OBJECTS) -o $@ 16 | 17 | Script: Script.o $(OBJECTS) 18 | $(CC) $(LDFLAGS) Script.o $(OBJECTS) -o $@ 19 | 20 | .cpp.o: 21 | $(CC) $(CFLAGS) $< -o $@ 22 | 23 | clean: 24 | rm -f run_tests Script run_tests.o Script.o $(OBJECTS) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tiny-js 2 | ======= 3 | 4 | (originally [on Google Code](https://code.google.com/p/tiny-js/)) 5 | 6 | This project aims to be an extremely simple (~2000 line) JavaScript interpreter, meant for 7 | inclusion in applications that require a simple, familiar script language that can be included 8 | with no dependencies other than normal C++ libraries. It currently consists of two source files: 9 | one containing the interpreter, another containing built-in functions such as String.substring. 10 | 11 | TinyJS is not designed to be fast or full-featured. However it is great for scripting simple 12 | behaviour, or loading & saving settings. 13 | 14 | I make absolutely no guarantees that this is compliant to JavaScript/EcmaScript standard. 15 | In fact I am sure it isn't. However I welcome suggestions for changes that will bring it 16 | closer to compliance without overly complicating the code, or useful test cases to add to 17 | the test suite. 18 | 19 | Currently TinyJS supports: 20 | 21 | * Variables, Arrays, Structures 22 | * JSON parsing and output 23 | * Functions 24 | * Calling C/C++ code from JavaScript 25 | * Objects with Inheritance (not fully implemented) 26 | 27 | Please see [CodeExamples](https://github.com/gfwilliams/tiny-js/blob/wiki/CodeExamples.md) for examples of code that works... 28 | 29 | For a list of known issues, please see the comments at the top of the TinyJS.cpp file, as well as the [GitHub issues](https://github.com/gfwilliams/tiny-js/issues) 30 | 31 | There is also the [42tiny-js branch](https://github.com/gfwilliams/tiny-js/tree/42tiny-js) - this is maintained by Armin and provides a more full-featured JavaScript implementation than GitHub master. 32 | 33 | TinyJS is released under an MIT licence. 34 | 35 | Internal Structure 36 | ------------------------ 37 | 38 | TinyJS uses a Recursive Descent Parser, so there is no 'Parser Generator' required. It does not 39 | compile to an intermediate code, and instead executes directly from source code. This makes it 40 | quite fast for code that is executed infrequently, and slow for loops. 41 | 42 | Variables, Arrays and Objects are stored in a simple linked list tree structure (42tiny-js uses a C++ Map). 43 | This is simple, but relatively slow for large structures or arrays. 44 | 45 | JavaScript for Microcontrollers 46 | -------------------------------- 47 | 48 | If you're after JavaScript for Microcontrollers, take a look at the 49 | [Espruino JavaScript Interpreter](http://www.espruino.com ) - it is a complete re-write of TinyJS 50 | targeted at processors with extremely low RAM (8kb or more). It is currently available for a range 51 | of STM32 ARM Microcontrollers, including [two boards that have it pre-installed](http://www.espruino.com/Order). 52 | 53 | 54 | -------------------------------------------------------------------------------- /Script.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * Authored By Gordon Williams 7 | * 8 | * Copyright (C) 2009 Pur3 Ltd 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | * of the Software, and to permit persons to whom the Software is furnished to do 15 | * so, subject to the following conditions: 16 | 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | /* 30 | * This is a simple program showing how to use TinyJS 31 | */ 32 | 33 | #include "TinyJS.h" 34 | #include "TinyJS_Functions.h" 35 | #include 36 | #include 37 | 38 | 39 | //const char *code = "var a = 5; if (a==5) a=4; else a=3;"; 40 | //const char *code = "{ var a = 4; var b = 1; while (a>0) { b = b * 2; a = a - 1; } var c = 5; }"; 41 | //const char *code = "{ var b = 1; for (var i=0;i<4;i=i+1) b = b * 2; }"; 42 | const char *code = "function myfunc(x, y) { return x + y; } var a = myfunc(1,2); print(a);"; 43 | 44 | void js_print(CScriptVar *v, void *userdata) { 45 | printf("> %s\n", v->getParameter("text")->getString().c_str()); 46 | } 47 | 48 | void js_dump(CScriptVar *v, void *userdata) { 49 | CTinyJS *js = (CTinyJS*)userdata; 50 | js->root->trace("> "); 51 | } 52 | 53 | 54 | int main(int argc, char **argv) 55 | { 56 | CTinyJS *js = new CTinyJS(); 57 | /* add the functions from TinyJS_Functions.cpp */ 58 | registerFunctions(js); 59 | /* Add a native function */ 60 | js->addNative("function print(text)", &js_print, 0); 61 | js->addNative("function dump()", &js_dump, js); 62 | /* Execute out bit of code - we could call 'evaluate' here if 63 | we wanted something returned */ 64 | try { 65 | js->execute("var lets_quit = 0; function quit() { lets_quit = 1; }"); 66 | js->execute("print(\"Interactive mode... Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!\");"); 67 | } catch (CScriptException *e) { 68 | printf("ERROR: %s\n", e->text.c_str()); 69 | } 70 | 71 | while (js->evaluate("lets_quit") == "0") { 72 | char buffer[2048]; 73 | fgets ( buffer, sizeof(buffer), stdin ); 74 | try { 75 | js->execute(buffer); 76 | } catch (CScriptException *e) { 77 | printf("ERROR: %s\n", e->text.c_str()); 78 | } 79 | } 80 | delete js; 81 | #ifdef _WIN32 82 | #ifdef _DEBUG 83 | _CrtDumpMemoryLeaks(); 84 | #endif 85 | #endif 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /TinyJS.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * Authored By Gordon Williams 7 | * 8 | * Copyright (C) 2009 Pur3 Ltd 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | * of the Software, and to permit persons to whom the Software is furnished to do 15 | * so, subject to the following conditions: 16 | 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | /* Version 0.1 : (gw) First published on Google Code 30 | Version 0.11 : Making sure the 'root' variable never changes 31 | 'symbol_base' added for the current base of the sybmbol table 32 | Version 0.12 : Added findChildOrCreate, changed string passing to use references 33 | Fixed broken string encoding in getJSString() 34 | Removed getInitCode and added getJSON instead 35 | Added nil 36 | Added rough JSON parsing 37 | Improved example app 38 | Version 0.13 : Added tokenEnd/tokenLastEnd to lexer to avoid parsing whitespace 39 | Ability to define functions without names 40 | Can now do "var mine = function(a,b) { ... };" 41 | Slightly better 'trace' function 42 | Added findChildOrCreateByPath function 43 | Added simple test suite 44 | Added skipping of blocks when not executing 45 | Version 0.14 : Added parsing of more number types 46 | Added parsing of string defined with ' 47 | Changed nil to null as per spec, added 'undefined' 48 | Now set variables with the correct scope, and treat unknown 49 | as 'undefined' rather than failing 50 | Added proper (I hope) handling of null and undefined 51 | Added === check 52 | Version 0.15 : Fix for possible memory leaks 53 | Version 0.16 : Removal of un-needed findRecursive calls 54 | symbol_base removed and replaced with 'scopes' stack 55 | Added reference counting a proper tree structure 56 | (Allowing pass by reference) 57 | Allowed JSON output to output IDs, not strings 58 | Added get/set for array indices 59 | Changed Callbacks to include user data pointer 60 | Added some support for objects 61 | Added more Java-esque builtin functions 62 | Version 0.17 : Now we don't deepCopy the parent object of the class 63 | Added JSON.stringify and eval() 64 | Nicer JSON indenting 65 | Fixed function output in JSON 66 | Added evaluateComplex 67 | Fixed some reentrancy issues with evaluate/execute 68 | Version 0.18 : Fixed some issues with code being executed when it shouldn't 69 | Version 0.19 : Added array.length 70 | Changed '__parent' to 'prototype' to bring it more in line with javascript 71 | Version 0.20 : Added '%' operator 72 | Version 0.21 : Added array type 73 | String.length() no more - now String.length 74 | Added extra constructors to reduce confusion 75 | Fixed checks against undefined 76 | Version 0.22 : First part of ardi's changes: 77 | sprintf -> sprintf_s 78 | extra tokens parsed 79 | array memory leak fixed 80 | Fixed memory leak in evaluateComplex 81 | Fixed memory leak in FOR loops 82 | Fixed memory leak for unary minus 83 | Version 0.23 : Allowed evaluate[Complex] to take in semi-colon separated 84 | statements and then only return the value from the last one. 85 | Also checks to make sure *everything* was parsed. 86 | Ints + doubles are now stored in binary form (faster + more precise) 87 | Version 0.24 : More useful error for maths ops 88 | Don't dump everything on a match error. 89 | Version 0.25 : Better string escaping 90 | Version 0.26 : Add CScriptVar::equals 91 | Add built-in array functions 92 | Version 0.27 : Added OZLB's TinyJS.setVariable (with some tweaks) 93 | Added OZLB's Maths Functions 94 | Version 0.28 : Ternary operator 95 | Rudimentary call stack on error 96 | Added String Character functions 97 | Added shift operators 98 | Version 0.29 : Added new object via functions 99 | Fixed getString() for double on some platforms 100 | Version 0.30 : Rlyeh Mario's patch for Math Functions on VC++ 101 | Version 0.31 : Add exec() to TinyJS functions 102 | Now print quoted JSON that can be read by PHP/Python parsers 103 | Fixed postfix increment operator 104 | Version 0.32 : Fixed Math.randInt on 32 bit PCs, where it was broken 105 | Version 0.33 : Fixed Memory leak + brokenness on === comparison 106 | 107 | NOTE: 108 | Constructing an array with an initial length 'Array(5)' doesn't work 109 | Recursive loops of data such as a.foo = a; fail to be garbage collected 110 | length variable cannot be set 111 | The postfix increment operator returns the current value, not the previous as it should. 112 | There is no prefix increment operator 113 | Arrays are implemented as a linked list - hence a lookup time is O(n) 114 | 115 | TODO: 116 | Utility va-args style function in TinyJS for executing a function directly 117 | Merge the parsing of expressions/statements so eval("statement") works like we'd expect. 118 | Move 'shift' implementation into mathsOp 119 | 120 | */ 121 | 122 | #include "TinyJS.h" 123 | #include 124 | 125 | #define ASSERT(X) assert(X) 126 | /* Frees the given link IF it isn't owned by anything else */ 127 | #define CLEAN(x) { CScriptVarLink *__v = x; if (__v && !__v->owned) { delete __v; } } 128 | /* Create a LINK to point to VAR and free the old link. 129 | * BUT this is more clever - it tries to keep the old link if it's not owned to save allocations */ 130 | #define CREATE_LINK(LINK, VAR) { if (!LINK || LINK->owned) LINK = new CScriptVarLink(VAR); else LINK->replaceWith(VAR); } 131 | 132 | #include 133 | #include 134 | #include 135 | #include 136 | #include 137 | 138 | using namespace std; 139 | 140 | #ifdef _WIN32 141 | #ifdef _DEBUG 142 | #ifndef DBG_NEW 143 | #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) 144 | #define new DBG_NEW 145 | #endif 146 | #endif 147 | #endif 148 | 149 | #ifdef __GNUC__ 150 | #define vsprintf_s vsnprintf 151 | #define sprintf_s snprintf 152 | #define _strdup strdup 153 | #endif 154 | 155 | // ----------------------------------------------------------------------------------- Memory Debug 156 | 157 | #define DEBUG_MEMORY 0 158 | 159 | #if DEBUG_MEMORY 160 | 161 | vector allocatedVars; 162 | vector allocatedLinks; 163 | 164 | void mark_allocated(CScriptVar *v) { 165 | allocatedVars.push_back(v); 166 | } 167 | 168 | void mark_deallocated(CScriptVar *v) { 169 | for (size_t i=0;igetRefs()); 193 | allocatedVars[i]->trace(" "); 194 | } 195 | for (size_t i=0;iname.c_str(), allocatedLinks[i]->var->getRefs()); 197 | allocatedLinks[i]->var->trace(" "); 198 | } 199 | allocatedVars.clear(); 200 | allocatedLinks.clear(); 201 | } 202 | #endif 203 | 204 | // ----------------------------------------------------------------------------------- Utils 205 | bool isWhitespace(char ch) { 206 | return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); 207 | } 208 | 209 | bool isNumeric(char ch) { 210 | return (ch>='0') && (ch<='9'); 211 | } 212 | bool isNumber(const string &str) { 213 | for (size_t i=0;i='0') && (ch<='9')) || 219 | ((ch>='a') && (ch<='f')) || 220 | ((ch>='A') && (ch<='F')); 221 | } 222 | bool isAlpha(char ch) { 223 | return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_'; 224 | } 225 | 226 | bool isIDString(const char *s) { 227 | if (!isAlpha(*s)) 228 | return false; 229 | while (*s) { 230 | if (!(isAlpha(*s) || isNumeric(*s))) 231 | return false; 232 | s++; 233 | } 234 | return true; 235 | } 236 | 237 | void replace(string &str, char textFrom, const char *textTo) { 238 | int sLen = strlen(textTo); 239 | size_t p = str.find(textFrom); 240 | while (p != string::npos) { 241 | str = str.substr(0, p) + textTo + str.substr(p+1); 242 | p = str.find(textFrom, p+sLen); 243 | } 244 | } 245 | 246 | /// convert the given string into a quoted string suitable for javascript 247 | std::string getJSString(const std::string &str) { 248 | std::string nStr = str; 249 | for (size_t i=0;i127) { 262 | char buffer[5]; 263 | sprintf_s(buffer, 5, "\\x%02X", nCh); 264 | replaceWith = buffer; 265 | } else replace=false; 266 | } 267 | } 268 | 269 | if (replace) { 270 | nStr = nStr.substr(0, i) + replaceWith + nStr.substr(i+1); 271 | i += strlen(replaceWith)-1; 272 | } 273 | } 274 | return "\"" + nStr + "\""; 275 | } 276 | 277 | /** Is the string alphanumeric */ 278 | bool isAlphaNum(const std::string &str) { 279 | if (str.size()==0) return true; 280 | if (!isAlpha(str[0])) return false; 281 | for (size_t i=0;idata; 305 | dataOwned = false; 306 | dataStart = startChar; 307 | dataEnd = endChar; 308 | reset(); 309 | } 310 | 311 | CScriptLex::~CScriptLex(void) 312 | { 313 | if (dataOwned) 314 | free((void*)data); 315 | } 316 | 317 | void CScriptLex::reset() { 318 | dataPos = dataStart; 319 | tokenStart = 0; 320 | tokenEnd = 0; 321 | tokenLastEnd = 0; 322 | tk = 0; 323 | tkStr = ""; 324 | getNextCh(); 325 | getNextCh(); 326 | getNextToken(); 327 | } 328 | 329 | void CScriptLex::match(int expected_tk) { 330 | if (tk!=expected_tk) { 331 | ostringstream errorString; 332 | errorString << "Got " << getTokenStr(tk) << " expected " << getTokenStr(expected_tk) 333 | << " at " << getPosition(tokenStart); 334 | throw new CScriptException(errorString.str()); 335 | } 336 | getNextToken(); 337 | } 338 | 339 | string CScriptLex::getTokenStr(int token) { 340 | if (token>32 && token<128) { 341 | char buf[4] = "' '"; 342 | buf[1] = (char)token; 343 | return buf; 344 | } 345 | switch (token) { 346 | case LEX_EOF : return "EOF"; 347 | case LEX_ID : return "ID"; 348 | case LEX_INT : return "INT"; 349 | case LEX_FLOAT : return "FLOAT"; 350 | case LEX_STR : return "STRING"; 351 | case LEX_EQUAL : return "=="; 352 | case LEX_TYPEEQUAL : return "==="; 353 | case LEX_NEQUAL : return "!="; 354 | case LEX_NTYPEEQUAL : return "!=="; 355 | case LEX_LEQUAL : return "<="; 356 | case LEX_LSHIFT : return "<<"; 357 | case LEX_LSHIFTEQUAL : return "<<="; 358 | case LEX_GEQUAL : return ">="; 359 | case LEX_RSHIFT : return ">>"; 360 | case LEX_RSHIFTUNSIGNED : return ">>"; 361 | case LEX_RSHIFTEQUAL : return ">>="; 362 | case LEX_PLUSEQUAL : return "+="; 363 | case LEX_MINUSEQUAL : return "-="; 364 | case LEX_PLUSPLUS : return "++"; 365 | case LEX_MINUSMINUS : return "--"; 366 | case LEX_ANDEQUAL : return "&="; 367 | case LEX_ANDAND : return "&&"; 368 | case LEX_OREQUAL : return "|="; 369 | case LEX_OROR : return "||"; 370 | case LEX_XOREQUAL : return "^="; 371 | // reserved words 372 | case LEX_R_IF : return "if"; 373 | case LEX_R_ELSE : return "else"; 374 | case LEX_R_DO : return "do"; 375 | case LEX_R_WHILE : return "while"; 376 | case LEX_R_FOR : return "for"; 377 | case LEX_R_BREAK : return "break"; 378 | case LEX_R_CONTINUE : return "continue"; 379 | case LEX_R_FUNCTION : return "function"; 380 | case LEX_R_RETURN : return "return"; 381 | case LEX_R_VAR : return "var"; 382 | case LEX_R_TRUE : return "true"; 383 | case LEX_R_FALSE : return "false"; 384 | case LEX_R_NULL : return "null"; 385 | case LEX_R_UNDEFINED : return "undefined"; 386 | case LEX_R_NEW : return "new"; 387 | } 388 | 389 | ostringstream msg; 390 | msg << "?[" << token << "]"; 391 | return msg.str(); 392 | } 393 | 394 | void CScriptLex::getNextCh() { 395 | currCh = nextCh; 396 | if (dataPos < dataEnd) 397 | nextCh = data[dataPos]; 398 | else 399 | nextCh = 0; 400 | dataPos++; 401 | } 402 | 403 | void CScriptLex::getNextToken() { 404 | tk = LEX_EOF; 405 | tkStr.clear(); 406 | while (currCh && isWhitespace(currCh)) getNextCh(); 407 | // newline comments 408 | if (currCh=='/' && nextCh=='/') { 409 | while (currCh && currCh!='\n') getNextCh(); 410 | getNextCh(); 411 | getNextToken(); 412 | return; 413 | } 414 | // block comments 415 | if (currCh=='/' && nextCh=='*') { 416 | while (currCh && (currCh!='*' || nextCh!='/')) getNextCh(); 417 | getNextCh(); 418 | getNextCh(); 419 | getNextToken(); 420 | return; 421 | } 422 | // record beginning of this token 423 | tokenStart = dataPos-2; 424 | // tokens 425 | if (isAlpha(currCh)) { // IDs 426 | while (isAlpha(currCh) || isNumeric(currCh)) { 427 | tkStr += currCh; 428 | getNextCh(); 429 | } 430 | tk = LEX_ID; 431 | if (tkStr=="if") tk = LEX_R_IF; 432 | else if (tkStr=="else") tk = LEX_R_ELSE; 433 | else if (tkStr=="do") tk = LEX_R_DO; 434 | else if (tkStr=="while") tk = LEX_R_WHILE; 435 | else if (tkStr=="for") tk = LEX_R_FOR; 436 | else if (tkStr=="break") tk = LEX_R_BREAK; 437 | else if (tkStr=="continue") tk = LEX_R_CONTINUE; 438 | else if (tkStr=="function") tk = LEX_R_FUNCTION; 439 | else if (tkStr=="return") tk = LEX_R_RETURN; 440 | else if (tkStr=="var") tk = LEX_R_VAR; 441 | else if (tkStr=="true") tk = LEX_R_TRUE; 442 | else if (tkStr=="false") tk = LEX_R_FALSE; 443 | else if (tkStr=="null") tk = LEX_R_NULL; 444 | else if (tkStr=="undefined") tk = LEX_R_UNDEFINED; 445 | else if (tkStr=="new") tk = LEX_R_NEW; 446 | } else if (isNumeric(currCh)) { // Numbers 447 | bool isHex = false; 448 | if (currCh=='0') { tkStr += currCh; getNextCh(); } 449 | if (currCh=='x') { 450 | isHex = true; 451 | tkStr += currCh; getNextCh(); 452 | } 453 | tk = LEX_INT; 454 | while (isNumeric(currCh) || (isHex && isHexadecimal(currCh))) { 455 | tkStr += currCh; 456 | getNextCh(); 457 | } 458 | if (!isHex && currCh=='.') { 459 | tk = LEX_FLOAT; 460 | tkStr += '.'; 461 | getNextCh(); 462 | while (isNumeric(currCh)) { 463 | tkStr += currCh; 464 | getNextCh(); 465 | } 466 | } 467 | // do fancy e-style floating point 468 | if (!isHex && (currCh=='e'||currCh=='E')) { 469 | tk = LEX_FLOAT; 470 | tkStr += currCh; getNextCh(); 471 | if (currCh=='-') { tkStr += currCh; getNextCh(); } 472 | while (isNumeric(currCh)) { 473 | tkStr += currCh; getNextCh(); 474 | } 475 | } 476 | } else if (currCh=='"') { 477 | // strings... 478 | getNextCh(); 479 | while (currCh && currCh!='"') { 480 | if (currCh == '\\') { 481 | getNextCh(); 482 | switch (currCh) { 483 | case 'n' : tkStr += '\n'; break; 484 | case '"' : tkStr += '"'; break; 485 | case '\\' : tkStr += '\\'; break; 486 | default: tkStr += currCh; 487 | } 488 | } else { 489 | tkStr += currCh; 490 | } 491 | getNextCh(); 492 | } 493 | getNextCh(); 494 | tk = LEX_STR; 495 | } else if (currCh=='\'') { 496 | // strings again... 497 | getNextCh(); 498 | while (currCh && currCh!='\'') { 499 | if (currCh == '\\') { 500 | getNextCh(); 501 | switch (currCh) { 502 | case 'n' : tkStr += '\n'; break; 503 | case 'a' : tkStr += '\a'; break; 504 | case 'r' : tkStr += '\r'; break; 505 | case 't' : tkStr += '\t'; break; 506 | case '\'' : tkStr += '\''; break; 507 | case '\\' : tkStr += '\\'; break; 508 | case 'x' : { // hex digits 509 | char buf[3] = "??"; 510 | getNextCh(); buf[0] = currCh; 511 | getNextCh(); buf[1] = currCh; 512 | tkStr += (char)strtol(buf,0,16); 513 | } break; 514 | default: if (currCh>='0' && currCh<='7') { 515 | // octal digits 516 | char buf[4] = "???"; 517 | buf[0] = currCh; 518 | getNextCh(); buf[1] = currCh; 519 | getNextCh(); buf[2] = currCh; 520 | tkStr += (char)strtol(buf,0,8); 521 | } else 522 | tkStr += currCh; 523 | } 524 | } else { 525 | tkStr += currCh; 526 | } 527 | getNextCh(); 528 | } 529 | getNextCh(); 530 | tk = LEX_STR; 531 | } else { 532 | // single chars 533 | tk = currCh; 534 | if (currCh) getNextCh(); 535 | if (tk=='=' && currCh=='=') { // == 536 | tk = LEX_EQUAL; 537 | getNextCh(); 538 | if (currCh=='=') { // === 539 | tk = LEX_TYPEEQUAL; 540 | getNextCh(); 541 | } 542 | } else if (tk=='!' && currCh=='=') { // != 543 | tk = LEX_NEQUAL; 544 | getNextCh(); 545 | if (currCh=='=') { // !== 546 | tk = LEX_NTYPEEQUAL; 547 | getNextCh(); 548 | } 549 | } else if (tk=='<' && currCh=='=') { 550 | tk = LEX_LEQUAL; 551 | getNextCh(); 552 | } else if (tk=='<' && currCh=='<') { 553 | tk = LEX_LSHIFT; 554 | getNextCh(); 555 | if (currCh=='=') { // <<= 556 | tk = LEX_LSHIFTEQUAL; 557 | getNextCh(); 558 | } 559 | } else if (tk=='>' && currCh=='=') { 560 | tk = LEX_GEQUAL; 561 | getNextCh(); 562 | } else if (tk=='>' && currCh=='>') { 563 | tk = LEX_RSHIFT; 564 | getNextCh(); 565 | if (currCh=='=') { // >>= 566 | tk = LEX_RSHIFTEQUAL; 567 | getNextCh(); 568 | } else if (currCh=='>') { // >>> 569 | tk = LEX_RSHIFTUNSIGNED; 570 | getNextCh(); 571 | } 572 | } else if (tk=='+' && currCh=='=') { 573 | tk = LEX_PLUSEQUAL; 574 | getNextCh(); 575 | } else if (tk=='-' && currCh=='=') { 576 | tk = LEX_MINUSEQUAL; 577 | getNextCh(); 578 | } else if (tk=='+' && currCh=='+') { 579 | tk = LEX_PLUSPLUS; 580 | getNextCh(); 581 | } else if (tk=='-' && currCh=='-') { 582 | tk = LEX_MINUSMINUS; 583 | getNextCh(); 584 | } else if (tk=='&' && currCh=='=') { 585 | tk = LEX_ANDEQUAL; 586 | getNextCh(); 587 | } else if (tk=='&' && currCh=='&') { 588 | tk = LEX_ANDAND; 589 | getNextCh(); 590 | } else if (tk=='|' && currCh=='=') { 591 | tk = LEX_OREQUAL; 592 | getNextCh(); 593 | } else if (tk=='|' && currCh=='|') { 594 | tk = LEX_OROR; 595 | getNextCh(); 596 | } else if (tk=='^' && currCh=='=') { 597 | tk = LEX_XOREQUAL; 598 | getNextCh(); 599 | } 600 | } 601 | /* This isn't quite right yet */ 602 | tokenLastEnd = tokenEnd; 603 | tokenEnd = dataPos-3; 604 | } 605 | 606 | string CScriptLex::getSubString(int lastPosition) { 607 | int lastCharIdx = tokenLastEnd+1; 608 | if (lastCharIdx < dataEnd) { 609 | /* save a memory alloc by using our data array to create the 610 | substring */ 611 | char old = data[lastCharIdx]; 612 | data[lastCharIdx] = 0; 613 | std::string value = &data[lastPosition]; 614 | data[lastCharIdx] = old; 615 | return value; 616 | } else { 617 | return std::string(&data[lastPosition]); 618 | } 619 | } 620 | 621 | 622 | CScriptLex *CScriptLex::getSubLex(int lastPosition) { 623 | int lastCharIdx = tokenLastEnd+1; 624 | if (lastCharIdx < dataEnd) 625 | return new CScriptLex(this, lastPosition, lastCharIdx); 626 | else 627 | return new CScriptLex(this, lastPosition, dataEnd ); 628 | } 629 | 630 | string CScriptLex::getPosition(int pos) { 631 | if (pos<0) pos=tokenLastEnd; 632 | int line = 1,col = 1; 633 | for (int i=0;iname = name; 657 | this->nextSibling = 0; 658 | this->prevSibling = 0; 659 | this->var = var->ref(); 660 | this->owned = false; 661 | } 662 | 663 | CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) { 664 | // Copy constructor 665 | #if DEBUG_MEMORY 666 | mark_allocated(this); 667 | #endif 668 | this->name = link.name; 669 | this->nextSibling = 0; 670 | this->prevSibling = 0; 671 | this->var = link.var->ref(); 672 | this->owned = false; 673 | } 674 | 675 | CScriptVarLink::~CScriptVarLink() { 676 | #if DEBUG_MEMORY 677 | mark_deallocated(this); 678 | #endif 679 | var->unref(); 680 | } 681 | 682 | void CScriptVarLink::replaceWith(CScriptVar *newVar) { 683 | CScriptVar *oldVar = var; 684 | var = newVar->ref(); 685 | oldVar->unref(); 686 | } 687 | 688 | void CScriptVarLink::replaceWith(CScriptVarLink *newVar) { 689 | if (newVar) 690 | replaceWith(newVar->var); 691 | else 692 | replaceWith(new CScriptVar()); 693 | } 694 | 695 | int CScriptVarLink::getIntName() { 696 | return atoi(name.c_str()); 697 | } 698 | void CScriptVarLink::setIntName(int n) { 699 | char sIdx[64]; 700 | sprintf_s(sIdx, sizeof(sIdx), "%d", n); 701 | name = sIdx; 702 | } 703 | 704 | // ----------------------------------------------------------------------------------- CSCRIPTVAR 705 | 706 | CScriptVar::CScriptVar() { 707 | refs = 0; 708 | #if DEBUG_MEMORY 709 | mark_allocated(this); 710 | #endif 711 | init(); 712 | flags = SCRIPTVAR_UNDEFINED; 713 | } 714 | 715 | CScriptVar::CScriptVar(const string &str) { 716 | refs = 0; 717 | #if DEBUG_MEMORY 718 | mark_allocated(this); 719 | #endif 720 | init(); 721 | flags = SCRIPTVAR_STRING; 722 | data = str; 723 | } 724 | 725 | 726 | CScriptVar::CScriptVar(const string &varData, int varFlags) { 727 | refs = 0; 728 | #if DEBUG_MEMORY 729 | mark_allocated(this); 730 | #endif 731 | init(); 732 | flags = varFlags; 733 | if (varFlags & SCRIPTVAR_INTEGER) { 734 | intData = strtol(varData.c_str(),0,0); 735 | } else if (varFlags & SCRIPTVAR_DOUBLE) { 736 | doubleData = strtod(varData.c_str(),0); 737 | } else 738 | data = varData; 739 | } 740 | 741 | CScriptVar::CScriptVar(double val) { 742 | refs = 0; 743 | #if DEBUG_MEMORY 744 | mark_allocated(this); 745 | #endif 746 | init(); 747 | setDouble(val); 748 | } 749 | 750 | CScriptVar::CScriptVar(int val) { 751 | refs = 0; 752 | #if DEBUG_MEMORY 753 | mark_allocated(this); 754 | #endif 755 | init(); 756 | setInt(val); 757 | } 758 | 759 | CScriptVar::~CScriptVar(void) { 760 | #if DEBUG_MEMORY 761 | mark_deallocated(this); 762 | #endif 763 | removeAllChildren(); 764 | } 765 | 766 | void CScriptVar::init() { 767 | firstChild = 0; 768 | lastChild = 0; 769 | flags = 0; 770 | jsCallback = 0; 771 | jsCallbackUserData = 0; 772 | data = TINYJS_BLANK_DATA; 773 | intData = 0; 774 | doubleData = 0; 775 | } 776 | 777 | CScriptVar *CScriptVar::getReturnVar() { 778 | return getParameter(TINYJS_RETURN_VAR); 779 | } 780 | 781 | void CScriptVar::setReturnVar(CScriptVar *var) { 782 | findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var); 783 | } 784 | 785 | 786 | CScriptVar *CScriptVar::getParameter(const std::string &name) { 787 | return findChildOrCreate(name)->var; 788 | } 789 | 790 | CScriptVarLink *CScriptVar::findChild(const string &childName) { 791 | CScriptVarLink *v = firstChild; 792 | while (v) { 793 | if (v->name.compare(childName)==0) 794 | return v; 795 | v = v->nextSibling; 796 | } 797 | return 0; 798 | } 799 | 800 | CScriptVarLink *CScriptVar::findChildOrCreate(const string &childName, int varFlags) { 801 | CScriptVarLink *l = findChild(childName); 802 | if (l) return l; 803 | 804 | return addChild(childName, new CScriptVar(TINYJS_BLANK_DATA, varFlags)); 805 | } 806 | 807 | CScriptVarLink *CScriptVar::findChildOrCreateByPath(const std::string &path) { 808 | size_t p = path.find('.'); 809 | if (p == string::npos) 810 | return findChildOrCreate(path); 811 | 812 | return findChildOrCreate(path.substr(0,p), SCRIPTVAR_OBJECT)->var-> 813 | findChildOrCreateByPath(path.substr(p+1)); 814 | } 815 | 816 | CScriptVarLink *CScriptVar::addChild(const std::string &childName, CScriptVar *child) { 817 | if (isUndefined()) { 818 | flags = SCRIPTVAR_OBJECT; 819 | } 820 | // if no child supplied, create one 821 | if (!child) 822 | child = new CScriptVar(); 823 | 824 | CScriptVarLink *link = new CScriptVarLink(child, childName); 825 | link->owned = true; 826 | if (lastChild) { 827 | lastChild->nextSibling = link; 828 | link->prevSibling = lastChild; 829 | lastChild = link; 830 | } else { 831 | firstChild = link; 832 | lastChild = link; 833 | } 834 | return link; 835 | } 836 | 837 | CScriptVarLink *CScriptVar::addChildNoDup(const std::string &childName, CScriptVar *child) { 838 | // if no child supplied, create one 839 | if (!child) 840 | child = new CScriptVar(); 841 | 842 | CScriptVarLink *v = findChild(childName); 843 | if (v) { 844 | v->replaceWith(child); 845 | } else { 846 | v = addChild(childName, child); 847 | } 848 | 849 | return v; 850 | } 851 | 852 | void CScriptVar::removeChild(CScriptVar *child) { 853 | CScriptVarLink *link = firstChild; 854 | while (link) { 855 | if (link->var == child) 856 | break; 857 | link = link->nextSibling; 858 | } 859 | ASSERT(link); 860 | removeLink(link); 861 | } 862 | 863 | void CScriptVar::removeLink(CScriptVarLink *link) { 864 | if (!link) return; 865 | if (link->nextSibling) 866 | link->nextSibling->prevSibling = link->prevSibling; 867 | if (link->prevSibling) 868 | link->prevSibling->nextSibling = link->nextSibling; 869 | if (lastChild == link) 870 | lastChild = link->prevSibling; 871 | if (firstChild == link) 872 | firstChild = link->nextSibling; 873 | delete link; 874 | } 875 | 876 | void CScriptVar::removeAllChildren() { 877 | CScriptVarLink *c = firstChild; 878 | while (c) { 879 | CScriptVarLink *t = c->nextSibling; 880 | delete c; 881 | c = t; 882 | } 883 | firstChild = 0; 884 | lastChild = 0; 885 | } 886 | 887 | CScriptVar *CScriptVar::getArrayIndex(int idx) { 888 | char sIdx[64]; 889 | sprintf_s(sIdx, sizeof(sIdx), "%d", idx); 890 | CScriptVarLink *link = findChild(sIdx); 891 | if (link) return link->var; 892 | else return new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_NULL); // undefined 893 | } 894 | 895 | void CScriptVar::setArrayIndex(int idx, CScriptVar *value) { 896 | char sIdx[64]; 897 | sprintf_s(sIdx, sizeof(sIdx), "%d", idx); 898 | CScriptVarLink *link = findChild(sIdx); 899 | 900 | if (link) { 901 | if (value->isUndefined()) 902 | removeLink(link); 903 | else 904 | link->replaceWith(value); 905 | } else { 906 | if (!value->isUndefined()) 907 | addChild(sIdx, value); 908 | } 909 | } 910 | 911 | int CScriptVar::getArrayLength() { 912 | int highest = -1; 913 | if (!isArray()) return 0; 914 | 915 | CScriptVarLink *link = firstChild; 916 | while (link) { 917 | if (isNumber(link->name)) { 918 | int val = atoi(link->name.c_str()); 919 | if (val > highest) highest = val; 920 | } 921 | link = link->nextSibling; 922 | } 923 | return highest+1; 924 | } 925 | 926 | int CScriptVar::getChildren() { 927 | int n = 0; 928 | CScriptVarLink *link = firstChild; 929 | while (link) { 930 | n++; 931 | link = link->nextSibling; 932 | } 933 | return n; 934 | } 935 | 936 | int CScriptVar::getInt() { 937 | /* strtol understands about hex and octal */ 938 | if (isInt()) return intData; 939 | if (isNull()) return 0; 940 | if (isUndefined()) return 0; 941 | if (isDouble()) return (int)doubleData; 942 | return 0; 943 | } 944 | 945 | double CScriptVar::getDouble() { 946 | if (isDouble()) return doubleData; 947 | if (isInt()) return intData; 948 | if (isNull()) return 0; 949 | if (isUndefined()) return 0; 950 | return 0; /* or NaN? */ 951 | } 952 | 953 | const string &CScriptVar::getString() { 954 | /* Because we can't return a string that is generated on demand. 955 | * I should really just use char* :) */ 956 | static string s_null = "null"; 957 | static string s_undefined = "undefined"; 958 | if (isInt()) { 959 | char buffer[32]; 960 | sprintf_s(buffer, sizeof(buffer), "%ld", intData); 961 | data = buffer; 962 | return data; 963 | } 964 | if (isDouble()) { 965 | char buffer[32]; 966 | sprintf_s(buffer, sizeof(buffer), "%f", doubleData); 967 | data = buffer; 968 | return data; 969 | } 970 | if (isNull()) return s_null; 971 | if (isUndefined()) return s_undefined; 972 | // are we just a string here? 973 | return data; 974 | } 975 | 976 | void CScriptVar::setInt(int val) { 977 | flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER; 978 | intData = val; 979 | doubleData = 0; 980 | data = TINYJS_BLANK_DATA; 981 | } 982 | 983 | void CScriptVar::setDouble(double val) { 984 | flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE; 985 | doubleData = val; 986 | intData = 0; 987 | data = TINYJS_BLANK_DATA; 988 | } 989 | 990 | void CScriptVar::setString(const string &str) { 991 | // name sure it's not still a number or integer 992 | flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_STRING; 993 | data = str; 994 | intData = 0; 995 | doubleData = 0; 996 | } 997 | 998 | void CScriptVar::setUndefined() { 999 | // name sure it's not still a number or integer 1000 | flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_UNDEFINED; 1001 | data = TINYJS_BLANK_DATA; 1002 | intData = 0; 1003 | doubleData = 0; 1004 | removeAllChildren(); 1005 | } 1006 | 1007 | void CScriptVar::setArray() { 1008 | // name sure it's not still a number or integer 1009 | flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_ARRAY; 1010 | data = TINYJS_BLANK_DATA; 1011 | intData = 0; 1012 | doubleData = 0; 1013 | removeAllChildren(); 1014 | } 1015 | 1016 | bool CScriptVar::equals(CScriptVar *v) { 1017 | CScriptVar *resV = mathsOp(v, LEX_EQUAL); 1018 | bool res = resV->getBool(); 1019 | delete resV; 1020 | return res; 1021 | } 1022 | 1023 | CScriptVar *CScriptVar::mathsOp(CScriptVar *b, int op) { 1024 | CScriptVar *a = this; 1025 | // Type equality check 1026 | if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) { 1027 | // check type first, then call again to check data 1028 | bool eql = ((a->flags & SCRIPTVAR_VARTYPEMASK) == 1029 | (b->flags & SCRIPTVAR_VARTYPEMASK)); 1030 | if (eql) { 1031 | CScriptVar *contents = a->mathsOp(b, LEX_EQUAL); 1032 | if (!contents->getBool()) eql = false; 1033 | if (!contents->refs) delete contents; 1034 | } 1035 | ; 1036 | if (op == LEX_TYPEEQUAL) 1037 | return new CScriptVar(eql); 1038 | else 1039 | return new CScriptVar(!eql); 1040 | } 1041 | // do maths... 1042 | if (a->isUndefined() && b->isUndefined()) { 1043 | if (op == LEX_EQUAL) return new CScriptVar(true); 1044 | else if (op == LEX_NEQUAL) return new CScriptVar(false); 1045 | else return new CScriptVar(); // undefined 1046 | } else if ((a->isNumeric() || a->isUndefined()) && 1047 | (b->isNumeric() || b->isUndefined())) { 1048 | if (!a->isDouble() && !b->isDouble()) { 1049 | // use ints 1050 | int da = a->getInt(); 1051 | int db = b->getInt(); 1052 | switch (op) { 1053 | case '+': return new CScriptVar(da+db); 1054 | case '-': return new CScriptVar(da-db); 1055 | case '*': return new CScriptVar(da*db); 1056 | case '/': return new CScriptVar(da/db); 1057 | case '&': return new CScriptVar(da&db); 1058 | case '|': return new CScriptVar(da|db); 1059 | case '^': return new CScriptVar(da^db); 1060 | case '%': return new CScriptVar(da%db); 1061 | case LEX_EQUAL: return new CScriptVar(da==db); 1062 | case LEX_NEQUAL: return new CScriptVar(da!=db); 1063 | case '<': return new CScriptVar(da': return new CScriptVar(da>db); 1066 | case LEX_GEQUAL: return new CScriptVar(da>=db); 1067 | default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Int datatype"); 1068 | } 1069 | } else { 1070 | // use doubles 1071 | double da = a->getDouble(); 1072 | double db = b->getDouble(); 1073 | switch (op) { 1074 | case '+': return new CScriptVar(da+db); 1075 | case '-': return new CScriptVar(da-db); 1076 | case '*': return new CScriptVar(da*db); 1077 | case '/': return new CScriptVar(da/db); 1078 | case LEX_EQUAL: return new CScriptVar(da==db); 1079 | case LEX_NEQUAL: return new CScriptVar(da!=db); 1080 | case '<': return new CScriptVar(da': return new CScriptVar(da>db); 1083 | case LEX_GEQUAL: return new CScriptVar(da>=db); 1084 | default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Double datatype"); 1085 | } 1086 | } 1087 | } else if (a->isArray()) { 1088 | /* Just check pointers */ 1089 | switch (op) { 1090 | case LEX_EQUAL: return new CScriptVar(a==b); 1091 | case LEX_NEQUAL: return new CScriptVar(a!=b); 1092 | default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Array datatype"); 1093 | } 1094 | } else if (a->isObject()) { 1095 | /* Just check pointers */ 1096 | switch (op) { 1097 | case LEX_EQUAL: return new CScriptVar(a==b); 1098 | case LEX_NEQUAL: return new CScriptVar(a!=b); 1099 | default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Object datatype"); 1100 | } 1101 | } else { 1102 | string da = a->getString(); 1103 | string db = b->getString(); 1104 | // use strings 1105 | switch (op) { 1106 | case '+': return new CScriptVar(da+db, SCRIPTVAR_STRING); 1107 | case LEX_EQUAL: return new CScriptVar(da==db); 1108 | case LEX_NEQUAL: return new CScriptVar(da!=db); 1109 | case '<': return new CScriptVar(da': return new CScriptVar(da>db); 1112 | case LEX_GEQUAL: return new CScriptVar(da>=db); 1113 | default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the string datatype"); 1114 | } 1115 | } 1116 | ASSERT(0); 1117 | return 0; 1118 | } 1119 | 1120 | void CScriptVar::copySimpleData(CScriptVar *val) { 1121 | data = val->data; 1122 | intData = val->intData; 1123 | doubleData = val->doubleData; 1124 | flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK); 1125 | } 1126 | 1127 | void CScriptVar::copyValue(CScriptVar *val) { 1128 | if (val) { 1129 | copySimpleData(val); 1130 | // remove all current children 1131 | removeAllChildren(); 1132 | // copy children of 'val' 1133 | CScriptVarLink *child = val->firstChild; 1134 | while (child) { 1135 | CScriptVar *copied; 1136 | // don't copy the 'parent' object... 1137 | if (child->name != TINYJS_PROTOTYPE_CLASS) 1138 | copied = child->var->deepCopy(); 1139 | else 1140 | copied = child->var; 1141 | 1142 | addChild(child->name, copied); 1143 | 1144 | child = child->nextSibling; 1145 | } 1146 | } else { 1147 | setUndefined(); 1148 | } 1149 | } 1150 | 1151 | CScriptVar *CScriptVar::deepCopy() { 1152 | CScriptVar *newVar = new CScriptVar(); 1153 | newVar->copySimpleData(this); 1154 | // copy children 1155 | CScriptVarLink *child = firstChild; 1156 | while (child) { 1157 | CScriptVar *copied; 1158 | // don't copy the 'parent' object... 1159 | if (child->name != TINYJS_PROTOTYPE_CLASS) 1160 | copied = child->var->deepCopy(); 1161 | else 1162 | copied = child->var; 1163 | 1164 | newVar->addChild(child->name, copied); 1165 | child = child->nextSibling; 1166 | } 1167 | return newVar; 1168 | } 1169 | 1170 | void CScriptVar::trace(string indentStr, const string &name) { 1171 | TRACE("%s'%s' = '%s' %s\n", 1172 | indentStr.c_str(), 1173 | name.c_str(), 1174 | getString().c_str(), 1175 | getFlagsAsString().c_str()); 1176 | string indent = indentStr+" "; 1177 | CScriptVarLink *link = firstChild; 1178 | while (link) { 1179 | link->var->trace(indent, link->name); 1180 | link = link->nextSibling; 1181 | } 1182 | } 1183 | 1184 | string CScriptVar::getFlagsAsString() { 1185 | string flagstr = ""; 1186 | if (flags&SCRIPTVAR_FUNCTION) flagstr = flagstr + "FUNCTION "; 1187 | if (flags&SCRIPTVAR_OBJECT) flagstr = flagstr + "OBJECT "; 1188 | if (flags&SCRIPTVAR_ARRAY) flagstr = flagstr + "ARRAY "; 1189 | if (flags&SCRIPTVAR_NATIVE) flagstr = flagstr + "NATIVE "; 1190 | if (flags&SCRIPTVAR_DOUBLE) flagstr = flagstr + "DOUBLE "; 1191 | if (flags&SCRIPTVAR_INTEGER) flagstr = flagstr + "INTEGER "; 1192 | if (flags&SCRIPTVAR_STRING) flagstr = flagstr + "STRING "; 1193 | return flagstr; 1194 | } 1195 | 1196 | string CScriptVar::getParsableString() { 1197 | // Numbers can just be put in directly 1198 | if (isNumeric()) 1199 | return getString(); 1200 | if (isFunction()) { 1201 | ostringstream funcStr; 1202 | funcStr << "function ("; 1203 | // get list of parameters 1204 | CScriptVarLink *link = firstChild; 1205 | while (link) { 1206 | funcStr << link->name; 1207 | if (link->nextSibling) funcStr << ","; 1208 | link = link->nextSibling; 1209 | } 1210 | // add function body 1211 | funcStr << ") " << getString(); 1212 | return funcStr.str(); 1213 | } 1214 | // if it is a string then we quote it 1215 | if (isString()) 1216 | return getJSString(getString()); 1217 | if (isNull()) 1218 | return "null"; 1219 | return "undefined"; 1220 | } 1221 | 1222 | void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) { 1223 | if (isObject()) { 1224 | string indentedLinePrefix = linePrefix+" "; 1225 | // children - handle with bracketed list 1226 | destination << "{ \n"; 1227 | CScriptVarLink *link = firstChild; 1228 | while (link) { 1229 | destination << indentedLinePrefix; 1230 | destination << getJSString(link->name); 1231 | destination << " : "; 1232 | link->var->getJSON(destination, indentedLinePrefix); 1233 | link = link->nextSibling; 1234 | if (link) { 1235 | destination << ",\n"; 1236 | } 1237 | } 1238 | destination << "\n" << linePrefix << "}"; 1239 | } else if (isArray()) { 1240 | string indentedLinePrefix = linePrefix+" "; 1241 | destination << "[\n"; 1242 | int len = getArrayLength(); 1243 | if (len>10000) len=10000; // we don't want to get stuck here! 1244 | 1245 | for (int i=0;igetJSON(destination, indentedLinePrefix); 1247 | if (iref(); 1285 | // Add built-in classes 1286 | stringClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); 1287 | arrayClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); 1288 | objectClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); 1289 | root->addChild("String", stringClass); 1290 | root->addChild("Array", arrayClass); 1291 | root->addChild("Object", objectClass); 1292 | } 1293 | 1294 | CTinyJS::~CTinyJS() { 1295 | ASSERT(!l); 1296 | scopes.clear(); 1297 | stringClass->unref(); 1298 | arrayClass->unref(); 1299 | objectClass->unref(); 1300 | root->unref(); 1301 | 1302 | #if DEBUG_MEMORY 1303 | show_allocated(); 1304 | #endif 1305 | } 1306 | 1307 | void CTinyJS::trace() { 1308 | root->trace(); 1309 | } 1310 | 1311 | void CTinyJS::execute(const string &code) { 1312 | CScriptLex *oldLex = l; 1313 | vector oldScopes = scopes; 1314 | l = new CScriptLex(code); 1315 | #ifdef TINYJS_CALL_STACK 1316 | call_stack.clear(); 1317 | #endif 1318 | scopes.clear(); 1319 | scopes.push_back(root); 1320 | try { 1321 | bool execute = true; 1322 | while (l->tk) statement(execute); 1323 | } catch (CScriptException *e) { 1324 | ostringstream msg; 1325 | msg << "Error " << e->text; 1326 | #ifdef TINYJS_CALL_STACK 1327 | for (int i=(int)call_stack.size()-1;i>=0;i--) 1328 | msg << "\n" << i << ": " << call_stack.at(i); 1329 | #endif 1330 | msg << " at " << l->getPosition(); 1331 | delete l; 1332 | l = oldLex; 1333 | 1334 | throw new CScriptException(msg.str()); 1335 | } 1336 | delete l; 1337 | l = oldLex; 1338 | scopes = oldScopes; 1339 | } 1340 | 1341 | CScriptVarLink CTinyJS::evaluateComplex(const string &code) { 1342 | CScriptLex *oldLex = l; 1343 | vector oldScopes = scopes; 1344 | 1345 | l = new CScriptLex(code); 1346 | #ifdef TINYJS_CALL_STACK 1347 | call_stack.clear(); 1348 | #endif 1349 | scopes.clear(); 1350 | scopes.push_back(root); 1351 | CScriptVarLink *v = 0; 1352 | try { 1353 | bool execute = true; 1354 | do { 1355 | CLEAN(v); 1356 | v = base(execute); 1357 | if (l->tk!=LEX_EOF) l->match(';'); 1358 | } while (l->tk!=LEX_EOF); 1359 | } catch (CScriptException *e) { 1360 | ostringstream msg; 1361 | msg << "Error " << e->text; 1362 | #ifdef TINYJS_CALL_STACK 1363 | for (int i=(int)call_stack.size()-1;i>=0;i--) 1364 | msg << "\n" << i << ": " << call_stack.at(i); 1365 | #endif 1366 | msg << " at " << l->getPosition(); 1367 | delete l; 1368 | l = oldLex; 1369 | 1370 | throw new CScriptException(msg.str()); 1371 | } 1372 | delete l; 1373 | l = oldLex; 1374 | scopes = oldScopes; 1375 | 1376 | if (v) { 1377 | CScriptVarLink r = *v; 1378 | CLEAN(v); 1379 | return r; 1380 | } 1381 | // return undefined... 1382 | return CScriptVarLink(new CScriptVar()); 1383 | } 1384 | 1385 | string CTinyJS::evaluate(const string &code) { 1386 | return evaluateComplex(code).var->getString(); 1387 | } 1388 | 1389 | void CTinyJS::parseFunctionArguments(CScriptVar *funcVar) { 1390 | l->match('('); 1391 | while (l->tk!=')') { 1392 | funcVar->addChildNoDup(l->tkStr); 1393 | l->match(LEX_ID); 1394 | if (l->tk!=')') l->match(','); 1395 | } 1396 | l->match(')'); 1397 | } 1398 | 1399 | void CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata) { 1400 | CScriptLex *oldLex = l; 1401 | l = new CScriptLex(funcDesc); 1402 | 1403 | CScriptVar *base = root; 1404 | 1405 | l->match(LEX_R_FUNCTION); 1406 | string funcName = l->tkStr; 1407 | l->match(LEX_ID); 1408 | /* Check for dots, we might want to do something like function String.substring ... */ 1409 | while (l->tk == '.') { 1410 | l->match('.'); 1411 | CScriptVarLink *link = base->findChild(funcName); 1412 | // if it doesn't exist, make an object class 1413 | if (!link) link = base->addChild(funcName, new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT)); 1414 | base = link->var; 1415 | funcName = l->tkStr; 1416 | l->match(LEX_ID); 1417 | } 1418 | 1419 | CScriptVar *funcVar = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE); 1420 | funcVar->setCallback(ptr, userdata); 1421 | parseFunctionArguments(funcVar); 1422 | delete l; 1423 | l = oldLex; 1424 | 1425 | base->addChild(funcName, funcVar); 1426 | } 1427 | 1428 | CScriptVarLink *CTinyJS::parseFunctionDefinition() { 1429 | // actually parse a function... 1430 | l->match(LEX_R_FUNCTION); 1431 | string funcName = TINYJS_TEMP_NAME; 1432 | /* we can have functions without names */ 1433 | if (l->tk==LEX_ID) { 1434 | funcName = l->tkStr; 1435 | l->match(LEX_ID); 1436 | } 1437 | CScriptVarLink *funcVar = new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION), funcName); 1438 | parseFunctionArguments(funcVar->var); 1439 | int funcBegin = l->tokenStart; 1440 | bool noexecute = false; 1441 | block(noexecute); 1442 | funcVar->var->data = l->getSubString(funcBegin); 1443 | return funcVar; 1444 | } 1445 | 1446 | /** Handle a function call (assumes we've parsed the function name and we're 1447 | * on the start bracket). 'parent' is the object that contains this method, 1448 | * if there was one (otherwise it's just a normnal function). 1449 | */ 1450 | CScriptVarLink *CTinyJS::functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent) { 1451 | if (execute) { 1452 | if (!function->var->isFunction()) { 1453 | string errorMsg = "Expecting '"; 1454 | errorMsg = errorMsg + function->name + "' to be a function"; 1455 | throw new CScriptException(errorMsg.c_str()); 1456 | } 1457 | l->match('('); 1458 | // create a new symbol table entry for execution of this function 1459 | CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION); 1460 | if (parent) 1461 | functionRoot->addChildNoDup("this", parent); 1462 | // grab in all parameters 1463 | CScriptVarLink *v = function->var->firstChild; 1464 | while (v) { 1465 | CScriptVarLink *value = base(execute); 1466 | if (execute) { 1467 | if (value->var->isBasic()) { 1468 | // pass by value 1469 | functionRoot->addChild(v->name, value->var->deepCopy()); 1470 | } else { 1471 | // pass by reference 1472 | functionRoot->addChild(v->name, value->var); 1473 | } 1474 | } 1475 | CLEAN(value); 1476 | if (l->tk!=')') l->match(','); 1477 | v = v->nextSibling; 1478 | } 1479 | l->match(')'); 1480 | // setup a return variable 1481 | CScriptVarLink *returnVar = NULL; 1482 | // execute function! 1483 | // add the function's execute space to the symbol table so we can recurse 1484 | CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR); 1485 | scopes.push_back(functionRoot); 1486 | #ifdef TINYJS_CALL_STACK 1487 | call_stack.push_back(function->name + " from " + l->getPosition()); 1488 | #endif 1489 | 1490 | if (function->var->isNative()) { 1491 | ASSERT(function->var->jsCallback); 1492 | function->var->jsCallback(functionRoot, function->var->jsCallbackUserData); 1493 | } else { 1494 | /* we just want to execute the block, but something could 1495 | * have messed up and left us with the wrong ScriptLex, so 1496 | * we want to be careful here... */ 1497 | CScriptException *exception = 0; 1498 | CScriptLex *oldLex = l; 1499 | CScriptLex *newLex = new CScriptLex(function->var->getString()); 1500 | l = newLex; 1501 | try { 1502 | block(execute); 1503 | // because return will probably have called this, and set execute to false 1504 | execute = true; 1505 | } catch (CScriptException *e) { 1506 | exception = e; 1507 | } 1508 | delete newLex; 1509 | l = oldLex; 1510 | 1511 | if (exception) 1512 | throw exception; 1513 | } 1514 | #ifdef TINYJS_CALL_STACK 1515 | if (!call_stack.empty()) call_stack.pop_back(); 1516 | #endif 1517 | scopes.pop_back(); 1518 | /* get the real return var before we remove it from our function */ 1519 | returnVar = new CScriptVarLink(returnVarLink->var); 1520 | functionRoot->removeLink(returnVarLink); 1521 | delete functionRoot; 1522 | if (returnVar) 1523 | return returnVar; 1524 | else 1525 | return new CScriptVarLink(new CScriptVar()); 1526 | } else { 1527 | // function, but not executing - just parse args and be done 1528 | l->match('('); 1529 | while (l->tk != ')') { 1530 | CScriptVarLink *value = base(execute); 1531 | CLEAN(value); 1532 | if (l->tk!=')') l->match(','); 1533 | } 1534 | l->match(')'); 1535 | if (l->tk == '{') { // TODO: why is this here? 1536 | block(execute); 1537 | } 1538 | /* function will be a blank scriptvarlink if we're not executing, 1539 | * so just return it rather than an alloc/free */ 1540 | return function; 1541 | } 1542 | } 1543 | 1544 | CScriptVarLink *CTinyJS::factor(bool &execute) { 1545 | if (l->tk=='(') { 1546 | l->match('('); 1547 | CScriptVarLink *a = base(execute); 1548 | l->match(')'); 1549 | return a; 1550 | } 1551 | if (l->tk==LEX_R_TRUE) { 1552 | l->match(LEX_R_TRUE); 1553 | return new CScriptVarLink(new CScriptVar(1)); 1554 | } 1555 | if (l->tk==LEX_R_FALSE) { 1556 | l->match(LEX_R_FALSE); 1557 | return new CScriptVarLink(new CScriptVar(0)); 1558 | } 1559 | if (l->tk==LEX_R_NULL) { 1560 | l->match(LEX_R_NULL); 1561 | return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_NULL)); 1562 | } 1563 | if (l->tk==LEX_R_UNDEFINED) { 1564 | l->match(LEX_R_UNDEFINED); 1565 | return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_UNDEFINED)); 1566 | } 1567 | if (l->tk==LEX_ID) { 1568 | CScriptVarLink *a = execute ? findInScopes(l->tkStr) : new CScriptVarLink(new CScriptVar()); 1569 | //printf("0x%08X for %s at %s\n", (unsigned int)a, l->tkStr.c_str(), l->getPosition().c_str()); 1570 | /* The parent if we're executing a method call */ 1571 | CScriptVar *parent = 0; 1572 | 1573 | if (execute && !a) { 1574 | /* Variable doesn't exist! JavaScript says we should create it 1575 | * (we won't add it here. This is done in the assignment operator)*/ 1576 | a = new CScriptVarLink(new CScriptVar(), l->tkStr); 1577 | } 1578 | l->match(LEX_ID); 1579 | while (l->tk=='(' || l->tk=='.' || l->tk=='[') { 1580 | if (l->tk=='(') { // ------------------------------------- Function Call 1581 | a = functionCall(execute, a, parent); 1582 | } else if (l->tk == '.') { // ------------------------------------- Record Access 1583 | l->match('.'); 1584 | if (execute) { 1585 | const string &name = l->tkStr; 1586 | CScriptVarLink *child = a->var->findChild(name); 1587 | if (!child) child = findInParentClasses(a->var, name); 1588 | if (!child) { 1589 | /* if we haven't found this defined yet, use the built-in 1590 | 'length' properly */ 1591 | if (a->var->isArray() && name == "length") { 1592 | int l = a->var->getArrayLength(); 1593 | child = new CScriptVarLink(new CScriptVar(l)); 1594 | } else if (a->var->isString() && name == "length") { 1595 | int l = a->var->getString().size(); 1596 | child = new CScriptVarLink(new CScriptVar(l)); 1597 | } else { 1598 | child = a->var->addChild(name); 1599 | } 1600 | } 1601 | parent = a->var; 1602 | a = child; 1603 | } 1604 | l->match(LEX_ID); 1605 | } else if (l->tk == '[') { // ------------------------------------- Array Access 1606 | l->match('['); 1607 | CScriptVarLink *index = base(execute); 1608 | l->match(']'); 1609 | if (execute) { 1610 | CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString()); 1611 | parent = a->var; 1612 | a = child; 1613 | } 1614 | CLEAN(index); 1615 | } else ASSERT(0); 1616 | } 1617 | return a; 1618 | } 1619 | if (l->tk==LEX_INT || l->tk==LEX_FLOAT) { 1620 | CScriptVar *a = new CScriptVar(l->tkStr, 1621 | ((l->tk==LEX_INT)?SCRIPTVAR_INTEGER:SCRIPTVAR_DOUBLE)); 1622 | l->match(l->tk); 1623 | return new CScriptVarLink(a); 1624 | } 1625 | if (l->tk==LEX_STR) { 1626 | CScriptVar *a = new CScriptVar(l->tkStr, SCRIPTVAR_STRING); 1627 | l->match(LEX_STR); 1628 | return new CScriptVarLink(a); 1629 | } 1630 | if (l->tk=='{') { 1631 | CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); 1632 | /* JSON-style object definition */ 1633 | l->match('{'); 1634 | while (l->tk != '}') { 1635 | string id = l->tkStr; 1636 | // we only allow strings or IDs on the left hand side of an initialisation 1637 | if (l->tk==LEX_STR) l->match(LEX_STR); 1638 | else l->match(LEX_ID); 1639 | l->match(':'); 1640 | if (execute) { 1641 | CScriptVarLink *a = base(execute); 1642 | contents->addChild(id, a->var); 1643 | CLEAN(a); 1644 | } 1645 | // no need to clean here, as it will definitely be used 1646 | if (l->tk != '}') l->match(','); 1647 | } 1648 | 1649 | l->match('}'); 1650 | return new CScriptVarLink(contents); 1651 | } 1652 | if (l->tk=='[') { 1653 | CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY); 1654 | /* JSON-style array */ 1655 | l->match('['); 1656 | int idx = 0; 1657 | while (l->tk != ']') { 1658 | if (execute) { 1659 | char idx_str[16]; // big enough for 2^32 1660 | sprintf_s(idx_str, sizeof(idx_str), "%d",idx); 1661 | 1662 | CScriptVarLink *a = base(execute); 1663 | contents->addChild(idx_str, a->var); 1664 | CLEAN(a); 1665 | } 1666 | // no need to clean here, as it will definitely be used 1667 | if (l->tk != ']') l->match(','); 1668 | idx++; 1669 | } 1670 | l->match(']'); 1671 | return new CScriptVarLink(contents); 1672 | } 1673 | if (l->tk==LEX_R_FUNCTION) { 1674 | CScriptVarLink *funcVar = parseFunctionDefinition(); 1675 | if (funcVar->name != TINYJS_TEMP_NAME) 1676 | TRACE("Functions not defined at statement-level are not meant to have a name"); 1677 | return funcVar; 1678 | } 1679 | if (l->tk==LEX_R_NEW) { 1680 | // new -> create a new object 1681 | l->match(LEX_R_NEW); 1682 | const string &className = l->tkStr; 1683 | if (execute) { 1684 | CScriptVarLink *objClassOrFunc = findInScopes(className); 1685 | if (!objClassOrFunc) { 1686 | TRACE("%s is not a valid class name", className.c_str()); 1687 | return new CScriptVarLink(new CScriptVar()); 1688 | } 1689 | l->match(LEX_ID); 1690 | CScriptVar *obj = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); 1691 | CScriptVarLink *objLink = new CScriptVarLink(obj); 1692 | if (objClassOrFunc->var->isFunction()) { 1693 | CLEAN(functionCall(execute, objClassOrFunc, obj)); 1694 | } else { 1695 | obj->addChild(TINYJS_PROTOTYPE_CLASS, objClassOrFunc->var); 1696 | if (l->tk == '(') { 1697 | l->match('('); 1698 | l->match(')'); 1699 | } 1700 | } 1701 | return objLink; 1702 | } else { 1703 | l->match(LEX_ID); 1704 | if (l->tk == '(') { 1705 | l->match('('); 1706 | l->match(')'); 1707 | } 1708 | } 1709 | } 1710 | // Nothing we can do here... just hope it's the end... 1711 | l->match(LEX_EOF); 1712 | return 0; 1713 | } 1714 | 1715 | CScriptVarLink *CTinyJS::unary(bool &execute) { 1716 | CScriptVarLink *a; 1717 | if (l->tk=='!') { 1718 | l->match('!'); // binary not 1719 | a = factor(execute); 1720 | if (execute) { 1721 | CScriptVar zero(0); 1722 | CScriptVar *res = a->var->mathsOp(&zero, LEX_EQUAL); 1723 | CREATE_LINK(a, res); 1724 | } 1725 | } else 1726 | a = factor(execute); 1727 | return a; 1728 | } 1729 | 1730 | CScriptVarLink *CTinyJS::term(bool &execute) { 1731 | CScriptVarLink *a = unary(execute); 1732 | while (l->tk=='*' || l->tk=='/' || l->tk=='%') { 1733 | int op = l->tk; 1734 | l->match(l->tk); 1735 | CScriptVarLink *b = unary(execute); 1736 | if (execute) { 1737 | CScriptVar *res = a->var->mathsOp(b->var, op); 1738 | CREATE_LINK(a, res); 1739 | } 1740 | CLEAN(b); 1741 | } 1742 | return a; 1743 | } 1744 | 1745 | CScriptVarLink *CTinyJS::expression(bool &execute) { 1746 | bool negate = false; 1747 | if (l->tk=='-') { 1748 | l->match('-'); 1749 | negate = true; 1750 | } 1751 | CScriptVarLink *a = term(execute); 1752 | if (negate) { 1753 | CScriptVar zero(0); 1754 | CScriptVar *res = zero.mathsOp(a->var, '-'); 1755 | CREATE_LINK(a, res); 1756 | } 1757 | 1758 | while (l->tk=='+' || l->tk=='-' || 1759 | l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS) { 1760 | int op = l->tk; 1761 | l->match(l->tk); 1762 | if (op==LEX_PLUSPLUS || op==LEX_MINUSMINUS) { 1763 | if (execute) { 1764 | CScriptVar one(1); 1765 | CScriptVar *res = a->var->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-'); 1766 | CScriptVarLink *oldValue = new CScriptVarLink(a->var); 1767 | // in-place add/subtract 1768 | a->replaceWith(res); 1769 | CLEAN(a); 1770 | a = oldValue; 1771 | } 1772 | } else { 1773 | CScriptVarLink *b = term(execute); 1774 | if (execute) { 1775 | // not in-place, so just replace 1776 | CScriptVar *res = a->var->mathsOp(b->var, op); 1777 | CREATE_LINK(a, res); 1778 | } 1779 | CLEAN(b); 1780 | } 1781 | } 1782 | return a; 1783 | } 1784 | 1785 | CScriptVarLink *CTinyJS::shift(bool &execute) { 1786 | CScriptVarLink *a = expression(execute); 1787 | if (l->tk==LEX_LSHIFT || l->tk==LEX_RSHIFT || l->tk==LEX_RSHIFTUNSIGNED) { 1788 | int op = l->tk; 1789 | l->match(op); 1790 | CScriptVarLink *b = base(execute); 1791 | int shift = execute ? b->var->getInt() : 0; 1792 | CLEAN(b); 1793 | if (execute) { 1794 | if (op==LEX_LSHIFT) a->var->setInt(a->var->getInt() << shift); 1795 | if (op==LEX_RSHIFT) a->var->setInt(a->var->getInt() >> shift); 1796 | if (op==LEX_RSHIFTUNSIGNED) a->var->setInt(((unsigned int)a->var->getInt()) >> shift); 1797 | } 1798 | } 1799 | return a; 1800 | } 1801 | 1802 | CScriptVarLink *CTinyJS::condition(bool &execute) { 1803 | CScriptVarLink *a = shift(execute); 1804 | CScriptVarLink *b; 1805 | while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL || 1806 | l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL || 1807 | l->tk==LEX_LEQUAL || l->tk==LEX_GEQUAL || 1808 | l->tk=='<' || l->tk=='>') { 1809 | int op = l->tk; 1810 | l->match(l->tk); 1811 | b = shift(execute); 1812 | if (execute) { 1813 | CScriptVar *res = a->var->mathsOp(b->var, op); 1814 | CREATE_LINK(a,res); 1815 | } 1816 | CLEAN(b); 1817 | } 1818 | return a; 1819 | } 1820 | 1821 | CScriptVarLink *CTinyJS::logic(bool &execute) { 1822 | CScriptVarLink *a = condition(execute); 1823 | CScriptVarLink *b; 1824 | while (l->tk=='&' || l->tk=='|' || l->tk=='^' || l->tk==LEX_ANDAND || l->tk==LEX_OROR) { 1825 | bool noexecute = false; 1826 | int op = l->tk; 1827 | l->match(l->tk); 1828 | bool shortCircuit = false; 1829 | bool boolean = false; 1830 | // if we have short-circuit ops, then if we know the outcome 1831 | // we don't bother to execute the other op. Even if not 1832 | // we need to tell mathsOp it's an & or | 1833 | if (op==LEX_ANDAND) { 1834 | op = '&'; 1835 | shortCircuit = !a->var->getBool(); 1836 | boolean = true; 1837 | } else if (op==LEX_OROR) { 1838 | op = '|'; 1839 | shortCircuit = a->var->getBool(); 1840 | boolean = true; 1841 | } 1842 | b = condition(shortCircuit ? noexecute : execute); 1843 | if (execute && !shortCircuit) { 1844 | if (boolean) { 1845 | CScriptVar *newa = new CScriptVar(a->var->getBool()); 1846 | CScriptVar *newb = new CScriptVar(b->var->getBool()); 1847 | CREATE_LINK(a, newa); 1848 | CREATE_LINK(b, newb); 1849 | } 1850 | CScriptVar *res = a->var->mathsOp(b->var, op); 1851 | CREATE_LINK(a, res); 1852 | } 1853 | CLEAN(b); 1854 | } 1855 | return a; 1856 | } 1857 | 1858 | CScriptVarLink *CTinyJS::ternary(bool &execute) { 1859 | CScriptVarLink *lhs = logic(execute); 1860 | bool noexec = false; 1861 | if (l->tk=='?') { 1862 | l->match('?'); 1863 | if (!execute) { 1864 | CLEAN(lhs); 1865 | CLEAN(base(noexec)); 1866 | l->match(':'); 1867 | CLEAN(base(noexec)); 1868 | } else { 1869 | bool first = lhs->var->getBool(); 1870 | CLEAN(lhs); 1871 | if (first) { 1872 | lhs = base(execute); 1873 | l->match(':'); 1874 | CLEAN(base(noexec)); 1875 | } else { 1876 | CLEAN(base(noexec)); 1877 | l->match(':'); 1878 | lhs = base(execute); 1879 | } 1880 | } 1881 | } 1882 | 1883 | return lhs; 1884 | } 1885 | 1886 | CScriptVarLink *CTinyJS::base(bool &execute) { 1887 | CScriptVarLink *lhs = ternary(execute); 1888 | if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) { 1889 | /* If we're assigning to this and we don't have a parent, 1890 | * add it to the symbol table root as per JavaScript. */ 1891 | if (execute && !lhs->owned) { 1892 | if (lhs->name.length()>0) { 1893 | CScriptVarLink *realLhs = root->addChildNoDup(lhs->name, lhs->var); 1894 | CLEAN(lhs); 1895 | lhs = realLhs; 1896 | } else 1897 | TRACE("Trying to assign to an un-named type\n"); 1898 | } 1899 | 1900 | int op = l->tk; 1901 | l->match(l->tk); 1902 | CScriptVarLink *rhs = base(execute); 1903 | if (execute) { 1904 | if (op=='=') { 1905 | lhs->replaceWith(rhs); 1906 | } else if (op==LEX_PLUSEQUAL) { 1907 | CScriptVar *res = lhs->var->mathsOp(rhs->var, '+'); 1908 | lhs->replaceWith(res); 1909 | } else if (op==LEX_MINUSEQUAL) { 1910 | CScriptVar *res = lhs->var->mathsOp(rhs->var, '-'); 1911 | lhs->replaceWith(res); 1912 | } else ASSERT(0); 1913 | } 1914 | CLEAN(rhs); 1915 | } 1916 | return lhs; 1917 | } 1918 | 1919 | void CTinyJS::block(bool &execute) { 1920 | l->match('{'); 1921 | if (execute) { 1922 | while (l->tk && l->tk!='}') 1923 | statement(execute); 1924 | l->match('}'); 1925 | } else { 1926 | // fast skip of blocks 1927 | int brackets = 1; 1928 | while (l->tk && brackets) { 1929 | if (l->tk == '{') brackets++; 1930 | if (l->tk == '}') brackets--; 1931 | l->match(l->tk); 1932 | } 1933 | } 1934 | 1935 | } 1936 | 1937 | void CTinyJS::statement(bool &execute) { 1938 | if (l->tk==LEX_ID || 1939 | l->tk==LEX_INT || 1940 | l->tk==LEX_FLOAT || 1941 | l->tk==LEX_STR || 1942 | l->tk=='-') { 1943 | /* Execute a simple statement that only contains basic arithmetic... */ 1944 | CLEAN(base(execute)); 1945 | l->match(';'); 1946 | } else if (l->tk=='{') { 1947 | /* A block of code */ 1948 | block(execute); 1949 | } else if (l->tk==';') { 1950 | /* Empty statement - to allow things like ;;; */ 1951 | l->match(';'); 1952 | } else if (l->tk==LEX_R_VAR) { 1953 | /* variable creation. TODO - we need a better way of parsing the left 1954 | * hand side. Maybe just have a flag called can_create_var that we 1955 | * set and then we parse as if we're doing a normal equals.*/ 1956 | l->match(LEX_R_VAR); 1957 | while (l->tk != ';') { 1958 | CScriptVarLink *a = 0; 1959 | if (execute) 1960 | a = scopes.back()->findChildOrCreate(l->tkStr); 1961 | l->match(LEX_ID); 1962 | // now do stuff defined with dots 1963 | while (l->tk == '.') { 1964 | l->match('.'); 1965 | if (execute) { 1966 | CScriptVarLink *lastA = a; 1967 | a = lastA->var->findChildOrCreate(l->tkStr); 1968 | } 1969 | l->match(LEX_ID); 1970 | } 1971 | // sort out initialiser 1972 | if (l->tk == '=') { 1973 | l->match('='); 1974 | CScriptVarLink *var = base(execute); 1975 | if (execute) 1976 | a->replaceWith(var); 1977 | CLEAN(var); 1978 | } 1979 | if (l->tk != ';') 1980 | l->match(','); 1981 | } 1982 | l->match(';'); 1983 | } else if (l->tk==LEX_R_IF) { 1984 | l->match(LEX_R_IF); 1985 | l->match('('); 1986 | CScriptVarLink *var = base(execute); 1987 | l->match(')'); 1988 | bool cond = execute && var->var->getBool(); 1989 | CLEAN(var); 1990 | bool noexecute = false; // because we need to be abl;e to write to it 1991 | statement(cond ? execute : noexecute); 1992 | if (l->tk==LEX_R_ELSE) { 1993 | l->match(LEX_R_ELSE); 1994 | statement(cond ? noexecute : execute); 1995 | } 1996 | } else if (l->tk==LEX_R_WHILE) { 1997 | // We do repetition by pulling out the string representing our statement 1998 | // there's definitely some opportunity for optimisation here 1999 | l->match(LEX_R_WHILE); 2000 | l->match('('); 2001 | int whileCondStart = l->tokenStart; 2002 | bool noexecute = false; 2003 | CScriptVarLink *cond = base(execute); 2004 | bool loopCond = execute && cond->var->getBool(); 2005 | CLEAN(cond); 2006 | CScriptLex *whileCond = l->getSubLex(whileCondStart); 2007 | l->match(')'); 2008 | int whileBodyStart = l->tokenStart; 2009 | statement(loopCond ? execute : noexecute); 2010 | CScriptLex *whileBody = l->getSubLex(whileBodyStart); 2011 | CScriptLex *oldLex = l; 2012 | int loopCount = TINYJS_LOOP_MAX_ITERATIONS; 2013 | while (loopCond && loopCount-->0) { 2014 | whileCond->reset(); 2015 | l = whileCond; 2016 | cond = base(execute); 2017 | loopCond = execute && cond->var->getBool(); 2018 | CLEAN(cond); 2019 | if (loopCond) { 2020 | whileBody->reset(); 2021 | l = whileBody; 2022 | statement(execute); 2023 | } 2024 | } 2025 | l = oldLex; 2026 | delete whileCond; 2027 | delete whileBody; 2028 | 2029 | if (loopCount<=0) { 2030 | root->trace(); 2031 | TRACE("WHILE Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str()); 2032 | throw new CScriptException("LOOP_ERROR"); 2033 | } 2034 | } else if (l->tk==LEX_R_FOR) { 2035 | l->match(LEX_R_FOR); 2036 | l->match('('); 2037 | statement(execute); // initialisation 2038 | //l->match(';'); 2039 | int forCondStart = l->tokenStart; 2040 | bool noexecute = false; 2041 | CScriptVarLink *cond = base(execute); // condition 2042 | bool loopCond = execute && cond->var->getBool(); 2043 | CLEAN(cond); 2044 | CScriptLex *forCond = l->getSubLex(forCondStart); 2045 | l->match(';'); 2046 | int forIterStart = l->tokenStart; 2047 | CLEAN(base(noexecute)); // iterator 2048 | CScriptLex *forIter = l->getSubLex(forIterStart); 2049 | l->match(')'); 2050 | int forBodyStart = l->tokenStart; 2051 | statement(loopCond ? execute : noexecute); 2052 | CScriptLex *forBody = l->getSubLex(forBodyStart); 2053 | CScriptLex *oldLex = l; 2054 | if (loopCond) { 2055 | forIter->reset(); 2056 | l = forIter; 2057 | CLEAN(base(execute)); 2058 | } 2059 | int loopCount = TINYJS_LOOP_MAX_ITERATIONS; 2060 | while (execute && loopCond && loopCount-->0) { 2061 | forCond->reset(); 2062 | l = forCond; 2063 | cond = base(execute); 2064 | loopCond = cond->var->getBool(); 2065 | CLEAN(cond); 2066 | if (execute && loopCond) { 2067 | forBody->reset(); 2068 | l = forBody; 2069 | statement(execute); 2070 | } 2071 | if (execute && loopCond) { 2072 | forIter->reset(); 2073 | l = forIter; 2074 | CLEAN(base(execute)); 2075 | } 2076 | } 2077 | l = oldLex; 2078 | delete forCond; 2079 | delete forIter; 2080 | delete forBody; 2081 | if (loopCount<=0) { 2082 | root->trace(); 2083 | TRACE("FOR Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str()); 2084 | throw new CScriptException("LOOP_ERROR"); 2085 | } 2086 | } else if (l->tk==LEX_R_RETURN) { 2087 | l->match(LEX_R_RETURN); 2088 | CScriptVarLink *result = 0; 2089 | if (l->tk != ';') 2090 | result = base(execute); 2091 | if (execute) { 2092 | CScriptVarLink *resultVar = scopes.back()->findChild(TINYJS_RETURN_VAR); 2093 | if (resultVar) 2094 | resultVar->replaceWith(result); 2095 | else 2096 | TRACE("RETURN statement, but not in a function.\n"); 2097 | execute = false; 2098 | } 2099 | CLEAN(result); 2100 | l->match(';'); 2101 | } else if (l->tk==LEX_R_FUNCTION) { 2102 | CScriptVarLink *funcVar = parseFunctionDefinition(); 2103 | if (execute) { 2104 | if (funcVar->name == TINYJS_TEMP_NAME) 2105 | TRACE("Functions defined at statement-level are meant to have a name\n"); 2106 | else 2107 | scopes.back()->addChildNoDup(funcVar->name, funcVar->var); 2108 | } 2109 | CLEAN(funcVar); 2110 | } else l->match(LEX_EOF); 2111 | } 2112 | 2113 | /// Get the given variable specified by a path (var1.var2.etc), or return 0 2114 | CScriptVar *CTinyJS::getScriptVariable(const string &path) { 2115 | // traverse path 2116 | size_t prevIdx = 0; 2117 | size_t thisIdx = path.find('.'); 2118 | if (thisIdx == string::npos) thisIdx = path.length(); 2119 | CScriptVar *var = root; 2120 | while (var && prevIdxfindChild(el); 2123 | var = varl?varl->var:0; 2124 | prevIdx = thisIdx+1; 2125 | thisIdx = path.find('.', prevIdx); 2126 | if (thisIdx == string::npos) thisIdx = path.length(); 2127 | } 2128 | return var; 2129 | } 2130 | 2131 | /// Get the value of the given variable, or return 0 2132 | const string *CTinyJS::getVariable(const string &path) { 2133 | CScriptVar *var = getScriptVariable(path); 2134 | // return result 2135 | if (var) 2136 | return &var->getString(); 2137 | else 2138 | return 0; 2139 | } 2140 | 2141 | /// set the value of the given variable, return trur if it exists and gets set 2142 | bool CTinyJS::setVariable(const std::string &path, const std::string &varData) { 2143 | CScriptVar *var = getScriptVariable(path); 2144 | // return result 2145 | if (var) { 2146 | if (var->isInt()) 2147 | var->setInt((int)strtol(varData.c_str(),0,0)); 2148 | else if (var->isDouble()) 2149 | var->setDouble(strtod(varData.c_str(),0)); 2150 | else 2151 | var->setString(varData.c_str()); 2152 | return true; 2153 | } 2154 | else 2155 | return false; 2156 | } 2157 | 2158 | /// Finds a child, looking recursively up the scopes 2159 | CScriptVarLink *CTinyJS::findInScopes(const std::string &childName) { 2160 | for (int s=scopes.size()-1;s>=0;s--) { 2161 | CScriptVarLink *v = scopes[s]->findChild(childName); 2162 | if (v) return v; 2163 | } 2164 | return NULL; 2165 | 2166 | } 2167 | 2168 | /// Look up in any parent classes of the given object 2169 | CScriptVarLink *CTinyJS::findInParentClasses(CScriptVar *object, const std::string &name) { 2170 | // Look for links to actual parent classes 2171 | CScriptVarLink *parentClass = object->findChild(TINYJS_PROTOTYPE_CLASS); 2172 | while (parentClass) { 2173 | CScriptVarLink *implementation = parentClass->var->findChild(name); 2174 | if (implementation) return implementation; 2175 | parentClass = parentClass->var->findChild(TINYJS_PROTOTYPE_CLASS); 2176 | } 2177 | // else fake it for strings and finally objects 2178 | if (object->isString()) { 2179 | CScriptVarLink *implementation = stringClass->findChild(name); 2180 | if (implementation) return implementation; 2181 | } 2182 | if (object->isArray()) { 2183 | CScriptVarLink *implementation = arrayClass->findChild(name); 2184 | if (implementation) return implementation; 2185 | } 2186 | CScriptVarLink *implementation = objectClass->findChild(name); 2187 | if (implementation) return implementation; 2188 | 2189 | return 0; 2190 | } 2191 | -------------------------------------------------------------------------------- /TinyJS.h: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * Authored By Gordon Williams 7 | * 8 | * Copyright (C) 2009 Pur3 Ltd 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | * of the Software, and to permit persons to whom the Software is furnished to do 15 | * so, subject to the following conditions: 16 | 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #ifndef TINYJS_H 30 | #define TINYJS_H 31 | 32 | // If defined, this keeps a note of all calls and where from in memory. This is slower, but good for debugging 33 | #define TINYJS_CALL_STACK 34 | 35 | #ifdef _WIN32 36 | #ifdef _DEBUG 37 | #define _CRTDBG_MAP_ALLOC 38 | #include 39 | #include 40 | #endif 41 | #endif 42 | #include 43 | #include 44 | 45 | #ifndef TRACE 46 | #define TRACE printf 47 | #endif // TRACE 48 | 49 | 50 | const int TINYJS_LOOP_MAX_ITERATIONS = 8192; 51 | 52 | enum LEX_TYPES { 53 | LEX_EOF = 0, 54 | LEX_ID = 256, 55 | LEX_INT, 56 | LEX_FLOAT, 57 | LEX_STR, 58 | 59 | LEX_EQUAL, 60 | LEX_TYPEEQUAL, 61 | LEX_NEQUAL, 62 | LEX_NTYPEEQUAL, 63 | LEX_LEQUAL, 64 | LEX_LSHIFT, 65 | LEX_LSHIFTEQUAL, 66 | LEX_GEQUAL, 67 | LEX_RSHIFT, 68 | LEX_RSHIFTUNSIGNED, 69 | LEX_RSHIFTEQUAL, 70 | LEX_PLUSEQUAL, 71 | LEX_MINUSEQUAL, 72 | LEX_PLUSPLUS, 73 | LEX_MINUSMINUS, 74 | LEX_ANDEQUAL, 75 | LEX_ANDAND, 76 | LEX_OREQUAL, 77 | LEX_OROR, 78 | LEX_XOREQUAL, 79 | // reserved words 80 | #define LEX_R_LIST_START LEX_R_IF 81 | LEX_R_IF, 82 | LEX_R_ELSE, 83 | LEX_R_DO, 84 | LEX_R_WHILE, 85 | LEX_R_FOR, 86 | LEX_R_BREAK, 87 | LEX_R_CONTINUE, 88 | LEX_R_FUNCTION, 89 | LEX_R_RETURN, 90 | LEX_R_VAR, 91 | LEX_R_TRUE, 92 | LEX_R_FALSE, 93 | LEX_R_NULL, 94 | LEX_R_UNDEFINED, 95 | LEX_R_NEW, 96 | 97 | LEX_R_LIST_END /* always the last entry */ 98 | }; 99 | 100 | enum SCRIPTVAR_FLAGS { 101 | SCRIPTVAR_UNDEFINED = 0, 102 | SCRIPTVAR_FUNCTION = 1, 103 | SCRIPTVAR_OBJECT = 2, 104 | SCRIPTVAR_ARRAY = 4, 105 | SCRIPTVAR_DOUBLE = 8, // floating point double 106 | SCRIPTVAR_INTEGER = 16, // integer number 107 | SCRIPTVAR_STRING = 32, // string 108 | SCRIPTVAR_NULL = 64, // it seems null is its own data type 109 | 110 | SCRIPTVAR_NATIVE = 128, // to specify this is a native function 111 | SCRIPTVAR_NUMERICMASK = SCRIPTVAR_NULL | 112 | SCRIPTVAR_DOUBLE | 113 | SCRIPTVAR_INTEGER, 114 | SCRIPTVAR_VARTYPEMASK = SCRIPTVAR_DOUBLE | 115 | SCRIPTVAR_INTEGER | 116 | SCRIPTVAR_STRING | 117 | SCRIPTVAR_FUNCTION | 118 | SCRIPTVAR_OBJECT | 119 | SCRIPTVAR_ARRAY | 120 | SCRIPTVAR_NULL, 121 | 122 | }; 123 | 124 | #define TINYJS_RETURN_VAR "return" 125 | #define TINYJS_PROTOTYPE_CLASS "prototype" 126 | #define TINYJS_TEMP_NAME "" 127 | #define TINYJS_BLANK_DATA "" 128 | 129 | /// convert the given string into a quoted string suitable for javascript 130 | std::string getJSString(const std::string &str); 131 | 132 | class CScriptException { 133 | public: 134 | std::string text; 135 | CScriptException(const std::string &exceptionText); 136 | }; 137 | 138 | class CScriptLex 139 | { 140 | public: 141 | CScriptLex(const std::string &input); 142 | CScriptLex(CScriptLex *owner, int startChar, int endChar); 143 | ~CScriptLex(void); 144 | 145 | char currCh, nextCh; 146 | int tk; ///< The type of the token that we have 147 | int tokenStart; ///< Position in the data at the beginning of the token we have here 148 | int tokenEnd; ///< Position in the data at the last character of the token we have here 149 | int tokenLastEnd; ///< Position in the data at the last character of the last token 150 | std::string tkStr; ///< Data contained in the token we have here 151 | 152 | void match(int expected_tk); ///< Lexical match wotsit 153 | static std::string getTokenStr(int token); ///< Get the string representation of the given token 154 | void reset(); ///< Reset this lex so we can start again 155 | 156 | std::string getSubString(int pos); ///< Return a sub-string from the given position up until right now 157 | CScriptLex *getSubLex(int lastPosition); ///< Return a sub-lexer from the given position up until right now 158 | 159 | std::string getPosition(int pos=-1); ///< Return a string representing the position in lines and columns of the character pos given 160 | 161 | protected: 162 | /* When we go into a loop, we use getSubLex to get a lexer for just the sub-part of the 163 | relevant string. This doesn't re-allocate and copy the string, but instead copies 164 | the data pointer and sets dataOwned to false, and dataStart/dataEnd to the relevant things. */ 165 | char *data; ///< Data string to get tokens from 166 | int dataStart, dataEnd; ///< Start and end position in data string 167 | bool dataOwned; ///< Do we own this data string? 168 | 169 | int dataPos; ///< Position in data (we CAN go past the end of the string here) 170 | 171 | void getNextCh(); 172 | void getNextToken(); ///< Get the text token from our text string 173 | }; 174 | 175 | class CScriptVar; 176 | 177 | typedef void (*JSCallback)(CScriptVar *var, void *userdata); 178 | 179 | class CScriptVarLink 180 | { 181 | public: 182 | std::string name; 183 | CScriptVarLink *nextSibling; 184 | CScriptVarLink *prevSibling; 185 | CScriptVar *var; 186 | bool owned; 187 | 188 | CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME); 189 | CScriptVarLink(const CScriptVarLink &link); ///< Copy constructor 190 | ~CScriptVarLink(); 191 | void replaceWith(CScriptVar *newVar); ///< Replace the Variable pointed to 192 | void replaceWith(CScriptVarLink *newVar); ///< Replace the Variable pointed to (just dereferences) 193 | int getIntName(); ///< Get the name as an integer (for arrays) 194 | void setIntName(int n); ///< Set the name as an integer (for arrays) 195 | }; 196 | 197 | /// Variable class (containing a doubly-linked list of children) 198 | class CScriptVar 199 | { 200 | public: 201 | CScriptVar(); ///< Create undefined 202 | CScriptVar(const std::string &varData, int varFlags); ///< User defined 203 | CScriptVar(const std::string &str); ///< Create a string 204 | CScriptVar(double varData); 205 | CScriptVar(int val); 206 | ~CScriptVar(void); 207 | 208 | CScriptVar *getReturnVar(); ///< If this is a function, get the result value (for use by native functions) 209 | void setReturnVar(CScriptVar *var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy() 210 | CScriptVar *getParameter(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions) 211 | 212 | CScriptVarLink *findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 213 | 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 214 | CScriptVarLink *findChildOrCreateByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) 215 | CScriptVarLink *addChild(const std::string &childName, CScriptVar *child=NULL); 216 | CScriptVarLink *addChildNoDup(const std::string &childName, CScriptVar *child=NULL); ///< add a child overwriting any with the same name 217 | void removeChild(CScriptVar *child); 218 | void removeLink(CScriptVarLink *link); ///< Remove a specific link (this is faster than finding via a child) 219 | void removeAllChildren(); 220 | CScriptVar *getArrayIndex(int idx); ///< The the value at an array index 221 | void setArrayIndex(int idx, CScriptVar *value); ///< Set the value at an array index 222 | int getArrayLength(); ///< If this is an array, return the number of items in it (else 0) 223 | int getChildren(); ///< Get the number of children 224 | 225 | int getInt(); 226 | bool getBool() { return getInt() != 0; } 227 | double getDouble(); 228 | const std::string &getString(); 229 | std::string getParsableString(); ///< get Data as a parsable javascript string 230 | void setInt(int num); 231 | void setDouble(double val); 232 | void setString(const std::string &str); 233 | void setUndefined(); 234 | void setArray(); 235 | bool equals(CScriptVar *v); 236 | 237 | bool isInt() { return (flags&SCRIPTVAR_INTEGER)!=0; } 238 | bool isDouble() { return (flags&SCRIPTVAR_DOUBLE)!=0; } 239 | bool isString() { return (flags&SCRIPTVAR_STRING)!=0; } 240 | bool isNumeric() { return (flags&SCRIPTVAR_NUMERICMASK)!=0; } 241 | bool isFunction() { return (flags&SCRIPTVAR_FUNCTION)!=0; } 242 | bool isObject() { return (flags&SCRIPTVAR_OBJECT)!=0; } 243 | bool isArray() { return (flags&SCRIPTVAR_ARRAY)!=0; } 244 | bool isNative() { return (flags&SCRIPTVAR_NATIVE)!=0; } 245 | bool isUndefined() { return (flags & SCRIPTVAR_VARTYPEMASK) == SCRIPTVAR_UNDEFINED; } 246 | bool isNull() { return (flags & SCRIPTVAR_NULL)!=0; } 247 | bool isBasic() { return firstChild==0; } ///< Is this *not* an array/object/etc 248 | 249 | CScriptVar *mathsOp(CScriptVar *b, int op); ///< do a maths op with another script variable 250 | void copyValue(CScriptVar *val); ///< copy the value from the value given 251 | CScriptVar *deepCopy(); ///< deep copy this node and return the result 252 | 253 | void trace(std::string indentStr = "", const std::string &name = ""); ///< Dump out the contents of this using trace 254 | std::string getFlagsAsString(); ///< For debugging - just dump a string version of the flags 255 | 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) 256 | void setCallback(JSCallback callback, void *userdata); ///< Set the callback for native functions 257 | 258 | CScriptVarLink *firstChild; 259 | CScriptVarLink *lastChild; 260 | 261 | /// For memory management/garbage collection 262 | CScriptVar *ref(); ///< Add reference to this variable 263 | void unref(); ///< Remove a reference, and delete this variable if required 264 | int getRefs(); ///< Get the number of references to this script variable 265 | protected: 266 | int refs; ///< The number of references held to this - used for garbage collection 267 | 268 | std::string data; ///< The contents of this variable if it is a string 269 | long intData; ///< The contents of this variable if it is an int 270 | double doubleData; ///< The contents of this variable if it is a double 271 | int flags; ///< the flags determine the type of the variable - int/double/string/etc 272 | JSCallback jsCallback; ///< Callback for native functions 273 | void *jsCallbackUserData; ///< user data passed as second argument to native functions 274 | 275 | void init(); ///< initialisation of data members 276 | 277 | /** Copy the basic data and flags from the variable given, with no 278 | * children. Should be used internally only - by copyValue and deepCopy */ 279 | void copySimpleData(CScriptVar *val); 280 | 281 | friend class CTinyJS; 282 | }; 283 | 284 | class CTinyJS { 285 | public: 286 | CTinyJS(); 287 | ~CTinyJS(); 288 | 289 | void execute(const std::string &code); 290 | /** Evaluate the given code and return a link to a javascript object, 291 | * useful for (dangerous) JSON parsing. If nothing to return, will return 292 | * 'undefined' variable type. CScriptVarLink is returned as this will 293 | * automatically unref the result as it goes out of scope. If you want to 294 | * keep it, you must use ref() and unref() */ 295 | CScriptVarLink evaluateComplex(const std::string &code); 296 | /** Evaluate the given code and return a string. If nothing to return, will return 297 | * 'undefined' */ 298 | std::string evaluate(const std::string &code); 299 | 300 | /// add a native function to be called from TinyJS 301 | /** example: 302 | \code 303 | void scRandInt(CScriptVar *c, void *userdata) { ... } 304 | tinyJS->addNative("function randInt(min, max)", scRandInt, 0); 305 | \endcode 306 | 307 | or 308 | 309 | \code 310 | void scSubstring(CScriptVar *c, void *userdata) { ... } 311 | tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); 312 | \endcode 313 | */ 314 | void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata); 315 | 316 | /// Get the given variable specified by a path (var1.var2.etc), or return 0 317 | CScriptVar *getScriptVariable(const std::string &path); 318 | /// Get the value of the given variable, or return 0 319 | const std::string *getVariable(const std::string &path); 320 | /// set the value of the given variable, return trur if it exists and gets set 321 | bool setVariable(const std::string &path, const std::string &varData); 322 | 323 | /// Send all variables to stdout 324 | void trace(); 325 | 326 | CScriptVar *root; /// root of symbol table 327 | private: 328 | CScriptLex *l; /// current lexer 329 | std::vector scopes; /// stack of scopes when parsing 330 | #ifdef TINYJS_CALL_STACK 331 | std::vector call_stack; /// Names of places called so we can show when erroring 332 | #endif 333 | 334 | CScriptVar *stringClass; /// Built in string class 335 | CScriptVar *objectClass; /// Built in object class 336 | CScriptVar *arrayClass; /// Built in array class 337 | 338 | // parsing - in order of precedence 339 | CScriptVarLink *functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent); 340 | CScriptVarLink *factor(bool &execute); 341 | CScriptVarLink *unary(bool &execute); 342 | CScriptVarLink *term(bool &execute); 343 | CScriptVarLink *expression(bool &execute); 344 | CScriptVarLink *shift(bool &execute); 345 | CScriptVarLink *condition(bool &execute); 346 | CScriptVarLink *logic(bool &execute); 347 | CScriptVarLink *ternary(bool &execute); 348 | CScriptVarLink *base(bool &execute); 349 | void block(bool &execute); 350 | void statement(bool &execute); 351 | // parsing utility functions 352 | CScriptVarLink *parseFunctionDefinition(); 353 | void parseFunctionArguments(CScriptVar *funcVar); 354 | 355 | CScriptVarLink *findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes 356 | /// Look up in any parent classes of the given object 357 | CScriptVarLink *findInParentClasses(CScriptVar *object, const std::string &name); 358 | }; 359 | 360 | #endif 361 | -------------------------------------------------------------------------------- /TinyJS_Functions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * - Useful language functions 7 | * 8 | * Authored By Gordon Williams 9 | * 10 | * Copyright (C) 2009 Pur3 Ltd 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 13 | * this software and associated documentation files (the "Software"), to deal in 14 | * the Software without restriction, including without limitation the rights to 15 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 16 | * of the Software, and to permit persons to whom the Software is furnished to do 17 | * so, subject to the following conditions: 18 | 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include "TinyJS_Functions.h" 32 | #include 33 | #include 34 | #include 35 | 36 | using namespace std; 37 | // ----------------------------------------------- Actual Functions 38 | void scTrace(CScriptVar *c, void *userdata) { 39 | CTinyJS *js = (CTinyJS*)userdata; 40 | js->root->trace(); 41 | } 42 | 43 | void scObjectDump(CScriptVar *c, void *) { 44 | c->getParameter("this")->trace("> "); 45 | } 46 | 47 | void scObjectClone(CScriptVar *c, void *) { 48 | CScriptVar *obj = c->getParameter("this"); 49 | c->getReturnVar()->copyValue(obj); 50 | } 51 | 52 | void scMathRand(CScriptVar *c, void *) { 53 | c->getReturnVar()->setDouble((double)rand()/RAND_MAX); 54 | } 55 | 56 | void scMathRandInt(CScriptVar *c, void *) { 57 | int min = c->getParameter("min")->getInt(); 58 | int max = c->getParameter("max")->getInt(); 59 | int val = min + (int)(rand()%(1+max-min)); 60 | c->getReturnVar()->setInt(val); 61 | } 62 | 63 | void scCharToInt(CScriptVar *c, void *) { 64 | string str = c->getParameter("ch")->getString();; 65 | int val = 0; 66 | if (str.length()>0) 67 | val = (int)str.c_str()[0]; 68 | c->getReturnVar()->setInt(val); 69 | } 70 | 71 | void scStringIndexOf(CScriptVar *c, void *) { 72 | string str = c->getParameter("this")->getString(); 73 | string search = c->getParameter("search")->getString(); 74 | size_t p = str.find(search); 75 | int val = (p==string::npos) ? -1 : p; 76 | c->getReturnVar()->setInt(val); 77 | } 78 | 79 | void scStringSubstring(CScriptVar *c, void *) { 80 | string str = c->getParameter("this")->getString(); 81 | int lo = c->getParameter("lo")->getInt(); 82 | int hi = c->getParameter("hi")->getInt(); 83 | 84 | int l = hi-lo; 85 | if (l>0 && lo>=0 && lo+l<=(int)str.length()) 86 | c->getReturnVar()->setString(str.substr(lo, l)); 87 | else 88 | c->getReturnVar()->setString(""); 89 | } 90 | 91 | void scStringCharAt(CScriptVar *c, void *) { 92 | string str = c->getParameter("this")->getString(); 93 | int p = c->getParameter("pos")->getInt(); 94 | if (p>=0 && p<(int)str.length()) 95 | c->getReturnVar()->setString(str.substr(p, 1)); 96 | else 97 | c->getReturnVar()->setString(""); 98 | } 99 | 100 | void scStringCharCodeAt(CScriptVar *c, void *) { 101 | string str = c->getParameter("this")->getString(); 102 | int p = c->getParameter("pos")->getInt(); 103 | if (p>=0 && p<(int)str.length()) 104 | c->getReturnVar()->setInt(str.at(p)); 105 | else 106 | c->getReturnVar()->setInt(0); 107 | } 108 | 109 | void scStringSplit(CScriptVar *c, void *) { 110 | string str = c->getParameter("this")->getString(); 111 | string sep = c->getParameter("separator")->getString(); 112 | CScriptVar *result = c->getReturnVar(); 113 | result->setArray(); 114 | int length = 0; 115 | 116 | size_t pos = str.find(sep); 117 | while (pos != string::npos) { 118 | result->setArrayIndex(length++, new CScriptVar(str.substr(0,pos))); 119 | str = str.substr(pos+1); 120 | pos = str.find(sep); 121 | } 122 | 123 | if (str.size()>0) 124 | result->setArrayIndex(length++, new CScriptVar(str)); 125 | } 126 | 127 | void scStringFromCharCode(CScriptVar *c, void *) { 128 | char str[2]; 129 | str[0] = c->getParameter("char")->getInt(); 130 | str[1] = 0; 131 | c->getReturnVar()->setString(str); 132 | } 133 | 134 | void scIntegerParseInt(CScriptVar *c, void *) { 135 | string str = c->getParameter("str")->getString(); 136 | int val = strtol(str.c_str(),0,0); 137 | c->getReturnVar()->setInt(val); 138 | } 139 | 140 | void scIntegerValueOf(CScriptVar *c, void *) { 141 | string str = c->getParameter("str")->getString(); 142 | 143 | int val = 0; 144 | if (str.length()==1) 145 | val = str[0]; 146 | c->getReturnVar()->setInt(val); 147 | } 148 | 149 | void scJSONStringify(CScriptVar *c, void *) { 150 | std::ostringstream result; 151 | c->getParameter("obj")->getJSON(result); 152 | c->getReturnVar()->setString(result.str()); 153 | } 154 | 155 | void scExec(CScriptVar *c, void *data) { 156 | CTinyJS *tinyJS = (CTinyJS *)data; 157 | std::string str = c->getParameter("jsCode")->getString(); 158 | tinyJS->execute(str); 159 | } 160 | 161 | void scEval(CScriptVar *c, void *data) { 162 | CTinyJS *tinyJS = (CTinyJS *)data; 163 | std::string str = c->getParameter("jsCode")->getString(); 164 | c->setReturnVar(tinyJS->evaluateComplex(str).var); 165 | } 166 | 167 | void scArrayContains(CScriptVar *c, void *data) { 168 | CScriptVar *obj = c->getParameter("obj"); 169 | CScriptVarLink *v = c->getParameter("this")->firstChild; 170 | 171 | bool contains = false; 172 | while (v) { 173 | if (v->var->equals(obj)) { 174 | contains = true; 175 | break; 176 | } 177 | v = v->nextSibling; 178 | } 179 | 180 | c->getReturnVar()->setInt(contains); 181 | } 182 | 183 | void scArrayRemove(CScriptVar *c, void *data) { 184 | CScriptVar *obj = c->getParameter("obj"); 185 | vector removedIndices; 186 | CScriptVarLink *v; 187 | // remove 188 | v = c->getParameter("this")->firstChild; 189 | while (v) { 190 | if (v->var->equals(obj)) { 191 | removedIndices.push_back(v->getIntName()); 192 | } 193 | v = v->nextSibling; 194 | } 195 | // renumber 196 | v = c->getParameter("this")->firstChild; 197 | while (v) { 198 | int n = v->getIntName(); 199 | int newn = n; 200 | for (size_t i=0;i=removedIndices[i]) 202 | newn--; 203 | if (newn!=n) 204 | v->setIntName(newn); 205 | v = v->nextSibling; 206 | } 207 | } 208 | 209 | void scArrayJoin(CScriptVar *c, void *data) { 210 | string sep = c->getParameter("separator")->getString(); 211 | CScriptVar *arr = c->getParameter("this"); 212 | 213 | ostringstream sstr; 214 | int l = arr->getArrayLength(); 215 | for (int i=0;i0) sstr << sep; 217 | sstr << arr->getArrayIndex(i)->getString(); 218 | } 219 | 220 | c->getReturnVar()->setString(sstr.str()); 221 | } 222 | 223 | // ----------------------------------------------- Register Functions 224 | void registerFunctions(CTinyJS *tinyJS) { 225 | tinyJS->addNative("function exec(jsCode)", scExec, tinyJS); // execute the given code 226 | tinyJS->addNative("function eval(jsCode)", scEval, tinyJS); // execute the given string (an expression) and return the result 227 | tinyJS->addNative("function trace()", scTrace, tinyJS); 228 | tinyJS->addNative("function Object.dump()", scObjectDump, 0); 229 | tinyJS->addNative("function Object.clone()", scObjectClone, 0); 230 | tinyJS->addNative("function Math.rand()", scMathRand, 0); 231 | tinyJS->addNative("function Math.randInt(min, max)", scMathRandInt, 0); 232 | tinyJS->addNative("function charToInt(ch)", scCharToInt, 0); // convert a character to an int - get its value 233 | tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not 234 | tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0); 235 | tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); 236 | tinyJS->addNative("function String.charCodeAt(pos)", scStringCharCodeAt, 0); 237 | tinyJS->addNative("function String.fromCharCode(char)", scStringFromCharCode, 0); 238 | tinyJS->addNative("function String.split(separator)", scStringSplit, 0); 239 | tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int 240 | tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character 241 | tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); // convert to JSON. replacer is ignored at the moment 242 | // JSON.parse is left out as you can (unsafely!) use eval instead 243 | tinyJS->addNative("function Array.contains(obj)", scArrayContains, 0); 244 | tinyJS->addNative("function Array.remove(obj)", scArrayRemove, 0); 245 | tinyJS->addNative("function Array.join(separator)", scArrayJoin, 0); 246 | } 247 | 248 | -------------------------------------------------------------------------------- /TinyJS_Functions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * Authored By Gordon Williams 7 | * 8 | * Copyright (C) 2009 Pur3 Ltd 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | * of the Software, and to permit persons to whom the Software is furnished to do 15 | * so, subject to the following conditions: 16 | 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #ifndef TINYJS_FUNCTIONS_H 30 | #define TINYJS_FUNCTIONS_H 31 | 32 | #include "TinyJS.h" 33 | 34 | /// Register useful functions with the TinyJS interpreter 35 | extern void registerFunctions(CTinyJS *tinyJS); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /TinyJS_MathFunctions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * - Math and Trigonometry functions 7 | * 8 | * Authored By O.Z.L.B. 9 | * 10 | * Copyright (C) 2011 O.Z.L.B. 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 13 | * this software and associated documentation files (the "Software"), to deal in 14 | * the Software without restriction, including without limitation the rights to 15 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 16 | * of the Software, and to permit persons to whom the Software is furnished to do 17 | * so, subject to the following conditions: 18 | 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include "TinyJS_MathFunctions.h" 35 | 36 | using namespace std; 37 | 38 | #define k_E exp(1.0) 39 | #define k_PI 3.1415926535897932384626433832795 40 | 41 | #define F_ABS(a) ((a)>=0 ? (a) : (-(a))) 42 | #define F_MIN(a,b) ((a)>(b) ? (b) : (a)) 43 | #define F_MAX(a,b) ((a)>(b) ? (a) : (b)) 44 | #define F_SGN(a) ((a)>0 ? 1 : ((a)<0 ? -1 : 0 )) 45 | #define F_RNG(a,min,max) ((a)<(min) ? min : ((a)>(max) ? max : a )) 46 | #define F_ROUND(a) ((a)>0 ? (int) ((a)+0.5) : (int) ((a)-0.5) ) 47 | 48 | //CScriptVar shortcut macro 49 | #define scIsInt(a) ( c->getParameter(a)->isInt() ) 50 | #define scIsDouble(a) ( c->getParameter(a)->isDouble() ) 51 | #define scGetInt(a) ( c->getParameter(a)->getInt() ) 52 | #define scGetDouble(a) ( c->getParameter(a)->getDouble() ) 53 | #define scReturnInt(a) ( c->getReturnVar()->setInt(a) ) 54 | #define scReturnDouble(a) ( c->getReturnVar()->setDouble(a) ) 55 | 56 | #ifdef _MSC_VER 57 | namespace 58 | { 59 | double asinh( const double &value ) 60 | { 61 | double returned; 62 | 63 | if(value>0) 64 | returned = log(value + sqrt(value * value + 1)); 65 | else 66 | returned = -log(-value + sqrt(value * value + 1)); 67 | 68 | return(returned); 69 | } 70 | 71 | double acosh( const double &value ) 72 | { 73 | double returned; 74 | 75 | if(value>0) 76 | returned = log(value + sqrt(value * value - 1)); 77 | else 78 | returned = -log(-value + sqrt(value * value - 1)); 79 | 80 | return(returned); 81 | } 82 | } 83 | #endif 84 | 85 | //Math.abs(x) - returns absolute of given value 86 | void scMathAbs(CScriptVar *c, void *userdata) { 87 | if ( scIsInt("a") ) { 88 | scReturnInt( F_ABS( scGetInt("a") ) ); 89 | } else if ( scIsDouble("a") ) { 90 | scReturnDouble( F_ABS( scGetDouble("a") ) ); 91 | } 92 | } 93 | 94 | //Math.round(a) - returns nearest round of given value 95 | void scMathRound(CScriptVar *c, void *userdata) { 96 | if ( scIsInt("a") ) { 97 | scReturnInt( F_ROUND( scGetInt("a") ) ); 98 | } else if ( scIsDouble("a") ) { 99 | scReturnDouble( F_ROUND( scGetDouble("a") ) ); 100 | } 101 | } 102 | 103 | //Math.min(a,b) - returns minimum of two given values 104 | void scMathMin(CScriptVar *c, void *userdata) { 105 | if ( (scIsInt("a")) && (scIsInt("b")) ) { 106 | scReturnInt( F_MIN( scGetInt("a"), scGetInt("b") ) ); 107 | } else { 108 | scReturnDouble( F_MIN( scGetDouble("a"), scGetDouble("b") ) ); 109 | } 110 | } 111 | 112 | //Math.max(a,b) - returns maximum of two given values 113 | void scMathMax(CScriptVar *c, void *userdata) { 114 | if ( (scIsInt("a")) && (scIsInt("b")) ) { 115 | scReturnInt( F_MAX( scGetInt("a"), scGetInt("b") ) ); 116 | } else { 117 | scReturnDouble( F_MAX( scGetDouble("a"), scGetDouble("b") ) ); 118 | } 119 | } 120 | 121 | //Math.range(x,a,b) - returns value limited between two given values 122 | void scMathRange(CScriptVar *c, void *userdata) { 123 | if ( (scIsInt("x")) ) { 124 | scReturnInt( F_RNG( scGetInt("x"), scGetInt("a"), scGetInt("b") ) ); 125 | } else { 126 | scReturnDouble( F_RNG( scGetDouble("x"), scGetDouble("a"), scGetDouble("b") ) ); 127 | } 128 | } 129 | 130 | //Math.sign(a) - returns sign of given value (-1==negative,0=zero,1=positive) 131 | void scMathSign(CScriptVar *c, void *userdata) { 132 | if ( scIsInt("a") ) { 133 | scReturnInt( F_SGN( scGetInt("a") ) ); 134 | } else if ( scIsDouble("a") ) { 135 | scReturnDouble( F_SGN( scGetDouble("a") ) ); 136 | } 137 | } 138 | 139 | //Math.PI() - returns PI value 140 | void scMathPI(CScriptVar *c, void *userdata) { 141 | scReturnDouble(k_PI); 142 | } 143 | 144 | //Math.toDegrees(a) - returns degree value of a given angle in radians 145 | void scMathToDegrees(CScriptVar *c, void *userdata) { 146 | scReturnDouble( (180.0/k_PI)*( scGetDouble("a") ) ); 147 | } 148 | 149 | //Math.toRadians(a) - returns radians value of a given angle in degrees 150 | void scMathToRadians(CScriptVar *c, void *userdata) { 151 | scReturnDouble( (k_PI/180.0)*( scGetDouble("a") ) ); 152 | } 153 | 154 | //Math.sin(a) - returns trig. sine of given angle in radians 155 | void scMathSin(CScriptVar *c, void *userdata) { 156 | scReturnDouble( sin( scGetDouble("a") ) ); 157 | } 158 | 159 | //Math.asin(a) - returns trig. arcsine of given angle in radians 160 | void scMathASin(CScriptVar *c, void *userdata) { 161 | scReturnDouble( asin( scGetDouble("a") ) ); 162 | } 163 | 164 | //Math.cos(a) - returns trig. cosine of given angle in radians 165 | void scMathCos(CScriptVar *c, void *userdata) { 166 | scReturnDouble( cos( scGetDouble("a") ) ); 167 | } 168 | 169 | //Math.acos(a) - returns trig. arccosine of given angle in radians 170 | void scMathACos(CScriptVar *c, void *userdata) { 171 | scReturnDouble( acos( scGetDouble("a") ) ); 172 | } 173 | 174 | //Math.tan(a) - returns trig. tangent of given angle in radians 175 | void scMathTan(CScriptVar *c, void *userdata) { 176 | scReturnDouble( tan( scGetDouble("a") ) ); 177 | } 178 | 179 | //Math.atan(a) - returns trig. arctangent of given angle in radians 180 | void scMathATan(CScriptVar *c, void *userdata) { 181 | scReturnDouble( atan( scGetDouble("a") ) ); 182 | } 183 | 184 | //Math.sinh(a) - returns trig. hyperbolic sine of given angle in radians 185 | void scMathSinh(CScriptVar *c, void *userdata) { 186 | scReturnDouble( sinh( scGetDouble("a") ) ); 187 | } 188 | 189 | //Math.asinh(a) - returns trig. hyperbolic arcsine of given angle in radians 190 | void scMathASinh(CScriptVar *c, void *userdata) { 191 | scReturnDouble( asinh( (long double)scGetDouble("a") ) ); 192 | } 193 | 194 | //Math.cosh(a) - returns trig. hyperbolic cosine of given angle in radians 195 | void scMathCosh(CScriptVar *c, void *userdata) { 196 | scReturnDouble( cosh( scGetDouble("a") ) ); 197 | } 198 | 199 | //Math.acosh(a) - returns trig. hyperbolic arccosine of given angle in radians 200 | void scMathACosh(CScriptVar *c, void *userdata) { 201 | scReturnDouble( acosh( (long double)scGetDouble("a") ) ); 202 | } 203 | 204 | //Math.tanh(a) - returns trig. hyperbolic tangent of given angle in radians 205 | void scMathTanh(CScriptVar *c, void *userdata) { 206 | scReturnDouble( tanh( scGetDouble("a") ) ); 207 | } 208 | 209 | //Math.atan(a) - returns trig. hyperbolic arctangent of given angle in radians 210 | void scMathATanh(CScriptVar *c, void *userdata) { 211 | scReturnDouble( atan( scGetDouble("a") ) ); 212 | } 213 | 214 | //Math.E() - returns E Neplero value 215 | void scMathE(CScriptVar *c, void *userdata) { 216 | scReturnDouble(k_E); 217 | } 218 | 219 | //Math.log(a) - returns natural logaritm (base E) of given value 220 | void scMathLog(CScriptVar *c, void *userdata) { 221 | scReturnDouble( log( scGetDouble("a") ) ); 222 | } 223 | 224 | //Math.log10(a) - returns logaritm(base 10) of given value 225 | void scMathLog10(CScriptVar *c, void *userdata) { 226 | scReturnDouble( log10( scGetDouble("a") ) ); 227 | } 228 | 229 | //Math.exp(a) - returns e raised to the power of a given number 230 | void scMathExp(CScriptVar *c, void *userdata) { 231 | scReturnDouble( exp( scGetDouble("a") ) ); 232 | } 233 | 234 | //Math.pow(a,b) - returns the result of a number raised to a power (a)^(b) 235 | void scMathPow(CScriptVar *c, void *userdata) { 236 | scReturnDouble( pow( scGetDouble("a"), scGetDouble("b") ) ); 237 | } 238 | 239 | //Math.sqr(a) - returns square of given value 240 | void scMathSqr(CScriptVar *c, void *userdata) { 241 | scReturnDouble( ( scGetDouble("a") * scGetDouble("a") ) ); 242 | } 243 | 244 | //Math.sqrt(a) - returns square root of given value 245 | void scMathSqrt(CScriptVar *c, void *userdata) { 246 | scReturnDouble( sqrtf( scGetDouble("a") ) ); 247 | } 248 | 249 | // ----------------------------------------------- Register Functions 250 | void registerMathFunctions(CTinyJS *tinyJS) { 251 | 252 | // --- Math and Trigonometry functions --- 253 | tinyJS->addNative("function Math.abs(a)", scMathAbs, 0); 254 | tinyJS->addNative("function Math.round(a)", scMathRound, 0); 255 | tinyJS->addNative("function Math.min(a,b)", scMathMin, 0); 256 | tinyJS->addNative("function Math.max(a,b)", scMathMax, 0); 257 | tinyJS->addNative("function Math.range(x,a,b)", scMathRange, 0); 258 | tinyJS->addNative("function Math.sign(a)", scMathSign, 0); 259 | 260 | tinyJS->addNative("function Math.PI()", scMathPI, 0); 261 | tinyJS->addNative("function Math.toDegrees(a)", scMathToDegrees, 0); 262 | tinyJS->addNative("function Math.toRadians(a)", scMathToRadians, 0); 263 | tinyJS->addNative("function Math.sin(a)", scMathSin, 0); 264 | tinyJS->addNative("function Math.asin(a)", scMathASin, 0); 265 | tinyJS->addNative("function Math.cos(a)", scMathCos, 0); 266 | tinyJS->addNative("function Math.acos(a)", scMathACos, 0); 267 | tinyJS->addNative("function Math.tan(a)", scMathTan, 0); 268 | tinyJS->addNative("function Math.atan(a)", scMathATan, 0); 269 | tinyJS->addNative("function Math.sinh(a)", scMathSinh, 0); 270 | tinyJS->addNative("function Math.asinh(a)", scMathASinh, 0); 271 | tinyJS->addNative("function Math.cosh(a)", scMathCosh, 0); 272 | tinyJS->addNative("function Math.acosh(a)", scMathACosh, 0); 273 | tinyJS->addNative("function Math.tanh(a)", scMathTanh, 0); 274 | tinyJS->addNative("function Math.atanh(a)", scMathATanh, 0); 275 | 276 | tinyJS->addNative("function Math.E()", scMathE, 0); 277 | tinyJS->addNative("function Math.log(a)", scMathLog, 0); 278 | tinyJS->addNative("function Math.log10(a)", scMathLog10, 0); 279 | tinyJS->addNative("function Math.exp(a)", scMathExp, 0); 280 | tinyJS->addNative("function Math.pow(a,b)", scMathPow, 0); 281 | 282 | tinyJS->addNative("function Math.sqr(a)", scMathSqr, 0); 283 | tinyJS->addNative("function Math.sqrt(a)", scMathSqrt, 0); 284 | 285 | } 286 | -------------------------------------------------------------------------------- /TinyJS_MathFunctions.h: -------------------------------------------------------------------------------- 1 | #ifndef TINYJS_MATHFUNCTIONS_H 2 | #define TINYJS_MATHFUNCTIONS_H 3 | 4 | #include "TinyJS.h" 5 | 6 | /// Register useful math. functions with the TinyJS interpreter 7 | extern void registerMathFunctions(CTinyJS *tinyJS); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /run_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TinyJS 3 | * 4 | * A single-file Javascript-alike engine 5 | * 6 | * Authored By Gordon Williams 7 | * 8 | * Copyright (C) 2009 Pur3 Ltd 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | * of the Software, and to permit persons to whom the Software is furnished to do 15 | * so, subject to the following conditions: 16 | 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | /* 30 | * This is a program to run all the tests in the tests folder... 31 | */ 32 | 33 | #include "TinyJS.h" 34 | #include "TinyJS_Functions.h" 35 | #include "TinyJS_MathFunctions.h" 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #ifdef MTRACE 43 | #include 44 | #endif 45 | 46 | //#define INSANE_MEMORY_DEBUG 47 | 48 | #ifdef INSANE_MEMORY_DEBUG 49 | // needs -rdynamic when compiling/linking 50 | #include 51 | #include 52 | #include 53 | #include 54 | using namespace std; 55 | 56 | void **get_stackframe() { 57 | void **trace = (void**)malloc(sizeof(void*)*17); 58 | int trace_size = 0; 59 | 60 | for (int i=0;i<17;i++) trace[i]=(void*)0; 61 | trace_size = backtrace(trace, 16); 62 | return trace; 63 | } 64 | 65 | void print_stackframe(char *header, void **trace) { 66 | char **messages = (char **)NULL; 67 | int trace_size = 0; 68 | 69 | trace_size = 0; 70 | while (trace[trace_size]) trace_size++; 71 | messages = backtrace_symbols(trace, trace_size); 72 | 73 | printf("%s\n", header); 74 | for (int i=0; i malloced; 87 | 88 | static void *my_malloc_hook(size_t size, const void *caller) { 89 | /* Restore all old hooks */ 90 | __malloc_hook = old_malloc_hook; 91 | __free_hook = old_free_hook; 92 | /* Call recursively */ 93 | void *result = malloc (size); 94 | /* we call malloc here, so protect it too. */ 95 | //printf ("malloc (%u) returns %p\n", (unsigned int) size, result); 96 | malloced[result] = get_stackframe(); 97 | 98 | /* Restore our own hooks */ 99 | __malloc_hook = my_malloc_hook; 100 | __free_hook = my_free_hook; 101 | return result; 102 | } 103 | 104 | static void my_free_hook(void *ptr, const void *caller) { 105 | /* Restore all old hooks */ 106 | __malloc_hook = old_malloc_hook; 107 | __free_hook = old_free_hook; 108 | /* Call recursively */ 109 | free (ptr); 110 | /* we call malloc here, so protect it too. */ 111 | //printf ("freed pointer %p\n", ptr); 112 | if (malloced.find(ptr) == malloced.end()) { 113 | /*fprintf(stderr, "INVALID FREE\n"); 114 | void *trace[16]; 115 | int trace_size = 0; 116 | trace_size = backtrace(trace, 16); 117 | backtrace_symbols_fd(trace, trace_size, STDERR_FILENO);*/ 118 | } else 119 | malloced.erase(ptr); 120 | /* Restore our own hooks */ 121 | __malloc_hook = my_malloc_hook; 122 | __free_hook = my_free_hook; 123 | } 124 | 125 | void memtracing_init() { 126 | old_malloc_hook = __malloc_hook; 127 | old_free_hook = __free_hook; 128 | __malloc_hook = my_malloc_hook; 129 | __free_hook = my_free_hook; 130 | } 131 | 132 | long gethash(void **trace) { 133 | unsigned long hash = 0; 134 | while (*trace) { 135 | hash = (hash<<1) ^ (hash>>63) ^ (unsigned long)*trace; 136 | trace++; 137 | } 138 | return hash; 139 | } 140 | 141 | void memtracing_kill() { 142 | /* Restore all old hooks */ 143 | __malloc_hook = old_malloc_hook; 144 | __free_hook = old_free_hook; 145 | 146 | map hashToReal; 147 | map counts; 148 | map::iterator it = malloced.begin(); 149 | while (it!=malloced.end()) { 150 | long hash = gethash(it->second); 151 | hashToReal[hash] = it->second; 152 | 153 | if (counts.find(hash) == counts.end()) 154 | counts[hash] = 1; 155 | else 156 | counts[hash]++; 157 | 158 | it++; 159 | } 160 | 161 | vector > sorting; 162 | map::iterator countit = counts.begin(); 163 | while (countit!=counts.end()) { 164 | sorting.push_back(pair(countit->second, countit->first)); 165 | countit++; 166 | } 167 | 168 | // sort 169 | bool done = false; 170 | while (!done) { 171 | done = true; 172 | for (int i=0;i t = sorting[i]; 175 | sorting[i] = sorting[i+1]; 176 | sorting[i+1] = t; 177 | done = false; 178 | } 179 | } 180 | } 181 | 182 | 183 | for (int i=0;i the size we read */ 204 | if( !file ) { 205 | printf("Unable to open file! '%s'\n", filename); 206 | return false; 207 | } 208 | char *buffer = new char[size+1]; 209 | long actualRead = fread(buffer,1,size,file); 210 | buffer[actualRead]=0; 211 | buffer[size]=0; 212 | fclose(file); 213 | 214 | CTinyJS s; 215 | registerFunctions(&s); 216 | registerMathFunctions(&s); 217 | s.root->addChild("result", new CScriptVar("0",SCRIPTVAR_INTEGER)); 218 | try { 219 | s.execute(buffer); 220 | } catch (CScriptException *e) { 221 | printf("ERROR: %s\n", e->text.c_str()); 222 | } 223 | bool pass = s.root->getParameter("result")->getBool(); 224 | 225 | if (pass) 226 | printf("PASS\n"); 227 | else { 228 | char fn[64]; 229 | sprintf(fn, "%s.fail.js", filename); 230 | FILE *f = fopen(fn, "wt"); 231 | if (f) { 232 | std::ostringstream symbols; 233 | s.root->getJSON(symbols); 234 | fprintf(f, "%s", symbols.str().c_str()); 235 | fclose(f); 236 | } 237 | 238 | printf("FAIL - symbols written to %s\n", fn); 239 | } 240 | 241 | delete[] buffer; 242 | return pass; 243 | } 244 | 245 | int main(int argc, char **argv) 246 | { 247 | #ifdef MTRACE 248 | mtrace(); 249 | #endif 250 | #ifdef INSANE_MEMORY_DEBUG 251 | memtracing_init(); 252 | #endif 253 | printf("TinyJS test runner\n"); 254 | printf("USAGE:\n"); 255 | printf(" ./run_tests test.js : run just one test\n"); 256 | printf(" ./run_tests : run all tests\n"); 257 | if (argc==2) { 258 | return !run_test(argv[1]); 259 | } 260 | 261 | int test_num = 1; 262 | int count = 0; 263 | int passed = 0; 264 | 265 | while (test_num<1000) { 266 | char fn[32]; 267 | sprintf(fn, "tests/test%03d.js", test_num); 268 | // check if the file exists - if not, assume we're at the end of our tests 269 | FILE *f = fopen(fn,"r"); 270 | if (!f) break; 271 | fclose(f); 272 | 273 | if (run_test(fn)) 274 | passed++; 275 | count++; 276 | test_num++; 277 | } 278 | 279 | printf("Done. %d tests, %d pass, %d fail\n", count, passed, count-passed); 280 | #ifdef INSANE_MEMORY_DEBUG 281 | memtracing_kill(); 282 | #endif 283 | 284 | #ifdef _DEBUG 285 | #ifdef _WIN32 286 | _CrtDumpMemoryLeaks(); 287 | #endif 288 | #endif 289 | #ifdef MTRACE 290 | muntrace(); 291 | #endif 292 | 293 | return 0; 294 | } 295 | -------------------------------------------------------------------------------- /tests/42tests/test001.js: -------------------------------------------------------------------------------- 1 | // switch-case-tests 2 | 3 | //////////////////////////////////////////////////// 4 | // switch-test 1: case with break; 5 | //////////////////////////////////////////////////// 6 | var a1=5; 7 | var b1=6; 8 | var r1=0; 9 | 10 | switch(a1+5){ 11 | case 6: 12 | r1 = 2; 13 | break; 14 | case b1+4: 15 | r1 = 42; 16 | break; 17 | case 7: 18 | r1 = 2; 19 | break; 20 | } 21 | 22 | //////////////////////////////////////////////////// 23 | // switch-test 2: case with out break; 24 | //////////////////////////////////////////////////// 25 | var a2=5; 26 | var b2=6; 27 | var r2=0; 28 | 29 | switch(a2+4){ 30 | case 6: 31 | r2 = 2; 32 | break; 33 | case b2+3: 34 | r2 = 40; 35 | //break; 36 | case 7: 37 | r2 += 2; 38 | break; 39 | } 40 | 41 | //////////////////////////////////////////////////// 42 | // switch-test 3: case with default; 43 | //////////////////////////////////////////////////// 44 | var a3=5; 45 | var b3=6; 46 | var r3=0; 47 | 48 | switch(a3+44){ 49 | case 6: 50 | r3 = 2; 51 | break; 52 | case b3+3: 53 | r3 = 1; 54 | break; 55 | default: 56 | r3 = 42; 57 | break; 58 | } 59 | 60 | //////////////////////////////////////////////////// 61 | // switch-test 4: case default before case; 62 | //////////////////////////////////////////////////// 63 | var a4=5; 64 | var b4=6; 65 | var r4=0; 66 | 67 | switch(a4+44){ 68 | default: 69 | r4 = 42; 70 | break; 71 | case 6: 72 | r4 = 2; 73 | break; 74 | case b4+3: 75 | r4 = 1; 76 | break; 77 | } 78 | 79 | 80 | result = r1 == 42 && r2 == 42 && r3 == 42 && r4 == 42; 81 | -------------------------------------------------------------------------------- /tests/42tests/test002.js: -------------------------------------------------------------------------------- 1 | // function-closure 2 | 3 | var a = 40; // a global var 4 | 5 | function closure() { 6 | var a = 39; // a local var; 7 | return function() { return a; }; 8 | } 9 | 10 | var b = closure(); // the local var a is now hidden 11 | 12 | result = b()+3 == 42 && a+2 == 42; 13 | -------------------------------------------------------------------------------- /tests/42tests/test003.js: -------------------------------------------------------------------------------- 1 | // with-test 2 | 3 | var a; 4 | 5 | with(Math) a=PI; 6 | 7 | var b = { get_member : function() { return this.member;}, member:41 }; 8 | 9 | with(b) { 10 | let a = get_member(); //<--- a is local for this block 11 | var c = a+1; 12 | } 13 | 14 | 15 | result = a == Math.PI && c == 42; 16 | -------------------------------------------------------------------------------- /tests/42tests/test004.js: -------------------------------------------------------------------------------- 1 | // generator-test 2 | 3 | function fibonacci(){ 4 | var fn1 = 1; 5 | var fn2 = 1; 6 | while (1){ 7 | var current = fn2; 8 | fn2 = fn1; 9 | fn1 = fn1 + current; 10 | var reset = yield current; 11 | if (reset){ 12 | fn1 = 1; 13 | fn2 = 1; 14 | } 15 | } 16 | } 17 | 18 | var generator = fibonacci(); 19 | 20 | generator.next(); // 1 21 | generator.next(); // 1 22 | generator.next(); // 2 23 | generator.next(); // 3 24 | generator.next(); // 5 25 | 26 | 27 | result = generator.next() == 8 && generator.send(true) == 1; 28 | -------------------------------------------------------------------------------- /tests/test001.js: -------------------------------------------------------------------------------- 1 | // simply testing we can return the correct value 2 | result = 1; 3 | -------------------------------------------------------------------------------- /tests/test002.js: -------------------------------------------------------------------------------- 1 | // comparison 2 | var a = 42; 3 | result = a==42; 4 | -------------------------------------------------------------------------------- /tests/test003.js: -------------------------------------------------------------------------------- 1 | // simple for loop 2 | var a = 0; 3 | var i; 4 | for (i=1;i<10;i++) a = a + i; 5 | result = a==45; 6 | -------------------------------------------------------------------------------- /tests/test004.js: -------------------------------------------------------------------------------- 1 | // simple if 2 | var a = 42; 3 | if (a < 43) 4 | result = 1; 5 | -------------------------------------------------------------------------------- /tests/test005.js: -------------------------------------------------------------------------------- 1 | // simple for loop containing initialisation, using += 2 | var a = 0; 3 | for (var i=1;i<10;i++) a += i; 4 | result = a==45; 5 | -------------------------------------------------------------------------------- /tests/test006.js: -------------------------------------------------------------------------------- 1 | // simple function 2 | function add(x,y) { return x+y; } 3 | result = add(3,6)==9; 4 | -------------------------------------------------------------------------------- /tests/test007.js: -------------------------------------------------------------------------------- 1 | // simple function scoping test 2 | var a = 7; 3 | function add(x,y) { var a=x+y; return a; } 4 | result = add(3,6)==9 && a==7; 5 | -------------------------------------------------------------------------------- /tests/test008.js: -------------------------------------------------------------------------------- 1 | // functions in variables 2 | var bob = {}; 3 | bob.add = function(x,y) { return x+y; }; 4 | 5 | result = bob.add(3,6)==9; 6 | -------------------------------------------------------------------------------- /tests/test009.js: -------------------------------------------------------------------------------- 1 | // functions in variables using JSON-style initialisation 2 | var bob = { add : function(x,y) { return x+y; } }; 3 | 4 | result = bob.add(3,6)==9; 5 | -------------------------------------------------------------------------------- /tests/test010.js: -------------------------------------------------------------------------------- 1 | // double function calls 2 | function a(x) { return x+2; } 3 | function b(x) { return a(x)+1; } 4 | result = a(3)==5 && b(3)==6; 5 | -------------------------------------------------------------------------------- /tests/test011.js: -------------------------------------------------------------------------------- 1 | // recursion 2 | function a(x) { 3 | if (x>1) 4 | return x*a(x-1); 5 | return 1; 6 | } 7 | result = a(5)==1*2*3*4*5; 8 | -------------------------------------------------------------------------------- /tests/test012.js: -------------------------------------------------------------------------------- 1 | // if .. else 2 | var a = 42; 3 | if (a != 42) 4 | result = 0; 5 | else 6 | result = 1; 7 | -------------------------------------------------------------------------------- /tests/test013.js: -------------------------------------------------------------------------------- 1 | // if .. else with blocks 2 | var a = 42; 3 | if (a != 42) { 4 | result = 0; 5 | } else { 6 | result = 1; 7 | } 8 | -------------------------------------------------------------------------------- /tests/test014.js: -------------------------------------------------------------------------------- 1 | // Variable creation and scope from http://en.wikipedia.org/wiki/JavaScript_syntax 2 | x = 0; // A global variable 3 | var y = 'Hello!'; // Another global variable 4 | z = 0; // yet another global variable 5 | 6 | function f(){ 7 | var z = 'foxes'; // A local variable 8 | twenty = 20; // Global because keyword var is not used 9 | return x; // We can use x here because it is global 10 | } 11 | // The value of z is no longer available 12 | 13 | 14 | // testing 15 | blah = f(); 16 | result = blah==0 && z!='foxes' && twenty==20; 17 | -------------------------------------------------------------------------------- /tests/test015.js: -------------------------------------------------------------------------------- 1 | // Number definition from http://en.wikipedia.org/wiki/JavaScript_syntax 2 | a = 345; // an "integer", although there is only one numeric type in JavaScript 3 | b = 34.5; // a floating-point number 4 | c = 3.45e2; // another floating-point, equivalent to 345 5 | d = 0377; // an octal integer equal to 255 6 | e = 0xFF; // a hexadecimal integer equal to 255, digits represented by the letters A-F may be upper or lowercase 7 | 8 | result = a==345 && b*10==345 && c==345 && d==255 && e==255; 9 | -------------------------------------------------------------------------------- /tests/test016.js: -------------------------------------------------------------------------------- 1 | // Undefined/null from http://en.wikipedia.org/wiki/JavaScript_syntax 2 | var testUndefined; // variable declared but not defined, set to value of undefined 3 | var testObj = {}; 4 | 5 | result = 1; 6 | if ((""+testUndefined) != "undefined") result = 0; // test variable exists but value not defined, displays undefined 7 | if ((""+testObj.myProp) != "undefined") result = 0; // testObj exists, property does not, displays undefined 8 | if (!(undefined == null)) result = 0; // unenforced type during check, displays true 9 | if (undefined === null) result = 0;// enforce type during check, displays false 10 | 11 | 12 | if (null != undefined) result = 0; // unenforced type during check, displays true 13 | if (null === undefined) result = 0; // enforce type during check, displays false 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/test017.js: -------------------------------------------------------------------------------- 1 | // references for arrays 2 | 3 | var a = []; 4 | a[0] = 10; 5 | a[1] = 22; 6 | 7 | b = a; 8 | 9 | b[0] = 5; 10 | 11 | result = a[0]==5 && a[1]==22 && b[1]==22; 12 | -------------------------------------------------------------------------------- /tests/test018.js: -------------------------------------------------------------------------------- 1 | // references with functions 2 | 3 | var a = 42; 4 | var b = []; 5 | b[0] = 43; 6 | 7 | function foo(myarray) { 8 | myarray[0]++; 9 | } 10 | 11 | function bar(myvalue) { 12 | myvalue++; 13 | } 14 | 15 | foo(b); 16 | bar(a); 17 | 18 | result = a==42 && b[0]==44; 19 | -------------------------------------------------------------------------------- /tests/test019.42.js: -------------------------------------------------------------------------------- 1 | // built-in functions 2 | 3 | foo = "foo bar stuff"; 4 | // 42-tiny-js change begin ---> 5 | // in JavaScript this function is called Math.random() 6 | //r = Math.rand(); 7 | r = Math.random(); 8 | //<--- 42-tiny-js change end 9 | 10 | // 42-tiny-js change begin ---> 11 | // in JavaScript parseInt is a methode in the global scope (root-scope) 12 | //parsed = Integer.parseInt("42"); 13 | parsed = parseInt("42"); 14 | //<--- 42-tiny-js change end 15 | 16 | aStr = "ABCD"; 17 | aChar = aStr.charAt(0); 18 | 19 | obj1 = new Object(); 20 | obj1.food = "cake"; 21 | obj1.desert = "pie"; 22 | 23 | obj2 = obj1.clone(); 24 | obj2.food = "kittens"; 25 | 26 | result = foo.length==13 && foo.indexOf("bar")==4 && foo.substring(8,13)=="stuff" && parsed==42 && 27 | // 42-tiny-js change begin ---> 28 | // in 42tiny-js the Integer-Objecte will be removed 29 | // Integer.valueOf can be replaced by String.charCodeAt 30 | // Integer.valueOf(aChar)==65 && obj1.food=="cake" && obj2.desert=="pie"; 31 | aChar.charCodeAt()==65 && obj1.food=="cake" && obj2.desert=="pie"; 32 | //<--- 42-tiny-js change end 33 | -------------------------------------------------------------------------------- /tests/test019.js: -------------------------------------------------------------------------------- 1 | // built-in functions 2 | 3 | foo = "foo bar stuff"; 4 | r = Math.rand(); 5 | 6 | parsed = Integer.parseInt("42"); 7 | 8 | aStr = "ABCD"; 9 | aChar = aStr.charAt(0); 10 | 11 | obj1 = new Object(); 12 | obj1.food = "cake"; 13 | obj1.desert = "pie"; 14 | 15 | obj2 = obj1.clone(); 16 | obj2.food = "kittens"; 17 | 18 | result = foo.length==13 && foo.indexOf("bar")==4 && foo.substring(8,13)=="stuff" && parsed==42 && 19 | Integer.valueOf(aChar)==65 && obj1.food=="cake" && obj2.desert=="pie"; 20 | -------------------------------------------------------------------------------- /tests/test020.js: -------------------------------------------------------------------------------- 1 | // Test reported by sterowang, Variable attribute defines conflict with function. 2 | /* 3 | What steps will reproduce the problem? 4 | 1. function a (){}; 5 | 2. b = {}; 6 | 3. b.a = {}; 7 | 4. a(); 8 | 9 | What is the expected output? What do you see instead? 10 | Function "a" should be called. But the error message "Error Expecting 'a' 11 | to be a function at (line: 1, col: 1)" received. 12 | 13 | What version of the product are you using? On what operating system? 14 | Version 1.6 is used on Cent OS 5.4 15 | 16 | 17 | Please provide any additional information below. 18 | When using dump() to show symbols, found the function "a" is reassigned to 19 | "{}" by "b.a = {};" call. 20 | */ 21 | 22 | function a (){}; 23 | b = {}; 24 | b.a = {}; 25 | a(); 26 | 27 | result = 1; 28 | -------------------------------------------------------------------------------- /tests/test021.42.js: -------------------------------------------------------------------------------- 1 | /* Javascript eval */ 2 | 3 | // 42-tiny-js change begin ---> 4 | // in JavaScript eval is not JSON.parse 5 | // use parentheses or JSON.parse instead 6 | //myfoo = eval("{ foo: 42 }"); 7 | myfoo = eval("("+"{ foo: 42 }"+")"); 8 | //<--- 42-tiny-js change end 9 | 10 | result = eval("4*10+2")==42 && myfoo.foo==42; 11 | -------------------------------------------------------------------------------- /tests/test021.js: -------------------------------------------------------------------------------- 1 | /* Javascript eval */ 2 | 3 | myfoo = eval("{ foo: 42 }"); 4 | 5 | result = eval("4*10+2")==42 && myfoo.foo==42; 6 | -------------------------------------------------------------------------------- /tests/test022.42.js: -------------------------------------------------------------------------------- 1 | /* Javascript eval */ 2 | 3 | mystructure = { a:39, b:3, addStuff : function(c,d) { return c+d; } }; 4 | 5 | mystring = JSON.stringify(mystructure, undefined); 6 | 7 | // 42-tiny-js change begin ---> 8 | // in JavaScript eval is not JSON.parse 9 | // use parentheses or JSON.parse instead 10 | //mynewstructure = eval(mystring); 11 | mynewstructure = eval("("+mystring+")"); 12 | mynewstructure2 = JSON.parse(mystring); 13 | //<--- 42-tiny-js change end 14 | 15 | result = mynewstructure.addStuff(mynewstructure.a, mynewstructure.b) == 42 && mynewstructure2.addStuff(mynewstructure2.a, mynewstructure2.b) == 42; 16 | -------------------------------------------------------------------------------- /tests/test022.js: -------------------------------------------------------------------------------- 1 | /* Javascript eval */ 2 | 3 | mystructure = { a:39, b:3, addStuff : function(c,d) { return c+d; } }; 4 | 5 | mystring = JSON.stringify(mystructure, undefined); 6 | 7 | mynewstructure = eval(mystring); 8 | 9 | result = mynewstructure.addStuff(mynewstructure.a, mynewstructure.b); 10 | -------------------------------------------------------------------------------- /tests/test023.js: -------------------------------------------------------------------------------- 1 | // mikael.kindborg@mobilesorcery.com - Function symbol is evaluated in bracket-less body of false if-statement 2 | var foo; // a var is only created automated by assignment 3 | 4 | if (foo !== undefined) foo(); 5 | 6 | result = 1; 7 | -------------------------------------------------------------------------------- /tests/test024.js: -------------------------------------------------------------------------------- 1 | /* Mandelbrot! */ 2 | 3 | X1 = -2.0; 4 | Y1 = -2.0; 5 | X2 = 2.0; 6 | Y2 = 2.0; 7 | PX = 32; 8 | PY = 32; 9 | 10 | 11 | lines = []; 12 | for (y=0;y>3); 4 | var c = (-1 >>> 16); 5 | result = a==8 && b==2 && c == 0xFFFF; 6 | -------------------------------------------------------------------------------- /tests/test034.js: -------------------------------------------------------------------------------- 1 | // test for ternary 2 | 3 | result = (true?3:4)==3 && (false?5:6)==6; 4 | -------------------------------------------------------------------------------- /tests/test035.js: -------------------------------------------------------------------------------- 1 | function Person(name) { 2 | this.name = name; 3 | this.kill = function() { this.name += " is dead"; }; 4 | } 5 | 6 | var a = new Person("Kenny"); 7 | a.kill(); 8 | result = a.name == "Kenny is dead"; 9 | 10 | -------------------------------------------------------------------------------- /tests/test036.js: -------------------------------------------------------------------------------- 1 | // the 'lf' in the printf caused issues writing doubles on some compilers 2 | var a=5.0/10.0*100.0; 3 | var b=5.0*110.0; 4 | var c=50.0/10.0; 5 | a.dump(); 6 | b.dump(); 7 | c.dump(); 8 | result = a==50 && b==550 && c==5; 9 | 10 | --------------------------------------------------------------------------------