diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index f4548a4c9..5836b7582 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "ghul.compiler": { - "version": "0.8.23", + "version": "0.8.24", "commands": [ "ghul-compiler" ] diff --git a/Directory.Build.props b/Directory.Build.props index 1d13fe961..7130b915a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 0.8.24-alpha.12 + 0.8.25-alpha.2 $(NoWarn);NU1507 diff --git a/integration-tests/execution/let-in-1/.vscode/tasks.json b/integration-tests/execution/let-in-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/execution/let-in-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/execution/let-in-1/err.expected b/integration-tests/execution/let-in-1/err.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/let-in-1/ghul.json b/integration-tests/execution/let-in-1/ghul.json new file mode 100644 index 000000000..a7c57e543 --- /dev/null +++ b/integration-tests/execution/let-in-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "../../../bin/Release/net8.0/ghul", + "source": [ + "." + ] +} diff --git a/integration-tests/execution/let-in-1/ghulflags b/integration-tests/execution/let-in-1/ghulflags new file mode 100644 index 000000000..7a27fea4a --- /dev/null +++ b/integration-tests/execution/let-in-1/ghulflags @@ -0,0 +1 @@ +--dotnet \ No newline at end of file diff --git a/integration-tests/execution/let-in-1/il.expected b/integration-tests/execution/let-in-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/let-in-1/run.expected b/integration-tests/execution/let-in-1/run.expected new file mode 100644 index 000000000..dcf507040 --- /dev/null +++ b/integration-tests/execution/let-in-1/run.expected @@ -0,0 +1,13 @@ +simple_expression_body(5) = 10 +simple_expression_body((5, 6)) = 11 +fib(0) = 0 +fib(1) = 1 +fib(2) = 1 +fib(3) = 2 +fib(4) = 3 +fib(5) = 5 +if_expression_condition_1(5) = 5 * 2 more than 10 +if_expression_condition_2(5, 6) = 5 * 2 and 6 * 2 more than 10 +generator_1().take(5).to_list() = 0, 1, 2, 3, 4 +generator_fibonacci().take(5).to_list() = 0, 1, 1, 2, 3 +generator_factorial().take(5).to_list() = 1, 2, 6, 24, 120 diff --git a/integration-tests/execution/let-in-1/test.ghul b/integration-tests/execution/let-in-1/test.ghul new file mode 100644 index 000000000..2d89fdb63 --- /dev/null +++ b/integration-tests/execution/let-in-1/test.ghul @@ -0,0 +1,115 @@ +use IO.Std.write_line; + +entry() is + write_line("simple_expression_body(5) = " + simple_expression_body_1(5)); // 10 + write_line("simple_expression_body((5, 6)) = " + simple_expression_body_2((5, 6))); // 11 + recursive_anonymous_function(); + + write_line("if_expression_condition_1(5) = " + if_expression_condition_1(5)); // 5 * 2 less than 10 + write_line("if_expression_condition_2(5, 6) = " + if_expression_condition_2(5, 6)); // 5 * 2 less than 10 + + write_line("generator_1().take(5).to_list() = " + generator_incremental() | .take(5)); // [0, 1, 2, 3, 4] + write_line("generator_fibonacci().take(5).to_list() = " + generator_fibonacci() | .take(5)); // [0, 1, 1, 2, 3] + write_line("generator_factorial().take(5).to_list() = " + generator_factorial() | .take(5)); // [1, 1, 2, 6, 24] +si + +simple_expression_body_1(i: int) -> int => + let times_2 = i * 2 + in times_2; + +simple_expression_body_2(i: (int, int)) -> int => + let (a, b) = i + in a + b; + +recursive_anonymous_function() is + let fib = (n: int) -> int rec => + let fib = rec in + if n < 2 then + n + else + fib(n - 1) + fib(n - 2); + fi; + + for (i, f) in (0..6) | .map(i => fib(i)) .index() do + write_line("fib(" + i + ") = " + f); + od +si + +generator_incremental() -> Collections.Iterable[int] => + generate(0, + (i: int) => + let next = i + 1 in (next, i)); + +generator_fibonacci() -> Collections.Iterable[int] => + generate((0, 1), + (state: (int, int)) => + let + (a, b) = state, + next = (b, a + b) + in (next, a)); + +generator_factorial() -> Collections.Iterable[int] => + generate((1, 1), + (state: (int, int)) => + let + (i, f) = state, + next = (i + 1, f * (i + 1)) + in (next, f)); + +if_expression_condition_1(i: int) -> string => + if + let x = i * 2 in x < 10 + then + "{i} * 2 less than 10" + else + "{i} * 2 more than 10" + fi; + +if_expression_condition_2(i: int, j: int) -> string => + if + let x = i * 2 in x < 10 + then + "{i} * 2 less than 10" + elif + let y = j * 2 in y < 10 + then + "{j} * 2 less than 10" + else + "{i} * 2 and {j} * 2 more than 10" + fi; + + +class GENERATOR[T, S]: Collections.Iterator[T], Collections.Iterable[T] is + current: T; + iterator: Collections.Iterator[T] => self; + + _initial: S; + _state: S; + _generator: S -> (S, T); // given the current state, return the next state and the current value + + init(initial: S, generator: S -> (S, T)) is + _initial = initial; + _generator = generator; + reset(); + si + + init() is + reset(); + si + + move_next() -> bool is + (_state, current) = _generator(_state); + return true; + si + + reset() is + _state = _initial; + si + + dispose() is + si +si + +// generator constructor helper so we don't have to specify types +generate[T, S](initial: S, generator: S -> (S, T)) -> GENERATOR[T, S] => + new GENERATOR[T, S](initial, generator); \ No newline at end of file diff --git a/integration-tests/execution/let-in-1/warn.expected b/integration-tests/execution/let-in-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/expression-function-with-explicit-type-arguments-3/err.expected b/integration-tests/parse/expression-function-with-explicit-type-arguments-3/err.expected index 69f237fd9..11d2f357b 100644 --- a/integration-tests/parse/expression-function-with-explicit-type-arguments-3/err.expected +++ b/integration-tests/parse/expression-function-with-explicit-type-arguments-3/err.expected @@ -1,2 +1,2 @@ -test.ghul: 11,37..11,39: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found [] +test.ghul: 11,37..11,39: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found [] test.ghul: 11,37..11,39: error: syntax error: expected ; but found [] diff --git a/integration-tests/parse/expression-function-with-explicit-type-arguments-6/err.expected b/integration-tests/parse/expression-function-with-explicit-type-arguments-6/err.expected index 228a8085b..c4b1c7dba 100644 --- a/integration-tests/parse/expression-function-with-explicit-type-arguments-6/err.expected +++ b/integration-tests/parse/expression-function-with-explicit-type-arguments-6/err.expected @@ -1,2 +1,2 @@ -test.ghul: 15,37..15,39: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found [] +test.ghul: 15,37..15,39: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found [] test.ghul: 15,37..15,39: error: syntax error: expected ; but found [] diff --git a/integration-tests/parse/incomplete-call-1/err.expected b/integration-tests/parse/incomplete-call-1/err.expected index defa88d44..32f22a862 100644 --- a/integration-tests/parse/incomplete-call-1/err.expected +++ b/integration-tests/parse/incomplete-call-1/err.expected @@ -1,3 +1,3 @@ test.ghul: 7,13..7,17: error: symbol not found: blah -test.ghul: 8,9..8,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 8,9..8,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si test.ghul: 8,9..8,11: error: in secondary expression: expected ) but found si diff --git a/integration-tests/parse/incomplete-enum-6/err.expected b/integration-tests/parse/incomplete-enum-6/err.expected index 8b1c9631c..9833e1b9b 100644 --- a/integration-tests/parse/incomplete-enum-6/err.expected +++ b/integration-tests/parse/incomplete-enum-6/err.expected @@ -1 +1 @@ -test.ghul: 8,1..8,2: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 8,1..8,2: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-for-10/err.expected b/integration-tests/parse/incomplete-for-10/err.expected index b23b2c1b8..f8a3a4984 100644 --- a/integration-tests/parse/incomplete-for-10/err.expected +++ b/integration-tests/parse/incomplete-for-10/err.expected @@ -1 +1 @@ -test.ghul: 2,14..2,16: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found do +test.ghul: 2,14..2,16: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found do diff --git a/integration-tests/parse/incomplete-for-3/err.expected b/integration-tests/parse/incomplete-for-3/err.expected index 0733f295b..1f7171878 100644 --- a/integration-tests/parse/incomplete-for-3/err.expected +++ b/integration-tests/parse/incomplete-for-3/err.expected @@ -1 +1 @@ -test.ghul: 4,1..4,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 4,1..4,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-for-4/err.expected b/integration-tests/parse/incomplete-for-4/err.expected index 6876c0609..64f82e6b2 100644 --- a/integration-tests/parse/incomplete-for-4/err.expected +++ b/integration-tests/parse/incomplete-for-4/err.expected @@ -1,2 +1,2 @@ test.ghul: 3,1..3,3: error: in for statement: expected do but found si -test.ghul: 3,1..3,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 3,1..3,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-if-1/err.expected b/integration-tests/parse/incomplete-if-1/err.expected index 654c9eba0..ed247c6f0 100644 --- a/integration-tests/parse/incomplete-if-1/err.expected +++ b/integration-tests/parse/incomplete-if-1/err.expected @@ -1 +1 @@ -test.ghul: 8,9..8,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 8,9..8,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-let-in-1/.vscode/tasks.json b/integration-tests/parse/incomplete-let-in-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/parse/incomplete-let-in-1/err.expected b/integration-tests/parse/incomplete-let-in-1/err.expected new file mode 100644 index 000000000..d962e8dcd --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-1/err.expected @@ -0,0 +1,2 @@ +test.ghul: 10,5..10,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 10,5..10,7: error: syntax error: expected ; but found si diff --git a/integration-tests/parse/incomplete-let-in-1/fail.expected b/integration-tests/parse/incomplete-let-in-1/fail.expected new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-1/fail.expected @@ -0,0 +1 @@ + diff --git a/integration-tests/parse/incomplete-let-in-1/ghul.json b/integration-tests/parse/incomplete-let-in-1/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/parse/incomplete-let-in-1/ghulflags b/integration-tests/parse/incomplete-let-in-1/ghulflags new file mode 100644 index 000000000..4d095b972 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-1/ghulflags @@ -0,0 +1 @@ +--type-check \ No newline at end of file diff --git a/integration-tests/parse/incomplete-let-in-1/il.expected b/integration-tests/parse/incomplete-let-in-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/incomplete-let-in-1/test.ghul b/integration-tests/parse/incomplete-let-in-1/test.ghul new file mode 100644 index 000000000..50dc07d84 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-1/test.ghul @@ -0,0 +1,17 @@ +namespace Test.ParseLetIn is + use Collections; + + class TestA is + test_simple() -> int => + let + x = new D() + in + + si + + class D is + value: int => 1234; + init() is + si + si +si \ No newline at end of file diff --git a/integration-tests/parse/incomplete-let-in-1/warn.expected b/integration-tests/parse/incomplete-let-in-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/incomplete-let-in-2/.vscode/tasks.json b/integration-tests/parse/incomplete-let-in-2/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-2/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/parse/incomplete-let-in-2/err.expected b/integration-tests/parse/incomplete-let-in-2/err.expected new file mode 100644 index 000000000..238a5fdd9 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-2/err.expected @@ -0,0 +1,3 @@ +test.ghul: 8,5..8,7: error: in variable: expected ( or identifier but found si +test.ghul: 8,5..8,7: error: syntax error: expected ; but found si +test.ghul: 8,5..8,7: error: syntax error: expected in but found si diff --git a/integration-tests/parse/incomplete-let-in-2/fail.expected b/integration-tests/parse/incomplete-let-in-2/fail.expected new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-2/fail.expected @@ -0,0 +1 @@ + diff --git a/integration-tests/parse/incomplete-let-in-2/ghul.json b/integration-tests/parse/incomplete-let-in-2/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-2/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/parse/incomplete-let-in-2/ghulflags b/integration-tests/parse/incomplete-let-in-2/ghulflags new file mode 100644 index 000000000..4d095b972 --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-2/ghulflags @@ -0,0 +1 @@ +--type-check \ No newline at end of file diff --git a/integration-tests/parse/incomplete-let-in-2/il.expected b/integration-tests/parse/incomplete-let-in-2/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/incomplete-let-in-2/test.ghul b/integration-tests/parse/incomplete-let-in-2/test.ghul new file mode 100644 index 000000000..64423e87a --- /dev/null +++ b/integration-tests/parse/incomplete-let-in-2/test.ghul @@ -0,0 +1,15 @@ +namespace Test.ParseLetIn is + use Collections; + + class TestA is + test_simple() -> int => + let + + si + + class D is + value: int => 1234; + init() is + si + si +si \ No newline at end of file diff --git a/integration-tests/parse/incomplete-let-in-2/warn.expected b/integration-tests/parse/incomplete-let-in-2/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/incomplete-property-14/err.expected b/integration-tests/parse/incomplete-property-14/err.expected index 4303cab72..be08b3c0d 100644 --- a/integration-tests/parse/incomplete-property-14/err.expected +++ b/integration-tests/parse/incomplete-property-14/err.expected @@ -1,2 +1,2 @@ -test.ghul: 5,5..5,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 5,5..5,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si test.ghul: 5,5..5,7: error: syntax error: expected ; but found si diff --git a/integration-tests/parse/incomplete-property-15/err.expected b/integration-tests/parse/incomplete-property-15/err.expected index 2ace0bcbf..478ef8f3e 100644 --- a/integration-tests/parse/incomplete-property-15/err.expected +++ b/integration-tests/parse/incomplete-property-15/err.expected @@ -1,2 +1,2 @@ -test.ghul: 6,5..6,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 6,5..6,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si test.ghul: 6,5..6,7: error: syntax error: expected ; but found si diff --git a/integration-tests/parse/incomplete-property-3/err.expected b/integration-tests/parse/incomplete-property-3/err.expected index 4303cab72..be08b3c0d 100644 --- a/integration-tests/parse/incomplete-property-3/err.expected +++ b/integration-tests/parse/incomplete-property-3/err.expected @@ -1,2 +1,2 @@ -test.ghul: 5,5..5,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 5,5..5,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si test.ghul: 5,5..5,7: error: syntax error: expected ; but found si diff --git a/integration-tests/parse/incomplete-property-4/err.expected b/integration-tests/parse/incomplete-property-4/err.expected index 2ace0bcbf..478ef8f3e 100644 --- a/integration-tests/parse/incomplete-property-4/err.expected +++ b/integration-tests/parse/incomplete-property-4/err.expected @@ -1,2 +1,2 @@ -test.ghul: 6,5..6,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 6,5..6,7: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si test.ghul: 6,5..6,7: error: syntax error: expected ; but found si diff --git a/integration-tests/parse/incomplete-string-interpolation-11/err.expected b/integration-tests/parse/incomplete-string-interpolation-11/err.expected index 55c2def63..25b945734 100644 --- a/integration-tests/parse/incomplete-string-interpolation-11/err.expected +++ b/integration-tests/parse/incomplete-string-interpolation-11/err.expected @@ -1 +1 @@ -test.ghul: 7,19..7,33: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found } interpolate +test.ghul: 7,19..7,33: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found } interpolate diff --git a/integration-tests/parse/incomplete-string-interpolation-3/err.expected b/integration-tests/parse/incomplete-string-interpolation-3/err.expected index cf245fbcc..a980b3ee7 100644 --- a/integration-tests/parse/incomplete-string-interpolation-3/err.expected +++ b/integration-tests/parse/incomplete-string-interpolation-3/err.expected @@ -1 +1 @@ -test.ghul: 7,1..7,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 7,1..7,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-string-interpolation-5/err.expected b/integration-tests/parse/incomplete-string-interpolation-5/err.expected index cf245fbcc..a980b3ee7 100644 --- a/integration-tests/parse/incomplete-string-interpolation-5/err.expected +++ b/integration-tests/parse/incomplete-string-interpolation-5/err.expected @@ -1 +1 @@ -test.ghul: 7,1..7,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 7,1..7,3: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-variable-5/err.expected b/integration-tests/parse/incomplete-variable-5/err.expected index ca4c86362..43721856a 100644 --- a/integration-tests/parse/incomplete-variable-5/err.expected +++ b/integration-tests/parse/incomplete-variable-5/err.expected @@ -1 +1 @@ -test.ghul: 7,9..7,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 7,9..7,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/parse/incomplete-variable-6/err.expected b/integration-tests/parse/incomplete-variable-6/err.expected index ca4c86362..43721856a 100644 --- a/integration-tests/parse/incomplete-variable-6/err.expected +++ b/integration-tests/parse/incomplete-variable-6/err.expected @@ -1 +1 @@ -test.ghul: 7,9..7,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, new, null, rec, self, string literal, super, true, typeof or { but found si +test.ghul: 7,9..7,11: error: in primary expression: expected (, [, cast, char literal, false, float literal, identifier, if, int literal, isa, let, new, null, rec, self, string literal, super, true, typeof or { but found si diff --git a/integration-tests/semantic/variable-let-in-1/.vscode/tasks.json b/integration-tests/semantic/variable-let-in-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/semantic/variable-let-in-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/semantic/variable-let-in-1/err.expected b/integration-tests/semantic/variable-let-in-1/err.expected new file mode 100644 index 000000000..a001c73b7 --- /dev/null +++ b/integration-tests/semantic/variable-let-in-1/err.expected @@ -0,0 +1 @@ +test.ghul: 99,13..99,39: error: let in expression result is not used diff --git a/integration-tests/semantic/variable-let-in-1/fail.expected b/integration-tests/semantic/variable-let-in-1/fail.expected new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/integration-tests/semantic/variable-let-in-1/fail.expected @@ -0,0 +1 @@ + diff --git a/integration-tests/semantic/variable-let-in-1/ghul.json b/integration-tests/semantic/variable-let-in-1/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/semantic/variable-let-in-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/semantic/variable-let-in-1/ghulflags b/integration-tests/semantic/variable-let-in-1/ghulflags new file mode 100644 index 000000000..4d095b972 --- /dev/null +++ b/integration-tests/semantic/variable-let-in-1/ghulflags @@ -0,0 +1 @@ +--type-check \ No newline at end of file diff --git a/integration-tests/semantic/variable-let-in-1/il.expected b/integration-tests/semantic/variable-let-in-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/semantic/variable-let-in-1/test.ghul b/integration-tests/semantic/variable-let-in-1/test.ghul new file mode 100644 index 000000000..0ee58f057 --- /dev/null +++ b/integration-tests/semantic/variable-let-in-1/test.ghul @@ -0,0 +1,108 @@ +namespace Test.ParseLetIn is + use Collections; + + class Test is + test_simple() -> int => + let + x = new D() + in + x.value; + + test_if_conditions() is + if + let x = new D(), y = new D() + in x.value < y.value + then + let x = new D(), y = new D() + else + let x = new D(), y = new D() + fi + + if + let x = new D() + in x.value != 0 + then + let x = new D(), y = new D() + elif + let x = new D(), y = new D(), z = new D() + in x.value < y.value /\ y.value < z.value + then + let x = new D(), y = new D() + else + let x = new D(), y = new D() + fi + si + + test_if_body_1() -> int => + if true then + let x = new D(), y = new D() + in x.value + y.value + else + 0 + fi; + + test_if_body_2() -> int => + if true then + let x = new D() + in x.value + elif true then + let x = new D(), y = new D() + in x.value + y.value + else + let x = new D(), y = new D(), z = new D() + in x.value + y.value + z.value + fi; + + test_for() is + for i in + let x = [1, 2, 3], y = [4, 5, 6] in x | .cat(y) + do + let x = new D(), y = new D(); + od + si + + test_while() is + while + let x = new D(), y = new D() + in x.value < y.value + do + let x = new D(), y = new D(); + od + si + + test_case() is + case + let x = new D(), y = new D() + in x.value + y.value + + when 1: let x = new D(), y = new D(); + when 2: let x = new D(), y = new D(); + default let x = new D(), y = new D(); + esac + si + + entry() is + let expect_int: int; + + expect_int = test_simple(); + test_if_conditions(); + expect_int = test_if_body_1(); + expect_int = test_if_body_2(); + + test_for(); + test_while(); + test_case(); + si + + test_error() is + // result of let in must be consumed by something + let x = new D() in x.value; + si + si + + class D is + value: int => 1234; + init() is + si + si +si \ No newline at end of file diff --git a/integration-tests/semantic/variable-let-in-1/warn.expected b/integration-tests/semantic/variable-let-in-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/src/ioc/container.ghul b/src/ioc/container.ghul index 6b40ae6b8..f98e86d4a 100644 --- a/src/ioc/container.ghul +++ b/src/ioc/container.ghul @@ -297,7 +297,8 @@ namespace IoC is expression_parser, expression_list_parser, expression_tuple_parser, - statement_parser + statement_parser, + variable_list_parser ); expression_secondary_parser.create = () => diff --git a/src/syntax/parsers/expressions/primary.ghul b/src/syntax/parsers/expressions/primary.ghul index 39ef8367b..a3dd571c8 100644 --- a/src/syntax/parsers/expressions/primary.ghul +++ b/src/syntax/parsers/expressions/primary.ghul @@ -12,6 +12,7 @@ namespace Syntax.Parsers.Expressions is expression_list_parser: Parser[Trees.Expressions.LIST]; expression_tuple_parser: Parser[Trees.Expressions.TUPLE]; statement_parser: Parser[Trees.Statements.Statement]; + variable_list_parser: Parser[Trees.Variables.LIST]; description: string => "primary expression"; @@ -21,7 +22,8 @@ namespace Syntax.Parsers.Expressions is expression_parser: Parser[Trees.Expressions.Expression], expression_list_parser: Parser[Trees.Expressions.LIST], expression_tuple_parser: Parser[Trees.Expressions.TUPLE], - statement_parser: Parser[Trees.Statements.Statement] + statement_parser: Parser[Trees.Statements.Statement], + variable_list_parser: Parser[Trees.Variables.LIST] ) is super.init(); @@ -32,6 +34,7 @@ namespace Syntax.Parsers.Expressions is self.expression_list_parser = expression_list_parser; self.expression_tuple_parser = expression_tuple_parser; self.statement_parser = statement_parser; + self.variable_list_parser = variable_list_parser; add_parsers(); si @@ -268,6 +271,46 @@ namespace Syntax.Parsers.Expressions is return new Trees.Expressions.STATEMENT(statement.location, statement); si, Lexical.TOKEN.IF ); + + add_parser( + (context: CONTEXT) is + let start = context.location; + context.next_token(); + let want_dispose = false; + if context.current_token == Lexical.TOKEN.USE then + // TODO it's not totally clear what the scope of the + // use should be - just the expression? There may not + // be a clear statement block given that we're in an + // expression context + context.error(context.location, "use is not supported in this context"); + + context.next_token(); + + // want_dispose = true; + fi + + let variable_list = variable_list_parser.parse(context); + + let expression: Trees.Expressions.Expression; + + if context.next_token(Lexical.TOKEN.IN) then + expression = expression_parser.parse(context); + else + expression = new Trees.Expressions.Literals.NONE(start::variable_list.location); + fi + + let result = + new Trees.Expressions.LET_IN( + start::expression.location, + want_dispose, + variable_list, + expression + ); + + return result; + si, + Lexical.TOKEN.LET + ); si parse_string_with_interpolations(context: CONTEXT) -> Trees.Expressions.Expression is diff --git a/src/syntax/parsers/statements/node.ghul b/src/syntax/parsers/statements/node.ghul index bd3a790b1..2b65c3748 100644 --- a/src/syntax/parsers/statements/node.ghul +++ b/src/syntax/parsers/statements/node.ghul @@ -67,7 +67,7 @@ namespace Syntax.Parsers.Statements is ]; add_parser( - (context: CONTEXT) is + (context: CONTEXT) -> Trees.Statements.Statement is let start = context.location; if context.next_token(Lexical.TOKEN.LET, syntax_error_message) then let want_dispose = false; @@ -78,9 +78,24 @@ namespace Syntax.Parsers.Statements is fi let variable_list = variable_list_parser.parse(context); - let result = new Trees.Statements.LET(start::variable_list.location, want_dispose, variable_list); - - return result; + + if context.current_token == Lexical.TOKEN.IN then + context.next_token(); + + let expression = expression_parser.parse(context); + + return new Trees.Statements.EXPRESSION( + start::expression.location, + new Trees.Expressions.LET_IN( + start::expression.location, + want_dispose, + variable_list, + expression + ) + ); + fi + + return new Trees.Statements.LET(start::variable_list.location, want_dispose, variable_list); fi si, Lexical.TOKEN.LET diff --git a/src/syntax/process/compile_expressions.ghul b/src/syntax/process/compile_expressions.ghul index 89eb32f24..6e90607c1 100644 --- a/src/syntax/process/compile_expressions.ghul +++ b/src/syntax/process/compile_expressions.ghul @@ -392,7 +392,27 @@ namespace Syntax.Process is left.value = block.close(); return true; - si + si + + pre(let_in: Trees.Expressions.LET_IN) -> bool is + super.pre(let_in); + + return false; + si + + visit(let_in: Trees.Expressions.LET_IN) is + super.visit(let_in); + + if + !let_in.expression.value? \/ + !let_in.expression.value.check_is_consumable_allow_void(_logger, let_in.expression.location) + then + let_in.value = new IR.Values.DUMMY(new Semantic.Types.ERROR(), let_in.location); + return; + fi + + let_in.value = let_in.expression.value; + si pre(assignment: Trees.Statements.ASSIGNMENT) -> bool is assignment.right.walk(self); @@ -429,7 +449,11 @@ namespace Syntax.Process is Value.check_is_consumable_allow_void(_logger, expression.expression.location, expression.expression.value); - if !expression? \/ !expression.want_value then + if !expression.want_value then + if expression.expression.must_be_consumed then + _logger.error(expression.expression.location, "{expression.expression.description} result is not used"); + fi + return; fi diff --git a/src/syntax/process/declare_symbols.ghul b/src/syntax/process/declare_symbols.ghul index 9c94815fa..fb99aeccb 100644 --- a/src/syntax/process/declare_symbols.ghul +++ b/src/syntax/process/declare_symbols.ghul @@ -648,7 +648,15 @@ namespace Syntax.Process is fi leave_scope(function); - si + si + + pre(let_in: Expressions.LET_IN) -> bool is + create_and_enter_block_scope(let_in); + si + + visit(let_in: Expressions.LET_IN) is + leave_scope(let_in); + si pre(expression: Bodies.EXPRESSION) -> bool is create_and_enter_block_scope(expression); diff --git a/src/syntax/process/scopedvisitor.ghul b/src/syntax/process/scopedvisitor.ghul index 0836e70c2..4404c252e 100644 --- a/src/syntax/process/scopedvisitor.ghul +++ b/src/syntax/process/scopedvisitor.ghul @@ -206,6 +206,14 @@ namespace Syntax.Process is leave_scope(function); si + pre(let_in: Expressions.LET_IN) -> bool is + enter_scope(let_in); + si + + visit(let_in: Expressions.LET_IN) is + leave_scope(let_in); + si + pre(expression: Bodies.EXPRESSION) -> bool is enter_scope(expression); si diff --git a/src/syntax/process/strictvisitor.ghul b/src/syntax/process/strictvisitor.ghul index f431b091c..25935f1ca 100644 --- a/src/syntax/process/strictvisitor.ghul +++ b/src/syntax/process/strictvisitor.ghul @@ -296,6 +296,10 @@ namespace Syntax is throwNotImplemented("destructure left", destructure_left); si + visit(statement: Expressions.LET_IN) is + throwNotImplemented("let in", statement); + si + visit(statement: Statements.Statement) is throwNotImplemented("statement", statement); si diff --git a/src/syntax/process/visitor.ghul b/src/syntax/process/visitor.ghul index fd04a61c1..d5c3dc900 100644 --- a/src/syntax/process/visitor.ghul +++ b/src/syntax/process/visitor.ghul @@ -525,6 +525,13 @@ namespace Syntax is visit(statement: Expressions.STATEMENT) is si + + pre(statement: Expressions.LET_IN) -> bool is + return false; + si + + visit(statement: Expressions.LET_IN) is + si pre(statement: Statements.Statement) -> bool is return false; diff --git a/src/syntax/trees/expressions/expression.ghul b/src/syntax/trees/expressions/expression.ghul index 7c2f30e81..804314b97 100644 --- a/src/syntax/trees/expressions/expression.ghul +++ b/src/syntax/trees/expressions/expression.ghul @@ -10,6 +10,9 @@ namespace Syntax.Trees.Expressions is is_tuple: bool => false; could_be_formal_argument: bool => false; could_be_nested_function_definition: bool => false; + must_be_consumed: bool => false; + + description: string => "expression"; init(location: LOCATION) is super.init(location); diff --git a/src/syntax/trees/expressions/let_in.ghul b/src/syntax/trees/expressions/let_in.ghul new file mode 100644 index 000000000..1b66b72a2 --- /dev/null +++ b/src/syntax/trees/expressions/let_in.ghul @@ -0,0 +1,42 @@ +namespace Syntax.Trees.Expressions is + use Source; + + class LET_IN: Expression is + variables: Variables.LIST; + expression: Expression; + want_dispose: bool; + + description: string => "let in expression"; + + expects_semicolon: bool => true; + must_be_consumed: bool => true; + + init(location: LOCATION, want_dispose: bool, variables: Variables.LIST, expression: Expression) is + super.init(location); + + assert variables? else "let variables list is null"; + assert expression? else "let expression is null"; + + self.variables = variables; + self.expression = expression; + self.want_dispose = want_dispose; + + if want_dispose then + variables.mark_want_dispose(); + fi + si + + accept(visitor: Visitor) is + visitor.visit(self); + si + + walk(visitor: Visitor) is + if !visitor.pre(self) then + variables.walk(visitor); + expression.walk(visitor); + fi + + accept(visitor); + si + si +si diff --git a/src/syntax/trees/statements/let.ghul b/src/syntax/trees/statements/let.ghul index 4b6c93813..73202f75b 100644 --- a/src/syntax/trees/statements/let.ghul +++ b/src/syntax/trees/statements/let.ghul @@ -1,7 +1,7 @@ namespace Syntax.Trees.Statements is use Source; - class LET: Statement /*, Collections.Iterable[Variables.Variable] */ is + class LET: Statement is variables: Variables.LIST; want_dispose: bool;