diff options
author | Petr Štetiar <ynezz@true.cz> | 2021-03-19 16:54:55 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2021-04-23 00:42:30 +0200 |
commit | 2b59097c3f61fa901e91ac4cea48940760439578 (patch) | |
tree | 958d739a78f959dfcd55b3d76e6e970ca53fa1c6 /tests/custom/00_syntax | |
parent | 80393611fb6634abcc0da1dee2da7c4418dbde8d (diff) |
tests: create custom tests from current tests cases
Signed-off-by: Petr Štetiar <ynezz@true.cz>
Diffstat (limited to 'tests/custom/00_syntax')
22 files changed, 1464 insertions, 0 deletions
diff --git a/tests/custom/00_syntax/00_single_line_comments b/tests/custom/00_syntax/00_single_line_comments new file mode 100644 index 0000000..24a32a2 --- /dev/null +++ b/tests/custom/00_syntax/00_single_line_comments @@ -0,0 +1,15 @@ +Single line comments. + +-- Expect stdout -- +This is a test. + a test. +A test . + +-- End -- + +-- Testcase -- +This is {# a comment within #} a test. +{# Comment before #} a test. +A test {# and a comment after #}. +{# This is a comment with embedded "{#" tag. #} +-- End -- diff --git a/tests/custom/00_syntax/01_unterminated_comment b/tests/custom/00_syntax/01_unterminated_comment new file mode 100644 index 0000000..1d3669c --- /dev/null +++ b/tests/custom/00_syntax/01_unterminated_comment @@ -0,0 +1,15 @@ +Unterminated comment + +-- Expect stderr -- +Syntax error: Unterminated template block +In line 1, byte 9: + + `This is {# an unclosed comment` + ^-- Near here + + +-- End -- + +-- Testcase -- +This is {# an unclosed comment +-- End -- diff --git a/tests/custom/00_syntax/02_multi_line_comments b/tests/custom/00_syntax/02_multi_line_comments new file mode 100644 index 0000000..99fc37e --- /dev/null +++ b/tests/custom/00_syntax/02_multi_line_comments @@ -0,0 +1,12 @@ +Multiline comments. + +-- Expect stdout -- +This is an example text for testing comment blocks. +-- End -- + +-- Testcase -- +This is an example text {# containing +a comment spanning multiple lines and + different indentation + #} for testing comment blocks. +-- End -- diff --git a/tests/custom/00_syntax/03_expression_blocks b/tests/custom/00_syntax/03_expression_blocks new file mode 100644 index 0000000..3568016 --- /dev/null +++ b/tests/custom/00_syntax/03_expression_blocks @@ -0,0 +1,11 @@ +Testing expression blocks. + +-- Expect stdout -- +The result of 3 * 7 is 21. +To escape the start tag, output it as string expression: {{ +-- End -- + +-- Testcase -- +The result of 3 * 7 is {{ 3 * 7 }}. +To escape the start tag, output it as string expression: {{ "{{" }} +-- End -- diff --git a/tests/custom/00_syntax/04_statement_blocks b/tests/custom/00_syntax/04_statement_blocks new file mode 100644 index 0000000..920ed71 --- /dev/null +++ b/tests/custom/00_syntax/04_statement_blocks @@ -0,0 +1,20 @@ +Testing statement blocks. + +-- Expect stdout -- +The result of 3 * 7 is 21. +A statement block may contain multiple statements: Hello World +To escape the start tag, output it as string expression: {% +Alternatively print it: {% +-- End -- + +-- Testcase -- +The result of 3 * 7 is {%+ print(3 * 7) %}. +A statement block may contain multiple statements: {%+ + print("Hello "); + print("World"); +%} + +To escape the start tag, output it as string expression: {{ "{%" }} +Alternatively print it: {%+ print("{%") %} + +-- End -- diff --git a/tests/custom/00_syntax/05_block_nesting b/tests/custom/00_syntax/05_block_nesting new file mode 100644 index 0000000..fcfd7da --- /dev/null +++ b/tests/custom/00_syntax/05_block_nesting @@ -0,0 +1,24 @@ +Nesting blocks into non-comment blocks should fail. + +-- Expect stderr -- +Syntax error: Template blocks may not be nested +In line 2, byte 61: + + `We may not nest statement blocks into expression blocks: {{ {% print(1 + 2) %} }}.` + Near here --------------------------------------------------^ + + +Syntax error: Template blocks may not be nested +In line 3, byte 61: + + `We may not nest expression blocks into statement blocks: {% {{ 1 + 2 }} %}.` + Near here --------------------------------------------------^ + + +-- End -- + +-- Testcase -- +We can nest other block types into comments: {# {% {{ 1 + 2 }} %} #} +We may not nest statement blocks into expression blocks: {{ {% print(1 + 2) %} }}. +We may not nest expression blocks into statement blocks: {% {{ 1 + 2 }} %}. +-- End -- diff --git a/tests/custom/00_syntax/06_open_statement_block b/tests/custom/00_syntax/06_open_statement_block new file mode 100644 index 0000000..9c2d142 --- /dev/null +++ b/tests/custom/00_syntax/06_open_statement_block @@ -0,0 +1,13 @@ +The last statement block of a template may remain open, this is useful for templates +that contain only code. + +-- Expect stdout -- +This template consists entirely of script code! +-- End -- + +-- Testcase -- +{% + print("This template "); + print("consists entirely "); + print("of script code!\n"); +-- End -- diff --git a/tests/custom/00_syntax/07_embedded_single_line_comments b/tests/custom/00_syntax/07_embedded_single_line_comments new file mode 100644 index 0000000..43f188c --- /dev/null +++ b/tests/custom/00_syntax/07_embedded_single_line_comments @@ -0,0 +1,21 @@ +Statement and expression blocks may contain C++-style comments. +A C++-style comment is started by two subsequent slashes and spans +until the next newline. + +-- Expect stdout -- +The result of 5 + 9 is 14. +Statement blocks may use C++ comments too: Test Another test. +-- End -- + +-- Testcase -- +The result of 5 + 9 is {{ // The block end tag is ignored: }} +// And the expression block continues here! +5 + 9 }}. +Statement blocks may use C++ comments too: {%+ + print("Test"); // A comment. + + // Another comment. + print(" Another test."); +%} + +-- End -- diff --git a/tests/custom/00_syntax/08_embedded_multi_line_comments b/tests/custom/00_syntax/08_embedded_multi_line_comments new file mode 100644 index 0000000..75aba5f --- /dev/null +++ b/tests/custom/00_syntax/08_embedded_multi_line_comments @@ -0,0 +1,24 @@ +Statement and expression blocks may contain C-style comments. +A C-style comment is started by a slash followed by an asterisk +and ended by an asterisk followed by a slash. + +Such comments may appear everywhere within statement or expression +blocks, even in the middle of statements or expressions. + +-- Expect stdout -- +The result of 12 - 4 is 8. +Statement blocks may use C comments too: Test Another test. The final test. +-- End -- + +-- Testcase -- +The result of 12 - 4 is {{ /* A comment before */ 12 - /* or even within */ 4 /* or after an expression */ }}. +Statement blocks may use C comments too: {%+ + print("Test"); /* A comment. */ + + /* Another comment. */ + print(" Another test."); + + print(/* A comment within */ " The final test."); +%} + +-- End -- diff --git a/tests/custom/00_syntax/09_string_literals b/tests/custom/00_syntax/09_string_literals new file mode 100644 index 0000000..0967850 --- /dev/null +++ b/tests/custom/00_syntax/09_string_literals @@ -0,0 +1,50 @@ +String literals may be enclosed in single or double quotes. +Embedded escape sequences are started with a backslash, followed +by either a hexadecimal, an octal or a single character escape sequence. + +-- Expect stdout -- +Single quoted string +Double quoted string +Unicode escape sequence: ☀💩 +Escaped double quote (") character +Escaped single quote (') character +Hexadecimal escape: XYZ xyz +Octal escape: ABC xyz +{ "Single char escape": "\u0007\b\u001b\f\r\t\u000b\\\n" } +-- End -- + +-- Testcase -- +{{ 'Single quoted string' }} +{{ "Double quoted string" }} +{{ "Unicode escape sequence: \u2600\uD83D\uDCA9" }} +{{ "Escaped double quote (\") character" }} +{{ 'Escaped single quote (\') character' }} +{{ "Hexadecimal escape: \x58\x59\x5A \x78\x79\x7a" }} +{{ "Octal escape: \101\102\103 \170\171\172" }} +{{ { "Single char escape": "\a\b\e\f\r\t\v\\\n" } }} +-- End -- + + +Testing various parsing corner cases. + +-- Expect stdout -- +[ "\t", "\n", "y", "\u0001", "\n", "\u0001\u0002", "\u0001\u0002", "\u0001\u0002", "\u0001a", "\na" ] +-- End -- + +-- Testcase -- +{% + print([ + "\ ", // properly handle escaped tab + "\ +", // properly handle escaped newline + "\y", // substitute unrecognized escape with escaped char + "\1", // handle short octal sequence at end of string + "\12", // handle short octal sequence at end of string + "\1\2", // handle subsequent short octal sequences + "\001\2", // handle short sequence after long one + "\1\002", // handle long sequence after short one + "\1a", // handle short octal sequence terminated by non-octal char + "\12a" // handle short octal sequence terminated by non-octal char + ], "\n"); +%} +-- End -- diff --git a/tests/custom/00_syntax/10_numeric_literals b/tests/custom/00_syntax/10_numeric_literals new file mode 100644 index 0000000..3e367d0 --- /dev/null +++ b/tests/custom/00_syntax/10_numeric_literals @@ -0,0 +1,26 @@ +C-style numeric integer and float literals are understood, as well +as the special keywords "Infinity" and "NaN" to denote the IEEE 754 +floating point values. + +Numeric values are either stored as signed 64 bit integers or signed +doubles internally. + +-- Expect stdout -- +Integers literals: 123, 127, 2748, 57082 +Float literals: 10, 10.3, 1.23456e-65, 16.0625 +Special values: Infinity, Infinity, NaN, NaN +Minimum values: -9223372036854775808, -1.79769e+308 +Maximum values: 9223372036854775807, 1.79769e+308 +Minimum truncation: -9223372036854775808, -Infinity +Maximum truncation: 9223372036854775807, Infinity +-- End -- + +-- Testcase -- +Integers literals: {{ 123 }}, {{ 0177 }}, {{ 0xabc }}, {{ 0xDEFA }} +Float literals: {{ 10. }}, {{ 10.3 }}, {{ 123.456e-67 }}, {{ 0x10.1 }} +Special values: {{ Infinity }}, {{ 1 / 0 }}, {{ NaN }}, {{ "x" / 1 }} +Minimum values: {{ -9223372036854775808 }}, {{ -1.7976931348623158e+308 }} +Maximum values: {{ 9223372036854775807 }}, {{ 1.7976931348623158e+308 }} +Minimum truncation: {{ -10000000000000000000 }}, {{ -1.0e309 }} +Maximum truncation: {{ 10000000000000000000 }}, {{ 1.0e309 }} +-- End -- diff --git a/tests/custom/00_syntax/11_misc_literals b/tests/custom/00_syntax/11_misc_literals new file mode 100644 index 0000000..372741c --- /dev/null +++ b/tests/custom/00_syntax/11_misc_literals @@ -0,0 +1,17 @@ +The utpl script language features a number of keywords which represent +certain special values. + +-- Expect stdout -- +The "this" keyword refers to the current function context: object +The "null" keyword represents the null value: null +The "true" keyword represents a true boolean value: true +The "false" keyword represents a false boolean value: false +-- End -- + +-- Testcase -- +{% let t = { f: function() { return this; } } %} +The "this" keyword refers to the current function context: {{ type(t.f()) }} +The "null" keyword represents the null value: {{ "" + null }} +The "true" keyword represents a true boolean value: {{ true }} +The "false" keyword represents a false boolean value: {{ false }} +-- End -- diff --git a/tests/custom/00_syntax/12_block_whitespace_control b/tests/custom/00_syntax/12_block_whitespace_control new file mode 100644 index 0000000..911171c --- /dev/null +++ b/tests/custom/00_syntax/12_block_whitespace_control @@ -0,0 +1,47 @@ +By default, whitespace before a block start tag or after a block end tag +is retained. By suffixing the start tag or prefixing the end tag with a +dash, the leading or trailing whitespace is trimmed respectively. + +-- Expect stdout -- +Whitespace control applies to all block types: +Comment before: | |, after: | |, both: || +Statement before: |test |, after: | test|, both: |test| +Expression before: |test |, after: | test|, both: |test| + +By default whitespace around a block is retained. +Leading whitespace can be trimmed like this. +The same applies to trailing whitespace. +It is also possible to trim bothleading and trailingwhitespace. + +Stripping works across multiple lines as well:test + +Likewise, stripping over multiple lines of trailing whitespace works as +expected too.This is after the block. +-- End -- + +-- Testcase -- +Whitespace control applies to all block types: +Comment before: | {#- test #} |, after: | {#- test #} |, both: | {#- test -#} | +Statement before: | {%- print("test") %} |, after: | {%+ print("test") -%} |, both: | {%- print("test") -%} | +Expression before: | {{- "test" }} |, after: | {{ "test" -}} |, both: | {{- "test" -}} | + +By default whitespace {{ "around a block" }} is retained. +Leading whitespace can be trimmed {#- note the leading dash #} like this. +The same applies to {# note the trailing dash -#} trailing whitespace. +It is also possible to trim both {{- "leading and trailing" -}} whitespace. + +Stripping works across multiple lines as well: + +{%- + /* The word "test" will be printed after "well:" above */ + print("test") +%} + + +Likewise, stripping over multiple lines of trailing whitespace works as +expected too. + +{#- Any whitespace after "expected too." and before "This is after the block" will be trimmed. -#} + +This is after the block. +-- End -- diff --git a/tests/custom/00_syntax/13_object_literals b/tests/custom/00_syntax/13_object_literals new file mode 100644 index 0000000..18fbbed --- /dev/null +++ b/tests/custom/00_syntax/13_object_literals @@ -0,0 +1,174 @@ +The utpl script language supports declaring objects (dictionaries) using +either JSON or JavaScript notation. + +-- Expect stdout -- +{ } +{ "name": "Bob", "age": 31, "email": { "work": "bob@example.com", "private": "bob@example.org" } } +{ "banana": "yellow", "tomato": "red", "broccoli": "green" } +{ "foo": "bar", "complex key": "qrx" } +{ "foo": { "bar": true } } +-- End -- + +-- Testcase -- +{% + // An empty object can be declared using a pair of curly brackets + empty_obj = { }; + + // It is also possible to use JSON notation to declare an object + json_obj = { + "name": "Bob", + "age": 31, + "email": { + "work": "bob@example.com", + "private": "bob@example.org" + } + }; + + // Declaring an object in JavaScript notation is supported as well + another_obj = { + banana: "yellow", + tomato: "red", + broccoli: "green" + }; + + // Mixing styles is allowed too + third_obj = { + foo: "bar", + "complex key": "qrx" + }; + + // Important caveat: when nesting objects, ensure that curly brackets + // are separated by space or newline to avoid interpretation as + // expression block tag! + nested_obj = { foo: { bar: true } }; // <-- mind the space in "} }" + + // Printing (or stringifying) objects will return their JSON representation + print(empty_obj, "\n"); + print(json_obj, "\n"); + print(another_obj, "\n"); + print(third_obj, "\n"); + print(nested_obj, "\n"); +%} +-- End -- + + +Additionally, utpl implements ES6-like spread operators to allow shallow copying +of object properties into other objects. + +-- Expect stdout -- +{ "foo": true, "bar": false } +{ "foo": true, "bar": false, "baz": 123, "qrx": 456 } +{ "foo": false, "bar": true, "baz": 123, "qrx": 456 } +{ "foo": true, "bar": false } +{ "foo": true, "bar": false, "level2": { "baz": 123, "qrx": 456 } } +{ "foo": true, "bar": false, "0": 7, "1": 8, "2": 9 } +-- End -- + +-- Testcase -- +{% + o1 = { foo: true, bar: false }; + o2 = { baz: 123, qrx: 456 }; + arr = [7, 8, 9]; + + print(join("\n", [ + // copying one object into another + { ...o1 }, + + // combining two objects + { ...o1, ...o2 }, + + // copying object and override properties + { ...o1, ...o2, foo: false, bar: true }, + + // default properties overwritten by spread operator + { foo: 123, bar: 456, ...o1 }, + + // nested spread operators + { ...o1, level2: { ...o2 } }, + + // merging array into objects + { ...o1, ...arr } + ]), "\n"); +%} +-- End -- + + +ES2015 short hand property notation is supported as well. + +-- Expect stdout -- +{ "a": 123, "b": true, "c": "test" } +-- End -- + +-- Testcase -- +{% + a = 123; + b = true; + c = "test"; + + o = { a, b, c }; + + print(o, "\n"); +%} +-- End -- + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ':' +In line 2, byte 14: + + ` o = { "foo" };` + Near here ------^ + + +-- End -- + +-- Testcase -- +{% + o = { "foo" }; +%} +-- End -- + + +ES2015 computed property names are supported. + +-- Expect stdout -- +{ "test": true, "hello": false, "ABC": 123 } +-- End -- + +-- Testcase -- +{% + s = "test"; + o = { + [s]: true, + ["he" + "llo"]: false, + [uc("abc")]: 123 + }; + + print(o, "\n"); +%} +-- End -- + +-- Expect stderr -- +Syntax error: Expecting expression +In line 2, byte 10: + + ` o1 = { []: true };` + Near here --^ + + +Syntax error: Unexpected token +Expecting ']' +In line 3, byte 14: + + ` o2 = { [true, false]: 123 };` + Near here ------^ + + +-- End -- + +-- Testcase -- +{% + o1 = { []: true }; + o2 = { [true, false]: 123 }; +%} +-- End -- diff --git a/tests/custom/00_syntax/14_array_literals b/tests/custom/00_syntax/14_array_literals new file mode 100644 index 0000000..941ee4a --- /dev/null +++ b/tests/custom/00_syntax/14_array_literals @@ -0,0 +1,82 @@ +The utpl script language supports declaring arrays using JSON notation. + +-- Expect stdout -- +[ ] +[ "first", "second", 123, [ "a", "nested", "array" ], { "a": "nested object" } ] +-- End -- + +-- Testcase -- +{% + // An empty array can be declared using a pair of square brackets + empty_array = [ ]; + + // JSON notation is used to declare an array with contents + json_array = [ + "first", + "second", + 123, + [ "a", "nested", "array" ], + { a: "nested object" } + ]; + + // Printing (or stringifying) arrays will return their JSON representation + print(empty_array, "\n"); + print(json_array, "\n"); +-- End -- + + +Additionally, utpl implements ES6-like spread operators to allow shallow copying +of array values into other arrays. + +-- Expect stdout -- +[ 1, 2, 3 ] +[ 1, 2, 3, 4, 5, 6 ] +[ 1, 2, 3, 4, 5, 6, false, true ] +[ 1, 2, 3, false, true, 4, 5, 6 ] +[ 1, 2, 3, [ 4, 5, 6 ] ] +-- End -- + +-- Testcase -- +{% + a1 = [ 1, 2, 3 ]; + a2 = [ 4, 5, 6 ]; + + print(join("\n", [ + // copying one array into another + [ ...a1 ], + + // combining two arrays + [ ...a1, ...a2 ], + + // copying array and append values + [ ...a1, ...a2, false, true ], + + // copy array and interleave values + [ ...a1, false, true, ...a2 ], + + // nested spread operators + [ ...a1, [ ...a2 ] ] + ]), "\n"); +%} +-- End -- + +Contrary to merging arrays into objects, objects cannot be merged into arrays. + +-- Expect stderr -- +Type error: ({ "foo": true, "bar": false }) is not iterable +In line 5, byte 21: + + ` print([ ...arr, ...obj ], "\n");` + Near here -------------^ + + +-- End -- + +-- Testcase -- +{% + arr = [ 1, 2, 3 ]; + obj = { foo: true, bar: false }; + + print([ ...arr, ...obj ], "\n"); +%} +-- End -- diff --git a/tests/custom/00_syntax/15_function_declarations b/tests/custom/00_syntax/15_function_declarations new file mode 100644 index 0000000..4257dd6 --- /dev/null +++ b/tests/custom/00_syntax/15_function_declarations @@ -0,0 +1,164 @@ +Function declarations follow the ECMAScript 5 syntax. Functions can be +declared anonymously, which is useful for "throw-away" functions such +as sort or filter callbacks or for building objects or arrays of function +values. + +If functions are declared with a name, the resulting function value is +automatically assigned under the given name to the current scope. + +When function values are stringifed, the resulting string will describe +the declaration of the function. + +Nesting function declarations is possible as well. + + +-- Expect stdout -- +function() { ... } +function test_fn(a, b) { ... } +function test2_fn(a, b) { ... } + +A function declaration using the alternative syntax: +The function was called with arguments 123 and 456. + +-- End -- + +-- Testcase -- +{% + // declare an anonymous function and + // assign resulting value + anon_fn = function() { + return "test"; + }; + + // declare a named function + function test_fn(a, b) { + return a + b; + } + + // nesting functions is legal + function test2_fn(a, b) { + function test3_fn(a, b) { + return a * b; + } + + return a + test3_fn(a, b); + } + + print(anon_fn, "\n"); + print(test_fn, "\n"); + print(test2_fn, "\n"); +%} + +A function declaration using the alternative syntax: +{% function test3_fn(a, b): %} +The function was called with arguments {{ a }} and {{ b }}. +{% endfunction %} +{{ test3_fn(123, 456) }} +-- End -- + + +Additionally, utpl implements ES6-like "rest" argument syntax to declare +variadic functions. + +-- Expect stdout -- +function non_variadic(a, b, c, d, e) { ... } +[ 1, 2, 3, 4, 5 ] +function variadic_1(a, b, ...args) { ... } +[ 1, 2, [ 3, 4, 5 ] ] +function variadic_2(...args) { ... } +[ [ 1, 2, 3, 4, 5 ] ] +-- End -- + +-- Testcase -- +{% + // ordinary, non-variadic function + function non_variadic(a, b, c, d, e) { + return [ a, b, c, d, e ]; + } + + // fixed amount of arguments with variable remainder + function variadic_1(a, b, ...args) { + return [ a, b, args ]; + } + + // only variable arguments + function variadic_2(...args) { + return [ args ]; + } + + print(join("\n", [ + non_variadic, + non_variadic(1, 2, 3, 4, 5), + variadic_1, + variadic_1(1, 2, 3, 4, 5), + variadic_2, + variadic_2(1, 2, 3, 4, 5) + ]), "\n"); +%} +-- End -- + + +Complementary to the "rest" argument syntax, the spread operator may be +used in function call arguments to pass arrays of values as argument list. + +-- Expect stdout -- +[ 1, 2, 3, 4, 5, 6 ] +[ 4, 5, 6, 1, 2, 3 ] +[ 1, 2, 3, 1, 2, 3 ] +[ 1, 2, 3 ] +-- End -- + +-- Testcase -- +{% + arr = [ 1, 2, 3 ]; + + function test(...args) { + return args; + } + + print(join("\n", [ + test(...arr, 4, 5, 6), + test(4, 5, 6, ...arr), + test(...arr, ...arr), + test(...arr) + ]), "\n"); +%} +-- End -- + + +Rest arguments may be only used once in a declaration and they must always +be the last item in the argument list. + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ')' +In line 2, byte 26: + + ` function illegal(...args, ...args2) {}` + Near here ------------------^ + + +-- End -- + +-- Testcase -- +{% + function illegal(...args, ...args2) {} +%} +-- End -- + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ')' +In line 2, byte 26: + + ` function illegal(...args, a, b) {}` + Near here ------------------^ + + +-- End -- + +-- Testcase -- +{% + function illegal(...args, a, b) {} +%} +-- End -- diff --git a/tests/custom/00_syntax/16_for_loop b/tests/custom/00_syntax/16_for_loop new file mode 100644 index 0000000..67edc21 --- /dev/null +++ b/tests/custom/00_syntax/16_for_loop @@ -0,0 +1,299 @@ +Two for-loop variants are supported: a C-style counting for loop +consisting of an initialization expression, a test condition +and a step expression and a for-in-loop variant which allows +enumerating properties of objects or items of arrays. + +Additionally, utpl supports an alternative syntax suitable for +template block tags. + + +-- Expect stdout -- +A simple counting for-loop: +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 + +If the loop body consists of only one statement, the curly braces +may be omitted: +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 + +Any of the init-, test- and increment expressions may be omitted. + +Loop without initialization statement: +Iteration null +Iteration 1 +Iteration 2 + +Loop without test statement: +Iteration 0 +Iteration 1 +Iteration 2 + +Loop without init-, test- or increment statement: +Iteration 1 +Iteration 2 +Iteration 3 + +For-in loop enumerating object properties: +Key: foo +Key: bar + +For-in loop enumerating array items: +Item: one +Item: two +Item: three + +A counting for-loop using the alternative syntax: +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 + +A for-in loop using the alternative syntax: +Item 123 +Item 456 +Item 789 + +For-in and counting for loops may declare variables: +Iteration 0 +Iteration 1 +Iteration 2 + +Item 123 +Item 456 +Item 789 +-- End -- + +-- Testcase -- +A simple counting for-loop: +{% + for (i = 0; i < 10; i++) { + print("Iteration "); + print(i); + print("\n"); + } +%} + +If the loop body consists of only one statement, the curly braces +may be omitted: +{% + for (i = 0; i < 10; i++) + print("Iteration ", i, "\n"); +%} + +Any of the init-, test- and increment expressions may be omitted. + +Loop without initialization statement: +{% + for (; j < 3; j++) + print("Iteration " + j + "\n"); +%} + +Loop without test statement: +{% + for (j = 0;; j++) { + if (j == 3) + break; + + print("Iteration ", j, "\n"); + } +%} + +Loop without init-, test- or increment statement: +{% + for (;;) { + if (k++ == 3) + break; + + print("Iteration ", k, "\n"); + } +%} + +For-in loop enumerating object properties: +{% + obj = { foo: true, bar: false }; + for (key in obj) + print("Key: ", key, "\n"); +%} + +For-in loop enumerating array items: +{% + arr = [ "one", "two", "three" ]; + for (item in arr) + print("Item: ", item, "\n"); +%} + +A counting for-loop using the alternative syntax: +{% for (x = 0; x < 10; x++): -%} +Iteration {{ x }} +{% endfor %} + +A for-in loop using the alternative syntax: +{% for (n in [123, 456, 789]): -%} +Item {{ n }} +{% endfor %} + +For-in and counting for loops may declare variables: +{% for (let i = 0; i < 3; i++): %} +Iteration {{ i }} +{% endfor %} + +{% for (let n in [123, 456, 789]): %} +Item {{ n }} +{% endfor %} +-- End -- + + +By specifying two loop variables in for-in loop expressions, keys +and values can be iterated simultaneously. + +-- Expect stdout -- +true +false +123 +456 +[ 0, true ] +[ 1, false ] +[ 2, 123 ] +[ 3, 456 ] +foo +bar +baz +qrx +[ "foo", true ] +[ "bar", false ] +[ "baz", 123 ] +[ "qrx", 456 ] +-- End -- + +-- Testcase -- +{% + let arr = [ true, false, 123, 456 ]; + let obj = { foo: true, bar: false, baz: 123, qrx: 456 }; + + // iterating arrays with one loop variable yields the array values + for (let x in arr) + print(x, "\n"); + + // iterating arrays with two loop variables yields the array indexes + // and their corresponding values + for (let x, y in arr) + print([x, y], "\n"); + + // iterating objects with one loop variable yields the object keys + for (let x in obj) + print(x, "\n"); + + // iterating objects with two loop variables yields the object keys + // and their corresponding values + for (let x, y in obj) + print([x, y], "\n"); +%} +-- End -- + + +Ensure that for-in loop expressions with more than two variables are +rejected. + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ';' +In line 2, byte 24: + + ` for (let x, y, z in {})` + Near here ----------------^ + + +-- End -- + +-- Testcase -- +{% + for (let x, y, z in {}) + ; +%} +-- End -- + + +Ensure that assignments in for-in loop expressions are rejected. + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ';' +In line 2, byte 25: + + ` for (let x = 1, y in {})` + Near here -----------------^ + + +-- End -- + +-- Testcase -- +{% + for (let x = 1, y in {}) + ; +%} +-- End -- + + +Ensure that too short for-in loop expressions are rejected (1/2). + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ';' +In line 2, byte 12: + + ` for (let x)` + Near here ----^ + + +-- End -- + +-- Testcase -- +{% + for (let x) + ; +%} +-- End -- + + +Ensure that too short for-in loop expressions are rejected (2/2). + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ';' +In line 2, byte 15: + + ` for (let x, y)` + Near here -------^ + + +-- End -- + +-- Testcase -- +{% + for (let x, y) + ; +%} +-- End -- diff --git a/tests/custom/00_syntax/17_while_loop b/tests/custom/00_syntax/17_while_loop new file mode 100644 index 0000000..1e68d6b --- /dev/null +++ b/tests/custom/00_syntax/17_while_loop @@ -0,0 +1,71 @@ +Utpl implements C-style while loops which run as long as the condition +is fulfilled. + +Like with for-loops, an alternative syntax form suitable for template +blocks is supported. + + +-- Expect stdout -- +A simple counting while-loop: +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 + +If the loop body consists of only one statement, the curly braces +may be omitted: +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 + +A counting while-loop using the alternative syntax: +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 +-- End -- + +-- Testcase -- +A simple counting while-loop: +{% + i = 0; + while (i < 10) { + print("Iteration "); + print(i); + print("\n"); + i++; + } +%} + +If the loop body consists of only one statement, the curly braces +may be omitted: +{% + i = 0; + while (i < 10) + print("Iteration ", i++, "\n"); +%} + +A counting while-loop using the alternative syntax: +{% while (x < 10): -%} +Iteration {{ "" + x++ }} +{% endwhile %} +-- End -- diff --git a/tests/custom/00_syntax/18_if_condition b/tests/custom/00_syntax/18_if_condition new file mode 100644 index 0000000..9e02767 --- /dev/null +++ b/tests/custom/00_syntax/18_if_condition @@ -0,0 +1,121 @@ +Utpl implements C-style if/else conditions and ?: ternary statements. + +Like with for- and while-loops, an alternative syntax form suitable +for template blocks is supported. + + +-- Expect stdout -- +This should print "one": +one + +This should print "two": +two + +Multiple conditions can be used by chaining if/else statements: +three + +If the conditional block consists of only one statement, the curly +braces may be omitted: +two + +An if-condition using the alternative syntax: +Variable x has another value. + + +An if-condition using the special "elif" keyword in alternative syntax mode: +Variable x was set to five. + + +Ternary expressions function similar to if/else statements but +only allow for a single expression in the true and false branches: +Variable x is one +-- End -- + +-- Testcase -- +This should print "one": +{% + x = 0; + + if (x == 0) { + print("one"); + } + else { + print("two"); + } +%} + + +This should print "two": +{% + x = 1; + + if (x == 0) { + print("one"); + } + else { + print("two"); + } +%} + + +Multiple conditions can be used by chaining if/else statements: +{% + x = 2; + + if (x == 0) { + print("one"); + } + else if (x == 1) { + print("two"); + } + else if (x == 2) { + print("three"); + } + else { + print("four"); + } +%} + + +If the conditional block consists of only one statement, the curly +braces may be omitted: +{% + x = 5; + + if (x == 0) + print("one"); + else + print("two"); +%} + + +An if-condition using the alternative syntax: +{% if (x == 1): -%} +Variable x was set to one. +{% else -%} +Variable x has another value. +{% endif %} + + +An if-condition using the special "elif" keyword in alternative syntax mode: +{% if (x == 0): -%} +Variable x was set to zero. +{% elif (x == 1): -%} +Variable x was set to one. +{% elif (x == 5): -%} +Variable x was set to five. +{% else -%} +Variable x has another value. +{% endif %} + + +Ternary expressions function similar to if/else statements but +only allow for a single expression in the true and false branches: +{% + x = 1; + s = (x == 1) ? "Variable x is one" : "Variable x has another value"; + + print(s); +%} + +-- End -- diff --git a/tests/custom/00_syntax/19_arrow_functions b/tests/custom/00_syntax/19_arrow_functions new file mode 100644 index 0000000..102c527 --- /dev/null +++ b/tests/custom/00_syntax/19_arrow_functions @@ -0,0 +1,124 @@ +Besides the ordinary ES5-like function declarations, utpl supports ES6 inspired +arrow function syntax as well. Such arrow functions are useful for callbacks to functions such as replace(), map() or filter(). + +-- Expect stdout -- +() => { ... } +test +(a, b) => { ... } +3 +(...args) => { ... } +15 +(a) => { ... } +10 +(a) => { ... } +36 +-- End -- + +-- Testcase -- +{% + + // assign arrow function to variable + test1_fn = () => { + return "test"; + }; + + // assign arrow function with parameters + test2_fn = (a, b) => { + return a + b; + }; + + // nesting functions is legal + test3_fn = (...args) => { + nested_fn = (a, b) => { + return a * b; + }; + + return args[0] + nested_fn(args[0], args[1]); + }; + + // parentheses may be omitted if arrow function takes only one argument + test4_fn = a => { + a * 2; + }; + + // curly braces may be omitted if function body is a single expression + test5_fn = a => a * a; + + print(join("\n", [ + test1_fn, + test1_fn(), + test2_fn, + test2_fn(1, 2), + test3_fn, + test3_fn(3, 4), + test4_fn, + test4_fn(5), + test5_fn, + test5_fn(6) + ]), "\n"); +%} +-- End -- + + +While the main advantage of arrow functions is the compact syntax, another +important difference to normal functions is the "this" context behaviour - +arrow functions do not have an own "this" context and simply inherit it from +the outer calling scope. + +-- Expect stdout -- +this is set to obj: true +arrow function uses outher this: true +normal function has own this: true +arrow function as method has no this: true +-- End -- + +-- Testcase -- +{% + obj = { + method: function() { + let that = this; + let arr = () => { + print("arrow function uses outher this: ", that == this, "\n"); + }; + let fn = function() { + print("normal function has own this: ", that != this, "\n"); + }; + + print("this is set to obj: ", this == obj, "\n"); + + arr(); + fn(); + }, + + arrowfn: () => { + print("arrow function as method has no this: ", this == null, "\n"); + } + }; + + obj.method(); + obj.arrowfn(); +%} +-- End -- + + +Due to the difficulty of recognizing arrow function expressions with an LR(1) +grammar the parser has to use a generic expression rule on the lhs argument list +and verify that it does not contain non-label nodes while building the ast. The +subsequent testcase asserts that case. + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ';' +In line 2, byte 10: + + ` (a + 1) => { print("test\n") }` + Near here --^ + + +-- End -- + +-- Testcase -- +{% + (a + 1) => { print("test\n") } +%} +-- End -- diff --git a/tests/custom/00_syntax/20_list_expressions b/tests/custom/00_syntax/20_list_expressions new file mode 100644 index 0000000..d5ba459 --- /dev/null +++ b/tests/custom/00_syntax/20_list_expressions @@ -0,0 +1,45 @@ +Similar to ES5, utpl's language grammar allows comma separated list expressions +in various contexts. Unless such lists happen to be part of a function call +or array construction expression, only the last result of such an expression +list should be used while still evaluating all sub-expressions, triggering +side effects such as function calls or variable assignments. + +-- Expect stdout -- +4 +[ 1, 3 ] +{ "a": true, "b": 1 } +function call +[ "test", "assigment" ] +true +true +true +[ 2, 3 ] +-- End -- + +-- Testcase -- +{% + // only the last value is considered + print(1 + (2, 3), "\n"); + + // in array constructors, parenthesized lists are reduced to the last value + print([ (0, 1), (2, 3) ], "\n"); + + // in object constructors, parenthesized lists are reduced to the last value + print({ a: (false, true), b: (0, 1) }, "\n"); + + // all list expressions are evaluated and may have side effects, even if + // results are discareded + x = (print("function call\n"), y = "assigment", "test"); + print([x, y], "\n"); + + // property access operates on the last value of a parenthesized list expression + print(({foo: false}, {foo: true}).foo, "\n"); + print(({foo: false}, {foo: true})["foo"], "\n"); + + // computed property access uses the last list expression value + print(({foo: true})["bar", "baz", "foo"], "\n"); + + // same list semantics apply to function call parameters + ((...args) => print(args, "\n"))((1, 2), 3); +%} +-- End -- diff --git a/tests/custom/00_syntax/21_regex_literals b/tests/custom/00_syntax/21_regex_literals new file mode 100644 index 0000000..3af53bb --- /dev/null +++ b/tests/custom/00_syntax/21_regex_literals @@ -0,0 +1,89 @@ +Regex literals are enclosed in forward slashes and may contain zero +or more trailing flag characters. Interpretation of escape sequences +within regular expression literals is subject of the underlying +regular expression engine. + +-- Expect stdout -- +[ "/Hello world/", "/test/gis", "/test/g", "/test1 \\\/ test2/", "/\\x31\n\\.\u0007\b\\c\\u2600\\\\/" ] +-- End -- + +-- Testcase -- +{% + print([ + /Hello world/, // A very simple expression + /test/gsi, // Regular expression flags + /test/gg, // Repeated flags + /test1 \/ test2/, // Escaped forward slash + /\x31\n\.\a\b\c\u2600\\/ // Ensure that escape sequences are passed as-is + ], "\n"); +%} +-- End -- + + +Testing regular expression type. + +-- Expect stdout -- +object +-- End -- + +-- Testcase -- +{{ type(/foo/) }} +-- End -- + + +Testing invalid flag characters. + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting ';' +In line 2, byte 8: + + ` /test/x` + ^-- Near here + + +-- End -- + +-- Testcase -- +{% + /test/x +%} +-- End -- + + +Testing unclosed regular expression. + +-- Expect stderr -- +Syntax error: Unterminated string +In line 2, byte 2: + + ` /foo \/` + ^-- Near here + + +-- End -- + +-- Testcase -- +{% + /foo \/ +%} +-- End -- + + +Testing regex compilation errors. + +-- Expect stderr -- +Syntax error: Unmatched \{ +In line 2, byte 3: + + ` /foo {/` + ^-- Near here + + +-- End -- + +-- Testcase -- +{% + /foo {/ +%} +-- End -- |