diff --git a/Test.hx b/Test.hx index 695af0f..288dc4b 100644 --- a/Test.hx +++ b/Test.hx @@ -1,131 +1,21 @@ package; -import hscript.AbstractHScriptClass; +import utest.Runner; +import utest.ui.Report; + +import cases.ClassFeaturesCase; +import cases.LanguageFeaturesCase; class Test { static function main() { - // TODO: Make proper unit tests, this stinks - - var parser = new hscript.SuperParser(); - var interp = new hscript.SuperInterp(); - - var script:String = " - package hscript; - - import haxe.ds.StringMap; - import haxe.io.Path; - - class TestClass extends TestClass - { - public static var hscriptStaticVar:Float = 2.7; - - static public function myStaticFunc(add:Float, mult:Float, div:Float = 1.3) - { - trace(hscriptStaticVar + add); - trace(hscriptStaticVar / div); - staticPrivateFunc(); - return hscriptStaticVar * mult; - } - - - - - public var hscriptVar:String = 'Hello World!'; - - public function new(num:Int, str:String) - { - trace('instanced!'); - trace(super); - super(); - trace(num); - trace(str); - trace(super); - } - - public function hscriptFunc(add:Float) - { - trace('hscript function called'); - trace(super.myVar + add); - } - - private function privateFunc() - { - trace('private func'); - trace(super.myFunc('bruh', 555555.5555)); - } - - private static function staticPrivateFunc() - { - mult = 10; // going to ignore this, should not happen; - trace('called the private function from static class'); - } - } - "; - trace("created interp and parser"); - - var program = parser.parseString(script, "TestScript.hx", 0 ); - trace("parsed script"); - parser.resumeErrors = true; - var declarations = parser.parseModule(script, "TestScript.hx", 0 ); - parser.resumeErrors = false; - trace("parsed modules"); - - trace("executing"); - - interp.execute(program); - - trace("registering structures"); + var runner = new Runner(); - interp.registerStructures(declarations); + runner.addCase(new LanguageFeaturesCase()); + runner.addCase(new ClassFeaturesCase()); - trace("getting class"); - - var cls:AbstractHScriptClass = hscript.SuperInterp.resolveClass("hscript.TestClass"); - trace("got the class"); - - //try { trace(cls.hscriptStaticVar); } catch(e:Dynamic) { trace(e); } // works - //try { trace(cls.myStaticFunc(5, 3)); } catch(e:Dynamic) { trace(e); } // works, need to test to see if function arguments remain in the local variables list - //try { trace(cls.staticPrivateFunc()); } catch(e:Dynamic) { trace(e); } // works - // dont need to test for class extensions because static classes do not need to extend other classes - - var inst:AbstractHScriptClass = cls.createInstance(85, "hi"); - - try { trace(inst.hscriptVar); } catch(e:Dynamic) { trace(e); } - try { trace(inst.hscriptFunc(2.5)); } catch(e:Dynamic) { trace(e); } - try { trace(inst.privateFunc()); } catch(e:Dynamic) { trace(e); } - try { trace(inst.myVar); } catch(e:Dynamic) { trace(e); } - try { trace(inst.myFunc(123,456)); } catch(e:Dynamic) { trace(e); } - try { trace(inst.super.myFunc(123,456)); } catch(e:Dynamic) { trace(e); } - try { inst.myVar += 8; } catch(e:Dynamic) { trace(e); } - try { trace(inst.myVar); } catch(e:Dynamic) { trace(e); } - try { trace(inst.myPrivateFunc(1)); } catch(e:Dynamic) { trace(e); } - - trace("DONE!"); - } -} - -class TestClass -{ - public static var staticVar:Float = 9.30; - - public var myVar:Int = 1; - - public function new() - { - trace("CONSTRUCTOR CALLED!!!"); - } - - public function myFunc(arg1:Dynamic, arg2:Dynamic, arg3:Dynamic) - { - trace('my function was called with args $arg1 $arg2 and $arg3'); - return 5; - } - - private function myPrivateFunc() - { - trace('my private function was called'); - return 'hello!'; + Report.create(runner); + runner.run(); } } \ No newline at end of file diff --git a/build.hxml b/build.hxml index 4c59a54..56987ca 100644 --- a/build.hxml +++ b/build.hxml @@ -1,10 +1,2 @@ --cp src --m Test --lib hscript - --D no-deprecation-warnings --D no-invalid-expression-warnings --D hscriptPos - ---macro addGlobalMetadata('hscript', '@:build(hscript.macros.ParserMacro.buildAll())', false, true, false) +test/build-base.hxml --interp \ No newline at end of file diff --git a/hscript/SuperInterp.hx b/hscript/SuperInterp.hx index fa65f25..445748e 100644 --- a/hscript/SuperInterp.hx +++ b/hscript/SuperInterp.hx @@ -186,7 +186,7 @@ class SuperInterp extends ReInterp switch( #if hscriptPos x #else e #end ) { case EIdent(id): - var val = super.expr(e); + var val:Dynamic = super.expr(e); if (blacklist.contains(val)) error(ECustom('Blacklisted expression $val referenced')); case _: diff --git a/hscript/macros/ParserMacro.hx b/hscript/macros/ParserMacro.hx index 1e0ae23..32e2be9 100644 --- a/hscript/macros/ParserMacro.hx +++ b/hscript/macros/ParserMacro.hx @@ -38,18 +38,23 @@ class ParserMacro function makeEnumField(name, kind):Field { - return { + for (f in fields) + if (f.name == name) + return null; + + var field:Field = { name: name, doc: null, meta: [], access: [], kind: kind, pos: Context.currentPos() - } + }; + fields.push(field); + return field; } - fields.push( - makeEnumField("TApostr", FVar(null, null)) - ); + + makeEnumField("TApostr", FVar(null, null)); case _: } diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..9eda26c --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +Build.* \ No newline at end of file diff --git a/test/build-base.hxml b/test/build-base.hxml new file mode 100644 index 0000000..171b9ee --- /dev/null +++ b/test/build-base.hxml @@ -0,0 +1,7 @@ +extraParams.hxml +-m Test +-lib utest +-cp test/source + +-D no-deprecation-warnings +-D no-invalid-expression-warnings \ No newline at end of file diff --git a/test/build-hl.hxml b/test/build-hl.hxml new file mode 100644 index 0000000..e7fc90c --- /dev/null +++ b/test/build-hl.hxml @@ -0,0 +1,2 @@ +test/build-base.hxml +--hl test/Build.hl \ No newline at end of file diff --git a/test/build-interp.hxml b/test/build-interp.hxml new file mode 100644 index 0000000..56987ca --- /dev/null +++ b/test/build-interp.hxml @@ -0,0 +1,2 @@ +test/build-base.hxml +--interp \ No newline at end of file diff --git a/test/build-js.hxml b/test/build-js.hxml new file mode 100644 index 0000000..4a34e3d --- /dev/null +++ b/test/build-js.hxml @@ -0,0 +1,2 @@ +test/build-base.hxml +--js test/Build.js \ No newline at end of file diff --git a/test/build-neko.hxml b/test/build-neko.hxml new file mode 100644 index 0000000..ba21461 --- /dev/null +++ b/test/build-neko.hxml @@ -0,0 +1,2 @@ +test/build-base.hxml +--neko test/Build.n \ No newline at end of file diff --git a/test/source/Shared.hx b/test/source/Shared.hx new file mode 100644 index 0000000..48e3005 --- /dev/null +++ b/test/source/Shared.hx @@ -0,0 +1,39 @@ +import haxe.io.Path; +import sys.io.File; +import hscript.*; + +function evalExpr(expr:String, ?vars: Map, ?params:Array):Dynamic +{ + var parser = new SuperParser(); + var interp = new SuperInterp(); + + var program = parser.parseString(expr, '', 0); + + if (params != null) + interp.variables.set("params", params); + + if (vars != null) + for (key => value in vars) + interp.variables.set(key, value); + + return interp.execute(program); +} + +function evalFile(file:String, ?vars:Map):Dynamic +{ + var expr = File.getContent(file); + var fname = Path.withoutDirectory(file); + + var parser = new SuperParser(); + var interp = new SuperInterp(); + + var program = parser.parseString(expr, fname, 0); + var modules = parser.parseModule(expr, fname, 0); + + if (vars != null) + for (key => value in vars) + interp.variables.set(key, value); + + interp.registerStructures(modules, file); + return interp.execute(program); +} \ No newline at end of file diff --git a/test/source/cases/ClassFeaturesCase.hx b/test/source/cases/ClassFeaturesCase.hx new file mode 100644 index 0000000..841e660 --- /dev/null +++ b/test/source/cases/ClassFeaturesCase.hx @@ -0,0 +1,10 @@ +package cases; + +import utest.ITest; +import utest.Assert; +import hscript.*; + +class ClassFeaturesCase implements ITest +{ + public function new() {} +} \ No newline at end of file diff --git a/test/source/cases/LanguageFeaturesCase.hx b/test/source/cases/LanguageFeaturesCase.hx new file mode 100644 index 0000000..5b074ac --- /dev/null +++ b/test/source/cases/LanguageFeaturesCase.hx @@ -0,0 +1,164 @@ +package cases; + +import modules.TestObj; +import Shared; + +import utest.ITest; +import utest.Assert; + +/** + * Test suite class for ensuring language features work correctly. + * It provides test functions for vanilla HScript features and also Super-HScript specific features. + */ +class LanguageFeaturesCase implements ITest +{ + public function new() {} + + /** + * Tests basic mathematic operators. + */ + function testArithmetic() + { + Assert.equals(7, evalExpr('3 + 4;')); + Assert.equals(14, evalExpr('3 * 4 + 2;')); + Assert.equals(4, evalExpr('16 / (7 - 3);')); + } + + /** + * Tests conditions with boolean algebra. + */ + function testConditions() + { + Assert.isTrue(evalExpr("3 > 1;")); + Assert.isTrue(evalExpr("3 >= 3;")); + Assert.isFalse(evalExpr("4 < 7 && 1 + 2 == 4;")); + Assert.isTrue(evalExpr("false || 1 % 2 == 1;")); + Assert.equals("I am at work", evalExpr("if (true == false) 'I am here'; else 'I am at work';")); + Assert.equals(3, evalExpr("false ? 10 : 3;")); + } + + /** + * Tests for string interpolation. + */ + function testStringInterpolation() + { + Assert.equals("Hello, World!", evalExpr("'Hello, ${params[0]}!';", ["World"])); + Assert.equals("I have 5 coins", evalExpr("'I have ${3+2} coins';")); + Assert.equals("OneTwoThree", evalExpr("'${params[0]}${params[1]}${params[2]}';", ["One","Two","Three"])); + } + + /** + * Tests the var operator, local assignments and other special assignment operators. + */ + function testAssignment() + { + Assert.equals(7, evalExpr('var a = 5; a += 2;')); + Assert.equals('foo', evalExpr("var a:String = null; a ??= 'foo'; a;")); + } + + /** + * Tests optional chaining expressions. + */ + function testOptionalChaining() + { + Assert.equals('ok', evalExpr('var a = { f: "ok" }; a?.f;')); + Assert.isNull(evalExpr('var a = null; a?.f;')); + } + + /** + * Tests for field write and read operations on both anonymous structures and class objects. + */ + function testFields() + { + Assert.equals(5.5, evalExpr("TestObj.myVar;", ["TestObj"=>TestObj])); + Assert.equals("3 apples", evalExpr("TestObj.testFunc(3, 'apple');", ["TestObj"=>TestObj])); + Assert.equals(2, evalExpr("var t = new TestObj(); t.secretCode[2];", ["TestObj"=>TestObj])); + Assert.equals("SECRET_CODE", evalExpr("var t = new TestObj(); t.getRecovery();", ["TestObj"=>TestObj])); + + var obj:Dynamic = evalExpr("{ n1: 5, others: {o1: true, o2: 'NO!'}}"); + + Assert.equals(5, obj.n1); + Assert.isTrue(obj.others.o1); + evalExpr("obj.others.o2 = 'YES!'", ["obj"=>obj]); + Assert.equals("YES!", obj.others.o2); + } + + /** + * Tests function definitions, function calls and function arguments. + * Type definitions are ignored. + */ + function testFunctions() + { + // First test: Tests for declaring a function and calling it from Haxe with some arguments. + + var fn:Dynamic = evalExpr('function(arg1, num) { return arg1 + num; }'); + if (Type.typeof(fn) == Type.ValueType.TFunction) + Assert.pass("fn correctly declared as a function"); + else + Assert.fail("fn test failed, function not declared"); + + Assert.equals(7, fn(3, 4)); + Assert.equals("Hi", fn("H", "i")); + + + // Second test: Tests for modifications on objects not present to the locals map. + + var fn2:Dynamic = evalExpr('function(obj) {obj.a = true; obj.n.b += 3;}'); + if (Type.typeof(fn2) == Type.ValueType.TFunction) + Assert.pass("fn2 correctly declared as a function"); + else + Assert.fail("fn2 test failed, function not declared"); + + var obj = { + a: false, + n: { + b: 2 + } + } + + fn2(obj); + + Assert.isTrue(obj.a); + Assert.equals(5, obj.n.b); + } + + /** + * Tests for while, do-while, for and recursion loops. + */ + function testLoops() + { + // For loops + Assert.equals(1024, evalExpr('var o = 1; for (i in 0...10) o *= 2; o;')); + + // While and Do-While loops + Assert.equals(9, evalExpr('var o = 0; while (o < 9) o+=1; o;')); + Assert.equals(-1, evalExpr('var o = 0; do o-=1 while (o > 0); o;')); + Assert.equals(0, evalExpr('var o = 3; do o-=1 while (o > 0); o;')); + Assert.equals(0, evalExpr('var o = 0; while (o > 0) o-=1; o;')); + Assert.equals(-3, evalExpr('var o = -3; while (o > 0) o-=1; o;')); + + // Recursion loops + try + { Assert.equals(9, evalExpr('var o = 0; function r() { if (o >= 9) return o; o+=1; return r(); } r();')); } + catch(e:Dynamic) + Assert.fail("function recursed forever"); + } + + /** + * Tests for basic iterators. + */ + function testIterators() + { + Assert.equals(6, evalExpr('var o = 0; for (i in [1, 2, 3]) o+=i; o;')); + } + + + + // TODO + + function testPartialFunction() {} + function testProperties() {} + function testDefines() {} + function testGADT() {} + function testPatternMatching() {} +} \ No newline at end of file diff --git a/test/source/modules/TestObj.hx b/test/source/modules/TestObj.hx new file mode 100644 index 0000000..4b2493c --- /dev/null +++ b/test/source/modules/TestObj.hx @@ -0,0 +1,18 @@ +package modules; + +class TestObj +{ + public static var myVar:Float = 5.5; + public static function testFunc(num:Int, fruit:String) + { + return '$num ${fruit}s'; + } + + public function new() {} + + public var secretCode:Array = [4,3,2,1]; + public function getRecovery() + { + return "SECRET_CODE"; + } +} \ No newline at end of file