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