diff options
Diffstat (limited to 'tests/custom/02_runtime')
-rw-r--r-- | tests/custom/02_runtime/00_scoping | 161 | ||||
-rw-r--r-- | tests/custom/02_runtime/01_break_continue | 50 | ||||
-rw-r--r-- | tests/custom/02_runtime/02_this | 50 | ||||
-rw-r--r-- | tests/custom/02_runtime/03_try_catch | 138 | ||||
-rw-r--r-- | tests/custom/02_runtime/04_switch_case | 325 | ||||
-rw-r--r-- | tests/custom/02_runtime/05_closure_scope | 35 | ||||
-rw-r--r-- | tests/custom/02_runtime/06_recursion | 59 |
7 files changed, 818 insertions, 0 deletions
diff --git a/tests/custom/02_runtime/00_scoping b/tests/custom/02_runtime/00_scoping new file mode 100644 index 0000000..5fadf43 --- /dev/null +++ b/tests/custom/02_runtime/00_scoping @@ -0,0 +1,161 @@ +Utpl implements function scoping, make sure that let variables are +invisible outside of the function scope. + +-- Expect stdout -- +a_global=true +a_local=true + +b_global=true +b_local=false + +c_global=true +c_local=false + + +When seting a nonlocal variable, it is set in the nearest parent +scope containing the variable or in the root scope if the variable +was not found. + +x=2 +y= +z=1 + + +Variables implicitly declared by for-in or counting for loops follow the same +scoping rules. + +inner2 f_a=3 +inner2 f_b= +inner2 f_c=3 +inner2 f_d= +inner2 f_e=3 + +inner f_a=3 +inner f_b= +inner f_c=3 +inner f_d= +inner f_e=3 + +outer f_a=3 +outer f_b= +outer f_c= +outer f_d= +outer f_e=3 +-- End -- + +-- Testcase -- +{% + a_global = true; + let a_local = true; + + function test() { + b_global = true; + let b_local = true; + + function test2() { + c_global = true; + let c_local = true; + } + + test2(); + } + + test(); +-%} + +a_global={{ !!a_global }} +a_local={{ !!a_local }} + +b_global={{ !!b_global }} +b_local={{ !!b_local }} + +c_global={{ !!c_global }} +c_local={{ !!c_local }} + + +When seting a nonlocal variable, it is set in the nearest parent +scope containing the variable or in the root scope if the variable +was not found. + +{% + x = 1; + + function scope1() { + x = 2; + let y; + + function scope2() { + // this does not set "y" in the root scope but overwrites the + // variable declared in the "scope1" function scope. + y = 2; + + // this sets "z" in the root scope because it was not declared + // anywhere yet + z = 1; + } + + scope2(); + } + + scope1(); +-%} + +x={{ x }} +y={{ y }} +z={{ z }} + + +Variables implicitly declared by for-in or counting for loops follow the same +scoping rules. + +{% + function scope3() { + // f_a is not declared local and be set in the root scope + for (f_a = 1; f_a < 3; f_a++) + ; + + for (let f_b = 1; f_b < 3; f_b++) + ; + + let f_c; + + function scope4() { + // f_c is not declared local but declared in the parent scope, it + // will be set there + for (f_c in [1, 2, 3]) + ; + + for (let f_d in [1, 2, 3]) + ; + + // f_e is not declared, it will be set in the root scope + for (f_e in [1, 2, 3]) + ; + + print("inner2 f_a=", f_a, "\n"); + print("inner2 f_b=", f_b, "\n"); + print("inner2 f_c=", f_c, "\n"); + print("inner2 f_d=", f_d, "\n"); + print("inner2 f_e=", f_e, "\n"); + print("\n"); + } + + scope4(); + + print("inner f_a=", f_a, "\n"); + print("inner f_b=", f_b, "\n"); + print("inner f_c=", f_c, "\n"); + print("inner f_d=", f_d, "\n"); + print("inner f_e=", f_e, "\n"); + print("\n"); + } + + scope3(); + + print("outer f_a=", f_a, "\n"); + print("outer f_b=", f_b, "\n"); + print("outer f_c=", f_c, "\n"); + print("outer f_d=", f_d, "\n"); + print("outer f_e=", f_e, "\n"); +%} +-- End -- diff --git a/tests/custom/02_runtime/01_break_continue b/tests/custom/02_runtime/01_break_continue new file mode 100644 index 0000000..a27d072 --- /dev/null +++ b/tests/custom/02_runtime/01_break_continue @@ -0,0 +1,50 @@ +The "break" and "continue" statements allow to abort a running loop or to +prematurely advance to the next cycle. + +-- Expect stdout -- +Testing break: + - Iteration 0 + - Iteration 1 + - Iteration 2 + - Iteration 3 + - Iteration 4 + - Iteration 5 + - Iteration 6 + - Iteration 7 + - Iteration 8 + - Iteration 9 + - Iteration 10 + +Testing continue: + - Iteration 0 + - Iteration 2 + - Iteration 4 + - Iteration 6 + - Iteration 8 +-- End -- + +-- Testcase -- +Testing break: +{% + let i = 0; + + while (true) { + print(" - Iteration ", i, "\n"); + + if (i == 10) + break; + + i++; + } +%} + +Testing continue: +{% + for (i = 0; i < 10; i++) { + if (i % 2) + continue; + + print(" - Iteration ", i, "\n"); + } +%} +-- End -- diff --git a/tests/custom/02_runtime/02_this b/tests/custom/02_runtime/02_this new file mode 100644 index 0000000..d8e85d2 --- /dev/null +++ b/tests/custom/02_runtime/02_this @@ -0,0 +1,50 @@ +The "this" object accesses the current function context. + +-- Expect stdout -- +true +true +-- End -- + +-- Testcase -- +{% + // Functions not invoked on objects have no this context + function test() { + return (this === null); + } + + // When invoked, "this" will point to the object containing the function + let o; + o = { + test: function() { + return (this === o); + } + }; + + print(test(), "\n"); + print(o.test(), "\n"); +%} +-- End -- + +Test that the context is properly restored if function call arguments are +dot or bracket expressions as well. + +-- Expect stdout -- +true +true +-- End -- + +-- Testcase -- +{% + let o; + o = { + test: function() { + return (this === o); + } + }; + + let dummy = { foo: true, bar: false }; + + print(o.test(dummy.foo, dummy.bar), "\n"); + print(o.test(dummy.foo, o.test(dummy.foo, dummy.bar)), "\n"); +%} +-- End -- diff --git a/tests/custom/02_runtime/03_try_catch b/tests/custom/02_runtime/03_try_catch new file mode 100644 index 0000000..751ca1d --- /dev/null +++ b/tests/custom/02_runtime/03_try_catch @@ -0,0 +1,138 @@ +Wrapping an exeptional operation in try {} catch {} allows handling the +resulting exception and to continue the execution flow. + +-- Expect stdout -- +Catched first exception. +Catched second exception: exception 2. +After exceptions. +-- End -- + +-- Testcase -- +{% + // A try-catch block that discards the exception information. + try { + die("exception 1"); + } + catch { + print("Catched first exception.\n"); + } + + // A try-catch block that captures the resulting exception in + // the given variable. + try { + die("exception 2"); + } + catch (e) { + print("Catched second exception: ", e, ".\n"); + } + + print("After exceptions.\n"); +%} +-- End -- + + +Ensure that exceptions are propagated through C function calls. + +-- Expect stderr -- +exception +In [anonymous function](), line 3, byte 18: + called from function replace ([C]) + called from anonymous function ([stdin]:4:3) + + ` die("exception");` + Near here -------------^ + + +-- End -- + +-- Testcase -- +{% + replace("test", "t", function(m) { + die("exception"); + }); +%} +-- End -- + + +Ensure that exception can be catched through C function calls. + +-- Expect stdout -- +Caught exception: exception +-- End -- + +-- Testcase -- +{% + try { + replace("test", "t", function(m) { + die("exception"); + }); + } + catch (e) { + print("Caught exception: ", e, "\n"); + } +%} +-- End -- + + +Ensure that exceptions are propagated through user function calls. + +-- Expect stderr -- +exception +In a(), line 3, byte 18: + called from function b ([stdin]:7:5) + called from function c ([stdin]:11:5) + called from anonymous function ([stdin]:14:4) + + ` die("exception");` + Near here -------------^ + + +-- End -- + +-- Testcase -- +{% + function a() { + die("exception"); + } + + function b() { + a(); + } + + function c() { + b(); + } + + c(); +%} +-- End -- + + +Ensure that exceptions can be caught in parent functions. + +-- Expect stdout -- +Caught exception: exception +-- End -- + +-- Testcase -- +{% + function a() { + die("exception"); + } + + function b() { + a(); + } + + function c() { + try { + b(); + } + catch (e) { + print("Caught exception: ", e, "\n"); + } + } + + c(); +%} +-- End -- diff --git a/tests/custom/02_runtime/04_switch_case b/tests/custom/02_runtime/04_switch_case new file mode 100644 index 0000000..4c1fc57 --- /dev/null +++ b/tests/custom/02_runtime/04_switch_case @@ -0,0 +1,325 @@ +Testing utpl switch statements. + + +1. Ensure that execution starts at the first matching case. + +-- Expect stdout -- +1a +-- End -- + +-- Testcase -- +{% + switch (1) { + case 1: + print("1a\n"); + break; + + case 1: + print("1b\n"); + break; + + case 2: + print("2\n"); + break; + } +%} +-- End -- + + +2. Ensure that default case is only used if no case matches, + even if declared first. + +-- Expect stdout -- +1 +default +-- End -- + +-- Testcase -- +{% + for (n in [1, 3]) { + switch (n) { + default: + print("default\n"); + break; + + case 1: + print("1\n"); + break; + + case 2: + print("2\n"); + break; + } + } +%} +-- End -- + + +3. Ensure that cases without break fall through into + subsequent cases. + +-- Expect stdout -- +1 +2 +default +1 +2 +-- End -- + +-- Testcase -- +{% + for (n in [1, 3]) { + switch (n) { + default: + print("default\n"); + + case 1: + print("1\n"); + + case 2: + print("2\n"); + } + } +%} +-- End -- + + +4. Ensure that a single default case matches. + +-- Expect stdout -- +default +default +-- End -- + +-- Testcase -- +{% + for (n in [1, 3]) { + switch (n) { + default: + print("default\n"); + } + } +%} +-- End -- + + +5. Ensure that duplicate default cases emit a syntax + error during parsing. + +-- Expect stderr -- +Syntax error: more than one switch default case +In line 6, byte 3: + + ` default:` + ^-- Near here + + +Syntax error: Expecting expression +In line 8, byte 2: + + ` }` + ^-- Near here + + +-- End -- + +-- Testcase -- +{% + switch (1) { + default: + print("default1\n"); + + default: + print("default2\n"); + } +%} +-- End -- + + +6. Ensure that case values use strict comparison. + +-- Expect stdout -- +b +b +-- End -- + +-- Testcase -- +{% + switch (1.0) { + case 1: + print("a\n"); + break; + + case 1.0: + print("b\n"); + break; + } + + switch ("123") { + case 123: + print("a\n"); + break; + + case "123": + print("b\n"); + break; + } +%} +-- End -- + + +7. Ensure that case values may be complex expressions. + +-- Expect stdout -- +2, 3, 1 +-- End -- + +-- Testcase -- +{% + switch (1) { + case a = 2, b = 3, c = 1: + print(join(", ", [ a, b, c ]), "\n"); + break; + } +%} +-- End -- + + +8. Ensure that empty switch statements are accepted by the + parser and that the test expression is evaluated. + +-- Expect stdout -- +true +-- End -- + +-- Testcase -- +{% + x = false; + + switch (x = true) { + + } + + print(x, "\n"); +%} +-- End -- + + +9. Ensure that `return` breaks out of switch statements. + +-- Expect stdout -- +one +two +-- End -- + +-- Testcase -- +{% + function test(n) { + switch (n) { + case 1: + return "one"; + + case 2: + return "two"; + + default: + return "three"; + } + } + + print(test(1), "\n"); + print(test(2), "\n"); +%} +-- End -- + + +10. Ensure that `continue` breaks out of switch statements. + +-- Expect stdout -- +one +two +-- End -- + +-- Testcase -- +{% + for (n in [1,2]) { + switch (n) { + case 1: + print("one\n"); + continue; + + case 2: + print("two\n"); + continue; + + default: + print("three\n"); + } + } +%} +-- End -- + + +11. Ensure that exceptions break out of switch statements. + +-- Expect stdout -- +one +-- End -- + +-- Expect stderr -- +Died +In test(), line 6, byte 8: + called from anonymous function ([stdin]:17:14) + + ` die();` + Near here ------^ + + +-- End -- + +-- Testcase -- +{% + function test(n) { + switch (n) { + case 1: + print("one\n"); + die(); + + case 2: + print("two\n"); + die(); + + default: + print("three\n"); + } + } + + print(test(1), "\n"); +%} +-- End -- + + +12. Ensure that consecutive cases values are properly handled. + +-- Expect stdout -- +three and four +-- End -- + +-- Testcase -- +{% + switch (3) { + case 1: + case 2: + print("one and two\n"); + break; + + case 3: + case 4: + print("three and four\n"); + break; + + default: + print("five\n"); + } +%} +-- End -- diff --git a/tests/custom/02_runtime/05_closure_scope b/tests/custom/02_runtime/05_closure_scope new file mode 100644 index 0000000..c59a433 --- /dev/null +++ b/tests/custom/02_runtime/05_closure_scope @@ -0,0 +1,35 @@ +Testing closure scopes. + + +1. Ensure that the declaring scope is retained in functions. + +-- Expect stdout -- +Make function with x=1 +Make function with x=2 +Make function with x=3 +x is 1 +x is 2 +x is 3 +-- End -- + +-- Testcase -- +{% + let count=0; + + function a() { + let x = ++count; + print("Make function with x=", x, "\n"); + return function() { + print("x is ", x, "\n"); + }; + } + + let fn1 = a(); + let fn2 = a(); + let fn3 = a(); + + fn1(); + fn2(); + fn3(); +%} +-- End -- diff --git a/tests/custom/02_runtime/06_recursion b/tests/custom/02_runtime/06_recursion new file mode 100644 index 0000000..470fc3a --- /dev/null +++ b/tests/custom/02_runtime/06_recursion @@ -0,0 +1,59 @@ +Testing recursive invocations. + + +1. Testing recursive invocation. + +-- Expect stdout -- +1 +2 +4 +8 +16 +32 +64 +128 +256 +512 +1024 +2048 +4096 +8192 +16384 +-- End -- + +-- Testcase -- +{% + function test(x) { + print(x, "\n"); + + if (x < 10000) + test(x * 2); + } + + test(1); +%} +-- End -- + + +2. Testing infinite recursion. + +-- Expect stderr -- +Runtime error: Too much recursion +In test(), line 3, byte 8: + called from anonymous function ([stdin]:6:7) + + ` test();` + Near here ---^ + + +-- End -- + +-- Testcase -- +{% + function test() { + test(); + } + + test(); +%} +-- End -- |