UNPKG

adder-script

Version:

Python like language to execute untrusted codes in browsers and Node.js.

1,196 lines (1,018 loc) 84.2 kB
// get the global object (either for browser or node.js) var _window = typeof window === "undefined" ? global : window; // for tests in nodejs if (typeof require != 'undefined') { if (typeof _window.AdderScript == 'undefined') { _window.AdderScript = require("./../dist/node-adder"); } } // like parseFloat, but returns NaN if contain english characters function strictParseFloat(value) { if (typeof value !== "string" && typeof value !== "number") {return NaN;} if(/^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/ .test(value)) return Number(value); return NaN; } // supported operators var operators = ["+=", "-=", "*=", "/=", "|=", "&=", "%=", "**", "==", "!=", ">=", "<=", ">", "<", "+", "-", "*", "/", "|", "&", "%", "=", "."]; var wordsOperators = ["is not", "not in", "not", "in", "or", "and", "is"]; operators = operators.concat(wordsOperators); // return if value is a number function isNumber(value) { return (!isNaN(strictParseFloat(value))); } // execute code and return [lastStatementCode, lastValue] function executeAndReturn(code) { // create interpreter var interpreter = new AdderScript._internals.Interpreter({throwErrors: true}); // to disable print() output interpreter.output = function() {}; // load all modules interpreter.addModule("ALL"); // on browsers only, connect output (on node.js we don't want it it will spam results) if (typeof window !== "undefined") { AdderScript._internals.Console.bindToNativeConsole(); } // execute interpreter.eval(code); interpreter.propagateExecutionErrors(); var ret = interpreter.getLastValue(); // make sure scope is back to global if (interpreter._context._stack.length !== 1) { throw "Context is not back to global scope!"; } // sanity test - run again to make sure second time we got the same value interpreter.execute(); var ret2 = interpreter.getLastValue(); if (ret.isSimple && ret.getValue() !== ret2.getValue()) { throw "Different return value on second run (" + ret.getValue() + ", " + ret2.getValue() + ")!"; } // get last statement var statement = interpreter.getLastStatement(); statement = statement ? statement.identifier : null // parse return value ret = ret && ret.getValue ? ret._value : ret; if (ret instanceof Array) {ret = ret.map(function(x) {return x ? x._value : x;});}; // return return [statement, ret]; } // convert return value into native js function toNativeJs(val) { // get value from variable val = val && val.getValue ? val._value : val; // handle list if (val && val.constructor == AdderScript._internals.Core.List) { val = val._list; return val.map(function(x) { return toNativeJs(x); }) } // handle set if (val && val.constructor == AdderScript._internals.Core.Set) { val = val._set; var ret = new Set(); val.forEach(function(x) { ret.add(toNativeJs(x)); }); return ret; } // handle dictionary if (val && val.constructor == AdderScript._internals.Core.Dict) { val = val._dict; var ret = {}; for (var key in val) { ret[key] = toNativeJs(val[key]); } return ret; } // handle plain variable return val; } // use assert.deepEqual or assert.strictEqual based on expected type function smartCompare(assert, ret, expected) { // special case - check if NaN if (isNaN(expected) && typeof expected === "number") { assert.ok(isNaN(ret) && typeof(ret) === "number"); } // check result for list else if ((expected !== null) && (expected !== undefined) && (expected instanceof Array || expected instanceof Set || expected.constructor == Object)) { assert.deepEqual(ret, expected); } // check result for plain objects else { assert.strictEqual(ret, expected); } } // execute code and check returned value // processResults - if provided will run this function on result and on expected function testReturnValue(assert, code, expected, processResults) { // execute and get value var ret = executeAndReturn(code)[1]; // convert return value to plain js object ret = toNativeJs(ret); // run result process if (processResults) { ret = processResults(ret); expected = processResults(expected); } // check return value smartCompare(assert, ret, expected); } // round result to few digits after dot function roundMathResult(res) { if (typeof res !== "number") {return res;} return Math.round(res * 100000.0) / 100000.0; } // execute arithmetic expression and compare rounded results function compareExpression(assert, code, expected) { testReturnValue(assert, code, expected, roundMathResult) } function listToTokensList(expression) { for (var i = 0; i < expression.length; ++i) { var curr = expression[i]; if (curr === "," || curr === ":") {curr = {t: "p", "v": curr};} else if (operators.indexOf(curr) !== -1) {curr = {t: "o", "v": curr};} else if (isNumber(curr)) {curr = {t: "n", "v": curr};} else if (curr[0] === '"' && curr[curr.length-1] === '"') {curr = {t: "s", "v": curr};} else if (curr === "(" || curr === ")") {curr = {t: "o", "v": curr};} else if (curr === "\n" || curr === ";") {curr = {t: "b", "v": curr};} else if (curr[0] === '~') {curr = {t: "_", "v": parseInt(curr[2])};} else {curr = {t: "v", "v": curr};} expression[i] = curr; } return expression; } // parse utils - parse expression QUnit.test("lexer", function( assert ) { // create lexer var lexer = new AdderScript._internals.Lexer(); // check basic expression assert.deepEqual(lexer.parseExpression( "5 + 5"), listToTokensList(["5", "+", "5"])); // with line break assert.deepEqual(lexer.parseExpression( "5 + 5\na"), listToTokensList(["5", "+", "5", "\n", "a"])); assert.deepEqual(lexer.parseExpression( "5 + 5;a"), listToTokensList(["5", "+", "5", ";", "a"])); assert.deepEqual(lexer.parseExpression( "5 + 5\\\na"), listToTokensList(["5", "+", "5", "a"])); // with different blocks assert.deepEqual(lexer.parseExpression( "5 + 5\n a;a"), listToTokensList(["5", "+", "5", "\n", "~:1", "a", ";", "a"])); assert.deepEqual(lexer.parseExpression( "5 + 5; a"), listToTokensList(["5", "+", "5", ";", "a"])); assert.deepEqual(lexer.parseExpression( "5 + 5\\\n a"), listToTokensList(["5", "+", "5", "a"])); // with comment assert.deepEqual(lexer.parseExpression( "5 + 5 # comment lol"), listToTokensList(["5", "+", "5"])); assert.deepEqual(lexer.parseExpression( "5 + 5#comment lol"), listToTokensList(["5", "+", "5"])); assert.deepEqual(lexer.parseExpression( '5 + 5 + "not # comment"'), listToTokensList(["5", "+", "5", "+", '"not # comment"'])); // some statements assert.deepEqual(lexer.parseExpression( 'print "test"'), listToTokensList(["print", '"test"'])); assert.deepEqual(lexer.parseExpression( 'if True == a:'), listToTokensList(["if", "True", "==", "a", ":"])); assert.deepEqual(lexer.parseExpression( 'for i in range(50)'), listToTokensList(["for", "i", "in", "range", "(", "50", ")"])); // check more complicated expression assert.deepEqual(lexer.parseExpression( "5 * 2 / 4 + 1 - 5 / 1.1 + 2.2 * 1.4 + -1"), listToTokensList(["5", "*", "2", "/", "4", "+", "1", "-", "5", "/", "1.1", "+", "2.2", "*", "1.4", "+", "-", "1"])); // check tricky spaces and tabs assert.deepEqual(lexer.parseExpression( "5 * 2 / 4 + 1 - 5 / 1.1 + 2.2 * 1.4 + -1"), listToTokensList(["5", "*", "2", "/", "4", "+", "1", "-", "5", "/", "1.1", "+", "2.2", "*", "1.4", "+", "-", "1"])); // check brackets assert.deepEqual(lexer.parseExpression( "5 * 2 + ( 6 +4)"), listToTokensList(["5", "*", "2", "+", "(", "6", "+", "4", ")"])); assert.deepEqual(lexer.parseExpression( "5 * 2 + ( 6 +(2-4))"), listToTokensList(["5", "*", "2", "+", "(", "6", "+", "(", "2", "-", "4", ")", ")"])); assert.deepEqual(lexer.parseExpression( "(5 * 2) + ( 6 +(2-4))"), listToTokensList(["(", "5", "*", "2", ")", "+", "(", "6", "+", "(", "2","-","4", ")", ")"])); assert.deepEqual(lexer.parseExpression( "((5 * 2))"), listToTokensList(["(", "(", "5", "*", "2", ")", ")"])); // check with strings assert.deepEqual(lexer.parseExpression( '"5 + 2"'), listToTokensList(['"5 + 2"'])); assert.deepEqual(lexer.parseExpression( '"5 \n 2"'), listToTokensList(['"5 \n 2"'])); assert.deepEqual(lexer.parseExpression( '"5 \\ \r \n 2"'), listToTokensList(['"5 \\ \r \n 2"'])); assert.deepEqual(lexer.parseExpression( '"5 + 2" + "" + 5'), listToTokensList(['"5 + 2"', "+", '""', "+", "5"])); assert.deepEqual(lexer.parseExpression( '5 + "5 +, 2" * 2'), listToTokensList(["5", "+", '"5 +, 2"', "*", "2"])); assert.deepEqual(lexer.parseExpression( '5 + " (5 + #, 2" * 2'), listToTokensList(["5", "+", '" (5 + #, 2"', "*", "2"])); assert.deepEqual(lexer.parseExpression( '5 + (" (5 + #, 2 )") * 2'), listToTokensList(["5", "+", "(", '" (5 + #, 2 )"', ")", "*", "2"])); // long, complex expression assert.deepEqual(lexer.parseExpression("(5 + 2) + foo(5)(2)*foo(4)+5*2"), listToTokensList(["(", "5","+","2", ")","+","foo","(", "5", ")", "(", "2", ")","*","foo","(", "4", ")","+","5","*","2"])); // try weird yet legal stuff assert.deepEqual(lexer.parseExpression("5 +- 2"), listToTokensList(["5", "+", "-", "2"])); assert.deepEqual(lexer.parseExpression("5 -+ 2"), listToTokensList(["5", "-", "+", "2"])); assert.deepEqual(lexer.parseExpression("5 *+ 2"), listToTokensList(["5", "*", "+", "2"])); assert.deepEqual(lexer.parseExpression("5 * +2"), listToTokensList(["5", "*", "+", "2"])); assert.deepEqual(lexer.parseExpression("5 / -2"), listToTokensList(["5", "/", "-", "2"])); assert.deepEqual(lexer.parseExpression("5 + - + + -2"), listToTokensList(["5", "+", "-", "+", "+", "-", "2"])); assert.deepEqual(lexer.parseExpression("5 +-+ 2"), listToTokensList(["5", "+", "-", "+", "2"])); assert.deepEqual(lexer.parseExpression("5 * + + 2"), listToTokensList(["5", "*", "+", "+", "2"])); // function calls assert.deepEqual(lexer.parseExpression("5 + func()"), listToTokensList(["5", "+", "func", "(", ")"])); assert.deepEqual(lexer.parseExpression("5 + func(2 * 3)"), listToTokensList(["5", "+", "func", "(", '2', '*', '3', ")"])); assert.deepEqual(lexer.parseExpression("5 + func(aa)"), listToTokensList(["5", "+", "func", "(", 'aa', ")"])); // check using variables assert.deepEqual(lexer.parseExpression("5 / test-2"), listToTokensList(["5", "/", "test", "-", "2"])); assert.deepEqual(lexer.parseExpression("5 / test5 -2"), listToTokensList(["5", "/", "test5", "-", "2"])); // check some illegal stuff assert.throws(function(){lexer.parseExpression("6 + 0test - 4")}, AdderScript.Errors.IllegalExpression); assert.throws(function(){lexer.parseExpression("6 + 5test - 4")}, AdderScript.Errors.IllegalExpression); assert.throws(function(){lexer.parseExpression("6 + 9test - 4")}, AdderScript.Errors.IllegalExpression); assert.throws(function(){lexer.parseExpression('4 + "bla')}, AdderScript.Errors.IllegalExpression); }); // test parser and AST QUnit.test("parser", function( assert ) { // create parser var lexer = new AdderScript._internals.Lexer(); // remove undefined values by json dumps-loads function cleanup(data) { return JSON.parse(JSON.stringify(data)); } assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression("5 + 2"))), [{"type":"+","left":{"type":"number","value":"5"},"right":{"type":"number","value":"2"}}]); assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression("5 + 2 * 1"))), [{"left": {"type": "number","value": "5"},"right": {"left": {"type": "number","value": "2"},"right": {"type": "number","value": "1"},"type": "*"},"type": "+"}]); assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression("func(2,3)"))), [{"args":[{"type":"number","value":"2"},{"type":"number","value":"3"}],"name":"func","type":"call"}]); assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression('5 + "2"'))), [{"type":"+","left":{"type":"number","value":"5"},"right":{"type":"string","value":'"2"'}}]); }); // pass statement QUnit.test("statement_pass", function( assert ) { // check legal cases assert.deepEqual(executeAndReturn("pass"), ["builtin.statements.pass", null]); assert.deepEqual(executeAndReturn("pass\n"), ["builtin.statements.pass", null]); // these are not a pass statement but evaluations.. assert.throws(function(){executeAndReturn("pass + NOT")}, AdderScript.Errors.UndefinedVariable); assert.throws(function(){executeAndReturn("passover")}, AdderScript.Errors.UndefinedVariable); }); // comments QUnit.test("statement_comment", function( assert ) { var compiler = new AdderScript._internals.Compiler(); // check legal cases assert.deepEqual(executeAndReturn("# foo"), [null, null]); assert.deepEqual(executeAndReturn("#foo"), [null, null]); }); // strings QUnit.test("strings", function( assert ) { // quotes with " compareExpression(assert, 'a = "test"', "test"); compareExpression(assert, 'a = "te;st"', "te;st"); compareExpression(assert, 'a = "te; \n \t ; # st"', "te; \n \t ; # st"); compareExpression(assert, 'a = "te; \n \' \' \t ; # st"', "te; \n ' ' \t ; # st"); // quotes with ' compareExpression(assert, "a = 'test'", "test"); compareExpression(assert, "a = 'te;st'", "te;st"); compareExpression(assert, "a = 'te; \n \t ; # st'", "te; \n \t ; # st"); compareExpression(assert, "a = 'te; \n \" \" \t ; # st'", "te; \n \" \" \t ; # st"); }); // compiler - compile def QUnit.test("statement_def", function( assert ) { // check legal cases assert.equal(executeAndReturn("def test(): pass")[0], "builtin.statements.def"); assert.equal(executeAndReturn("def test (\t ) : pass")[0], "builtin.statements.def"); assert.equal(executeAndReturn("def test(foo2): pass")[0], "builtin.statements.def"); assert.equal(executeAndReturn("def test($foo_2): pass")[0], "builtin.statements.def"); assert.equal(executeAndReturn("def test(foo): pass")[0], "builtin.statements.def"); assert.equal(executeAndReturn("def test(__foo): pass# just a comment")[0], "builtin.statements.def"); assert.equal(executeAndReturn("def test(\tfoo ): pass")[0], "builtin.statements.def"); assert.equal(executeAndReturn('def test(\tfoo, bar ): pass')[0], "builtin.statements.def"); // check syntax errors assert.throws(function(){executeAndReturn("def (): pass")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn("defoo test(): pass")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn("def test ()")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn("def test (: pass")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn("def test ):")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn("def test (#):")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn("def test () #:")}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('def test(\tfoo, "bar" ):')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('def test(\tfoo, "bar" + "gaga," ):')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('def test(1+2-3*4/5.0, 6 ):')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('def test(foo, 5):')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('def test(5):')}, AdderScript.Errors.SyntaxError); }); // defining and using functions QUnit.test("functions", function( assert ) { // basic functions calls compareExpression(assert, "def test(): pass; test()", null); compareExpression(assert, "def test(): return 15; test()", 15); compareExpression(assert, "a = 5; def test(): return a; test()", 5); compareExpression(assert, "a = 5; def test(): return a+=1; test()", 6); compareExpression(assert, "a = 5; def test(): return a+=1; test(); a", 6); compareExpression(assert, "a = 5; b = 2; def test1(): return a; def test2(): return test1() + b; test2()", 5+2); compareExpression(assert, "def test(a): return 15 + a; test(2)", 15 + 2); compareExpression(assert, "def test(a): return 15 + a; test(2); test(3)", 15 + 3); compareExpression(assert, "a = 0; def test(): return a; a", 0); // try function with block var code = "" + "def func(a,b,c):\n" + " return a + b + c\n" + "\n" + "func(1,2,3)\n"; compareExpression(assert, code, 1+2+3); // function inside function! var code = "" + "def func(a,b,c):\n" + " def internal(): return 5\n" + " return a + b + c + internal()\n" + "func(1,2,3)\n"; compareExpression(assert, code, 1+2+3+5); // function inside function with block body! var code = "" + "def func(a,b,c):\n" + " def internal():\n" + " return 5\n" + " return a + b + c + internal()\n" + "func(1,2,3)\n"; compareExpression(assert, code, 1+2+3+5); // function that calls another function and return value var code = "" + "def test2():\n" + " return 5\n" + "def test():\n" + " return test2()\n" + "test()\n"; compareExpression(assert, code, 5); // function that calls another function but note that the wrapping function "forgets" to add return, so should return None var code = "" + "def test2():\n" + " return 5\n" + "def test():\n" + " test2() # missing return statement here \n" + "test()\n"; compareExpression(assert, code, null); }); // basic expressions evaluation QUnit.test("basic_expressions", function( assert ) { // test basic expressions compareExpression(assert, "5", 5); compareExpression(assert, "5 + 5", 5 + 5); compareExpression(assert, "5 + -5", 5 + -5); compareExpression(assert, "-5 + -5", -5 + -5); compareExpression(assert, "5 + 5 * 2", 5 + 5 * 2); compareExpression(assert, "5 + 5 * 2 / 5 ", 5 + 5 * 2 / 5); compareExpression(assert, "5 + 5 * 2 / 5 + (2+5/2) * 3", 5 + 5 * 2 / 5 + (2+5/2) * 3); compareExpression(assert, "5 + -5 * -2 / -5 + (2+5/-2) * 3", 5 + -5 * -2 / -5 + (2+5/-2) * 3); compareExpression(assert, "-5.2 + - - + - - + 5.1 * - -2.6 / (2+3.0) -+5 + (2+\t5/-2) * 3", -5.2 + - - + - - + 5.1 * - -2.6 / (2+3.0) -+5 + (2+ 5/-2) * 3); compareExpression(assert, "5 - 3 + 52 / 2.1 * 5.2 + 2 - 4 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2", 5 - 3 + 52 / 2.1 * 5.2 + 2 - 4 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2); compareExpression(assert, "4 + 63 | 52", 4 + 63 | 52); compareExpression(assert, "4 + 63 & 52", 4 + 63 & 52); compareExpression(assert, "4 + 63 % 52", 4 + 63 % 52); compareExpression(assert, "3 | 5 & 2 * 54 % 23 * 21.2", 3 | 5 & 2 * 54 % 23 * 21.2); compareExpression(assert, "52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2", 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2); compareExpression(assert, "5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2", 5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2); compareExpression(assert, "a = 5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2; a", 5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2); // now do expressions with functions calls _window.__adder_script_test = function() {return 5.2;}; var _sec_test = _window.__adder_script_test; compareExpression(assert, "_sec_test()", _sec_test()); compareExpression(assert, "5 + 5 - _sec_test()", 5 + 5 - _sec_test()); compareExpression(assert, "5 + -_sec_test()", 5 + -_sec_test()); compareExpression(assert, "-5 + -_sec_test()", -5 + -_sec_test()); compareExpression(assert, "5 + _sec_test() * 2", 5 + _sec_test() * 2); compareExpression(assert, "5 + 5 * _sec_test() / 5 ", 5 + 5 * _sec_test() / 5); compareExpression(assert, "5 + 5 * _sec_test() / 5 + (2+5/_sec_test()) * 3", 5 + 5 * _sec_test() / 5 + (2+5/_sec_test()) * 3); compareExpression(assert, "5 + -5 * -2 / -5 + (2+5/-_sec_test()) * 3", 5 + -5 * -2 / -5 + (2+5/-_sec_test()) * 3); compareExpression(assert, "-_sec_test() + - - + - - + 5.1 * - -_sec_test() / (2+3.0) -+5 + (2+\t5/-2) * 3", -_sec_test() + - - + - - + 5.1 * - -_sec_test() / (2+3.0) -+5 + (2+ 5/-2) * 3); // with variables compareExpression(assert, "a = 5; a + 5", 5 + 5); compareExpression(assert, "a = 5; b = 2; a + b", 5 + 2); var a = 5; var b = 6; var c = 2.3; compareExpression(assert, "a = 5; b = 6; a + 5 * b / 5 + (2+a/2) * 3", a + 5 * b / 5 + (2+a/2) * 3); compareExpression(assert, "a = 5; b = 6; a + -a * -b / -5 + (a+5/-b) * a", a + -a * -b / -5 + (a+5/-b) * a); compareExpression(assert, "a = 5; b = 6; c = 2.3; -c + - - + - - + a * - -b / (2+a) -+c + (2+\t5/-2) * 3", -c + - - + - - + a * - -b / (2+a) -+c + (2+ 5/-2) * 3); compareExpression(assert, "a = 5; b = 6; c = 2.3; a = a | b & c; a", a | b & c); // now with a function that gets param _window.__adder_script_test = function(x) {return x;}; var _sec_test = _window.__adder_script_test; compareExpression(assert, "_sec_test(5)", _sec_test(5)); compareExpression(assert, "5 + 5 - _sec_test(5)", 5 + 5 - _sec_test(5)); }); // test all operators QUnit.test("operators", function( assert ) { // just a reminder: // ["+=", "-=", "*=", "/=", "|=", "&=", "%=", "**", "==", "!=", ">", "<", ">=", "<=", // "+", "-", "*", "/", "|", "&", "%", "=", "not", "in", "or", "and"]; // test all the following: "+=", "-=", "*=", "/=", "|=", "&=", "%=" var ops = ["+=", "-=", "*=", "/=", "|=", "&=", "%="]; for (var i = 0; i < ops.length; ++i) { var op = ops[i]; var expected = eval("var d = 5; d " + op + " 2; d"); compareExpression(assert, "d = 5; d " + op + " 2; d", expected); } // test ** operator compareExpression(assert, "3 ** 2", Math.pow(3, 2)); // test "==", "!=", ">", "<", ">=", "<=" compareExpression(assert, "3 == 3", true); compareExpression(assert, "3 == 2", false); compareExpression(assert, "3 != 2", true); compareExpression(assert, "3 != 3", false); compareExpression(assert, "3 > 1", true); compareExpression(assert, "3 > 3", false); compareExpression(assert, "3 > 5", false); compareExpression(assert, "3 < 1", false); compareExpression(assert, "3 < 3", false); compareExpression(assert, "3 < 5", true); compareExpression(assert, "3 >= 3", true); compareExpression(assert, "3 >= 5", false); compareExpression(assert, "3 <= 1", false); compareExpression(assert, "3 <= 3", true); compareExpression(assert, "3 <= 5", true); // test the basics: +, -, *, /, |, &, % compareExpression(assert, "3 + 2", 3 + 2); compareExpression(assert, "3 - 2", 3 - 2); compareExpression(assert, "3 * 2", 3 * 2); compareExpression(assert, "3 / 2", 3 / 2); compareExpression(assert, "3 + 2.1", 3 + 2.1); compareExpression(assert, "3 - 2.1", 3 - 2.1); compareExpression(assert, "3 * 2.1", 3 * 2.1); compareExpression(assert, "3 / 2.1", 3 / 2.1); compareExpression(assert, "3 | 2", 3 | 2); compareExpression(assert, "3 & 2", 3 & 2); compareExpression(assert, "3 % 2", 3 % 2); // check basic assignment compareExpression(assert, "a = 2; a", 2); // test 'not' compareExpression(assert, "not True", false); compareExpression(assert, "not False", true); compareExpression(assert, "not 5", false); compareExpression(assert, "not 0", true); // test 'or' compareExpression(assert, "0 or 5", 5); compareExpression(assert, "5 or 0", 5); compareExpression(assert, "True or False", true); compareExpression(assert, "False or True", true); compareExpression(assert, "False or None or 5 or 10", 5); compareExpression(assert, "a = False or None or 5 or 10; a", 5); compareExpression(assert, '"aa" or False', "aa"); compareExpression(assert, 'True or "aa" or False', true); compareExpression(assert, '0 or False', false); compareExpression(assert, 'a = False and None and "yay!" or True; a', true); compareExpression(assert, 'a = False or None and "yay!" and True; a', null); // test 'and' compareExpression(assert, "0 and 5", 0); compareExpression(assert, "5 and 0", 0); compareExpression(assert, "True and False", false); compareExpression(assert, "False and True", false); compareExpression(assert, '"aa" and False', false); compareExpression(assert, 'True and "aa" and False', false); compareExpression(assert, '1 and True', true); // test 'in' compareExpression(assert, '1 in 100', true); compareExpression(assert, '1 in 430', false); compareExpression(assert, '"a" in "abcd"', true); compareExpression(assert, '"z" in "abcd"', false); compareExpression(assert, '"a" not in "abcd"', false); compareExpression(assert, '"z" not in "abcd"', true); compareExpression(assert, '"a" in list("a", "b", "c")', true); compareExpression(assert, '"z" in list("a", "b", "c")', false); compareExpression(assert, '1 in list(1,2,3)', true); compareExpression(assert, '5 in list(1,2,3)', false); compareExpression(assert, '"1" in list(1,2,3)', false); compareExpression(assert, '"True" in dir()', true); compareExpression(assert, '"bla bla" in dir()', false); // test 'is' compareExpression(assert, '1 is 1', true); compareExpression(assert, 'True is True', true); compareExpression(assert, 'True is False', false); compareExpression(assert, 'None is False', false); compareExpression(assert, 'list() is list()', false); compareExpression(assert, 'list() is not list()', true); compareExpression(assert, 'True is not True', false); compareExpression(assert, 'True is not False', true); }); // test for loops QUnit.test("loop_while", function( assert ) { // legal values compareExpression(assert, 'a = ""; letters = list("t", "e", "s", "t"); while letters.len(): a += letters.shift(); a', "test"); compareExpression(assert, 'a = ""; letters = list("t", "e", "s", "t"); while False: a += letters.shift(); a', ""); compareExpression(assert, 'a = 0; while a < 10: a += 1; a', 10); compareExpression(assert, 'a = 0; while a < 10 and False: a += 1; a', 0); // invalid syntax assert.throws(function(){executeAndReturn('while pass')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('while True pass')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('while True False: pass')}, AdderScript.Errors.SyntaxError); }); // test for loops QUnit.test("loop_for", function( assert ) { // legal values compareExpression(assert, 'a = ""; for i in "test": a += i; a', "test"); compareExpression(assert, 'a = 0; for i in range(10): a += i; a', 1+2+3+4+5+6+7+8+9); compareExpression(assert, 'a = 0; for i in range(10): continue; a', 0); compareExpression(assert, 'a = 0; for i in range(10): break; a', 0); compareExpression(assert, 'a = 0; def test(): a+=1; for i in range(10): test(); a', 10); compareExpression(assert, 'a = 1; for i in range(10): pass; a', 1); // invalid syntax assert.throws(function(){executeAndReturn('a = 5; for 5 in "test": pass')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a = 5; for "a" in "test": pass')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a = 5; for vg in "test" pass')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a = 5; for vg + 5 in "test": pass')}, AdderScript.Errors.SyntaxError); }); // test loop continue QUnit.test("loop_continue", function( assert ) { // continue to skip odd values var code = "" + "a = 0;\n" + "for i in range(10):\n" + " if i % 2 == 0: continue\n" + " a += 1\n" + "a"; compareExpression(assert, code, 5); // continue to skip all var code = "" + "a = 0;\n" + "for i in range(10):\n" + " if True: continue\n" + " a += 1\n" + "a"; compareExpression(assert, code, 0); // continue to skip odd values var code = "" + "a = 0; i = 0;\n" + "while i < 10:\n" + " i += 1\n" + " if i % 2 == 0: continue\n" + " a += 1\n" + "a"; compareExpression(assert, code, 5); // continue to skip all var code = "" + "a = 0; i = 0;\n" + "while i < 10:\n" + " i += 1\n" + " if True: continue\n" + " a += 1\n" + "a"; compareExpression(assert, code, 0); // invalid syntax - using continue / break outside loops assert.throws(function(){executeAndReturn('a = 5; continue;')}, AdderScript.Errors.RuntimeError); }); // test loop break QUnit.test("loop_break", function( assert ) { // continue to skip odd values var code = "" + "a = 0;\n" + "for i in range(10):\n" + " if i == 5: break\n" + " a += 1\n" + "a"; compareExpression(assert, code, 5); // continue to skip all var code = "" + "a = 0;\n" + "for i in range(10):\n" + " if i is 0: break\n" + " a += 1\n" + "a"; compareExpression(assert, code, 0); // invalid syntax - using continue / break outside loops assert.throws(function(){executeAndReturn('a = 5; break;')}, AdderScript.Errors.RuntimeError); }); // test if QUnit.test("if_statement", function( assert ) { var code = "" + "a = 0;\n" + "if True: a = 1\n" + "a"; compareExpression(assert, code, 1); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "a"; compareExpression(assert, code, 0); var code = "" + "n = 1\n" + "a = 0\n" + "if n == 1 or n == 2: a = 1\n" + "a"; compareExpression(assert, code, 1); var code = "" + "n = 1\n" + "a = 0\n" + "if n == 1 and n == 2: a = 1\n" + "a"; compareExpression(assert, code, 0); // invalid syntax assert.throws(function(){executeAndReturn('if True a = 5')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('if True')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('if : a = 5')}, AdderScript.Errors.SyntaxError); }); // test else QUnit.test("else_statement", function( assert ) { var code = "" + "a = 0;\n" + "if True: a = 1\n" + "else: a = 2\n" + "a"; compareExpression(assert, code, 1); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "else: a = 2\n" + "a"; compareExpression(assert, code, 2); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "else: if True: a = 2\n" + "a"; compareExpression(assert, code, 2); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "else: if None: a = 2\n" + "a"; compareExpression(assert, code, 0); // some basic cases compareExpression(assert, 'if False: a=5; else: a=1; a', 1); compareExpression(assert, 'if True: a=5; else: a=1; a', 5); compareExpression(assert, 'a=5; if False: a=1; else:; a', 5); var code = "" + "a=0\n"+ "if False:\n" + " pass\n" + "else:\n" + " if 0:\n" + " a = 1\n" + " else:\n" + " a = 5\n" + "a" compareExpression(assert, code, 5); var code = "" + "a=0\n"+ "if False:\n" + " pass\n" + "else:\n" + " if True:\n" + " a = 1\n" + " else:\n" + " a = 5\n" + "a" compareExpression(assert, code, 1); // invalid syntax assert.throws(function(){executeAndReturn('if True: a = 5; else foo')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('else: a = 5')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a = 6; else: a = 5;')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a=0; if False: pass; else: if 0: a = 1; else: a = 5; a')}, AdderScript.Errors.SyntaxError); }); // test elif QUnit.test("elif_statement", function( assert ) { var code = "" + "a = 0;\n" + "if True: a = 1\n" + "elif True: a = 2\n" + "a"; compareExpression(assert, code, 1); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "elif True: a = 2\n" + "a"; compareExpression(assert, code, 2); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "elif False: a = 2\n" + "a"; compareExpression(assert, code, 0); var code = "" + "a = 0;\n" + "if False: a = 1\n" + "elif a == 0: \n" + " a = 2\n" + "a"; compareExpression(assert, code, 2); // some basic cases compareExpression(assert, 'if False: a=5; elif True: a=1; a', 1); compareExpression(assert, 'if True: a=5; elif 1: a=1; a', 5); compareExpression(assert, 'a=5; if False: a=1; elif list():; a', 5); var code = "" + "a=0\n"+ "if False:\n" + " pass\n" + "elif a == 1 or True:\n" + " if 0:\n" + " a = 1\n" + " else:\n" + " a = 5\n" + "a" compareExpression(assert, code, 5); var code = "" + "a=0\n"+ "if False:\n" + " pass\n" + "elif a == 0 or False and True:\n" + " if True:\n" + " a = 1\n" + " elif False:\n" + " a = 5\n" + "a" compareExpression(assert, code, 1); // invalid syntax assert.throws(function(){executeAndReturn('if True: a = 5; elif foo')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('elif True: a = 5')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a = 6; elif True: a = 5;')}, AdderScript.Errors.SyntaxError); assert.throws(function(){executeAndReturn('a=0; if False: pass; elif: if 0: a = 1; else: a = 5; a')}, AdderScript.Errors.SyntaxError); }); // basic function calls QUnit.test("basic_function_call", function( assert ) { // register temp test function _window.__adder_script_test = function() { var args = AdderScript._internals.Utils.toArray(arguments); if (args.length === 1) {return args[0]._value;} return args.map(function(x) { return x._value; }); } // call with simple values var values = ["1,2,3", '"das"', "15", "__VERSION__", "15 + 5", "15 + 5 * 2 + -1", '"5+2"', '1,2 + 2,3 + "abcd"', '1, "2,3"']; var results = [[1,2,3], 'das', 15, AdderScript._internals.version, 20, 15 + 5 * 2 + -1, '5+2', [1,2 + 2 ,3 + 'abcd'], [1, '2,3']]; for (var i = 0; i < values.length; ++i) { var val = values[i]; var expected = results[i]; assert.deepEqual(executeAndReturn("_sec_test(" + val + ")")[1], expected); } }); // test strings api QUnit.test("strings_api", function( assert ) { // test len() testReturnValue(assert, '"".len()', 0); testReturnValue(assert, '"abcd".len()', 4); testReturnValue(assert, 'a = "abcd"; a.len()', 4); // test split() testReturnValue(assert, '"abc def".split()', ["abc", "def"]); testReturnValue(assert, '"abc def".split(" ")', ["abc", "def"]); testReturnValue(assert, '"abc def".split("-")', ["abc def"]); testReturnValue(assert, '"abc-word-def".split("-word-")', ["abc", "def"]); testReturnValue(assert, '"abc def".split("")', ["a", "b", "c", " ", "d", "e", "f"]); // test replace() testReturnValue(assert, '"abc def".replace("abc", "foo")', "foo def"); testReturnValue(assert, '"abc def abc".replace("abc", "foo")', "foo def foo"); testReturnValue(assert, 'a = "abc def abc"; a.replace("abc", "foo"); a', "abc def abc"); testReturnValue(assert, 'a = "abc def abc abc234"; a.replace("abc", "foo")', "foo def foo foo234"); // test remove() testReturnValue(assert, '"abc def".remove("abc")', " def"); testReturnValue(assert, '"abc def abc".remove("abc")', " def "); testReturnValue(assert, 'a = "abc def abc"; a.remove("abc"); a', "abc def abc"); testReturnValue(assert, 'a = "abc def abc abc234"; a.remove("abc")', " def 234"); // test index() testReturnValue(assert, '"abcdefg".index("abc")', 0); testReturnValue(assert, '"abcdefg".index("fofo")', -1); testReturnValue(assert, '"abcdefg".index("ef")', 4); testReturnValue(assert, '"abcdefg".index("efaaaa")', -1); // test has() testReturnValue(assert, '"abcdefg".has("abc")', true); testReturnValue(assert, '"abcdefg".has("fofo")', false); testReturnValue(assert, '"abcdefg".has("ef")', true); testReturnValue(assert, '"abcdefg".has("efaaaa")', false); // test count() testReturnValue(assert, '"abcdefgabcabc".count("abc")', 3); testReturnValue(assert, '"abcfoodefg".count("foo")', 1); testReturnValue(assert, '"abcdefg".count("nope")', 0); // test trim() testReturnValue(assert, '" abcd ".trim()', "abcd"); testReturnValue(assert, '" \n abcd \t ".trim()', "abcd"); // test hash() testReturnValue(assert, '"abcd".hash()', 2987074); // test ends_with() testReturnValue(assert, '"abcdefg".ends_with("efg")', true); testReturnValue(assert, '"abcdefg".ends_with("g")', true); testReturnValue(assert, '"abcdefg".ends_with("aaa")', false); testReturnValue(assert, '"abcdefg".ends_with("")', false); // test starts_with() testReturnValue(assert, '"abcdefg".starts_with("abc")', true); testReturnValue(assert, '"abcdefg".starts_with("a")', true); testReturnValue(assert, '"abcdefg".starts_with("aaa")', false); testReturnValue(assert, '"abcdefg".starts_with("")', true); // test is_alpha() testReturnValue(assert, '"abcdefg".is_alpha()', true); testReturnValue(assert, '"".is_alpha()', false); testReturnValue(assert, '"abc defg".is_alpha()', false); testReturnValue(assert, '"abcd1efg".is_alpha()', false); testReturnValue(assert, '"abcd?efg".is_alpha()', false); testReturnValue(assert, '"_".is_alpha()', false); testReturnValue(assert, '"ab\ncdefg".is_alpha()', false); // test is_digit() testReturnValue(assert, '"123".is_digit()', true); testReturnValue(assert, '"".is_digit()', false); testReturnValue(assert, '"123 456".is_digit()', false); testReturnValue(assert, '"123a456".is_digit()', false); testReturnValue(assert, '"123?456".is_digit()', false); testReturnValue(assert, '"1_".is_digit()', false); testReturnValue(assert, '"1\n2".is_digit()', false); // test lower() testReturnValue(assert, '"AbCdE fG12".lower()', "abcde fg12"); // test upper() testReturnValue(assert, '"AbCdE fG12".upper()', "ABCDE FG12"); // test title() testReturnValue(assert, '"hello there my friend! nice_to\nmeet\tyou".title()', "Hello There My Friend! Nice_to\nMeet\tYou"); // test slice() testReturnValue(assert, '"hello world".slice(3)', "lo world"); testReturnValue(assert, '"hello world".slice(3, 2)', "lo"); }); // test some builtin functions QUnit.test("basic_function_builtins", function( assert ) { // test all() testReturnValue(assert, "all()", true); testReturnValue(assert, "all(5)", true); testReturnValue(assert, "all(True, 1, 5 + 2)", true); testReturnValue(assert, "all(0)", false); testReturnValue(assert, "all(True, 1, False)", false); testReturnValue(assert, "all(True, 1, None, 4)", false); testReturnValue(assert, 'all(1 + 1, "fw", True, False)', false); // test any() testReturnValue(assert, "any()", false); testReturnValue(assert, "any(5)", true); testReturnValue(assert, "any(True, 1, 5 + 2)", true); testReturnValue(assert, "any(0)", false); testReturnValue(assert, "any(0, False, None)", false); testReturnValue(assert, "any(True, 1, False)", true); testReturnValue(assert, "any(True, 1, None, 4)", true); testReturnValue(assert, 'any(1 + 1, "fw", True, False)', true); // test bin() testReturnValue(assert, "bin(0)", "0"); testReturnValue(assert, "bin(1)", "1"); testReturnValue(assert, "bin(5238965)", "10011111111000010110101"); // test delete testReturnValue(assert, "a = 5; b = a; delete(a); b", 5); assert.throws(function() {testReturnValue(assert, "a = 5; b = a; delete(a); a", 5)}, AdderScript.Errors.UndefinedVariable); assert.throws(function() {testReturnValue(assert, "delete(Math)", 5)}, AdderScript.Errors.RuntimeError); assert.throws(function() {testReturnValue(assert, "a = 5; def test(): delete(a); test(); a", 5)}, AdderScript.Errors.UndefinedVariable); assert.throws(function() {testReturnValue(assert, "a = 5; def test(): a = 5 and delete(a); test(); a", 5)}, AdderScript.Errors.UndefinedVariable); testReturnValue(assert, 'a = 5; delete(a); exist("a");', false); // test exist testReturnValue(assert, 'exist("Math")', true); testReturnValue(assert, 'exist("None")', true); testReturnValue(assert, 'exist("True")', true); testReturnValue(assert, 'a=5; exist("a")', true); testReturnValue(assert, 'a=5; exist("b")', false); // test bool() testReturnValue(assert, "bool(1)", true); testReturnValue(assert, 'bool("5")', true); testReturnValue(assert, "bool(1 + 3)", true); testReturnValue(assert, "bool(True)", true); testReturnValue(assert, "bool(0)", false); testReturnValue(assert, "bool(None)", false); testReturnValue(assert, "bool(False)", false); // test callable testReturnValue(assert, "callable(any)", true); testReturnValue(assert, "callable(\"55\")", false); testReturnValue(assert, "callable(435)", false); testReturnValue(assert, "callable(False)", false); testReturnValue(assert, "callable(None)", false); // test chr testReturnValue(assert, "chr(90)", 'Z'); // test cmp testReturnValue(assert, 'cmp("a", "b")', -1); testReturnValue(assert, 'cmp("b", "a")', 1); testReturnValue(assert, 'cmp(5, 1)', 4); testReturnValue(assert, 'cmp(1, 5)', -4); // test float testReturnValue(assert, 'float("0")', 0); testReturnValue(assert, 'float("0.5")', 0.5); testReturnValue(assert, 'float("bla")', NaN); // test int testReturnValue(assert, 'int("0")', 0); testReturnValue(assert, 'int("5.0")', 5); testReturnValue(assert, 'int("100", 8)', 64); testReturnValue(assert, 'int("100")', 100); testReturnValue(assert, 'int("bla")', NaN); // test ord testReturnValue(assert, 'ord("a")', 97); // test repr testReturnValue(assert, 'repr("foo")', '"foo"'); testReturnValue(assert, 'repr(10)', '10'); testReturnValue(assert, 'repr(list("foo"))', 'list(foo)'); testReturnValue(assert, 'repr(None)', 'None'); testReturnValue(assert, 'repr(True)', 'True'); // test range testReturnValue(assert, 'equal(range(3), list(0,1,2))', true); testReturnValue(assert, 'equal(range(1, 3), list(1,2))', true); testReturnValue(assert, 'equal(range(5, 1, -1), list(5,4,3,2))', true); testReturnValue(assert, 'equal(range(5, 1, -2), list(5,3))', true); testReturnValue(assert, 'equal(range(1, 3, -1), list())', true); testReturnValue(assert, 'equal(range(5, 2, 1), list())', true); testReturnValue(assert, 'equal(range(0), list())', true); compareExpression(assert, 'range(0)', []); compareExpression(assert, 'range(1)', [0]); compareExpression(assert, 'range(10)', [0,1,2,3,4,5,6,7,8,9]); // test str testReturnValue(assert, 'str("5")', "5"); testReturnValue(assert, 'str(None)', ""); testReturnValue(assert, 'str(True)', "True"); testReturnValue(assert, 'str(False)', "False"); testReturnValue(assert, 'str("abc")', "abc"); // test type testReturnValue(assert, 'type(True)', "boolean"); testReturnValue(assert, 'type(False)', "boolean"); testReturnValue(assert, 'type(None)', "none"); testReturnValue(assert, 'type("a")', "string"); testReturnValue(assert, 'type(123)', "number"); testReturnValue(assert, 'type(__VERSION__)', "string"); // test list creation testReturnValue(assert, 'type(list())', "list"); testReturnValue(assert, 'type(list(1,2,3))', "list"); // test reversed testReturnValue(assert, 'equal(reversed(list(1,2,3)), list(3,2,1))', true); }); // test the built-in Random module QUnit.test("module_random", function( assert ) { // test basic rand() var oldRand = Math.random; Math.random = function() {return 0.2;} testReturnValue(assert, "Random.rand()", 0.2); testReturnValue(assert, "Random.rand_int(5)", 1); testReturnValue(assert, "Random.rand_int(5, 10)", 6); testReturnValue(assert, "Random.rand_float(4.0)", 0.8); testReturnValue(assert, "Random.rand_float(5.5, 10.0)", 6.4); testReturnValue(assert, "Random.binary()", 0); testReturnValue(assert, "Random.boolean()", false); testReturnValue(assert, 'Random.select(list("foo", "bar", "goo"))', "foo"); testReturnValue(assert, 'Random.select(set("foo", "bar", "goo"))', "foo"); testReturnValue(assert, 'a = dict(); a.set("foo", 5); a.set("bar", 6); Random.select(a)', "foo"); Math.random = oldRand; }); // test the built-in Math module QUnit.test("module_math", function( assert ) { // test abs() testReturnValue(assert, "Math.abs(5)", 5); testReturnValue(assert, "Math.abs(-5)", 5); testReturnValue(assert, "Math.abs(5 + 2 * -1)", Math.abs(5 + 2 * -1)); testReturnValue(assert, "Math.abs(5 + 2 * -1 - 10)", Math.abs(5 + 2 * -1 - 10)); // test min testReturnValue(assert, 'Math.min(1,2,3,1)', 1); testReturnValue(assert, 'Math.min(1,3)', 1); testReturnValue(assert, 'Math.min(-5,-1)', -5); testReturnValue(assert, 'Math.min(list(2, 4, -5,-1))', -5); testReturnValue(assert, 'Math.min(set(2, 4, -5,-1))', -5); // test max testReturnValue(assert, 'Math.max(1,2,3, 1)', 3); testReturnValue(assert, 'Math.max(1,3)', 3); testReturnValue(assert, 'Math.max(-5,-1)', -1); testReturnValue(assert, 'Math.max(list(2, 4, -5,-1))', 4); testReturnValue(assert, 'Math.max(set(2, 4, -5,-1))', 4); // test pow testReturnValue(assert, 'Math.pow(2, 2)', 4); testReturnValue(assert, 'Math.pow(3, 2)', 9); // test round / floor / ceil testReturnValue(assert, 'Mat