├── .gitignore ├── Makefile ├── README.md ├── Script.cpp ├── TinyJS.cpp ├── TinyJS.h ├── TinyJS_Functions.cpp ├── TinyJS_Functions.h ├── TinyJS_MathFunctions.cpp ├── TinyJS_MathFunctions.h ├── TinyJS_SyntaxTree.cpp ├── TinyJS_SyntaxTree.h ├── run_profiler.cpp ├── run_tests.cpp └── tests ├── 42tests ├── test001.js ├── test002.js ├── test003.js └── test004.js ├── profiling ├── 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 | *.exe 2 | *.o 3 | *.so 4 | .vs/ 5 | Script 6 | run_profiler 7 | run_tests 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=g++ 2 | CFLAGS=-g -Wall -D_DEBUG -std=c++11 -rdynamic 3 | LDFLAGS=-g -rdynamic -Wl,-rpath=$$ORIGIN/ 4 | 5 | LIBS=libtinyjs.so 6 | 7 | SOURCES= \ 8 | TinyJS.cpp \ 9 | TinyJS_Functions.cpp \ 10 | TinyJS_MathFunctions.cpp \ 11 | TinyJS_SyntaxTree.cpp 12 | 13 | OBJECTS=$(SOURCES:.cpp=.o) 14 | 15 | all: run_tests Script run_profiler libtinyjs 16 | 17 | run_tests: run_tests.o $(OBJECTS) libtinyjs 18 | $(CC) $(LDFLAGS) run_tests.o $(OBJECTS) $(LIBS) -o $@ -ldl 19 | 20 | Script: Script.o $(OBJECTS) libtinyjs 21 | $(CC) $(LDFLAGS) Script.o $(OBJECTS) $(LIBS) -o $@ -ldl 22 | 23 | run_profiler: run_profiler.o $(OBJECTS) libtinyjs 24 | $(CC) $(LDFLAGS) run_profiler.o $(OBJECTS) $(LIBS) -o $@ -ldl 25 | 26 | libtinyjs: 27 | $(CC) $(CFLAGS) -shared -o libtinyjs.so -fPIC $(SOURCES) -ldl 28 | 29 | .cpp.o: 30 | $(CC) -c $(CFLAGS) $< -o $@ -ldl 31 | 32 | clean: 33 | rm -f run_tests Script run_profiler run_tests.o run_profiler.o Script.o $(OBJECTS) $(LIBS) 34 | -------------------------------------------------------------------------------- /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 | { 46 | printf("> %s\n", v->getParameter("text")->getString().c_str()); 47 | } 48 | 49 | void js_dump(CScriptVar *v, void *userdata) 50 | { 51 | CTinyJS *js = (CTinyJS*)userdata; 52 | js->root->trace("> "); 53 | } 54 | 55 | 56 | int main(int argc, char **argv) 57 | { 58 | CTinyJS *js = new CTinyJS(); 59 | /* add the functions from TinyJS_Functions.cpp */ 60 | registerFunctions(js); 61 | /* Add a native function */ 62 | js->addNative("function print(text)", &js_print, 0); 63 | js->addNative("function dump()", &js_dump, js); 64 | /* Execute out bit of code - we could call 'evaluate' here if 65 | we wanted something returned */ 66 | try 67 | { 68 | js->execute("var lets_quit = 0; function quit() { lets_quit = 1; }"); 69 | js->execute("print(\"Interactive mode... Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!\");"); 70 | } 71 | catch(CScriptException *e) 72 | { 73 | printf("ERROR: %s\n", e->text.c_str()); 74 | } 75 | 76 | while(js->evaluate("lets_quit") == "0") 77 | { 78 | char buffer[2048]; 79 | fgets(buffer, sizeof(buffer), stdin); 80 | try 81 | { 82 | js->execute(buffer); 83 | } 84 | catch(CScriptException *e) 85 | { 86 | printf("ERROR: %s\n", e->text.c_str()); 87 | } 88 | } 89 | delete js; 90 | #ifdef _WIN32 91 | #ifdef _DEBUG 92 | _CrtDumpMemoryLeaks(); 93 | #endif 94 | #endif 95 | return 0; 96 | } 97 | -------------------------------------------------------------------------------- /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 | #include 45 | 46 | #ifdef _MSC_VER 47 | #include 48 | #define LIBHANDLE HMODULE 49 | #else 50 | #define LIBHANDLE void* 51 | #endif 52 | 53 | #ifndef TRACE 54 | #define TRACE printf 55 | #endif // TRACE 56 | 57 | enum LEX_TYPES 58 | { 59 | LEX_EOF = 0, 60 | LEX_ID = 256, 61 | LEX_INT, 62 | LEX_FLOAT, 63 | LEX_STR, 64 | 65 | LEX_EQUAL, 66 | LEX_TYPEEQUAL, 67 | LEX_NEQUAL, 68 | LEX_NTYPEEQUAL, 69 | LEX_LEQUAL, 70 | LEX_LSHIFT, 71 | LEX_LSHIFTEQUAL, 72 | LEX_GEQUAL, 73 | LEX_RSHIFT, 74 | LEX_RSHIFTUNSIGNED, 75 | LEX_RSHIFTEQUAL, 76 | LEX_PLUSEQUAL, 77 | LEX_MINUSEQUAL, 78 | LEX_PLUSPLUS, 79 | LEX_MINUSMINUS, 80 | LEX_ANDEQUAL, 81 | LEX_ANDAND, 82 | LEX_OREQUAL, 83 | LEX_OROR, 84 | LEX_XOREQUAL, 85 | // reserved words 86 | #define LEX_R_LIST_START LEX_R_IF 87 | LEX_R_IF, 88 | LEX_R_ELSE, 89 | LEX_R_DO, 90 | LEX_R_WHILE, 91 | LEX_R_FOR, 92 | LEX_R_BREAK, 93 | LEX_R_CONTINUE, 94 | LEX_R_FUNCTION, 95 | LEX_R_RETURN, 96 | LEX_R_VAR, 97 | LEX_R_TRUE, 98 | LEX_R_FALSE, 99 | LEX_R_NULL, 100 | LEX_R_UNDEFINED, 101 | LEX_R_NEW, 102 | LEX_R_RESERVED, /* Used for reserved JIT-compile function names; these cannot appear in original source */ 103 | 104 | LEX_R_LIST_END /* always the last entry */ 105 | }; 106 | 107 | enum SCRIPTVAR_FLAGS 108 | { 109 | SCRIPTVAR_UNDEFINED = 0, 110 | SCRIPTVAR_FUNCTION = 1, 111 | SCRIPTVAR_OBJECT = 2, 112 | SCRIPTVAR_ARRAY = 4, 113 | SCRIPTVAR_DOUBLE = 8, // floating point double 114 | SCRIPTVAR_INTEGER = 16, // integer number 115 | SCRIPTVAR_STRING = 32, // string 116 | SCRIPTVAR_NULL = 64, // it seems null is its own data type 117 | 118 | SCRIPTVAR_NATIVE = 128, // to specify this is a native function 119 | SCRIPTVAR_NUMERICMASK = SCRIPTVAR_NULL | 120 | SCRIPTVAR_DOUBLE | 121 | SCRIPTVAR_INTEGER, 122 | SCRIPTVAR_VARTYPEMASK = SCRIPTVAR_DOUBLE | 123 | SCRIPTVAR_INTEGER | 124 | SCRIPTVAR_STRING | 125 | SCRIPTVAR_FUNCTION | 126 | SCRIPTVAR_OBJECT | 127 | SCRIPTVAR_ARRAY | 128 | SCRIPTVAR_NULL, 129 | 130 | }; 131 | 132 | #define TINYJS_RETURN_VAR "return" 133 | #define TINYJS_PROTOTYPE_CLASS "prototype" 134 | #define TINYJS_TEMP_NAME "" 135 | #define TINYJS_BLANK_DATA "" 136 | #define TINYJS_NEW_FUNCTION_NAME "__new_" 137 | #define TINYJS_ARRAY_FUNCTION_NAME "__array_" 138 | #define TINYJS_OBJECT_FUNCTION_NAME "__object_" 139 | 140 | /// convert the given string into a quoted string suitable for javascript 141 | std::string getJSString(const std::string &str); 142 | 143 | class CScriptException 144 | { 145 | public: 146 | std::string text; 147 | CScriptException(const std::string &exceptionText); 148 | }; 149 | 150 | class CScriptLex 151 | { 152 | public: 153 | CScriptLex(const std::string &input); 154 | CScriptLex(CScriptLex *owner, int startChar, int endChar); 155 | ~CScriptLex(void); 156 | 157 | char currCh, nextCh; 158 | int tk; ///< The type of the token that we have 159 | int tokenStart; ///< Position in the data at the beginning of the token we have here 160 | int tokenEnd; ///< Position in the data at the last character of the token we have here 161 | int tokenLastEnd; ///< Position in the data at the last character of the last token 162 | std::string tkStr; ///< Data contained in the token we have here 163 | 164 | void match(int expected_tk); ///< Lexical match wotsit 165 | static std::string getTokenStr(int token, bool raw_tokens = false); ///< Get the string representation of the given token 166 | void reset(); ///< Reset this lex so we can start again 167 | 168 | std::string getSubString(int pos); ///< Return a sub-string from the given position up until right now 169 | CScriptLex *getSubLex(int lastPosition); ///< Return a sub-lexer from the given position up until right now 170 | 171 | std::string getPosition(int pos = -1); ///< Return a string representing the position in lines and columns of the character pos given 172 | 173 | protected: 174 | /* When we go into a loop, we use getSubLex to get a lexer for just the sub-part of the 175 | relevant string. This doesn't re-allocate and copy the string, but instead copies 176 | the data pointer and sets dataOwned to false, and dataStart/dataEnd to the relevant things. */ 177 | char *data; ///< Data string to get tokens from 178 | int dataStart, dataEnd; ///< Start and end position in data string 179 | bool dataOwned; ///< Do we own this data string? 180 | 181 | int dataPos; ///< Position in data (we CAN go past the end of the string here) 182 | 183 | void getNextCh(); 184 | void getNextToken(); ///< Get the text token from our text string 185 | }; 186 | 187 | class CScriptVar; 188 | 189 | typedef void(*JSCallback)(CScriptVar *var, void *userdata); 190 | typedef CScriptVar* (*NativeImpl)(bool& execute, CScriptLex* lexer); 191 | 192 | class CScriptVarLink 193 | { 194 | public: 195 | std::string name; 196 | CScriptVarLink *nextSibling; 197 | CScriptVarLink *prevSibling; 198 | CScriptVar *var; 199 | bool owned; 200 | 201 | CScriptVarLink(); // for use as an immediate (only used in jit code) 202 | CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME); 203 | CScriptVarLink(const CScriptVarLink &link); ///< Copy constructor 204 | ~CScriptVarLink(); 205 | 206 | // both versions of "replaceWith" return "this". This seems like a sort of 207 | // intuitive thing to do, and it helps for emitting interesting JIT code. 208 | CScriptVarLink* replaceWith(CScriptVar *newVar); ///< Replace the Variable pointed to 209 | CScriptVarLink* replaceWith(CScriptVarLink *newVar); ///< Replace the Variable pointed to (just dereferences) 210 | int getIntName(); ///< Get the name as an integer (for arrays) 211 | void setIntName(int n); ///< Set the name as an integer (for arrays) 212 | 213 | private: 214 | void unref(CScriptVar* oldVar); 215 | }; 216 | 217 | /// Variable class (containing a doubly-linked list of children) 218 | class CScriptVar 219 | { 220 | public: 221 | CScriptVar(); ///< Create undefined 222 | CScriptVar(const std::string &varData, int varFlags); ///< User defined 223 | CScriptVar(const std::string &str); ///< Create a string 224 | CScriptVar(double varData); 225 | CScriptVar(int val); 226 | ~CScriptVar(void); 227 | 228 | CScriptVar *getReturnVar(); ///< If this is a function, get the result value (for use by native functions) 229 | void setReturnVar(CScriptVar *var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy() 230 | CScriptVar *getParameter(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions) 231 | void addExecution(); ///< If this is a function, add one to the number of times its been executed 232 | int getExecutions(); ///< If this is a function, get the number of times it's been executed 233 | 234 | CScriptVarLink *findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 235 | 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 236 | CScriptVarLink *findChildOrCreateByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) 237 | CScriptVarLink *addChild(const std::string &childName, CScriptVar *child = NULL); 238 | CScriptVarLink *addChildNoDup(const std::string &childName, CScriptVar *child = NULL); ///< add a child overwriting any with the same name 239 | void removeChild(const std::string &childName, CScriptVar *child, bool throwIfMissing = false); 240 | void removeLink(CScriptVarLink *link); ///< Remove a specific link (this is faster than finding via a child) 241 | void removeAllChildren(); 242 | CScriptVar *getArrayIndex(int idx); ///< The the value at an array index 243 | void setArrayIndex(int idx, CScriptVar *value); ///< Set the value at an array index 244 | int getArrayLength(); ///< If this is an array, return the number of items in it (else 0) 245 | int getChildren(); ///< Get the number of children 246 | 247 | int getInt(); 248 | bool getBool() { return getInt() != 0; } 249 | double getDouble(); 250 | const std::string &getString(); 251 | std::string getParsableString(); ///< get Data as a parsable javascript string 252 | void setInt(int num); 253 | void setDouble(double val); 254 | void setString(const std::string &str); 255 | void setUndefined(); 256 | void setArray(); 257 | bool equals(CScriptVar *v); 258 | 259 | bool isInt() { return (flags&SCRIPTVAR_INTEGER) != 0; } 260 | bool isDouble() { return (flags&SCRIPTVAR_DOUBLE) != 0; } 261 | bool isString() { return (flags&SCRIPTVAR_STRING) != 0; } 262 | bool isNumeric() { return (flags&SCRIPTVAR_NUMERICMASK) != 0; } 263 | bool isFunction() { return (flags&SCRIPTVAR_FUNCTION) != 0; } 264 | bool isObject() { return (flags&SCRIPTVAR_OBJECT) != 0; } 265 | bool isArray() { return (flags&SCRIPTVAR_ARRAY) != 0; } 266 | bool isNative() { return (flags&SCRIPTVAR_NATIVE) != 0; } 267 | bool isUndefined() { return (flags & SCRIPTVAR_VARTYPEMASK) == SCRIPTVAR_UNDEFINED; } 268 | bool isNull() { return (flags & SCRIPTVAR_NULL) != 0; } 269 | bool isBasic() { return children.empty(); } ///< Is this *not* an array/object/etc 270 | 271 | CScriptVar *mathsOp(CScriptVar *b, int op); ///< do a maths op with another script variable 272 | void copyValue(CScriptVar *val); ///< copy the value from the value given 273 | CScriptVar *deepCopy(); ///< deep copy this node and return the result 274 | 275 | void trace(std::string indentStr = "", const std::string &name = ""); ///< Dump out the contents of this using trace 276 | std::string getFlagsAsString(); ///< For debugging - just dump a string version of the flags 277 | 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) 278 | void setCallback(JSCallback callback, void *userdata); ///< Set the callback for native functions 279 | 280 | std::unordered_map children; 281 | std::vector orderedChildren(); ///< Returns a vector of the children of this variable ordered by most recently added last 282 | 283 | /// For memory management/garbage collection 284 | CScriptVar *ref(); ///< Add reference to this variable 285 | void unref(); ///< Remove a reference, and delete this variable if required 286 | int getRefs(); ///< Get the number of references to this script variable 287 | protected: 288 | int refs; ///< The number of references held to this - used for garbage collection 289 | int executions; ///< The number of times this function has been executed (if this is a function) 290 | LIBHANDLE nativeHandle; ///< The handle to a jit-code compiled library 291 | 292 | std::string data; ///< The contents of this variable if it is a string 293 | long intData; ///< The contents of this variable if it is an int 294 | double doubleData; ///< The contents of this variable if it is a double 295 | int flags; ///< the flags determine the type of the variable - int/double/string/etc 296 | JSCallback jsCallback; ///< Callback for native functions 297 | void *jsCallbackUserData; ///< user data passed as second argument to native functions 298 | 299 | /** Copy the basic data and flags from the variable given, with no 300 | * children. Should be used internally only - by copyValue and deepCopy */ 301 | void copySimpleData(CScriptVar *val); 302 | 303 | CScriptVarLink* firstChild; ///< used for ordered iteration through children (function defs, etc) 304 | CScriptVarLink* lastChild; ///< only used to maintain script link linked list 305 | 306 | friend class CTinyJS; 307 | }; 308 | 309 | class CTinyJS 310 | { 311 | public: 312 | CTinyJS(int executions_before_compile = 30); 313 | ~CTinyJS(); 314 | 315 | void execute(const std::string &code); 316 | /** Evaluate the given code and return a link to a javascript object, 317 | * useful for (dangerous) JSON parsing. If nothing to return, will return 318 | * 'undefined' variable type. CScriptVarLink is returned as this will 319 | * automatically unref the result as it goes out of scope. If you want to 320 | * keep it, you must use ref() and unref() */ 321 | CScriptVarLink evaluateComplex(const std::string &code); 322 | /** Evaluate the given code and return a string. If nothing to return, will return 323 | * 'undefined' */ 324 | std::string evaluate(const std::string &code); 325 | 326 | /// add a native function to be called from TinyJS 327 | /** example: 328 | \code 329 | void scRandInt(CScriptVar *c, void *userdata) { ... } 330 | tinyJS->addNative("function randInt(min, max)", scRandInt, 0); 331 | \endcode 332 | 333 | or 334 | 335 | \code 336 | void scSubstring(CScriptVar *c, void *userdata) { ... } 337 | tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); 338 | \endcode 339 | */ 340 | void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata); 341 | 342 | /// Get the given variable specified by a path (var1.var2.etc), or return 0 343 | CScriptVar *getScriptVariable(const std::string &path); 344 | /// Get the value of the given variable, or return 0 345 | const std::string *getVariable(const std::string &path); 346 | /// set the value of the given variable, return true if it exists and gets set 347 | bool setVariable(const std::string &path, const std::string &varData); 348 | 349 | /// Send all variables to stdout 350 | void trace(); 351 | 352 | CScriptVar *root; /// root of symbol table 353 | private: 354 | int executions_to_compile; 355 | CScriptLex *l; /// current lexer 356 | std::vector scopes; /// stack of scopes when parsing 357 | #ifdef TINYJS_CALL_STACK 358 | std::vector call_stack; /// Names of places called so we can show when erroring 359 | #endif 360 | 361 | CScriptVar *stringClass; /// Built in string class 362 | CScriptVar *objectClass; /// Built in object class 363 | CScriptVar *arrayClass; /// Built in array class 364 | 365 | // parsing - in order of precedence 366 | CScriptVarLink *functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent); 367 | CScriptVarLink *factor(bool &execute); 368 | CScriptVarLink *unary(bool &execute); 369 | CScriptVarLink *term(bool &execute); 370 | CScriptVarLink *expression(bool &execute); 371 | CScriptVarLink *shift(bool &execute); 372 | CScriptVarLink *condition(bool &execute); 373 | CScriptVarLink *logic(bool &execute); 374 | CScriptVarLink *ternary(bool &execute); 375 | CScriptVarLink *base(bool &execute); 376 | void block(bool &execute); 377 | void statement(bool &execute); 378 | // parsing utility functions 379 | CScriptVarLink *parseFunctionDefinition(); 380 | void parseFunctionArguments(CScriptVar *funcVar); 381 | 382 | CScriptVarLink *findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes 383 | /// Look up in any parent classes of the given object 384 | CScriptVarLink *findInParentClasses(CScriptVar *object, const std::string &name); 385 | 386 | /* Used for new keyword and object/array definitions in JIT compiling */ 387 | CScriptVar* createObject(bool& execute, CScriptLex* lexer); 388 | CScriptVar* createArray(bool& execute, CScriptLex* lexer); 389 | CScriptVar* keywordNew(bool& execute, CScriptLex* lexer); 390 | /* These are the functions actually registered with addNative to implement 391 | new, [], and {} in the JIT-compiled code. The second parameter is the instance of 392 | CTinyJS, since these functions must be declared static to match the signature 393 | of JSCallback. */ 394 | static void createObjectNative(CScriptVar* root, void* impl); 395 | static void createArrayNative(CScriptVar* root, void* impl); 396 | static void keywordNewNative(CScriptVar* root, void* impl); 397 | 398 | /* Compiles a function into native code. */ 399 | void compile(CScriptVarLink* function); 400 | }; 401 | 402 | #endif 403 | -------------------------------------------------------------------------------- /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 | #include 36 | 37 | using namespace std; 38 | // ----------------------------------------------- Actual Functions 39 | void scTrace(CScriptVar *c, void *userdata) 40 | { 41 | CTinyJS *js = (CTinyJS*)userdata; 42 | js->root->trace(); 43 | } 44 | 45 | void scObjectDump(CScriptVar *c, void *) 46 | { 47 | c->getParameter("this")->trace("> "); 48 | } 49 | 50 | void scObjectClone(CScriptVar *c, void *) 51 | { 52 | CScriptVar *obj = c->getParameter("this"); 53 | c->getReturnVar()->copyValue(obj); 54 | } 55 | 56 | void scMathRand(CScriptVar *c, void *) 57 | { 58 | c->getReturnVar()->setDouble((double)rand() / RAND_MAX); 59 | } 60 | 61 | void scMathRandInt(CScriptVar *c, void *) 62 | { 63 | int min = c->getParameter("min")->getInt(); 64 | int max = c->getParameter("max")->getInt(); 65 | int val = min + (int)(rand() % (1 + max - min)); 66 | c->getReturnVar()->setInt(val); 67 | } 68 | 69 | void scCharToInt(CScriptVar *c, void *) 70 | { 71 | string str = c->getParameter("ch")->getString();; 72 | int val = 0; 73 | if(str.length() > 0) 74 | val = (int)str.c_str()[0]; 75 | c->getReturnVar()->setInt(val); 76 | } 77 | 78 | void scStringIndexOf(CScriptVar *c, void *) 79 | { 80 | string str = c->getParameter("this")->getString(); 81 | string search = c->getParameter("search")->getString(); 82 | size_t p = str.find(search); 83 | int val = (p == string::npos) ? -1 : p; 84 | c->getReturnVar()->setInt(val); 85 | } 86 | 87 | void scStringSubstring(CScriptVar *c, void *) 88 | { 89 | string str = c->getParameter("this")->getString(); 90 | int lo = c->getParameter("lo")->getInt(); 91 | int hi = c->getParameter("hi")->getInt(); 92 | 93 | int l = hi - lo; 94 | if(l > 0 && lo >= 0 && lo + l <= (int)str.length()) 95 | c->getReturnVar()->setString(str.substr(lo, l)); 96 | else 97 | c->getReturnVar()->setString(""); 98 | } 99 | 100 | void scStringCharAt(CScriptVar *c, void *) 101 | { 102 | string str = c->getParameter("this")->getString(); 103 | int p = c->getParameter("pos")->getInt(); 104 | if(p >= 0 && p < (int)str.length()) 105 | c->getReturnVar()->setString(str.substr(p, 1)); 106 | else 107 | c->getReturnVar()->setString(""); 108 | } 109 | 110 | void scStringCharCodeAt(CScriptVar *c, void *) 111 | { 112 | string str = c->getParameter("this")->getString(); 113 | int p = c->getParameter("pos")->getInt(); 114 | if(p >= 0 && p < (int)str.length()) 115 | c->getReturnVar()->setInt(str.at(p)); 116 | else 117 | c->getReturnVar()->setInt(0); 118 | } 119 | 120 | void scStringSplit(CScriptVar *c, void *) 121 | { 122 | string str = c->getParameter("this")->getString(); 123 | string sep = c->getParameter("separator")->getString(); 124 | CScriptVar *result = c->getReturnVar(); 125 | result->setArray(); 126 | int length = 0; 127 | 128 | size_t pos = str.find(sep); 129 | while(pos != string::npos) 130 | { 131 | result->setArrayIndex(length++, new CScriptVar(str.substr(0, pos))); 132 | str = str.substr(pos + 1); 133 | pos = str.find(sep); 134 | } 135 | 136 | if(str.size() > 0) 137 | result->setArrayIndex(length++, new CScriptVar(str)); 138 | } 139 | 140 | void scStringFromCharCode(CScriptVar *c, void *) 141 | { 142 | char str[2]; 143 | str[0] = c->getParameter("char")->getInt(); 144 | str[1] = 0; 145 | c->getReturnVar()->setString(str); 146 | } 147 | 148 | void scIntegerParseInt(CScriptVar *c, void *) 149 | { 150 | string str = c->getParameter("str")->getString(); 151 | int val = strtol(str.c_str(), 0, 0); 152 | c->getReturnVar()->setInt(val); 153 | } 154 | 155 | void scIntegerValueOf(CScriptVar *c, void *) 156 | { 157 | string str = c->getParameter("str")->getString(); 158 | 159 | int val = 0; 160 | if(str.length() == 1) 161 | val = str[0]; 162 | c->getReturnVar()->setInt(val); 163 | } 164 | 165 | void scJSONStringify(CScriptVar *c, void *) 166 | { 167 | std::ostringstream result; 168 | c->getParameter("obj")->getJSON(result); 169 | c->getReturnVar()->setString(result.str()); 170 | } 171 | 172 | void scExec(CScriptVar *c, void *data) 173 | { 174 | CTinyJS *tinyJS = (CTinyJS *)data; 175 | std::string str = c->getParameter("jsCode")->getString(); 176 | tinyJS->execute(str); 177 | } 178 | 179 | void scEval(CScriptVar *c, void *data) 180 | { 181 | CTinyJS *tinyJS = (CTinyJS *)data; 182 | std::string str = c->getParameter("jsCode")->getString(); 183 | c->setReturnVar(tinyJS->evaluateComplex(str).var); 184 | } 185 | 186 | void scArrayContains(CScriptVar *c, void *data) 187 | { 188 | CScriptVar *obj = c->getParameter("obj"); 189 | 190 | bool contains = false; 191 | for(auto& it : c->getParameter("this")->children) 192 | { 193 | CScriptVarLink* v = it.second; 194 | if(v->var->equals(obj)) 195 | { 196 | contains = true; 197 | break; 198 | } 199 | } 200 | 201 | c->getReturnVar()->setInt(contains); 202 | } 203 | 204 | void scArrayRemove(CScriptVar *c, void *data) 205 | { 206 | // The implementation for this function is straight garbage. 207 | // There has to be a better way to keep the linked list and map 208 | // synced up, as well as keep link names and keys consistent, but 209 | // I was unable to come up with one. This may be in part because 210 | // the linked-list based algorithm only worked because of implementation 211 | // details and left extra children in the array, and this algorithm 212 | // is more or less based on that one. 213 | CScriptVar *obj = c->getParameter("obj"); 214 | vector removedIndices; 215 | vector children; 216 | // remove 217 | CScriptVar* var = c->getParameter("this"); 218 | children = var->orderedChildren(); 219 | for(auto& child : children) 220 | { 221 | if(child->var->equals(obj)) 222 | { 223 | removedIndices.push_back(child->getIntName()); 224 | var->removeLink(child); // this also deletes the link 225 | } 226 | } 227 | if(removedIndices.empty()) 228 | return; 229 | 230 | // refetch the list of children since some have been deleted 231 | children = var->orderedChildren(); 232 | // renumber 233 | for(auto& child : children) 234 | { 235 | int n = child->getIntName(); 236 | int newn = n; 237 | for(size_t i = 0; i < removedIndices.size(); i++) 238 | if(n >= removedIndices[i]) 239 | newn--; 240 | if(newn != n) 241 | { 242 | child->setIntName(newn); 243 | // fix up keys in the map 244 | CScriptVarLink* link = var->addChildNoDup(child->name, child->var); 245 | link->name = child->name; 246 | } 247 | } 248 | // we need to eliminate children that are stale 249 | // (ie children whose name does not match their key) 250 | // however we cannot do that while iterating over the map 251 | children.clear(); // reuse children 252 | for(auto& pair : var->children) 253 | if(pair.first != pair.second->name) 254 | { 255 | // it is impossible to delete an element using the 256 | // given methods if the link's name does not match 257 | // the key's name. 258 | pair.second->name = pair.first; 259 | children.push_back(pair.second); 260 | } 261 | for(auto& child : children) 262 | var->removeLink(child); 263 | } 264 | 265 | void scArrayJoin(CScriptVar *c, void *data) 266 | { 267 | string sep = c->getParameter("separator")->getString(); 268 | CScriptVar *arr = c->getParameter("this"); 269 | 270 | ostringstream sstr; 271 | int l = arr->getArrayLength(); 272 | for(int i = 0; i < l; i++) 273 | { 274 | if(i > 0) sstr << sep; 275 | sstr << arr->getArrayIndex(i)->getString(); 276 | } 277 | 278 | c->getReturnVar()->setString(sstr.str()); 279 | } 280 | 281 | // ----------------------------------------------- Register Functions 282 | void registerFunctions(CTinyJS *tinyJS) 283 | { 284 | tinyJS->addNative("function exec(jsCode)", scExec, tinyJS); // execute the given code 285 | tinyJS->addNative("function eval(jsCode)", scEval, tinyJS); // execute the given string (an expression) and return the result 286 | tinyJS->addNative("function trace()", scTrace, tinyJS); 287 | tinyJS->addNative("function Object.dump()", scObjectDump, 0); 288 | tinyJS->addNative("function Object.clone()", scObjectClone, 0); 289 | tinyJS->addNative("function Math.rand()", scMathRand, 0); 290 | tinyJS->addNative("function Math.randInt(min, max)", scMathRandInt, 0); 291 | tinyJS->addNative("function charToInt(ch)", scCharToInt, 0); // convert a character to an int - get its value 292 | tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not 293 | tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0); 294 | tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); 295 | tinyJS->addNative("function String.charCodeAt(pos)", scStringCharCodeAt, 0); 296 | tinyJS->addNative("function String.fromCharCode(char)", scStringFromCharCode, 0); 297 | tinyJS->addNative("function String.split(separator)", scStringSplit, 0); 298 | tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int 299 | tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character 300 | tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); // convert to JSON. replacer is ignored at the moment 301 | // JSON.parse is left out as you can (unsafely!) use eval instead 302 | tinyJS->addNative("function Array.contains(obj)", scArrayContains, 0); 303 | tinyJS->addNative("function Array.remove(obj)", scArrayRemove, 0); 304 | tinyJS->addNative("function Array.join(separator)", scArrayJoin, 0); 305 | } 306 | 307 | -------------------------------------------------------------------------------- /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 | //Math.abs(x) - returns absolute of given value 57 | void scMathAbs(CScriptVar *c, void *userdata) 58 | { 59 | if(scIsInt("a")) 60 | { 61 | scReturnInt(F_ABS(scGetInt("a"))); 62 | } 63 | else if(scIsDouble("a")) 64 | { 65 | scReturnDouble(F_ABS(scGetDouble("a"))); 66 | } 67 | } 68 | 69 | //Math.round(a) - returns nearest round of given value 70 | void scMathRound(CScriptVar *c, void *userdata) 71 | { 72 | if(scIsInt("a")) 73 | { 74 | scReturnInt(F_ROUND(scGetInt("a"))); 75 | } 76 | else if(scIsDouble("a")) 77 | { 78 | scReturnDouble(F_ROUND(scGetDouble("a"))); 79 | } 80 | } 81 | 82 | //Math.min(a,b) - returns minimum of two given values 83 | void scMathMin(CScriptVar *c, void *userdata) 84 | { 85 | if((scIsInt("a")) && (scIsInt("b"))) 86 | { 87 | scReturnInt(F_MIN(scGetInt("a"), scGetInt("b"))); 88 | } 89 | else 90 | { 91 | scReturnDouble(F_MIN(scGetDouble("a"), scGetDouble("b"))); 92 | } 93 | } 94 | 95 | //Math.max(a,b) - returns maximum of two given values 96 | void scMathMax(CScriptVar *c, void *userdata) 97 | { 98 | if((scIsInt("a")) && (scIsInt("b"))) 99 | { 100 | scReturnInt(F_MAX(scGetInt("a"), scGetInt("b"))); 101 | } 102 | else 103 | { 104 | scReturnDouble(F_MAX(scGetDouble("a"), scGetDouble("b"))); 105 | } 106 | } 107 | 108 | //Math.range(x,a,b) - returns value limited between two given values 109 | void scMathRange(CScriptVar *c, void *userdata) 110 | { 111 | if((scIsInt("x"))) 112 | { 113 | scReturnInt(F_RNG(scGetInt("x"), scGetInt("a"), scGetInt("b"))); 114 | } 115 | else 116 | { 117 | scReturnDouble(F_RNG(scGetDouble("x"), scGetDouble("a"), scGetDouble("b"))); 118 | } 119 | } 120 | 121 | //Math.sign(a) - returns sign of given value (-1==negative,0=zero,1=positive) 122 | void scMathSign(CScriptVar *c, void *userdata) 123 | { 124 | if(scIsInt("a")) 125 | { 126 | scReturnInt(F_SGN(scGetInt("a"))); 127 | } 128 | else if(scIsDouble("a")) 129 | { 130 | scReturnDouble(F_SGN(scGetDouble("a"))); 131 | } 132 | } 133 | 134 | //Math.PI() - returns PI value 135 | void scMathPI(CScriptVar *c, void *userdata) 136 | { 137 | scReturnDouble(k_PI); 138 | } 139 | 140 | //Math.toDegrees(a) - returns degree value of a given angle in radians 141 | void scMathToDegrees(CScriptVar *c, void *userdata) 142 | { 143 | scReturnDouble((180.0 / k_PI)*(scGetDouble("a"))); 144 | } 145 | 146 | //Math.toRadians(a) - returns radians value of a given angle in degrees 147 | void scMathToRadians(CScriptVar *c, void *userdata) 148 | { 149 | scReturnDouble((k_PI / 180.0)*(scGetDouble("a"))); 150 | } 151 | 152 | //Math.sin(a) - returns trig. sine of given angle in radians 153 | void scMathSin(CScriptVar *c, void *userdata) 154 | { 155 | scReturnDouble(sin(scGetDouble("a"))); 156 | } 157 | 158 | //Math.asin(a) - returns trig. arcsine of given angle in radians 159 | void scMathASin(CScriptVar *c, void *userdata) 160 | { 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 | { 167 | scReturnDouble(cos(scGetDouble("a"))); 168 | } 169 | 170 | //Math.acos(a) - returns trig. arccosine of given angle in radians 171 | void scMathACos(CScriptVar *c, void *userdata) 172 | { 173 | scReturnDouble(acos(scGetDouble("a"))); 174 | } 175 | 176 | //Math.tan(a) - returns trig. tangent of given angle in radians 177 | void scMathTan(CScriptVar *c, void *userdata) 178 | { 179 | scReturnDouble(tan(scGetDouble("a"))); 180 | } 181 | 182 | //Math.atan(a) - returns trig. arctangent of given angle in radians 183 | void scMathATan(CScriptVar *c, void *userdata) 184 | { 185 | scReturnDouble(atan(scGetDouble("a"))); 186 | } 187 | 188 | //Math.sinh(a) - returns trig. hyperbolic sine of given angle in radians 189 | void scMathSinh(CScriptVar *c, void *userdata) 190 | { 191 | scReturnDouble(sinh(scGetDouble("a"))); 192 | } 193 | 194 | //Math.asinh(a) - returns trig. hyperbolic arcsine of given angle in radians 195 | void scMathASinh(CScriptVar *c, void *userdata) 196 | { 197 | scReturnDouble(asinh(scGetDouble("a"))); 198 | } 199 | 200 | //Math.cosh(a) - returns trig. hyperbolic cosine of given angle in radians 201 | void scMathCosh(CScriptVar *c, void *userdata) 202 | { 203 | scReturnDouble(cosh(scGetDouble("a"))); 204 | } 205 | 206 | //Math.acosh(a) - returns trig. hyperbolic arccosine of given angle in radians 207 | void scMathACosh(CScriptVar *c, void *userdata) 208 | { 209 | scReturnDouble(acosh(scGetDouble("a"))); 210 | } 211 | 212 | //Math.tanh(a) - returns trig. hyperbolic tangent of given angle in radians 213 | void scMathTanh(CScriptVar *c, void *userdata) 214 | { 215 | scReturnDouble(tanh(scGetDouble("a"))); 216 | } 217 | 218 | //Math.atan(a) - returns trig. hyperbolic arctangent of given angle in radians 219 | void scMathATanh(CScriptVar *c, void *userdata) 220 | { 221 | scReturnDouble(atan(scGetDouble("a"))); 222 | } 223 | 224 | //Math.E() - returns E Neplero value 225 | void scMathE(CScriptVar *c, void *userdata) 226 | { 227 | scReturnDouble(k_E); 228 | } 229 | 230 | //Math.log(a) - returns natural logaritm (base E) of given value 231 | void scMathLog(CScriptVar *c, void *userdata) 232 | { 233 | scReturnDouble(log(scGetDouble("a"))); 234 | } 235 | 236 | //Math.log10(a) - returns logaritm(base 10) of given value 237 | void scMathLog10(CScriptVar *c, void *userdata) 238 | { 239 | scReturnDouble(log10(scGetDouble("a"))); 240 | } 241 | 242 | //Math.exp(a) - returns e raised to the power of a given number 243 | void scMathExp(CScriptVar *c, void *userdata) 244 | { 245 | scReturnDouble(exp(scGetDouble("a"))); 246 | } 247 | 248 | //Math.pow(a,b) - returns the result of a number raised to a power (a)^(b) 249 | void scMathPow(CScriptVar *c, void *userdata) 250 | { 251 | scReturnDouble(pow(scGetDouble("a"), scGetDouble("b"))); 252 | } 253 | 254 | //Math.sqr(a) - returns square of given value 255 | void scMathSqr(CScriptVar *c, void *userdata) 256 | { 257 | scReturnDouble((scGetDouble("a") * scGetDouble("a"))); 258 | } 259 | 260 | //Math.sqrt(a) - returns square root of given value 261 | void scMathSqrt(CScriptVar *c, void *userdata) 262 | { 263 | scReturnDouble(sqrtf(scGetDouble("a"))); 264 | } 265 | 266 | // ----------------------------------------------- Register Functions 267 | void registerMathFunctions(CTinyJS *tinyJS) 268 | { 269 | 270 | // --- Math and Trigonometry functions --- 271 | tinyJS->addNative("function Math.abs(a)", scMathAbs, 0); 272 | tinyJS->addNative("function Math.round(a)", scMathRound, 0); 273 | tinyJS->addNative("function Math.min(a,b)", scMathMin, 0); 274 | tinyJS->addNative("function Math.max(a,b)", scMathMax, 0); 275 | tinyJS->addNative("function Math.range(x,a,b)", scMathRange, 0); 276 | tinyJS->addNative("function Math.sign(a)", scMathSign, 0); 277 | 278 | tinyJS->addNative("function Math.PI()", scMathPI, 0); 279 | tinyJS->addNative("function Math.toDegrees(a)", scMathToDegrees, 0); 280 | tinyJS->addNative("function Math.toRadians(a)", scMathToRadians, 0); 281 | tinyJS->addNative("function Math.sin(a)", scMathSin, 0); 282 | tinyJS->addNative("function Math.asin(a)", scMathASin, 0); 283 | tinyJS->addNative("function Math.cos(a)", scMathCos, 0); 284 | tinyJS->addNative("function Math.acos(a)", scMathACos, 0); 285 | tinyJS->addNative("function Math.tan(a)", scMathTan, 0); 286 | tinyJS->addNative("function Math.atan(a)", scMathATan, 0); 287 | tinyJS->addNative("function Math.sinh(a)", scMathSinh, 0); 288 | tinyJS->addNative("function Math.asinh(a)", scMathASinh, 0); 289 | tinyJS->addNative("function Math.cosh(a)", scMathCosh, 0); 290 | tinyJS->addNative("function Math.acosh(a)", scMathACosh, 0); 291 | tinyJS->addNative("function Math.tanh(a)", scMathTanh, 0); 292 | tinyJS->addNative("function Math.atanh(a)", scMathATanh, 0); 293 | 294 | tinyJS->addNative("function Math.E()", scMathE, 0); 295 | tinyJS->addNative("function Math.log(a)", scMathLog, 0); 296 | tinyJS->addNative("function Math.log10(a)", scMathLog10, 0); 297 | tinyJS->addNative("function Math.exp(a)", scMathExp, 0); 298 | tinyJS->addNative("function Math.pow(a,b)", scMathPow, 0); 299 | 300 | tinyJS->addNative("function Math.sqr(a)", scMathSqr, 0); 301 | tinyJS->addNative("function Math.sqrt(a)", scMathSqrt, 0); 302 | 303 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /TinyJS_SyntaxTree.cpp: -------------------------------------------------------------------------------- 1 | #include "TinyJS_SyntaxTree.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define ASSERT(X) assert(X) 8 | // if this flag is enabled, the constructors of CSyntax constructs 9 | // will ensure that the simplifying assumptions they make in their 10 | // emit() methods are not violated 11 | #define CHECK_SYNTAX_TREE 12 | 13 | // these three macros are used to avoid memory leaking in JIT-ed code. 14 | #define FUNCTION_VECTOR_NAME "__t_" 15 | #define NO_LEAK_BEGIN() (std::string("(*") + FUNCTION_VECTOR_NAME + ".insert(" + FUNCTION_VECTOR_NAME + ".end(), ") 16 | #define NO_LEAK_END() ("))") 17 | 18 | CScriptSyntaxTree::CScriptSyntaxTree(CScriptLex* lexer) 19 | { 20 | this->lexer = lexer; 21 | lexerOwned = false; 22 | root = 0; 23 | 24 | } 25 | 26 | CScriptSyntaxTree::CScriptSyntaxTree(const std::string& buffer) 27 | { 28 | this->lexer = new CScriptLex(buffer); 29 | lexerOwned = true; 30 | root = 0; 31 | } 32 | 33 | CScriptSyntaxTree::~CScriptSyntaxTree() 34 | { 35 | if(root) 36 | delete root; 37 | if(lexerOwned) 38 | delete lexer; 39 | } 40 | 41 | void CScriptSyntaxTree::parse() 42 | { 43 | CSyntaxSequence* stmts = 0; 44 | while(lexer->tk) 45 | stmts = new CSyntaxSequence(stmts, statement()); 46 | lexer->match(LEX_EOF); 47 | if(stmts && !stmts->first()) 48 | root = stmts->second(); 49 | else 50 | root = stmts; 51 | } 52 | 53 | void CScriptSyntaxTree::compile(std::ostream & out) 54 | { 55 | if(!(CSyntaxFunction*)root) 56 | TRACE("Warning: the root of this syntax tree is not a function definition. Compiled code may not function correctly.\n"); 57 | root->emit(out); 58 | } 59 | 60 | std::vector CScriptSyntaxTree::functionCall() 61 | { 62 | std::vector args; 63 | lexer->match('('); 64 | while(lexer->tk != ')') 65 | { 66 | args.push_back(base()); 67 | if(lexer->tk != ')') lexer->match(','); 68 | } 69 | lexer->match(')'); 70 | return args; 71 | } 72 | 73 | CSyntaxExpression* CScriptSyntaxTree::factor() 74 | { 75 | if(lexer->tk == '(') 76 | { 77 | lexer->match('('); 78 | CSyntaxExpression* a = base(); 79 | lexer->match(')'); 80 | return a; 81 | } 82 | if(lexer->tk == LEX_R_TRUE) 83 | { 84 | lexer->match(LEX_R_TRUE); 85 | return new CSyntaxFactor("1"); 86 | } 87 | if(lexer->tk == LEX_R_FALSE) 88 | { 89 | lexer->match(LEX_R_FALSE); 90 | return new CSyntaxFactor("0"); 91 | } 92 | if(lexer->tk == LEX_R_NULL) 93 | { 94 | lexer->match(LEX_R_NULL); 95 | return new CSyntaxFactor("null"); 96 | } 97 | if(lexer->tk == LEX_R_UNDEFINED) 98 | { 99 | lexer->match(LEX_R_UNDEFINED); 100 | return new CSyntaxFactor("undefined"); 101 | } 102 | if(lexer->tk == LEX_ID) 103 | { 104 | int nameStart = lexer->tokenStart; 105 | std::string tokenName = lexer->tkStr; 106 | lexer->match(LEX_ID); 107 | CSyntaxExpression* a = 0; 108 | while(lexer->tk == '(' || lexer->tk == '.' || lexer->tk == '[') 109 | { 110 | if(lexer->tk == '(') 111 | { 112 | std::string argString = lexer->getSubString(nameStart); 113 | int argStart = lexer->tokenStart; 114 | auto args = functionCall(); 115 | argString += lexer->getSubString(argStart); 116 | a = new CSyntaxFunctionCall(a ? a : new CSyntaxID(tokenName), args, argString); 117 | } 118 | else if(lexer->tk == '.') 119 | { 120 | lexer->match('.'); 121 | const std::string &name = lexer->tkStr; 122 | a = new CSyntaxBinaryOperator('.', a ? a : new CSyntaxID(tokenName), new CSyntaxID(name)); 123 | lexer->match(LEX_ID); 124 | } 125 | else if(lexer->tk == '[') 126 | { 127 | lexer->match('['); 128 | CSyntaxExpression* index = base(); 129 | lexer->match(']'); 130 | a = new CSyntaxBinaryOperator('[', a ? a : new CSyntaxID(tokenName), index); 131 | } 132 | else ASSERT(0); 133 | } 134 | // likely the lhs of an assignment (or rhs, really) 135 | if(!a) 136 | a = new CSyntaxID(tokenName); 137 | return a; 138 | } 139 | if(lexer->tk == LEX_INT || lexer->tk == LEX_FLOAT) 140 | { 141 | CSyntaxFactor* a = new CSyntaxFactor(lexer->tkStr); 142 | lexer->match(lexer->tk); 143 | return a; 144 | } 145 | if(lexer->tk == LEX_STR) 146 | { 147 | CSyntaxFactor* a = new CSyntaxFactor(lexer->tkStr); 148 | lexer->match(LEX_STR); 149 | return a; 150 | } 151 | if(lexer->tk == '{') 152 | { 153 | /* JSON-style object definition */ 154 | std::string arg = "{"; 155 | lexer->match('{'); 156 | // compile string 157 | while(lexer->tk != '}') 158 | { 159 | arg += lexer->tkStr.size() ? lexer->tkStr : std::string(1, (char)lexer->tk); 160 | lexer->match(lexer->tk); 161 | } 162 | arg += "}"; 163 | lexer->match('}'); 164 | // "__object_()" is a special native function that constructs an object from a string 165 | return new CSyntaxFunctionCall(new CSyntaxID(TINYJS_OBJECT_FUNCTION_NAME), std::vector(1, 166 | new CSyntaxFactor(arg.c_str())), TINYJS_OBJECT_FUNCTION_NAME + ("(\"" + arg + "\")")); 167 | } 168 | if(lexer->tk == '[') 169 | { 170 | /* JSON-style array definition */ 171 | lexer->match('['); 172 | std::string arg = "["; 173 | while(lexer->tk != ']') 174 | { 175 | arg += lexer->tkStr; 176 | lexer->match(lexer->tk); 177 | } 178 | lexer->match(']'); 179 | arg += ']'; 180 | return new CSyntaxFunctionCall(new CSyntaxID(TINYJS_ARRAY_FUNCTION_NAME), std::vector(1, 181 | new CSyntaxFactor(arg.c_str())), TINYJS_ARRAY_FUNCTION_NAME + ("(\"" + arg + "\")")); 182 | } 183 | if(lexer->tk == LEX_R_FUNCTION) 184 | { 185 | return parseFunctionDefinition(); 186 | } 187 | if(lexer->tk == LEX_R_NEW) 188 | { 189 | // new -> create a new object 190 | // this means that if the id referenced is a function, 191 | // call the function with a new object "this" in scope, 192 | // otherwise create a new object and set its .prototype attribute 193 | // to the thing specified. 194 | lexer->match(LEX_R_NEW); 195 | const std::string &className = lexer->tkStr; 196 | lexer->match(LEX_ID); 197 | std::string arg = className + "("; 198 | while(lexer->tk != ')') 199 | { 200 | arg += lexer->tkStr; 201 | lexer->match(lexer->tk); 202 | } 203 | lexer->match(')'); 204 | arg += ')'; 205 | return new CSyntaxFunctionCall(new CSyntaxID(TINYJS_NEW_FUNCTION_NAME), std::vector(1, 206 | new CSyntaxFactor(arg.c_str())), TINYJS_NEW_FUNCTION_NAME + arg); 207 | } 208 | // Nothing we can do here... just hope it's the end... 209 | lexer->match(LEX_EOF); 210 | return NULL; 211 | } 212 | 213 | CSyntaxExpression* CScriptSyntaxTree::unary() 214 | { 215 | if(lexer->tk == '!') 216 | { 217 | lexer->match('!'); // binary not 218 | return new CSyntaxUnaryOperator('!', factor()); 219 | } 220 | return factor(); 221 | } 222 | 223 | CSyntaxExpression* CScriptSyntaxTree::term() 224 | { 225 | CSyntaxExpression* a = unary(); 226 | while(lexer->tk == '*' || lexer->tk == '/' || lexer->tk == '%') 227 | { 228 | int op = lexer->tk; 229 | lexer->match(lexer->tk); 230 | return new CSyntaxBinaryOperator(op, a, unary()); 231 | } 232 | return a; 233 | } 234 | 235 | CSyntaxExpression* CScriptSyntaxTree::expression() 236 | { 237 | bool negate = false; 238 | if(lexer->tk == '-') 239 | { 240 | lexer->match('-'); 241 | negate = true; 242 | } 243 | CSyntaxExpression* a = term(); 244 | if(negate) 245 | { 246 | a = new CSyntaxBinaryOperator('-', new CSyntaxFactor("0"), a); 247 | } 248 | 249 | while(lexer->tk == '+' || lexer->tk == '-' || 250 | lexer->tk == LEX_PLUSPLUS || lexer->tk == LEX_MINUSMINUS) 251 | { 252 | int op = lexer->tk; 253 | lexer->match(lexer->tk); 254 | if(op == LEX_PLUSPLUS || op == LEX_MINUSMINUS) 255 | { 256 | // more desugaring 257 | a = new CSyntaxAssign('=', a, 258 | new CSyntaxBinaryOperator(op == LEX_PLUSPLUS ? '+' : '-', a, new CSyntaxFactor("1"))); 259 | } 260 | else 261 | { 262 | a = new CSyntaxBinaryOperator(op, a, term()); 263 | } 264 | } 265 | return a; 266 | } 267 | 268 | CSyntaxExpression* CScriptSyntaxTree::shift() 269 | { 270 | CSyntaxExpression* a = expression(); 271 | if(lexer->tk == LEX_LSHIFT || lexer->tk == LEX_RSHIFT || lexer->tk == LEX_RSHIFTUNSIGNED) 272 | { 273 | int op = lexer->tk; 274 | lexer->match(op); 275 | return new CSyntaxBinaryOperator(op, a, base()); 276 | } 277 | return a; 278 | } 279 | 280 | CSyntaxExpression* CScriptSyntaxTree::condition() 281 | { 282 | CSyntaxExpression* a = shift(); 283 | while(lexer->tk == LEX_EQUAL || lexer->tk == LEX_NEQUAL || 284 | lexer->tk == LEX_TYPEEQUAL || lexer->tk == LEX_NTYPEEQUAL || 285 | lexer->tk == LEX_LEQUAL || lexer->tk == LEX_GEQUAL || 286 | lexer->tk == '<' || lexer->tk == '>') 287 | { 288 | int op = lexer->tk; 289 | lexer->match(lexer->tk); 290 | a = new CSyntaxRelation(op, a, shift()); 291 | } 292 | return a; 293 | } 294 | 295 | CSyntaxExpression* CScriptSyntaxTree::logic() 296 | { 297 | CSyntaxExpression* a = condition(); 298 | CSyntaxExpression* b; 299 | while(lexer->tk == '&' || lexer->tk == '|' || lexer->tk == '^' || lexer->tk == LEX_ANDAND || lexer->tk == LEX_OROR) 300 | { 301 | int op = lexer->tk; 302 | lexer->match(lexer->tk); 303 | b = condition(); 304 | if(op == LEX_ANDAND || op == LEX_OROR) 305 | a = new CSyntaxCondition(op, a, b); 306 | else 307 | a = new CSyntaxBinaryOperator(op, a, b); 308 | } 309 | return a; 310 | } 311 | 312 | CSyntaxExpression* CScriptSyntaxTree::ternary() 313 | { 314 | CSyntaxExpression* lhs = logic(); 315 | if(lexer->tk == '?') 316 | { 317 | lexer->match('?'); 318 | CSyntaxExpression* b1 = base(); 319 | lexer->match(':'); 320 | lhs = new CSyntaxTernaryOperator('?', lhs, b1, base()); 321 | } 322 | 323 | return lhs; 324 | } 325 | 326 | CSyntaxExpression* CScriptSyntaxTree::base() 327 | { 328 | CSyntaxExpression* lhs = ternary(); 329 | if(lexer->tk == '=' || lexer->tk == LEX_PLUSEQUAL || lexer->tk == LEX_MINUSEQUAL) 330 | { 331 | int op = lexer->tk; 332 | lexer->match(lexer->tk); 333 | CSyntaxExpression* rhs = base(); 334 | if(op == '=') 335 | { 336 | lhs = new CSyntaxAssign('=', lhs, rhs); 337 | } 338 | // hey look, syntactic desugaring 339 | else if(op == LEX_PLUSEQUAL) 340 | { 341 | lhs = new CSyntaxAssign('=', lhs, new CSyntaxBinaryOperator('+', lhs, rhs)); 342 | } 343 | else if(op == LEX_MINUSEQUAL) 344 | { 345 | lhs = new CSyntaxAssign('=', lhs, new CSyntaxBinaryOperator('-', lhs, rhs)); 346 | } 347 | else ASSERT(0); 348 | } 349 | return lhs; 350 | } 351 | 352 | CSyntaxStatement* CScriptSyntaxTree::block() 353 | { 354 | lexer->match('{'); 355 | CSyntaxSequence* stmts = 0; 356 | while(lexer->tk && lexer->tk != '}') 357 | { 358 | CSyntaxNode* stmt = statement(); 359 | CSyntaxSequence* seq = dynamic_cast(stmt); 360 | if(seq) 361 | { 362 | std::vector statements = seq->normalize(); 363 | delete seq; 364 | for(auto& stmt2 : statements) 365 | stmts = new CSyntaxSequence(stmts, stmt2); 366 | } 367 | else 368 | stmts = new CSyntaxSequence(stmts, stmt); 369 | } 370 | lexer->match('}'); 371 | return stmts; 372 | } 373 | 374 | CSyntaxNode* CScriptSyntaxTree::statement() 375 | { 376 | if(lexer->tk == LEX_ID || 377 | lexer->tk == LEX_INT || 378 | lexer->tk == LEX_FLOAT || 379 | lexer->tk == LEX_STR || 380 | lexer->tk == '-') 381 | { 382 | /* Execute a simple statement that only contains basic arithmetic... */ 383 | CSyntaxNode* out = base(); 384 | lexer->match(';'); 385 | return out; 386 | } 387 | else if(lexer->tk == '{') 388 | { 389 | /* A block of code */ 390 | return block(); 391 | } 392 | else if(lexer->tk == ';') 393 | { 394 | /* Empty statement - to allow things like ;;; */ 395 | lexer->match(';'); 396 | return statement(); 397 | } 398 | else if(lexer->tk == LEX_R_VAR) 399 | { 400 | lexer->match(LEX_R_VAR); 401 | CSyntaxSequence* stmts = 0; 402 | CSyntaxNode* stmt = 0; 403 | while(lexer->tk != ';') 404 | { 405 | CSyntaxExpression* lhs = new CSyntaxID(lexer->tkStr); 406 | lexer->match(LEX_ID); 407 | // now do stuff defined with dots 408 | while(lexer->tk == '.') 409 | { 410 | lexer->match('.'); 411 | lhs = new CSyntaxBinaryOperator('.', lhs, new CSyntaxID(lexer->tkStr)); 412 | lexer->match(LEX_ID); 413 | } 414 | // sort out initialiser 415 | if(lexer->tk == '=') 416 | { 417 | lexer->match('='); 418 | stmt = new CSyntaxDefinition(lhs, base()); 419 | } 420 | else 421 | stmt = new CSyntaxDefinition(lhs, NULL); 422 | if(lexer->tk != ';') 423 | { 424 | lexer->match(','); 425 | stmts = new CSyntaxSequence(stmts, stmt ? stmt : lhs); 426 | stmt = 0; 427 | } 428 | else if(stmts) 429 | stmts = new CSyntaxSequence(stmts, stmt ? stmt : lhs); 430 | } 431 | lexer->match(';'); 432 | return stmts ? stmts : (CSyntaxNode*)stmt; 433 | } 434 | else if(lexer->tk == LEX_R_IF) 435 | { 436 | lexer->match(LEX_R_IF); 437 | lexer->match('('); 438 | CSyntaxExpression* cond = base(); 439 | lexer->match(')'); 440 | CSyntaxNode* body = statement(); 441 | CSyntaxNode* else_ = 0; 442 | if(lexer->tk == LEX_R_ELSE) 443 | { 444 | lexer->match(LEX_R_ELSE); 445 | else_ = statement(); 446 | } 447 | return new CSyntaxIf(cond, body, else_); 448 | } 449 | else if(lexer->tk == LEX_R_WHILE) 450 | { 451 | lexer->match(LEX_R_WHILE); 452 | lexer->match('('); 453 | CSyntaxExpression* cond = base(); 454 | lexer->match(')'); 455 | CSyntaxNode* body = statement(); 456 | return new CSyntaxWhile(cond, body); 457 | } 458 | else if(lexer->tk == LEX_R_FOR) 459 | { 460 | lexer->match(LEX_R_FOR); 461 | lexer->match('('); 462 | CSyntaxNode* init = statement(); // initialisation 463 | CSyntaxExpression* cond = base(); // condition 464 | lexer->match(';'); 465 | CSyntaxExpression* iter = base(); // iterator 466 | lexer->match(')'); 467 | CSyntaxNode* body = statement(); 468 | return new CSyntaxFor(init, cond, iter, body); 469 | } 470 | else if(lexer->tk == LEX_R_RETURN) 471 | { 472 | lexer->match(LEX_R_RETURN); 473 | CSyntaxExpression* value = base(); 474 | lexer->match(';'); 475 | return new CSyntaxReturn(value); 476 | } 477 | else if(lexer->tk == LEX_R_FUNCTION) 478 | { 479 | return parseFunctionDefinition(); 480 | } 481 | else 482 | { 483 | lexer->match(LEX_EOF); 484 | return NULL; 485 | } 486 | } 487 | 488 | CSyntaxFunction* CScriptSyntaxTree::parseFunctionDefinition() 489 | { 490 | lexer->match(LEX_R_FUNCTION); 491 | std::string funcName = TINYJS_TEMP_NAME; 492 | /* we can have functions without names */ 493 | if(lexer->tk == LEX_ID) 494 | { 495 | funcName = lexer->tkStr; 496 | lexer->match(LEX_ID); 497 | } 498 | std::vector args = parseFunctionArguments(); 499 | CSyntaxStatement* body = block(); 500 | return new CSyntaxFunction(funcName == TINYJS_TEMP_NAME ? 0 : new CSyntaxID(funcName), args, body); 501 | } 502 | 503 | std::vector CScriptSyntaxTree::parseFunctionArguments() 504 | { 505 | std::vector out; 506 | lexer->match('('); 507 | while(lexer->tk != ')') 508 | { 509 | out.push_back(new CSyntaxID(lexer->tkStr)); 510 | lexer->match(LEX_ID); 511 | if(lexer->tk != ')') lexer->match(','); 512 | } 513 | lexer->match(')'); 514 | return out; 515 | } 516 | 517 | CSyntaxNode::CSyntaxNode() 518 | { 519 | node = 0; 520 | } 521 | 522 | CSyntaxNode::~CSyntaxNode() 523 | { 524 | if(node) 525 | delete node; 526 | } 527 | 528 | CSyntaxSequence::CSyntaxSequence(CSyntaxNode* front, CSyntaxNode* last) 529 | { 530 | node = front; 531 | this->last = last; 532 | disowned = false; 533 | #ifdef CHECK_SYNTAX_TREE 534 | ASSERT(this->last && !dynamic_cast(this->last)); 535 | ASSERT(this->node || this->last); 536 | #endif 537 | } 538 | 539 | CSyntaxSequence::~CSyntaxSequence() 540 | { 541 | // make sure to still delete sequences 542 | if(disowned) 543 | { 544 | if(node && !dynamic_cast(node)) 545 | node = 0; 546 | if(last && dynamic_cast(last)) 547 | delete last; 548 | } 549 | else if(last) 550 | delete last; 551 | } 552 | 553 | std::vector CSyntaxSequence::normalize(bool disown_children) 554 | { 555 | disowned = disown_children; 556 | std::vector stmts; 557 | CSyntaxSequence* child; 558 | if(node) 559 | { 560 | if(child = dynamic_cast(node)) 561 | { 562 | std::vector children = child->normalize(disown_children); 563 | stmts.insert(stmts.end(), children.begin(), children.end()); 564 | } 565 | else 566 | stmts.push_back(node); 567 | } 568 | if(last) 569 | { 570 | if(child = dynamic_cast(last)) 571 | { 572 | std::vector children = child->normalize(disown_children); 573 | stmts.insert(stmts.end(), children.begin(), children.end()); 574 | } 575 | else 576 | stmts.push_back(last); 577 | } 578 | return stmts; 579 | } 580 | 581 | // this function assumes that the tree of sequencing is entirely left-heavy; 582 | // ie the structure is: 583 | // left := sequence|statement|null 584 | // right := statement 585 | // enable the compile-time constant CHECK_SYNTAX_TREE to confirm this 586 | void CSyntaxSequence::emit(std::ostream & out, const std::string indentation) 587 | { 588 | std::vector parents; 589 | CSyntaxNode* n = node; 590 | CSyntaxSequence* seq; 591 | while(seq = dynamic_cast(n)) 592 | { 593 | parents.push_back(seq); 594 | n = seq->node; 595 | } 596 | while(!parents.empty()) 597 | { 598 | seq = *parents.rbegin(); 599 | parents.pop_back(); 600 | if(seq->node && !dynamic_cast(seq->node)) 601 | { 602 | seq->node->emit(out, indentation); 603 | if(seq->node->semicolonizable()) 604 | out << ";"; 605 | out << "\n"; 606 | } 607 | if(seq->last) 608 | { 609 | seq->last->emit(out, indentation); 610 | if(seq->last->semicolonizable()) 611 | out << ";"; 612 | out << "\n"; 613 | } 614 | } 615 | if(node && !dynamic_cast(node)) 616 | { 617 | node->emit(out, indentation); 618 | if(node->semicolonizable()) 619 | out << ";"; 620 | out << "\n"; 621 | } 622 | if(last) 623 | { 624 | last->emit(out, indentation); 625 | if(last->semicolonizable()) 626 | out << ";"; 627 | out << "\n"; 628 | } 629 | } 630 | 631 | CSyntaxIf::CSyntaxIf(CSyntaxExpression* expr, CSyntaxNode* body, CSyntaxNode* else_) 632 | { 633 | ASSERT(expr); 634 | ASSERT(body); 635 | node = body; 636 | this->else_ = else_; 637 | this->expr = expr; 638 | } 639 | 640 | CSyntaxIf::CSyntaxIf(CSyntaxExpression* expr, CSyntaxNode* body) : CSyntaxIf(expr, body, 0) { } 641 | 642 | CSyntaxIf::~CSyntaxIf() 643 | { 644 | delete expr; 645 | if(else_) 646 | delete else_; 647 | } 648 | 649 | void CSyntaxIf::emit(std::ostream & out, const std::string indentation) 650 | { 651 | out << indentation << "if("; 652 | expr->emit(out); 653 | out << "->var->getBool()) {\n"; 654 | node->emit(out, indentation + " "); 655 | out << indentation << "} "; 656 | if(else_) 657 | { 658 | out << "else {\n"; 659 | else_->emit(out, indentation + " "); 660 | out << indentation << "}"; 661 | } 662 | } 663 | 664 | CSyntaxWhile::CSyntaxWhile(CSyntaxExpression* expr, CSyntaxNode* body) 665 | { 666 | ASSERT(body); 667 | ASSERT(expr); 668 | node = body; 669 | this->expr = expr; 670 | } 671 | 672 | CSyntaxWhile::~CSyntaxWhile() 673 | { 674 | delete expr; 675 | } 676 | 677 | void CSyntaxWhile::emit(std::ostream & out, const std::string indentation) 678 | { 679 | out << indentation << "while("; 680 | expr->emit(out); 681 | out << "->var->getBool()) {\n"; 682 | node->emit(out, indentation + " "); 683 | out << indentation << "}"; 684 | } 685 | 686 | CSyntaxFor::CSyntaxFor(CSyntaxNode* init, CSyntaxExpression* expr, CSyntaxExpression* update, CSyntaxNode* body) 687 | { 688 | ASSERT(body); 689 | node = body; 690 | this->init = init; 691 | this->update = update; 692 | cond = expr; 693 | } 694 | 695 | CSyntaxFor::~CSyntaxFor() 696 | { 697 | if(init) 698 | delete init; 699 | if(update) 700 | delete update; 701 | if(cond) 702 | delete cond; 703 | } 704 | 705 | void CSyntaxFor::emit(std::ostream & out, const std::string indentation) 706 | { 707 | out << indentation << "for("; 708 | if(init) 709 | { 710 | init->emit(out); 711 | } 712 | out << "; "; 713 | if(cond) 714 | { 715 | cond->emit(out); 716 | out << "->var->getBool()"; 717 | } 718 | out << "; "; 719 | if(update) 720 | { 721 | update->emit(out); 722 | } 723 | out << ") {\n"; 724 | node->emit(out, indentation + " "); 725 | out << indentation << "}"; 726 | } 727 | 728 | CSyntaxFactor::CSyntaxFactor(std::string val) 729 | { 730 | value = val; 731 | node = 0; 732 | 733 | char f = value.front(); 734 | if(f == '"') 735 | factorType = F_TYPE_STRING; 736 | else if(isdigit(f)) 737 | { 738 | if(value.find('.') != std::string::npos) 739 | factorType = F_TYPE_DOUBLE; 740 | else 741 | factorType = F_TYPE_INT; 742 | } 743 | else 744 | factorType = F_TYPE_IDENTIFIER; 745 | } 746 | 747 | void CSyntaxFactor::emit(std::ostream & out, const std::string indentation) 748 | { 749 | out << indentation; 750 | out << NO_LEAK_BEGIN(); 751 | out << "new CScriptVarLink(new CScriptVar("; 752 | switch(factorType) 753 | { 754 | case F_TYPE_INT: 755 | out << getInt(); 756 | break; 757 | case F_TYPE_DOUBLE: 758 | out << getDouble(); 759 | break; 760 | case F_TYPE_STRING: 761 | out << "\"" << value.c_str() << "\""; 762 | break; 763 | case F_TYPE_IDENTIFIER: 764 | out << value.c_str(); 765 | break; 766 | } 767 | out << "))"; 768 | out << NO_LEAK_END(); 769 | } 770 | 771 | CSyntaxID::CSyntaxID(std::string id) : CSyntaxFactor(id) { } 772 | 773 | void CSyntaxID::emit(std::ostream & out, const std::string indentation) 774 | { 775 | // this is not correct 776 | out << indentation << value.c_str(); 777 | } 778 | 779 | CSyntaxFunction::CSyntaxFunction(CSyntaxID* name, std::vector& arguments, CSyntaxStatement* body) 780 | { 781 | ASSERT(body); 782 | this->name = name; 783 | this->arguments = arguments; 784 | node = body; 785 | } 786 | 787 | CSyntaxFunction::~CSyntaxFunction() 788 | { 789 | if(name) 790 | delete name; 791 | for(CSyntaxID* arg : arguments) 792 | if(arg) 793 | delete arg; 794 | } 795 | 796 | void CSyntaxFunction::emit(std::ostream & out, const std::string indentation) 797 | { 798 | out << indentation << "extern \"C\" {\n"; 799 | std::string realIndent = indentation + " "; 800 | out << realIndent << "void "; 801 | getName()->emit(out); 802 | out << "(CScriptVar* root, void* userData) {\n"; 803 | // to avoid memory leaks, we need a structure to hold any newly allocated CScriptVarLink*s. 804 | // we need to mangle the name of any locals so as to avoid name collisions 805 | // (however, if any variables in the function were named "__t_", this would 806 | // generate bad code due to a redeclaration). 807 | // thus, we should endeavor to declare as little extra as possible. 808 | out << realIndent + " " << "std::vector " << FUNCTION_VECTOR_NAME << ";\n"; 809 | for(auto& arg : arguments) 810 | { 811 | // setup variable declarations/assignments 812 | out << realIndent + " " << "CScriptVarLink* "; 813 | arg->emit(out); 814 | out << " = new CScriptVarLink(root->getParameter(\""; 815 | arg->emit(out); 816 | out << "\"));\n"; 817 | } 818 | node->emit(out, realIndent + " "); 819 | // cleanup 820 | for(auto& arg : arguments) 821 | { 822 | out << realIndent + " " << "delete "; 823 | arg->emit(out); 824 | out << ";\n"; 825 | } 826 | out << realIndent + " " << "for(CScriptVarLink* __to_del_: " << FUNCTION_VECTOR_NAME << ")\n"; 827 | out << realIndent + " " << "delete __to_del_;\n"; 828 | out << realIndent << "}\n"; 829 | out << indentation << "}\n"; 830 | } 831 | 832 | CSyntaxID* CSyntaxFunction::getName() 833 | { 834 | if(!name) 835 | generateRandomId(); 836 | return name; 837 | } 838 | 839 | void CSyntaxFunction::generateRandomId() 840 | { 841 | // generate a random (likely unused) identifier so that this function is not unnamed 842 | static const char alphanum[] = 843 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 844 | "abcdefghijklmnopqrstuvwxyz"; 845 | std::string randomId = ""; 846 | for(int i = 0; i < 20; ++i) 847 | { 848 | randomId += alphanum[rand() % (sizeof(alphanum) - 1)]; 849 | } 850 | name = new CSyntaxID(randomId); 851 | } 852 | 853 | CSyntaxAssign::CSyntaxAssign(int op, CSyntaxExpression* lvalue, CSyntaxExpression* rvalue) 854 | { 855 | ASSERT(lvalue); 856 | ASSERT(rvalue); 857 | this->op = op; 858 | lval = lvalue; 859 | node = rvalue; 860 | } 861 | 862 | CSyntaxAssign::~CSyntaxAssign() 863 | { 864 | delete lval; 865 | } 866 | 867 | void CSyntaxAssign::emit(std::ostream & out, const std::string indentation) 868 | { 869 | out << indentation; 870 | lval->emit(out); 871 | out << "->replaceWith("; 872 | node->emit(out); 873 | out << "->var)"; 874 | } 875 | 876 | CSyntaxTernaryOperator::CSyntaxTernaryOperator(int op, CSyntaxExpression* cond, CSyntaxExpression* b1, CSyntaxExpression* b2) 877 | { 878 | ASSERT(cond); 879 | ASSERT(b1); 880 | ASSERT(b2); 881 | this->op = op; 882 | node = cond; 883 | this->b1 = b1; 884 | this->b2 = b2; 885 | #ifdef CHECK_SYNTAX_TREE 886 | ASSERT(op == '?'); 887 | #endif 888 | } 889 | 890 | CSyntaxTernaryOperator::~CSyntaxTernaryOperator() 891 | { 892 | delete b1; 893 | delete b2; 894 | } 895 | 896 | void CSyntaxTernaryOperator::emit(std::ostream & out, const std::string indentation) 897 | { 898 | // op can only be '?' 899 | out << indentation; 900 | node->emit(out); 901 | out << "->var->getBool() ? "; 902 | b1->emit(out); 903 | out << " : "; 904 | b2->emit(out); 905 | } 906 | 907 | CSyntaxRelation::CSyntaxRelation(int rel, CSyntaxExpression* left, CSyntaxExpression* right) 908 | : CSyntaxBinaryOperator(rel, left, right) 909 | { } 910 | 911 | void CSyntaxRelation::emit(std::ostream & out, const std::string indentation) 912 | { 913 | CSyntaxBinaryOperator::emit(out); 914 | } 915 | 916 | CSyntaxBinaryOperator::CSyntaxBinaryOperator(int op, CSyntaxExpression* left, CSyntaxExpression* right) 917 | { 918 | ASSERT(left); 919 | ASSERT(right); 920 | this->op = op; 921 | node = left; 922 | this->right = right; 923 | #ifdef CHECK_SYNTAX_TREE 924 | // right side has to be an ID 925 | ASSERT(op != '.' || dynamic_cast(right)); 926 | #endif 927 | } 928 | 929 | CSyntaxBinaryOperator::~CSyntaxBinaryOperator() 930 | { 931 | delete right; 932 | } 933 | 934 | void CSyntaxBinaryOperator::emit(std::ostream & out, const std::string indentation) 935 | { 936 | out << indentation; 937 | if(op != '[' && op != '.') 938 | { 939 | out << NO_LEAK_BEGIN(); 940 | out << "new CScriptVarLink("; 941 | node->emit(out); 942 | out << "->var->mathsOp("; 943 | right->emit(out); 944 | out << "->var, " << op << "))"; 945 | out << NO_LEAK_END(); 946 | } 947 | else 948 | { 949 | // beautifully enough, this will return a CScriptVarLink*, which can be used as 950 | // /either/ an lvalue OR an rvalue depending on the calling code's fancy! 951 | // The more I work on this code, the more I begin to think that it was actually 952 | // fairly well designed. 953 | node->emit(out); 954 | out << "->var->findChildOrCreate("; 955 | if(op == '.') 956 | { 957 | out << '"'; 958 | right->emit(out); 959 | out << '"'; 960 | } 961 | else 962 | { 963 | right->emit(out); 964 | out << "->var->getString()"; 965 | } 966 | out << ")"; 967 | } 968 | } 969 | 970 | std::string CSyntaxBinaryOperator::lvaluePath() 971 | { 972 | if(op == '.') 973 | return ((CSyntaxExpression*)node)->lvaluePath() + "." + right->lvaluePath(); 974 | else if(op == '[') 975 | { 976 | generateRandomID(); 977 | return ((CSyntaxExpression*)node)->lvaluePath() + "_array__" + randomArrayName; 978 | } 979 | ASSERT(0); 980 | return std::string(); 981 | } 982 | 983 | void CSyntaxBinaryOperator::generateRandomID() 984 | { 985 | if(randomArrayName.length() != 0) 986 | return; 987 | 988 | // generate a random (likely unused) identifier so that this function is not unnamed 989 | static const char alphanum[] = 990 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 991 | "abcdefghijklmnopqrstuvwxyz"; 992 | std::string randomId = ""; 993 | for(int i = 0; i < 5; ++i) 994 | { 995 | randomId += alphanum[rand() % (sizeof(alphanum) - 1)]; 996 | } 997 | randomArrayName = randomId; 998 | } 999 | 1000 | CSyntaxUnaryOperator::CSyntaxUnaryOperator(int op, CSyntaxExpression* expr) 1001 | { 1002 | this->op = op; 1003 | node = expr; 1004 | } 1005 | 1006 | void CSyntaxUnaryOperator::emit(std::ostream & out, const std::string indentation) 1007 | { 1008 | // normally, you would have to check to see if the operators we're talking about 1009 | // are prefix or postfix operators; however, in TinyJS.cpp::111-112 it is mentioned 1010 | // that postfix operators work as prefix operators, and prefix operators don't exist. 1011 | // so, placing the unary operator always at the beginning happens to preserve behavior! 1012 | // also, I don't currenly use prefix/postfix operators due to desugaring 1013 | out << indentation; 1014 | out << CScriptLex::getTokenStr(op, true); 1015 | node->emit(out); 1016 | } 1017 | 1018 | CSyntaxReturn::CSyntaxReturn(CSyntaxExpression* value) 1019 | { 1020 | node = value; 1021 | } 1022 | 1023 | void CSyntaxReturn::emit(std::ostream & out, const std::string indentation) 1024 | { 1025 | out << indentation; 1026 | out << "root->setReturnVar("; 1027 | // todo: ensure this returns a CScriptVar or CScriptVarLink 1028 | node->emit(out); 1029 | out << "->var)"; 1030 | } 1031 | 1032 | CSyntaxCondition::CSyntaxCondition(int op, CSyntaxExpression* left, CSyntaxExpression* right) 1033 | : CSyntaxBinaryOperator(op, left, right) 1034 | { } 1035 | 1036 | void CSyntaxCondition::emit(std::ostream & out, const std::string indentation) 1037 | { 1038 | CSyntaxBinaryOperator::emit(out); 1039 | } 1040 | 1041 | CSyntaxFunctionCall::CSyntaxFunctionCall(CSyntaxExpression* name, 1042 | std::vector arguments, std::string originalString) 1043 | { 1044 | node = name; 1045 | actuals = arguments; 1046 | origString = originalString; 1047 | } 1048 | 1049 | CSyntaxFunctionCall::~CSyntaxFunctionCall() 1050 | { 1051 | for(CSyntaxExpression* arg : actuals) 1052 | delete arg; 1053 | } 1054 | 1055 | void CSyntaxFunctionCall::emit(std::ostream & out, const std::string indentation) 1056 | { 1057 | // we have to call back into the interpreter to make function calls. 1058 | // the exception is recursive calls; we can set those up ourselves. 1059 | // however, setting up a recursive call is a non-trivial amount of work 1060 | // (clear the symbol table for function root, set up new parameters in the 1061 | // symbol table, ensure "this" gets set up if necessary and not accidentally 1062 | // unref'd), and since function calls to ourselves can appear in a non-tail 1063 | // position (ie as part of an expression), it's a little bit of extra work 1064 | // to set up a recursive call. 1065 | // so, for now, just make an interpreter call. 1066 | 1067 | // evaluateComplex returns a CScriptVarLink (but not a pointer), so assign it to 1068 | // our local placeholder and immediately take the reference of it so that this 1069 | // expression returns a CScriptVarLink*. 1070 | 1071 | // CSyntaxFunction::emit() doesn't set up a local CTinyJS variable because it 1072 | // is only used here and we want to avoid accidental redeclarations of variables. 1073 | out << indentation << NO_LEAK_BEGIN(); 1074 | out << "new CScriptVarLink(((CTinyJS*)userData)->evaluateComplex(\""; 1075 | // we need to escape quotes 1076 | std::string str = origString; 1077 | size_t pos = 0; 1078 | while((pos = str.find_first_of('"', pos)) != std::string::npos) 1079 | { 1080 | str.insert(str.begin() + pos, '\\'); 1081 | pos += 2; // make sure to skip past both the \ and the " to avoid infinite loops 1082 | } 1083 | out << str << "\").var)"; 1084 | out << NO_LEAK_END(); 1085 | } 1086 | 1087 | CSyntaxDefinition::CSyntaxDefinition(CSyntaxExpression * lvalue, CSyntaxExpression * rvalue) 1088 | { 1089 | ASSERT(lvalue); 1090 | lval = lvalue; 1091 | node = rvalue; 1092 | } 1093 | 1094 | CSyntaxDefinition::~CSyntaxDefinition() 1095 | { 1096 | delete lval; 1097 | } 1098 | 1099 | void CSyntaxDefinition::emit(std::ostream & out, const std::string indentation) 1100 | { 1101 | // this doesn't work with array indexing, although it IS 1102 | // illegal to use an array index with "var" 1103 | std::string lvalPath = lval->lvaluePath(); 1104 | out << indentation << "CScriptVarLink* "; 1105 | out << lvalPath << " = " << "root->findChildOrCreateByPath(\""; 1106 | std::replace(lvalPath.begin(), lvalPath.end(), '.', '_'); 1107 | out << lvalPath << "\")"; 1108 | if(node) 1109 | { 1110 | out << "->replaceWith("; 1111 | node->emit(out); 1112 | out << ")"; 1113 | } 1114 | } 1115 | -------------------------------------------------------------------------------- /TinyJS_SyntaxTree.h: -------------------------------------------------------------------------------- 1 | #include "TinyJS.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #pragma once 8 | 9 | class CSyntaxNode 10 | { 11 | public: 12 | CSyntaxNode(); 13 | virtual ~CSyntaxNode(); 14 | 15 | virtual void emit(std::ostream& out, const std::string indentation = "") = 0; 16 | // returns true if this node is the kind that should have a semicolon after it. 17 | // since returns and declarations are statement-types but require semicolons, 18 | // and the latter of which can appear in for statements, it cannot emit a semicolon 19 | // itself. plus this is a little more "OO". 20 | virtual bool semicolonizable() = 0; 21 | 22 | protected: 23 | CSyntaxNode* node; 24 | }; 25 | 26 | // these two classes serve no purpose except to divide the two 27 | // sides of the syntax tree. 28 | class CSyntaxStatement : public CSyntaxNode 29 | { 30 | public: 31 | virtual bool semicolonizable() { return false; } 32 | }; 33 | class CSyntaxExpression : public CSyntaxNode 34 | { 35 | public: 36 | virtual bool semicolonizable() { return true; } 37 | virtual std::string lvaluePath() { assert(0); return std::string(); } 38 | }; 39 | 40 | class CSyntaxSequence : public CSyntaxStatement 41 | { 42 | public: 43 | CSyntaxSequence(CSyntaxNode* front, CSyntaxNode* last); 44 | ~CSyntaxSequence(); 45 | 46 | /// converts the sequence (and any subsequences) into a serial vector of statements. 47 | /// Using this function will also "disown" all the statements in this sequence if 48 | /// disown_children is true, ie the leaves of the tree represented by this sequence 49 | /// will no longer be deleted when this sequence is deleted. 50 | std::vector normalize(bool disown_children = true); 51 | 52 | CSyntaxNode* first() { return node; } 53 | CSyntaxNode* second() { return last; } 54 | 55 | virtual void emit(std::ostream& out, const std::string indentation = ""); 56 | 57 | private: 58 | CSyntaxNode* last; 59 | bool disowned; 60 | }; 61 | 62 | class CSyntaxIf : public CSyntaxStatement 63 | { 64 | public: 65 | CSyntaxIf(CSyntaxExpression* expr, CSyntaxNode* body, CSyntaxNode* else_); 66 | CSyntaxIf(CSyntaxExpression* expr, CSyntaxNode* body); 67 | ~CSyntaxIf(); 68 | 69 | virtual void emit(std::ostream& out, const std::string indentation = ""); 70 | 71 | private: 72 | CSyntaxExpression* expr; 73 | CSyntaxNode* else_; 74 | }; 75 | 76 | class CSyntaxWhile : public CSyntaxStatement 77 | { 78 | public: 79 | CSyntaxWhile(CSyntaxExpression* expr, CSyntaxNode* body); 80 | ~CSyntaxWhile(); 81 | 82 | virtual void emit(std::ostream& out, const std::string indentation = ""); 83 | 84 | private: 85 | CSyntaxExpression* expr; 86 | }; 87 | 88 | class CSyntaxFor : public CSyntaxStatement 89 | { 90 | public: 91 | CSyntaxFor(CSyntaxNode* init, CSyntaxExpression* cond, CSyntaxExpression* update, CSyntaxNode* body); 92 | ~CSyntaxFor(); 93 | 94 | virtual void emit(std::ostream& out, const std::string indentation = ""); 95 | 96 | private: 97 | CSyntaxNode* init; 98 | CSyntaxExpression* cond; 99 | CSyntaxExpression* update; 100 | }; 101 | 102 | class CSyntaxFactor : public CSyntaxExpression 103 | { 104 | public: 105 | enum FACTOR_TYPES 106 | { 107 | F_TYPE_INT = 1, 108 | F_TYPE_DOUBLE = 2, 109 | F_TYPE_STRING = 4, 110 | F_TYPE_IDENTIFIER = 8 111 | }; 112 | CSyntaxFactor(std::string val); 113 | 114 | bool isValueType() { return factorType & (F_TYPE_INT | F_TYPE_DOUBLE | F_TYPE_STRING); } 115 | std::string getRawValue() { return value; } 116 | double getDouble() { if(factorType != F_TYPE_DOUBLE) return getInt(); return std::strtod(value.c_str(), 0); } 117 | int getInt() { if(factorType != F_TYPE_INT) return 0; return std::strtol(value.c_str(), 0, 0); } 118 | 119 | virtual void emit(std::ostream& out, const std::string indentation = ""); 120 | 121 | protected: 122 | std::string value; 123 | int factorType; 124 | }; 125 | 126 | class CSyntaxID : public CSyntaxFactor 127 | { 128 | public: 129 | CSyntaxID(std::string id); 130 | 131 | virtual void emit(std::ostream& out, const std::string indentation = ""); 132 | std::string getName() { return value; } 133 | std::string lvaluePath() { return getName(); } 134 | }; 135 | 136 | class CSyntaxFunction : public CSyntaxExpression 137 | { 138 | public: 139 | CSyntaxFunction(CSyntaxID* name, std::vector& arguments, CSyntaxStatement* body); 140 | ~CSyntaxFunction(); 141 | 142 | virtual void emit(std::ostream& out, const std::string indentation = ""); 143 | CSyntaxID* getName(); 144 | 145 | private: 146 | CSyntaxID* name; 147 | std::vector arguments; 148 | 149 | void generateRandomId(); 150 | }; 151 | 152 | class CSyntaxFunctionCall : public CSyntaxExpression 153 | { 154 | public: 155 | CSyntaxFunctionCall(CSyntaxExpression* name, std::vector arguments, std::string originalArguments); 156 | ~CSyntaxFunctionCall(); 157 | 158 | virtual void emit(std::ostream& out, const std::string indentation = ""); 159 | 160 | protected: 161 | std::vector actuals; 162 | std::string origString; 163 | }; 164 | 165 | class CSyntaxReturn : public CSyntaxStatement 166 | { 167 | public: 168 | CSyntaxReturn(CSyntaxExpression* value); 169 | virtual void emit(std::ostream& out, const std::string indentation = ""); 170 | virtual bool semicolonizable() { return true; } 171 | }; 172 | 173 | class CSyntaxAssign : public CSyntaxExpression 174 | { 175 | public: 176 | // this has to be an expression on the lhs to deal with things like 177 | // var some.path.to.thing = something; 178 | // or things like 179 | // a["myfavoriteindex"] = something; 180 | // the thing on the lhs is an l-value, but it is an expression that evaluates 181 | // to one. 182 | CSyntaxAssign(int op, CSyntaxExpression* lvalue, CSyntaxExpression* rvalue); 183 | ~CSyntaxAssign(); 184 | 185 | virtual void emit(std::ostream& out, const std::string indentation = ""); 186 | 187 | private: 188 | int op; 189 | CSyntaxExpression* lval; 190 | }; 191 | 192 | class CSyntaxDefinition : public CSyntaxStatement 193 | { 194 | public: 195 | CSyntaxDefinition(CSyntaxExpression* lvalue, CSyntaxExpression* rvalue); 196 | ~CSyntaxDefinition(); 197 | 198 | virtual void emit(std::ostream& out, const std::string indentation = ""); 199 | virtual bool semicolonizable() { return true; } 200 | 201 | private: 202 | CSyntaxExpression* lval; 203 | }; 204 | 205 | class CSyntaxTernaryOperator : public CSyntaxExpression 206 | { 207 | public: 208 | CSyntaxTernaryOperator(int op, CSyntaxExpression* cond, CSyntaxExpression* b1, CSyntaxExpression* b2); 209 | ~CSyntaxTernaryOperator(); 210 | 211 | virtual void emit(std::ostream& out, const std::string indentation = ""); 212 | 213 | private: 214 | int op; 215 | CSyntaxExpression* b1; 216 | CSyntaxExpression* b2; 217 | }; 218 | 219 | class CSyntaxBinaryOperator : public CSyntaxExpression 220 | { 221 | public: 222 | CSyntaxBinaryOperator(int op, CSyntaxExpression* left, CSyntaxExpression* right); 223 | ~CSyntaxBinaryOperator(); 224 | 225 | bool canBeLval() { return op == '.' || op == '['; } 226 | 227 | virtual void emit(std::ostream& out, const std::string indentation = ""); 228 | virtual std::string lvaluePath(); 229 | 230 | private: 231 | int op; 232 | CSyntaxExpression* right; 233 | std::string randomArrayName = ""; 234 | 235 | void generateRandomID(); 236 | }; 237 | 238 | class CSyntaxCondition : public CSyntaxBinaryOperator 239 | { 240 | public: 241 | CSyntaxCondition(int op, CSyntaxExpression* left, CSyntaxExpression* right); 242 | 243 | virtual void emit(std::ostream& out, const std::string indentation = ""); 244 | }; 245 | 246 | class CSyntaxRelation : public CSyntaxBinaryOperator 247 | { 248 | public: 249 | CSyntaxRelation(int rel, CSyntaxExpression* left, CSyntaxExpression* right); 250 | 251 | virtual void emit(std::ostream& out, const std::string indentation = ""); 252 | }; 253 | 254 | class CSyntaxUnaryOperator : public CSyntaxExpression 255 | { 256 | public: 257 | CSyntaxUnaryOperator(int op, CSyntaxExpression* expr); 258 | 259 | virtual void emit(std::ostream& out, const std::string indentation = ""); 260 | 261 | private: 262 | int op; 263 | }; 264 | 265 | class CScriptSyntaxTree 266 | { 267 | public: 268 | CScriptSyntaxTree(CScriptLex* lexer); 269 | CScriptSyntaxTree(const std::string& buffer); 270 | ~CScriptSyntaxTree(); 271 | 272 | void parse(); 273 | void compile(std::ostream& out); 274 | 275 | protected: 276 | CScriptLex* lexer; 277 | bool lexerOwned; 278 | CSyntaxNode* root; 279 | 280 | // taken from TinyJS.h 281 | // parsing - in order of precedence 282 | std::vector functionCall(); 283 | CSyntaxExpression* factor(); 284 | CSyntaxExpression* unary(); 285 | CSyntaxExpression* term(); 286 | CSyntaxExpression* expression(); 287 | CSyntaxExpression* shift(); 288 | CSyntaxExpression* condition(); 289 | CSyntaxExpression* logic(); 290 | CSyntaxExpression* ternary(); 291 | CSyntaxExpression* base(); 292 | // can return null for blocks like "{ }" or possibly "{ ;* }" 293 | CSyntaxStatement* block(); 294 | CSyntaxNode* statement(); 295 | // parsing utility functions 296 | CSyntaxFunction* parseFunctionDefinition(); 297 | std::vector parseFunctionArguments(); 298 | }; -------------------------------------------------------------------------------- /run_profiler.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 | * Profiler for TinyJS. Written by Alex Van Liew. 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 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #ifdef _MSC_VER 48 | #include 49 | #else 50 | #include 51 | #include 52 | #endif 53 | 54 | using std::cout; 55 | using std::endl; 56 | using std::string; 57 | 58 | void js_print(CScriptVar *v, void *userdata) 59 | { 60 | printf("> %s\n", v->getParameter("text")->getString().c_str()); 61 | } 62 | 63 | int usage(const char* name) 64 | { 65 | printf("Usage: %s [--jit n] profile.js [NAME=VALUE...]\n", name); 66 | printf(" --jit n: Set the JIT compilation to occur after n executions. Default is 1.\n"); 67 | printf(" Setting n=0 will disable compilation.\n"); 68 | printf(" (This argument must appear here or nowhere.)\n"); 69 | printf(" profile.js: Name of file to run.\n"); 70 | printf(" NAME=VALUE: Name/value pairs to override configuration values in the profiled file\n"); 71 | printf("\n"); 72 | printf(" Utility for profiling the JIT-compiling function of the tiny-js\n"); 73 | printf(" library. Profiles times per execution pre-JIT, post-JIT, and time to\n"); 74 | printf(" compile. Also profiles memory pressure in the form of max resident set\n"); 75 | printf(" for pre-compile and post-compile executions; this profiling includes\n"); 76 | printf(" attempts to screen out startup memory costs so that displayed values are\n"); 77 | printf(" exclusively representative of memory pressure due to JITted code. However,\n"); 78 | printf(" on some systems, a large chunk of memory is allocated during startup and\n"); 79 | printf(" then freed, causing the resident set to be artifically inflated and shadow\n"); 80 | printf(" the memory used by the actual program. These cases are detected and noted by\n"); 81 | printf(" the profiler.\n"); 82 | return 1; 83 | } 84 | 85 | bool getmemusage(int& usage) 86 | { 87 | #ifdef _MSC_VER 88 | PROCESS_MEMORY_COUNTERS pmc; 89 | if(!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) 90 | { 91 | TRACE("Failed in GetProcessMemoryInfo(), memory profiling disabled.\n"); 92 | return false; 93 | } 94 | else 95 | usage = pmc.PeakWorkingSetSize / 1024; 96 | #else 97 | struct rusage memusage; 98 | if(getrusage(RUSAGE_SELF, &memusage)) 99 | { 100 | TRACE("Failed in getrusage(), memory profiling disabled.\n"); 101 | return false; 102 | } 103 | else 104 | usage = memusage.ru_maxrss; 105 | #endif 106 | return true; 107 | } 108 | 109 | int main(int argc, char **argv) 110 | { 111 | if(argc < 2) 112 | return usage(argc ? argv[0] : "run_profiler"); 113 | 114 | char* fname; 115 | int jit_at = 1; 116 | int i = 2; 117 | if(!strcmp(argv[1], "--jit")) 118 | { 119 | if(argc < 4) 120 | return usage(argv[0]); 121 | 122 | std::stringstream st(argv[2]); 123 | st >> jit_at; 124 | if(!st) 125 | { 126 | printf("Argument to --jit was not an int."); 127 | return usage(argv[0]); 128 | } 129 | fname = argv[3]; 130 | i = 4; 131 | } 132 | else 133 | fname = argv[1]; 134 | 135 | /* Create the interpreter with the specified number of executions */ 136 | CTinyJS *js = new CTinyJS(jit_at); 137 | /* add the functions from TinyJS_Functions.cpp */ 138 | registerFunctions(js); 139 | registerMathFunctions(js); 140 | /* Add a native function (this particular one isn't used in our scripts anymore, though) */ 141 | js->addNative("function print(text)", &js_print, 0); 142 | /* Execute out bit of code - we could call 'evaluate' here if 143 | we wanted something returned */ 144 | 145 | const char* filename = fname; 146 | struct stat results; 147 | if(!stat(filename, &results) == 0) 148 | { 149 | printf("Cannot stat file! '%s'\n", filename); 150 | return false; 151 | } 152 | int size = results.st_size; 153 | FILE *file = fopen(filename, "rb"); 154 | /* if we open as text, the number of bytes read may be > the size we read */ 155 | if(!file) 156 | { 157 | printf("Unable to open file! '%s'\n", filename); 158 | return false; 159 | } 160 | char *buffer = new char[size + 1]; 161 | long actualRead = fread(buffer, 1, size, file); 162 | buffer[actualRead] = 0; 163 | buffer[size] = 0; 164 | fclose(file); 165 | 166 | try 167 | { 168 | // read in definitions from the buffer 169 | js->execute(buffer); 170 | 171 | // initialize default script parameters 172 | js->execute("init();"); 173 | 174 | // set parameters from arguments 175 | // i is already set when arguments are parsed above 176 | for(; i < argc; i++) 177 | { 178 | int idx = 0; 179 | while(argv[i][idx] != '=' && argv[i][idx] != '\0') idx++; 180 | if(argv[i][idx] == '\0') continue; 181 | 182 | argv[i][idx++] = '\0'; 183 | // really, you might be fine just prepending "var " and appending ";" but 184 | // might as well be safe 185 | js->execute("var " + string(argv[i]) + "= (" + &(argv[i][idx]) + ");"); 186 | } 187 | 188 | // get iterations 189 | CScriptVarLink iter = js->evaluateComplex("get_iterations();"); 190 | int iterations = iter.var->getInt(); 191 | 192 | // get function 193 | string fnname = js->evaluate("get_function_name();"); 194 | string profstring = fnname + "(" + js->evaluate("get_arg_list();") + ");"; 195 | 196 | // run algorithm setup 197 | js->execute("setup();"); 198 | 199 | // setup memory profiling 200 | bool memprof = false; 201 | int prestart_wss; 202 | int postmemoryusage; 203 | int prememoryusage; 204 | 205 | // start actual profiling 206 | double tstart, tstop; 207 | double* times = new double[iterations]; 208 | printf("Beginning profiling, please be patient!\n"); 209 | 210 | string precompileresult; 211 | string postcompileresult; 212 | 213 | std::ostringstream s; 214 | memprof = getmemusage(prestart_wss); 215 | for(i = 0; i < iterations; i++) 216 | { 217 | if(i == jit_at && memprof) 218 | { 219 | int precompile_usage; 220 | prememoryusage = (memprof = getmemusage(precompile_usage)) ? precompile_usage - prestart_wss : 0; 221 | if(memprof && !prememoryusage) 222 | prememoryusage = prestart_wss; 223 | } 224 | 225 | tstart = (double)clock() / CLOCKS_PER_SEC; 226 | CScriptVarLink result = js->evaluateComplex(profstring); 227 | if(!i) 228 | { 229 | result.var->getJSON(s); 230 | precompileresult = s.str(); 231 | s.str(""); 232 | s.clear(); 233 | } 234 | else if(i == jit_at) 235 | { 236 | result.var->getJSON(s); 237 | postcompileresult = s.str(); 238 | } 239 | tstop = (double)clock() / CLOCKS_PER_SEC; 240 | times[i] = tstop - tstart; 241 | } 242 | 243 | if(precompileresult != postcompileresult) 244 | { 245 | cout << "WARNING: first execution result does not match first compiled execution result." << endl; 246 | cout << "Results were:" << endl; 247 | cout << precompileresult << endl << endl << postcompileresult; 248 | } 249 | 250 | // calculate max/min for precompile/postcompile, avg for pre/post 251 | double premax = 0, premin = DBL_MAX, preavg = 0; 252 | double postmax = 0, postmin = DBL_MAX, postavgincl = 0, postavgexcl = 0; 253 | double compileexec = 0; 254 | double avg = 0; 255 | for(i = 0; i < jit_at; i++) 256 | { 257 | double comp = times[i]; 258 | if(premax < comp) 259 | premax = comp; 260 | if(premin > comp) 261 | premin = comp; 262 | preavg += comp; 263 | } 264 | avg += preavg; 265 | preavg /= jit_at; 266 | for(; i < iterations; i++) 267 | { 268 | double comp = times[i]; 269 | if(i == jit_at) 270 | compileexec = comp; 271 | if(postmax < comp && i != jit_at) 272 | postmax = comp; 273 | if(postmin > comp) 274 | postmin = comp; 275 | postavgincl += comp; 276 | } 277 | // there was only one execution, the compilation one 278 | if(postmax == DBL_MAX) 279 | postmax = compileexec; 280 | avg += postavgincl; 281 | postavgexcl = postavgincl - compileexec; 282 | postavgexcl /= iterations - jit_at; 283 | postavgincl /= iterations - jit_at; 284 | avg /= iterations; 285 | 286 | if(memprof) 287 | { 288 | int finish_wss; 289 | postmemoryusage = (memprof = getmemusage(finish_wss)) ? finish_wss - prestart_wss : 0; 290 | if(memprof && !postmemoryusage) 291 | postmemoryusage = prestart_wss; 292 | } 293 | 294 | cout << "Profiled function " << fnname << " a total of " + iter.var->getString() + " time(s)." << endl; 295 | cout << "Precompile min:\t\t\t\t" << premin << endl; 296 | cout << "Precompile max:\t\t\t\t" << premax << endl; 297 | cout << "Precompile avg:\t\t\t\t" << preavg << endl; 298 | cout << "Compilation execution time:\t\t" << compileexec << endl; 299 | cout << "Postcompile min:\t\t\t" << postmin << endl; 300 | cout << "Postcompile max:\t\t\t" << postmax << endl; 301 | cout << "Postcompile avg (incl. compile exec):\t" << postavgincl << endl; 302 | cout << "Postcompile avg (excl. compile exec):\t" << postavgexcl << endl; 303 | cout << "Total avg:\t\t\t\t" << avg << endl; 304 | if(memprof) 305 | { 306 | cout << "pre-JIT max rss:\t\t\t" << prememoryusage << ((prememoryusage == prestart_wss) ? "kb (shadowed by startup)" : "kb") << endl; 307 | cout << "post-JIT max rss:\t\t\t" << postmemoryusage << ((postmemoryusage == prestart_wss) ? "kb (shadowed by startup)" : "kb") << endl; 308 | } 309 | else 310 | cout << "(Memory profiling disabled due to error)" << endl; 311 | 312 | delete[] times; 313 | } 314 | catch(CScriptException *e) 315 | { 316 | printf("ERROR: %s\n", e->text.c_str()); 317 | } 318 | 319 | delete js; 320 | #ifdef _WIN32 321 | #ifdef _DEBUG 322 | _CrtDumpMemoryLeaks(); 323 | #endif 324 | #endif 325 | return 0; 326 | } 327 | -------------------------------------------------------------------------------- /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 | { 58 | void **trace = (void**)malloc(sizeof(void*) * 17); 59 | int trace_size = 0; 60 | 61 | for(int i = 0; i < 17; i++) trace[i] = (void*)0; 62 | trace_size = backtrace(trace, 16); 63 | return trace; 64 | } 65 | 66 | void print_stackframe(char *header, void **trace) 67 | { 68 | char **messages = (char **)NULL; 69 | int trace_size = 0; 70 | 71 | trace_size = 0; 72 | while(trace[trace_size]) trace_size++; 73 | messages = backtrace_symbols(trace, trace_size); 74 | 75 | printf("%s\n", header); 76 | for(int i = 0; i < trace_size; ++i) 77 | { 78 | printf("%s\n", messages[i]); 79 | } 80 | //free(messages); 81 | } 82 | 83 | /* Prototypes for our hooks. */ 84 | static void *my_malloc_hook(size_t, const void *); 85 | static void my_free_hook(void*, const void *); 86 | static void *(*old_malloc_hook) (size_t, const void *); 87 | static void(*old_free_hook) (void*, const void *); 88 | 89 | map malloced; 90 | 91 | static void *my_malloc_hook(size_t size, const void *caller) 92 | { 93 | /* Restore all old hooks */ 94 | __malloc_hook = old_malloc_hook; 95 | __free_hook = old_free_hook; 96 | /* Call recursively */ 97 | void *result = malloc(size); 98 | /* we call malloc here, so protect it too. */ 99 | //printf ("malloc (%u) returns %p\n", (unsigned int) size, result); 100 | malloced[result] = get_stackframe(); 101 | 102 | /* Restore our own hooks */ 103 | __malloc_hook = my_malloc_hook; 104 | __free_hook = my_free_hook; 105 | return result; 106 | } 107 | 108 | static void my_free_hook(void *ptr, const void *caller) 109 | { 110 | /* Restore all old hooks */ 111 | __malloc_hook = old_malloc_hook; 112 | __free_hook = old_free_hook; 113 | /* Call recursively */ 114 | free(ptr); 115 | /* we call malloc here, so protect it too. */ 116 | //printf ("freed pointer %p\n", ptr); 117 | if(malloced.find(ptr) == malloced.end()) 118 | { 119 | /*fprintf(stderr, "INVALID FREE\n"); 120 | void *trace[16]; 121 | int trace_size = 0; 122 | trace_size = backtrace(trace, 16); 123 | backtrace_symbols_fd(trace, trace_size, STDERR_FILENO);*/ 124 | } 125 | else 126 | malloced.erase(ptr); 127 | /* Restore our own hooks */ 128 | __malloc_hook = my_malloc_hook; 129 | __free_hook = my_free_hook; 130 | } 131 | 132 | void memtracing_init() 133 | { 134 | old_malloc_hook = __malloc_hook; 135 | old_free_hook = __free_hook; 136 | __malloc_hook = my_malloc_hook; 137 | __free_hook = my_free_hook; 138 | } 139 | 140 | long gethash(void **trace) 141 | { 142 | unsigned long hash = 0; 143 | while(*trace) 144 | { 145 | hash = (hash << 1) ^ (hash >> 63) ^ (unsigned long)*trace; 146 | trace++; 147 | } 148 | return hash; 149 | } 150 | 151 | void memtracing_kill() 152 | { 153 | /* Restore all old hooks */ 154 | __malloc_hook = old_malloc_hook; 155 | __free_hook = old_free_hook; 156 | 157 | map hashToReal; 158 | map counts; 159 | map::iterator it = malloced.begin(); 160 | while(it != malloced.end()) 161 | { 162 | long hash = gethash(it->second); 163 | hashToReal[hash] = it->second; 164 | 165 | if(counts.find(hash) == counts.end()) 166 | counts[hash] = 1; 167 | else 168 | counts[hash]++; 169 | 170 | it++; 171 | } 172 | 173 | vector > sorting; 174 | map::iterator countit = counts.begin(); 175 | while(countit != counts.end()) 176 | { 177 | sorting.push_back(pair(countit->second, countit->first)); 178 | countit++; 179 | } 180 | 181 | // sort 182 | bool done = false; 183 | while(!done) 184 | { 185 | done = true; 186 | for(int i = 0; i < sorting.size() - 1; i++) 187 | { 188 | if(sorting[i].first < sorting[i + 1].first) 189 | { 190 | pair t = sorting[i]; 191 | sorting[i] = sorting[i + 1]; 192 | sorting[i + 1] = t; 193 | done = false; 194 | } 195 | } 196 | } 197 | 198 | 199 | for(int i = 0; i < sorting.size(); i++) 200 | { 201 | long hash = sorting[i].second; 202 | int count = sorting[i].first; 203 | char header[256]; 204 | sprintf(header, "--------------------------- LEAKED %d", count); 205 | print_stackframe(header, hashToReal[hash]); 206 | } 207 | } 208 | #endif // INSANE_MEMORY_DEBUG 209 | 210 | bool run_test(const char *filename) 211 | { 212 | printf("TEST %s ", filename); 213 | struct stat results; 214 | if(!stat(filename, &results) == 0) 215 | { 216 | printf("Cannot stat file! '%s'\n", filename); 217 | return false; 218 | } 219 | int size = results.st_size; 220 | FILE *file = fopen(filename, "rb"); 221 | /* if we open as text, the number of bytes read may be > the size we read */ 222 | if(!file) 223 | { 224 | printf("Unable to open file! '%s'\n", filename); 225 | return false; 226 | } 227 | char *buffer = new char[size + 1]; 228 | long actualRead = fread(buffer, 1, size, file); 229 | buffer[actualRead] = 0; 230 | buffer[size] = 0; 231 | fclose(file); 232 | 233 | CTinyJS s; 234 | registerFunctions(&s); 235 | registerMathFunctions(&s); 236 | s.root->addChild("result", new CScriptVar("0", SCRIPTVAR_INTEGER)); 237 | try 238 | { 239 | s.execute(buffer); 240 | } 241 | catch(CScriptException *e) 242 | { 243 | printf("ERROR: %s\n", e->text.c_str()); 244 | delete e; 245 | } 246 | bool pass = s.root->getParameter("result")->getBool(); 247 | 248 | if(pass) 249 | printf("PASS\n"); 250 | else 251 | { 252 | char fn[64]; 253 | sprintf(fn, "%s.fail.js", filename); 254 | FILE *f = fopen(fn, "wt"); 255 | if(f) 256 | { 257 | std::ostringstream symbols; 258 | s.root->getJSON(symbols); 259 | fprintf(f, "%s", symbols.str().c_str()); 260 | fclose(f); 261 | } 262 | 263 | printf("FAIL - symbols written to %s\n", fn); 264 | } 265 | 266 | delete[] buffer; 267 | return pass; 268 | } 269 | 270 | int main(int argc, char **argv) 271 | { 272 | #ifdef MTRACE 273 | mtrace(); 274 | #endif 275 | #ifdef INSANE_MEMORY_DEBUG 276 | memtracing_init(); 277 | #endif 278 | printf("TinyJS test runner\n"); 279 | printf("USAGE:\n"); 280 | printf(" ./run_tests test.js : run just one test\n"); 281 | printf(" ./run_tests : run all tests\n"); 282 | if(argc == 2) 283 | { 284 | return !run_test(argv[1]); 285 | } 286 | 287 | int test_num = 1; 288 | int count = 0; 289 | int passed = 0; 290 | 291 | while(test_num < 1000) 292 | { 293 | char fn[32]; 294 | sprintf(fn, "tests/test%03d.js", test_num); 295 | // check if the file exists - if not, assume we're at the end of our tests 296 | FILE *f = fopen(fn, "r"); 297 | if(!f) break; 298 | fclose(f); 299 | 300 | if(run_test(fn)) 301 | passed++; 302 | count++; 303 | test_num++; 304 | } 305 | 306 | printf("Done. %d tests, %d pass, %d fail\n", count, passed, count - passed); 307 | #ifdef INSANE_MEMORY_DEBUG 308 | memtracing_kill(); 309 | #endif 310 | 311 | #ifdef _DEBUG 312 | #ifdef _WIN32 313 | _CrtDumpMemoryLeaks(); 314 | #endif 315 | #endif 316 | #ifdef MTRACE 317 | muntrace(); 318 | #endif 319 | 320 | return 0; 321 | } 322 | -------------------------------------------------------------------------------- /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/profiling/test001.js: -------------------------------------------------------------------------------- 1 | // fibonacci numbers 2 | 3 | function fib(n) { 4 | var current, last = 0, penult = 1; 5 | for(var i = 0; i < n; i++) { 6 | current = last + penult; 7 | penult = last; 8 | last = current; 9 | } 10 | return current; 11 | } 12 | 13 | function init() { 14 | NUM_ITERATIONS = 50; 15 | MAX_FIB = 1000; 16 | } 17 | 18 | function get_iterations() { return NUM_ITERATIONS; } 19 | function get_arg_list() { return "MAX_FIB"; } 20 | function get_function_name() { return "fib"; } 21 | 22 | function setup() { } -------------------------------------------------------------------------------- /tests/profiling/test002.js: -------------------------------------------------------------------------------- 1 | // factorial 2 | 3 | function factorial(n) { 4 | var total = 1; 5 | for(var i = 1; i <= n; i++) { 6 | total = total * i; 7 | } 8 | return total; 9 | } 10 | 11 | function init() { 12 | NUM_ITERATIONS = 50; 13 | MAX_FACT = 1000; 14 | } 15 | 16 | function get_iterations() { return NUM_ITERATIONS; } 17 | function get_arg_list() { return "MAX_FACT"; } 18 | function get_function_name() { return "factorial"; } 19 | 20 | function setup() { } -------------------------------------------------------------------------------- /tests/profiling/test003.js: -------------------------------------------------------------------------------- 1 | // Bubble sort 2 | 3 | function sort(array) { 4 | var a_len = array.length; 5 | for(var i = 0; i < a_len-1; i++) { 6 | for(var j = i; j < a_len; j++) { 7 | if(array[i] > array[j]) { 8 | var temp = array[j]; 9 | array[j] = array[i]; 10 | array[i] = temp; 11 | } 12 | } 13 | } 14 | return array; 15 | } 16 | 17 | function create() { 18 | var a = []; 19 | for(var i = 0; i < NUM_ELEMENTS; i++) 20 | a[i] = Math.rand(); 21 | return a; 22 | } 23 | 24 | function init() { 25 | NUM_ITERATIONS = 30; 26 | NUM_ELEMENTS = 500; 27 | FUNCTION_TO_PROFILE = "sort"; 28 | } 29 | 30 | function get_iterations() { return NUM_ITERATIONS; } 31 | function get_arg_list() { return "ARRAY"; } 32 | function get_function_name() { return "sort"; } 33 | 34 | function setup() { 35 | ARRAY = create(); 36 | } -------------------------------------------------------------------------------- /tests/profiling/test004.js: -------------------------------------------------------------------------------- 1 | // naive smoothing 2 | 3 | function smooth(image) { 4 | var output = []; 5 | var image_length = image.length; 6 | for(var i = 0; i < image_length; i++) { 7 | output[i] = []; 8 | var ii_max = i+1 < image[i].length ? i+1 : image[i].length; 9 | var j_max = image[i].length; 10 | for(var j = 0; j < j_max; j++) { 11 | var jj_max = Math.min(j+1, image[i][j].length); 12 | var result = { r: 0, g: 0, b: 0 }; 13 | for(var ii = Math.max(0, i-1); ii < ii_max; ii++) { 14 | for(var jj = Math.max(0, j-1); jj < jj_max; jj++) { 15 | result.r += image[i][j].r; 16 | result.g += image[i][j].g; 17 | result.b += image[i][j].b; 18 | } 19 | } 20 | result.r = result.r / 9; 21 | result.g = result.g / 9; 22 | result.b = result.b / 9; 23 | output[i][j] = result; 24 | } 25 | } 26 | return output; 27 | } 28 | 29 | function init() { 30 | NUM_ITERATIONS = 2; 31 | IMAGE_WIDTH = 100; 32 | IMAGE_HEIGHT = 100; 33 | } 34 | 35 | function create() { 36 | var output = []; 37 | for(var i = 0; i < IMAGE_WIDTH; i++) { 38 | output[i] = []; 39 | for(var j = 0; j < IMAGE_HEIGHT; j++) 40 | output[i][j] = { 41 | r: Math.randInt(0, 255), 42 | g: Math.randInt(0, 255), 43 | b: Math.randInt(0, 255) 44 | }; 45 | } 46 | return output; 47 | } 48 | 49 | function get_iterations() { return NUM_ITERATIONS; } 50 | function get_arg_list() { return "IMAGE"; } 51 | function get_function_name() { return "smooth"; } 52 | 53 | function setup() { 54 | IMAGE = create(); 55 | } -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------