From 7af4faa5ff526413da8e091f27100827b6fc15d4 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 10:51:27 +0200 Subject: [PATCH 01/44] doc: proposal new syntax Blocks are now a litertal 'list of statements'. This means we use square instead of curly brackets to denote them. --- README.md | 25 +++++++++++++++---------- docs/spec/characters.md | 2 ++ docs/spec/grammar.md | 28 ++++++++++++++-------------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 03592346..7bbc5a42 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,10 @@ def factorial(x: Int) -> Int := match x n => n * factorial(n - 1) def num := input("Compute factorial: ") -if num.is_digit() then +if num.is_digit() then [ def result := factorial(Int(num)) print("Factorial {num} is: {result}.") -else +] else print("Input was not an integer.") ``` @@ -94,10 +94,11 @@ _Note_ One could use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic ```mamba def factorial(x: Int) -> Int := match x 0 => 1 - n => - def ans := 1 - for i in 1 ..= n do ans := ans * i - ans + n => [ + def ans := 1 + for i in 1 ..= n do ans := ans * i + ans + ] ``` ### 📋 Types, Classes, and Mutability @@ -120,9 +121,10 @@ class MyServer(def ip_address: IPv4Address) def last_sent(fin self) -> Str ! ServerError := self._last_message - def connect(self) := + def connect(self) := [ self.is_connected := True print(always_the_same_message) + ] def send(self, message: Str) ! ServerError := if self.is_connected then @@ -271,11 +273,12 @@ Immutable variables and pure functions make it easier to write declarative progr def fin taylor := 7 # the sin function is pure, its output depends solely on the input -def pure sin(x: Int) := +def pure sin(x: Int) := [ def ans := x for i in 1 ..= taylor .. 2 do ans := ans + (x ^ (i + 2)) / (factorial (i + 2)) ans +] ``` ### ⚠ Error handling @@ -311,12 +314,14 @@ This is shown below: ```mamba def a := function_may_throw_err() - err: MyErr => + err: MyErr => [ print("We have a problem: {err.message}.") return # we return, halting execution - err: MyOtherErr => + ] + err: MyOtherErr => [ print("We have another problem: {err.message}.") 0 # ... or we assign default value 0 to a + ] print("a has value {a}.") ``` diff --git a/docs/spec/characters.md b/docs/spec/characters.md index 5d7a9bb7..76331c73 100644 --- a/docs/spec/characters.md +++ b/docs/spec/characters.md @@ -15,7 +15,9 @@ Symbol | Use `{` | Denote start of set, set constructor, or map `}` | Denote end of set, set constructor, or map `[` | Denote start of list, or opening bracket of generics of a class + | Also denotes the start of a list of statements and/or expressions `]` | Denote end of list, or closing bracket of generics of a class + | Also denotes the end of a list of statements and/or expressions ## Type diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 77f03858..2da10179 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -11,15 +11,15 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). - ```{ ... }``` = zero or more ```ebnf - file ::= block + file ::= { expr-or-stmt } import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] - type-def ::= "type" type [ ":" type ] ( newline block | "when" [ conditions ] ) - conditions ::= ( newline indent { condition newline } dedent | condition ) + type-def ::= "type" type [ ":" type ] ( code-block | "when" [ conditions ] ) + conditions ::= "[" condition { newline condition } "]" | condition condition ::= expression [ "!" expression ] type-tuple ::= "(" [ type ] { "," type } ")" - class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( newline block ) + class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( code-block ) generics ::= "[" id { "," id } "]" id ::= { character } @@ -28,8 +28,6 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). type ::= ( id [ generics ] | type-tuple ) [ "->" type ] type-tuple ::= "(" [ type { "," type } ] ")" - block ::= indent { expr-or-stmt } dedent - expr-or-stmt ::= ( statement | expression ) [ comment ] statement ::= control-flow-stmt | definition @@ -46,7 +44,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). | "return" [ expression ] | expression "as" id | control-flow-expr - | newline block + | code-block | collection | index | key-value @@ -74,10 +72,10 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). variable-def ::= [ "fin" ] ( id-maybe-type | collection ) [ ":=" expression ] [ forward ] operator-def ::= [ "pure" ] overridable-op [ "(" [ id-maybe-type ] ")" ] "->" type - [ ":=" ( expr-or-stmt | newline block ) ] + [ ":=" ( expr-or-stmt | code-block ) ] fun-def ::= [ "pure" ] id fun-args [ "->" type ] [ raise ] - [ ":=" ( expr-or-stmt | newline block ) ] + [ ":=" ( expr-or-stmt | code-block ) ] fun-args ::= "(" [ fun-arg ] { "," fun-arg } ")" fun-arg ::= id-maybe-type [ ":=" expression ] forward ::= "forward" id { "," id } @@ -108,18 +106,20 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). e-notation ::= ( integer | real ) "E" [ "-" ] integer string ::= """ { character } """ - newline-block ::= newline block | expr-or-stmt + code-block ::= "[" expr-or-statement "]" + | expr-or-stmt + | "[" newline expr-or-statement { newline expr-or-statement } "]" one-or-more-expr ::= expression { "," expression } control-flow-expr::= if | match - if ::= "if" one-or-more-expr "then" newline-block [ "else" newline-block ] + if ::= "if" one-or-more-expr "then" code-block [ "else" code-block ] match ::= "match" one-or-more-expr "with" newline match-cases - match-cases ::= indent { match-case { newline } } dedent + match-cases ::= match-case | "[" match-case { newline match-case } "]" match-case ::= expression "=>" expr-or-stmt control-flow-stmt::= while | foreach | "break" | "continue" - while ::= "while" one-or-more-expr "do" newline-block - foreach ::= "for" one-or-more-expr "in" expression "do" newline-block + while ::= "while" one-or-more-expr "do" code-block + foreach ::= "for" one-or-more-expr "in" expression "do" code-block newline ::= newline-char newline-char ::= \n | \r\n From 713b458caaa532e12a2ebce92608faed85c8348c Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 11:07:25 +0200 Subject: [PATCH 02/44] doc: list indexing --- README.md | 30 +++++++++++++++++++++++++++++- docs/spec/characters.md | 4 ++-- docs/spec/grammar.md | 2 -- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7bbc5a42..e3f29290 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,34 @@ def factorial(x: Int) -> Int := match x ] ``` +### 🍡 Collections + +In mamba sets, lists and maps are first class citizens, they are baked into the language. +Unlike C-style languages, collection indexes are also a function. + +```mamba +# lists +def a := [0, 2, 51] +def b := ["list", "of", "strings"] +# lists of tuples, buidler syntax +def ab := [(x, y) | x in a, x > 0, y in b, b != "of" ] + +# sets +def c := { 10, 20 } +def d := { 3 } +# sets, builder syntax +def cd := { x ^ y | x in c, y in d } + +# maps +def e := { "do" => 1, "ree" => 2, "meee" => 3 } +# maps, builder syntax +def ef := { x => y - 2 | x in e, y = x.len() } + +# indexing works for lists and maps/mappings (sets cannot be indexed because these are unordered) +print(ab(2)) # prints '(2, "list")' +print(ef(1)) # prints '1' +``` + ### 📋 Types, Classes, and Mutability Classes are similar to classes in Python, though we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. @@ -273,7 +301,7 @@ Immutable variables and pure functions make it easier to write declarative progr def fin taylor := 7 # the sin function is pure, its output depends solely on the input -def pure sin(x: Int) := [ +def pure sin(x: Int) -> Int := [ def ans := x for i in 1 ..= taylor .. 2 do ans := ans + (x ^ (i + 2)) / (factorial (i + 2)) diff --git a/docs/spec/characters.md b/docs/spec/characters.md index 76331c73..91126bbf 100644 --- a/docs/spec/characters.md +++ b/docs/spec/characters.md @@ -10,8 +10,8 @@ The following is a list of characters in the language Symbol | Use ---|--- -`(` | Denote start of tuple elements or function arguments -`)` | Denote end of tuple elements or function arguments +`(` | Denote start of tuple elements or function arguments, including collection indexing (list or mapping) +`)` | Denote end of tuple elements or function arguments, including collection indexing (list or mapping) `{` | Denote start of set, set constructor, or map `}` | Denote end of set, set constructor, or map `[` | Denote start of list, or opening bracket of generics of a class diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 2da10179..85ed4061 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -46,7 +46,6 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). | control-flow-expr | code-block | collection - | index | key-value | operation | anon-fun @@ -66,7 +65,6 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). slice ::= expression ( "::" | "::=" ) expression [ "::" expression ] range ::= expression ( ".." | "..=" ) expression [ ".." expression ] - index ::= expression "[" expression "]" definition ::= "def" ( variable-def | fun-def | operator-def ) From 38973844c74642685f13070a8af431b913587ec0 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 11:13:05 +0200 Subject: [PATCH 03/44] doc: simplify slice --- README.md | 2 +- docs/spec/grammar.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e3f29290..b1f4ddca 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ def fin taylor := 7 # the sin function is pure, its output depends solely on the input def pure sin(x: Int) -> Int := [ def ans := x - for i in 1 ..= taylor .. 2 do + for i in (1 ..= taylor).step(2) do ans := ans + (x ^ (i + 2)) / (factorial (i + 2)) ans ] diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 85ed4061..264659f3 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -63,8 +63,8 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). list ::= "[" { expression } "]" | list-builder list-builder ::= "[" expression "|" expression { "," expression } "]" - slice ::= expression ( "::" | "::=" ) expression [ "::" expression ] - range ::= expression ( ".." | "..=" ) expression [ ".." expression ] + slice ::= expression ( "::" | "::=" ) expression + range ::= expression ( ".." | "..=" ) expression definition ::= "def" ( variable-def | fun-def | operator-def ) From fd31560d076465e74325deada3359d9f6b893ee1 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 13:23:50 +0200 Subject: [PATCH 04/44] doc: class is assigned to as well --- README.md | 6 ++++-- docs/spec/grammar.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b1f4ddca..9947e4c7 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ class ServerError(def message: Str): Exception(message) def fin always_the_same_message := "Connected!" -class MyServer(def ip_address: IPv4Address) +class MyServer(def ip_address: IPv4Address) := [ def is_connected: Bool := False def _last_message: Str := "temp" @@ -161,6 +161,7 @@ class MyServer(def ip_address: IPv4Address) ! ServerError("Not connected!") def disconnect(self) := self.is_connected := False +] ``` Notice how `self` is not mutable in `last_sent`, meaning we can only read variables, whereas in connect `self` is mutable, so we can change properties of `self`. @@ -201,7 +202,7 @@ type DisConnMyServer: MyServer when not self.is_connected class ServerErr(def message: Str): Exception(message) -class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) +class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ def is_connected: Bool := False def _last_message: Str? := None @@ -216,6 +217,7 @@ class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) def send(self: ConnMyServer, message: Str) := self._last_message := message def disconnect(self: ConnMyServer) := self.is_connected := False +] ``` Within the then branch of the if statement, we know that `self._last_message` is a `Str`. diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 264659f3..42bace4e 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -19,7 +19,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). condition ::= expression [ "!" expression ] type-tuple ::= "(" [ type ] { "," type } ")" - class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( code-block ) + class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( := code-block ) generics ::= "[" id { "," id } "]" id ::= { character } From 57e0b5aa205c3d07824386936dc0d8c0eace42e9 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 14:08:09 +0200 Subject: [PATCH 05/44] doc: experiment with error handling syntax --- README.md | 76 +++++++++++++++++++------- docs/features/safety/error_handling.md | 8 +-- docs/spec/grammar.md | 2 +- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 9947e4c7..4cf87089 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,10 @@ We highlight how functions work, how de define classes, how types and type refin We can write a simple script that computes the factorial of a value given by the user. ```mamba -def factorial(x: Int) -> Int := match x +def factorial(x: Int) -> Int := match x { 0 => 1 n => n * factorial(n - 1) +} def num := input("Compute factorial: ") if num.is_digit() then [ @@ -88,17 +89,23 @@ if num.is_digit() then [ Notice how here we specify the type of argument `x`, in this case an `Int`, by writing `x: Int`. This means that the compiler will check for us that factorial is only used with integers as argument. +Also note that: + +- Code blocks are denoted using `[` and `]` because this is a list of statements and expressions that gets executed _in order_. +- For a match, each case is denoted using `{` and `}`, as this is a set of cases we match on. + You you can read `match x {}` , where we read this as "match `x` on this set of conditions in `{...}`", though we omit the "on" as to not introduce another keyword. _Note_ One could use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) in the above example so that we consume less memory: ```mamba -def factorial(x: Int) -> Int := match x +def factorial(x: Int) -> Int := match x { 0 => 1 n => [ def ans := 1 for i in 1 ..= n do ans := ans * i ans ] +} ``` ### 🍡 Collections @@ -238,9 +245,10 @@ def my_server := MyServer(some_ip) http_server.connect() # We check the state -if my_server isa ConnMyServer then +if my_server isa ConnMyServer then [ # http_server is a Connected Server if the above is true my_server.send("Hello World!") +] print("last message sent before disconnect: \"{my_server.last_sent}\".") if my_server isa ConnectedMyServer then my_server.disconnect() @@ -255,9 +263,10 @@ Type refinement also allows us to specify the domain and co-domain of a function type PosInt: Int when self >= 0 ! NegativeError("Must be greater than 0") -def factorial(x: PosInt) -> PosInt := match x +def factorial(x: PosInt) -> PosInt := match x { 0 => 1 n => n * factorial(n - 1) +} ``` In short, types allow us to specify the domain and co-domain of functions with regards to the type of input, say, `Int` or `Str`. @@ -315,7 +324,10 @@ def pure sin(x: Int) -> Int := [ Unlike Python, Mamba does not have `try` `except` and `finally` (or `try` `catch` as it is sometimes known). Instead, we aim to directly handle errors on-site so the origin of errors is more tracable. -The following is only a brief example. +The following is an attempt mixing and matching `Result` monad (of languages like Rust and Scala), +with a more first-class approach of exceptions is languages like Kotlin. +This is a trade-off between elegancy of the type system versus first-class language features. +Arguably it may be easier to just use Monads and get rid of this, but lets see how this goes in practice. We can modify the above script such that we don't check whether the server is connected or not. In that case, we must handle the case where `my_server` throws a `ServerErr`: @@ -328,22 +340,38 @@ def fin some_ip := ipaddress.ip_address("151.101.193.140") def my_server := MyServer(some_ip) def message := "Hello World!" -my_server.send(message) +my_server.send(message) ! { err: ServerErr => print("Error while sending message: \"{message}\": {err}") +} if my_server isa ConnectedMyServer then my_server.disconnect() ``` +`my_server.send(message) ! { ... }` is syntax sugar for + +```mamba +match my_server.send(message) { + err: Exception(ServerErr) => print("Error while sending message: \"{message}\": {err}") +} +``` + +So esentially, we add `!` as a way to shorthand match on exceptions. +Again, recall that this is a tradeoff. +Currently, we allow both notations, but this comes at the cost of there not being "one way" to handle exceptions. +This can lead to similar problems like with Scala where we have multiple ways to do the same thing. + In the above script, we will always print the error since we forgot to actually connect to the server. Here we showcase how we try to handle errors on-site instead of in a (large) `try` block. -This means that we don't need a `finally` block: We aim to deal with the error where it happens and then continue executing the remaining code. +This means that we don't need a `finally` block: +We aim to deal with the error where it happens and then continue executing the remaining code. This also prevents us from wrapping large code blocks in a `try`, where it might not be clear what statement or expression might throw what error. -This can also be combined with an assign. In that case, we must either always return (halting execution or exiting the function), or evaluate to a value. +This can also be combined with an assign. +In that case, we must either always return (halting execution or exiting the function), or evaluate to a value. This is shown below: ```mamba -def a := function_may_throw_err() +def a: Int := function_may_throw_err() ! { err: MyErr => [ print("We have a problem: {err.message}.") return # we return, halting execution @@ -352,30 +380,40 @@ def a := function_may_throw_err() print("We have another problem: {err.message}.") 0 # ... or we assign default value 0 to a ] +} print("a has value {a}.") ``` -I we don't want to handle the exception cases here, we just append a `!` to a function. +We can also opt to not do any error handling, making this + +``` +def a: Result[Int, Union[MyErr, MyOtherErr]] := function_may_throw_err() +``` + +By extension, if we don't handle all cases, then the union becomes smaller, only when the union is empty is `a` an `Int`. +The type of `a` is then result, and we are required to do error handling later. +I we don't want to handle any of the exception cases here, we just append a `!` to a function. This means that this exception must be handeld further up the stack. ```mamba -def a := function_may_throw_err()! - +def a := function_may_throw_err() ! # if `function_may_throw_err` returned an exception, we will never reach this point print("a has value {a}.") ``` -We can also mix and match, handling a subset of the exceptions. -The type checker will keep track of what we handle locally and what is passed up the stack. +This also gives an alternative way to write the above example, where we only case about a subset of the exceptions here. ```mamba -def a := function_may_throw_err()! - err: MyErr => - print("We have a problem: {err.message}.") - return # we return, halting execution +def a: Result[Int, MyErr] := function_may_throw_err() ! { + err: MyOtherErr => [ + print("We have another problem: {err.message}.") + 0 # ... or we assign default value 0 to a + ] +} + +a = a ! # Result[Int, MyErr] => Int, where if error case, an exception is raised. -# if `function_may_throw_err` returned an exception, we will never reach this point print("a has value {a}.") ``` diff --git a/docs/features/safety/error_handling.md b/docs/features/safety/error_handling.md index f3a8044d..5f44091f 100644 --- a/docs/features/safety/error_handling.md +++ b/docs/features/safety/error_handling.md @@ -53,14 +53,14 @@ The first way of writing is preferred, as this more clearly separates the return However, using Result may be better in some other situations. For instance, it allows us to use the type alias feature of the language, which can be convenient in certain situations, such as when we wish to enforce consistency. See "Types" for a more in-depth explanation. A trivial case would be: - type MyResult <- Result[Int, MyErr] + type MyResult := Result[Int, MyErr] - def g (x: Int): MyResult := if x is 10 then MyErr("x was 10") else x + def g (x: Int): MyResult := if x is 10 then ! MyErr("x was 10") else x Small side note: using the default behaviour feature of the language, we can rewrite `g` as such: - def g (x: Int): Int ! [MyErr] := x - def g (0) := ! MyErr("x was 10") + def g (x: Int): Int ! { MyErr } := x + def g (0) := ! MyErr("x was 10") Note that if the signature of a function states that a certain type of exception is thrown, it must be thrown at some point, or we will get a type error: diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 42bace4e..022404c5 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -112,7 +112,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). control-flow-expr::= if | match if ::= "if" one-or-more-expr "then" code-block [ "else" code-block ] match ::= "match" one-or-more-expr "with" newline match-cases - match-cases ::= match-case | "[" match-case { newline match-case } "]" + match-cases ::= match-case | "{" match-case { newline match-case } "}" match-case ::= expression "=>" expr-or-stmt control-flow-stmt::= while | foreach | "break" | "continue" From 3eaf9189cc272672d4df7fda3388c64978d9f5ab Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 14:15:34 +0200 Subject: [PATCH 06/44] doc: set of type conds is unordered --- README.md | 26 +++++++++++--------------- docs/spec/grammar.md | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4cf87089..180f862f 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,9 @@ _Note_ One could use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic def factorial(x: Int) -> Int := match x { 0 => 1 n => [ - def ans := 1 - for i in 1 ..= n do ans := ans * i - ans + def ans := 1 + for i in 1 ..= n do ans := ans * i + ans ] } ``` @@ -153,19 +153,16 @@ class MyServer(def ip_address: IPv4Address) := [ def is_connected: Bool := False def _last_message: Str := "temp" - def last_sent(fin self) -> Str ! ServerError := - self._last_message + def last_sent(fin self) -> Str ! ServerError := self._last_message def connect(self) := [ self.is_connected := True print(always_the_same_message) ] - def send(self, message: Str) ! ServerError := - if self.is_connected then - self._last_message := message - else - ! ServerError("Not connected!") + def send(self, message: Str) ! ServerError := + if self.is_connected then self._last_message := message + else ! ServerError("Not connected!") def disconnect(self) := self.is_connected := False ] @@ -214,10 +211,8 @@ class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ def _last_message: Str? := None def last_sent(self) -> Str ! ServerErr := - if self.last_message != None then - self._last_message - else - ! ServerError("No last message!") + if self.last_message != None then self._last_message + else ! ServerError("No last message!") def connect(self: DisConnMyServer) := self.is_connected := True @@ -260,8 +255,9 @@ Type refinement also allows us to specify the domain and co-domain of a function # we list the conditions below, which are a list of boolean expressions. # this first-class language feature desugars to an list of checks which are done at the call site. # we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. -type PosInt: Int when +type PosInt: Int when { self >= 0 ! NegativeError("Must be greater than 0") +} def factorial(x: PosInt) -> PosInt := match x { 0 => 1 diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 022404c5..c8f360f3 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -15,7 +15,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] type-def ::= "type" type [ ":" type ] ( code-block | "when" [ conditions ] ) - conditions ::= "[" condition { newline condition } "]" | condition + conditions ::= "{" condition { newline condition } "}" | condition condition ::= expression [ "!" expression ] type-tuple ::= "(" [ type ] { "," type } ")" From 1b94595d4080f259915cbc3ac7e842d6a26450e9 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Tue, 8 Jul 2025 14:57:15 +0200 Subject: [PATCH 07/44] doc: add reasoning for index notation --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 180f862f..1b216ebd 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,18 @@ Mamba is like Python, but with a few key features: - A distinction between mutability and immutability - Pure functions, or, functions without side effects -See [docs](/docs/) for a more extensive overview of the langauge philosophy. +See [docs](/docs/) for a more extensive overview of the language philosophy. This is a transpiler, written in [Rust](https://www.rust-lang.org/), which converts Mamba source files to Python source files. Mamba code should therefore be interoperable with Python code. Functions written in Python can be called in Mamba and vice versa (from the generated Python files). +The below README: + +- Gives a quickstart for developer +- Give a short overview of most of the syntax and language features in quick succession, as well as the occasional reasoning behind them. + ## 🧑‍💻 Quickstart for developers 👨‍💻 To get started right away, if on a Linux machine and you wish to use the Nix flake (which has all the tooling setup, including nushell, githooks, etc.). @@ -92,7 +97,7 @@ This means that the compiler will check for us that factorial is only used with Also note that: - Code blocks are denoted using `[` and `]` because this is a list of statements and expressions that gets executed _in order_. -- For a match, each case is denoted using `{` and `}`, as this is a set of cases we match on. +- For a match expression or statement, each case is denoted using `{` and `}`, as this is a set of cases we match on. You you can read `match x {}` , where we read this as "match `x` on this set of conditions in `{...}`", though we omit the "on" as to not introduce another keyword. _Note_ One could use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) in the above example so that we consume less memory: @@ -111,7 +116,6 @@ def factorial(x: Int) -> Int := match x { ### 🍡 Collections In mamba sets, lists and maps are first class citizens, they are baked into the language. -Unlike C-style languages, collection indexes are also a function. ```mamba # lists @@ -136,6 +140,24 @@ print(ab(2)) # prints '(2, "list")' print(ef(1)) # prints '1' ``` +Unlike C-style languages (which is nearly the whole world at this point), we index lists using `()`. +The main reason for doing so is that we see collections, which can be indexed, as mappings. +This mapping itself is another function! +So, we perform a function call index to perform indexing, from which it follows that we use function call notation. + +Also good to note is that we consider a list simply a mapping where keys are the indexes of the list. +I.e. + +```mamba +def numbers := [32, 504, 59] +``` + +Is just shorthand for + +```mamba +def numbers := { 0 => 32, 1 => 504, 2 => 59 } +``` + ### 📋 Types, Classes, and Mutability Classes are similar to classes in Python, though we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. @@ -169,6 +191,19 @@ class MyServer(def ip_address: IPv4Address) := [ ``` Notice how `self` is not mutable in `last_sent`, meaning we can only read variables, whereas in connect `self` is mutable, so we can change properties of `self`. +In general, the notation of a class is: + +`class MyClass() := []` + +Though the body is optional. +As for constructor arguments: + +- If they are prefixed with `def`, then they are immediately accessible (e.g. `my_server.ip_address`). +- If they are **not** prefixed with `def`, then they are only constructor arguments, and they are used in the body of the class only. + This means that they are a class-constant, meaning that they may be used in any part of the class (body, functions, methods), but they are invariant. + +See below type refinement for where we expan the example. + We can then use `MyServer` as follows: ```mamba @@ -206,9 +241,13 @@ type DisConnMyServer: MyServer when not self.is_connected class ServerErr(def message: Str): Exception(message) -class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ +class MyServer(self: DisConnMyServer, ip_address: IPv4Address) := [ def is_connected: Bool := False def _last_message: Str? := None + # this showcases that the constructor arguments from above, if they are not prefixed with 'def', we need to manually assign it here + # essentially, the constructor body are the top-level expressions and statements of a class. + # The above ip_address has become a class-constant. + def ip_address: IPv4Address := ip_address def last_sent(self) -> Str ! ServerErr := if self.last_message != None then self._last_message @@ -219,6 +258,17 @@ class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ def send(self: ConnMyServer, message: Str) := self._last_message := message def disconnect(self: ConnMyServer) := self.is_connected := False + + def change_ip(self, new_address: IPv4Address) := [ + # notice that we access ip_address directly, not self.ip_address + print("When we first created this server, the address was {ip_address}") + print("In the meantime, our address is {self.ip_address}") + print("And now we will change our address to {new_address}") + + self.ip_address := new_address + # The following would result in a compilation error, we cannot assign to constants! + # ip_address := new_address + ] ] ``` From e4eafa1d1f2044accfd546889f9e729d8236a5ee Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 09:40:46 +0200 Subject: [PATCH 08/44] doc: improve grammar and spelling in README --- README.md | 184 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 1b216ebd..bf4bde3e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@

Mamba

This is the Mamba programming language. -Mamba is like Python, but with a few key features: +Mamba is similar to Python, but with a few key features: - Strict static typing rules, but with type inference so it doesn't get in the way too much - Type refinement features @@ -40,6 +40,7 @@ This is a transpiler, written in [Rust](https://www.rust-lang.org/), which conve files. Mamba code should therefore be interoperable with Python code. Functions written in Python can be called in Mamba and vice versa (from the generated Python files). +This interoparability is still a work in progress. The below README: @@ -72,13 +73,13 @@ To get more elaboration, see the tooling documentation in [CONTRIBUTING.md](./CO ## ⌨️ Code Examples Below are some code examples to showcase the features of Mamba. -We highlight how functions work, how de define classes, how types and type refinement features are applied, how Mamba can be used to ensure pureness, and how error handling works. ### ➕ Functions We can write a simple script that computes the factorial of a value given by the user. ```mamba +## Factorial of x def factorial(x: Int) -> Int := match x { 0 => 1 n => n * factorial(n - 1) @@ -92,12 +93,13 @@ if num.is_digit() then [ print("Input was not an integer.") ``` -Notice how here we specify the type of argument `x`, in this case an `Int`, by writing `x: Int`. +We specify the type of argument `x`, in this case an `Int`, by writing `x: Int`. +This is part of the signature of the function, and is required (it cannot be inferred). This means that the compiler will check for us that factorial is only used with integers as argument. Also note that: - Code blocks are denoted using `[` and `]` because this is a list of statements and expressions that gets executed _in order_. -- For a match expression or statement, each case is denoted using `{` and `}`, as this is a set of cases we match on. +- For a match expression or statement, each case is denoted using `{` and `}`, as this is a _set_ of cases which we match on. You you can read `match x {}` , where we read this as "match `x` on this set of conditions in `{...}`", though we omit the "on" as to not introduce another keyword. _Note_ One could use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) in the above example so that we consume less memory: @@ -113,9 +115,14 @@ def factorial(x: Int) -> Int := match x { } ``` +Logically, the `[...]` and `{...}` notation leads us nicely to collections in Mamba. + ### 🍡 Collections -In mamba sets, lists and maps are first class citizens, they are baked into the language. +In Mamba, sets, lists, and maps are first class citizens. +They are baked into the language, including and its grammar. + +Lists make use of square brackets: ```mamba # lists @@ -124,6 +131,13 @@ def b := ["list", "of", "strings"] # lists of tuples, buidler syntax def ab := [(x, y) | x in a, x > 0, y in b, b != "of" ] +# Indexing is done using curly brackets! +print(a(0)) # prints '0' +``` + +Sets and mappings, which are unordered, make use of curly brackets: + +```mamba # sets def c := { 10, 20 } def d := { 3 } @@ -140,13 +154,8 @@ print(ab(2)) # prints '(2, "list")' print(ef(1)) # prints '1' ``` -Unlike C-style languages (which is nearly the whole world at this point), we index lists using `()`. -The main reason for doing so is that we see collections, which can be indexed, as mappings. -This mapping itself is another function! -So, we perform a function call index to perform indexing, from which it follows that we use function call notation. - -Also good to note is that we consider a list simply a mapping where keys are the indexes of the list. -I.e. +In a way, a list is a type of mapping where the keys are the indexes of each item. +So: ```mamba def numbers := [32, 504, 59] @@ -158,11 +167,39 @@ Is just shorthand for def numbers := { 0 => 32, 1 => 504, 2 => 59 } ``` +Unlike C-style languages (which is nearly the whole world at this point), we index collections using `collection()`. +The main reason for doing so is that we see collections which can be indexed as mappings. +Consider the above argument that a list is just another type of mapping. + +We don't distinguish between a mapping and a function, because a function is (generally speaking) also a type of mapping. +We also argue that the above mapping is a representation of some functino with a very small domain (only three items). +Therefore, we index indexable collections (mappings and list) using the `collection()` notation. + ### 📋 Types, Classes, and Mutability -Classes are similar to classes in Python, though we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. +We introduce first two concepts here, mutability and classes. +Classes are similar to classes other object oriented language like Python, Kotlin and to an extent Rust. +Mutability gives us the power to modify an object in the language after it is created (we consider everything to be an object, though we don't consider Mamba to be strictly object-oriented). +So for instance + +``` +def a := 10 # we may modify a +def fin b := 20 # we may not modify b + +a := a + 2 # allowed +# b := b + 2 # compilation error +``` + +We opt to make mutability the default (unlike say in Rust, where you have to use the `mut` keyword to make something mutable). +The reason for doing so is domain; +Mamba is geared more for mathematical use, for lack of a better term, meaning this design choice follows from the language philosophy. +This same philosophy will also influence later how we deal with equality checks between objects in the language and how we copy items in the language, where we favour a pure functional apporach similar to Haskell. + +Continuing on classes, in Mamba, like Python and Rust, each method in a class has an explicit `self` argument, which gives access to the state of this class instance. +However, we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. If we write `self`, it is mutable, whereas if we write `fin self`, it is immutable and we cannot change its fields. -We can do the same for any field. We showcase this using a simple dummy `Server` object. +We can do the same for any field. +We showcase this using a simple dummy `Server` object. ```mamba from ipaddress import IPv4Address @@ -173,7 +210,8 @@ def fin always_the_same_message := "Connected!" class MyServer(def ip_address: IPv4Address) := [ def is_connected: Bool := False - def _last_message: Str := "temp" + # We can use constructor arguments in the body of the class + def _last_message: Str := "my ip address when I was created was {ip_address}" def last_sent(fin self) -> Str ! ServerError := self._last_message @@ -199,38 +237,40 @@ Though the body is optional. As for constructor arguments: - If they are prefixed with `def`, then they are immediately accessible (e.g. `my_server.ip_address`). -- If they are **not** prefixed with `def`, then they are only constructor arguments, and they are used in the body of the class only. - This means that they are a class-constant, meaning that they may be used in any part of the class (body, functions, methods), but they are invariant. - -See below type refinement for where we expan the example. +- If they are **not** prefixed with `def`, then they are only constructor arguments. + This means that they are a class-constant, a constant which is defined in the context of a class. + This means that they may be used in any part of the class (body, functions, methods). +- The body of the class is evaluated for each object we created, effectively making this the constructor body. -We can then use `MyServer` as follows: +We can change the relevant parts of the above example to use a class constant: ```mamba -import ipaddress -from server import MyServer +from ipaddress import IPv4Address -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) +class MyServer(IP_ADDRESS: IPv4Address) := [ + # The above IP_ADDRESS is a constant defined within the context of this class + # The intial value of ip_address is the value we passed to the constructor, but it may change + def ip_address: IPv4Address := IP_ADDRESS -http_server.connect() -if my_server.is_connected then http_server.send("Hello World!") + def change_ip(self, new_address: IPv4Address) := [ + print("When we first created this server, the address was {IP_ADDRESS}") + print("In the meantime, our address is {self.ip_address}") + print("And now we will change our address to {new_address}") -# This statement may raise an error, but for now de simply leave it as-is -# See the error handling section for more detail -print("last message sent before disconnect: \"{my_server.last_sent()}\".") -my_server.disconnect()! + self.ip_address := new_address + # The following would result in a compilation error, we cannot assign to constants! + # IP_ADDRESS := new_address + ] +] ``` ### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) -As shown above Mamba has a type system. -Mamba however also has type refinement features to assign additional properties to types. -We should not that this is a very experimental feature/thought. +Mamba also has type refinement features to assign additional properties to types. Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the comlexit of the language. Arguably, it might detract from the elegance of the type system as well; A different solution could be to just have a dedicated interface baked into the standard library for this purpose. - +However, were we to implement this, our proposal would be as follows. Lets expand our server example from above, and rewrite it slightly: ```mamba @@ -241,13 +281,9 @@ type DisConnMyServer: MyServer when not self.is_connected class ServerErr(def message: Str): Exception(message) -class MyServer(self: DisConnMyServer, ip_address: IPv4Address) := [ +class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ def is_connected: Bool := False def _last_message: Str? := None - # this showcases that the constructor arguments from above, if they are not prefixed with 'def', we need to manually assign it here - # essentially, the constructor body are the top-level expressions and statements of a class. - # The above ip_address has become a class-constant. - def ip_address: IPv4Address := ip_address def last_sent(self) -> Str ! ServerErr := if self.last_message != None then self._last_message @@ -258,24 +294,13 @@ class MyServer(self: DisConnMyServer, ip_address: IPv4Address) := [ def send(self: ConnMyServer, message: Str) := self._last_message := message def disconnect(self: ConnMyServer) := self.is_connected := False - - def change_ip(self, new_address: IPv4Address) := [ - # notice that we access ip_address directly, not self.ip_address - print("When we first created this server, the address was {ip_address}") - print("In the meantime, our address is {self.ip_address}") - print("And now we will change our address to {new_address}") - - self.ip_address := new_address - # The following would result in a compilation error, we cannot assign to constants! - # ip_address := new_address - ] ] ``` Within the then branch of the if statement, we know that `self._last_message` is a `Str`. This is because we performed a check in the if condition. -Also Notice how above, we define the type of `self`. +We now define the type of `self`. Each type effectively denotes another state that `self` can be in. For each type, we use `when` to show that it is a type refinement, which certain conditions. @@ -306,6 +331,7 @@ Type refinement also allows us to specify the domain and co-domain of a function # this first-class language feature desugars to an list of checks which are done at the call site. # we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. type PosInt: Int when { + # The '!' is part of the error handling notation of Mamba, see below self >= 0 ! NegativeError("Must be greater than 0") } @@ -331,23 +357,30 @@ The goal of the compiler becomes: - Limit the amount of checks that need to be done - Detect when it becomes impossible to raise an exception, i.e. if it is impossible to break an invariant then we will never raise an exception. +Overall, the goal of type refinement it to allow us to express in greater detail the expected behaviour of functions in a more concise manner. +It is similar to "design by contract", though it should hopefully also create a clearer mental model of domains and codomains of functions. + ### 🔒 Pure functions (🇻 0.4.1+) -Mamba has features to ensure that functions are pure, meaning that if `x = y`, for any `f`, `f(x) = f(y)`. -(Except if the output of the function is say `None` or `NaN`.) -By default, functions are not pure, and can read any variable they want, such as in Python. -When we make a function `pure`, it cannot: +Mamba has features to ensure that functions are pure, meaning that if `x = y`, for a pure function `f`, `f(x) = f(y)`. +`=` is the equality operator in Mamba, which checks for structural equality and not whether this is the same object in memory (with the same address). +This is inspired originally by pure functions in proof assistant tools. + +By default, functions are not pure. +When we mark a function `pure`, restrictions are enforced by the language: -- Read non-final properties of `self`. +- Read non-final properties of `self` (if this is a method). + This means that its output depends only on the direct input, and input to the constructor of the class. - Call impure functions. -Some rules hold for calling and assigning to passed arguments to uphold the pure property (meaning, no side-effects): +Some additional rules hold for calling and assigning to passed arguments to uphold the pure property (meaning, no side-effects): - Anything defined within the function body is fair game, it may be used whatever way, as it will be destroyed upon exiting the function. - An argument may be assigned to, as this will not modify the original reference. - The field of an argument may not be assigned to, as this will modify the original reference. - One may only read fields of an argument which are final (`fin`). - One may only call methods of an argument which are pure (`pure`). +- It should be emphasized that all of the above also hold accesses to `self` in the case of methods. When a function is `pure`, its output is always the same for a given input. It also has no side-effects, meaning that it cannot write anything (assign to mutable variables) or read from them. @@ -370,12 +403,13 @@ def pure sin(x: Int) -> Int := [ Unlike Python, Mamba does not have `try` `except` and `finally` (or `try` `catch` as it is sometimes known). Instead, we aim to directly handle errors on-site so the origin of errors is more tracable. -The following is an attempt mixing and matching `Result` monad (of languages like Rust and Scala), -with a more first-class approach of exceptions is languages like Kotlin. -This is a trade-off between elegancy of the type system versus first-class language features. -Arguably it may be easier to just use Monads and get rid of this, but lets see how this goes in practice. +The following is an attempt mixing and matching `Result` monad (of languages like Rust and Scala), with a more first-class approach of exceptions in languages like Kotlin. +Again, this represents a trade-off between elegancy of the type system and simplicity of the grammar versus having first-class language features. +Arguably it may be easier to just use Monads, similar to how Rust's solution. +But, we are operating in a different domain, so that may be overly verbose for our purposes. -We can modify the above script such that we don't check whether the server is connected or not. +Lets continue with our Server example. +We modify the above script such that we don't check whether the server is connected or not. In that case, we must handle the case where `my_server` throws a `ServerErr`: ```mamba @@ -393,6 +427,12 @@ my_server.send(message) ! { if my_server isa ConnectedMyServer then my_server.disconnect() ``` +In the above script, we will always print the error since we forgot to actually connect to the server. +Here we showcase how we try to handle errors on-site instead of in a (large) `try` block. +This means that we don't need a `finally` block: +We aim to deal with the error where it happens and then continue executing the remaining code. +This also prevents us from wrapping large code blocks in a `try`, where it might not be clear what statement or expression might throw what error. + `my_server.send(message) ! { ... }` is syntax sugar for ```mamba @@ -402,15 +442,9 @@ match my_server.send(message) { ``` So esentially, we add `!` as a way to shorthand match on exceptions. -Again, recall that this is a tradeoff. Currently, we allow both notations, but this comes at the cost of there not being "one way" to handle exceptions. This can lead to similar problems like with Scala where we have multiple ways to do the same thing. - -In the above script, we will always print the error since we forgot to actually connect to the server. -Here we showcase how we try to handle errors on-site instead of in a (large) `try` block. -This means that we don't need a `finally` block: -We aim to deal with the error where it happens and then continue executing the remaining code. -This also prevents us from wrapping large code blocks in a `try`, where it might not be clear what statement or expression might throw what error. +We can, of course, add warnings to strongly encourage the "right" way to handle exceptions. This can also be combined with an assign. In that case, we must either always return (halting execution or exiting the function), or evaluate to a value. @@ -431,16 +465,18 @@ def a: Int := function_may_throw_err() ! { print("a has value {a}.") ``` -We can also opt to not do any error handling, making this +We can also opt to not do any error handling, making the type of `a`: ``` def a: Result[Int, Union[MyErr, MyOtherErr]] := function_may_throw_err() ``` -By extension, if we don't handle all cases, then the union becomes smaller, only when the union is empty is `a` an `Int`. -The type of `a` is then result, and we are required to do error handling later. -I we don't want to handle any of the exception cases here, we just append a `!` to a function. -This means that this exception must be handeld further up the stack. +By extension, if we don't handle all cases, then the union becomes smaller. +Only when the union is empty, which happens when every error case is covered, does `a` have type `Int`. + +If `a` is is type `Result[...,...]`, and we are required to do error handling later. +So if we don't want to handle any of the exception cases at a given point, we just append an `!` to a function. +The exception(s) must be handeld further up the stack. ```mamba def a := function_may_throw_err() ! From 42cae173d5a2ef7ae19f38947a29e1dd3c015940 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 10:56:00 +0200 Subject: [PATCH 09/44] doc: expand type refinement, add recover --- README.md | 103 ++++++++++++++++++++++++++++++------------ docs/spec/grammar.md | 6 +-- docs/spec/keywords.md | 9 ++-- 3 files changed, 81 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index bf4bde3e..f944326b 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Logically, the `[...]` and `{...}` notation leads us nicely to collections in Ma ### 🍡 Collections In Mamba, sets, lists, and maps are first class citizens. -They are baked into the language, including and its grammar. +They are baked into the language, including its grammar. Lists make use of square brackets: @@ -270,7 +270,54 @@ Mamba also has type refinement features to assign additional properties to types Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the comlexit of the language. Arguably, it might detract from the elegance of the type system as well; A different solution could be to just have a dedicated interface baked into the standard library for this purpose. -However, were we to implement this, our proposal would be as follows. + +The general syntax is `type MyType [: OtherType] when `, where specifying `OtherType` is optional. +The expression can be of any form (and size), but **must** evaluate to a boolean. + +```mamba +type SpecialInt: Int when self >= 0 and self <= 100 or self mod 2 = 0 +``` + +We also introduce some syntax sugar again, where we can use `{` `}` to write each element of the conjunction on its own line. +_Note on performance: In terms of correctness, the order of the conjunctions obviously doesn't matter, but those who care about performance should know they are evaluated in order, so best to have simple ones first._ + +```mamba +type SpecialInt: Int when { + self >= 0 + self <= 100 or self mod 2 = 0 +} +``` + +Type refinement also allows us to specify the domain and co-domain of a function, say, one that only takes and returns positive integers: + +```mamba +# we list the conditions below, which are a list of boolean expressions. +# this first-class language feature desugars to an list of checks which are done at the call site. +# we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. +type PosInt: Int when self >= 0 + +def factorial(x: PosInt) -> PosInt := match x { + 0 => 1 + n => n * factorial(n - 1) +} +``` + +At the call site, one could do + +```mamba +def x := -42 # some value + +# currently this is a compilation error, x is type Int +# we cannot yet evaluate refined types at compile time, only runtime +# factorial(x) # error: 'x' is type Int, but signature is factorial(PosInt) + +if x isa PosInt then + print(factorial(x)) +else + print("x must be positive") +``` +In short, types allow us to specify the domain and co-domain of functions with regards to the type of input, say, `Int` or `Str`. + Lets expand our server example from above, and rewrite it slightly: ```mamba @@ -324,35 +371,11 @@ print("last message sent before disconnect: \"{my_server.last_sent}\".") if my_server isa ConnectedMyServer then my_server.disconnect() ``` -Type refinement also allows us to specify the domain and co-domain of a function, say, one that only takes and returns positive integers: - -```mamba -# we list the conditions below, which are a list of boolean expressions. -# this first-class language feature desugars to an list of checks which are done at the call site. -# we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. -type PosInt: Int when { - # The '!' is part of the error handling notation of Mamba, see below - self >= 0 ! NegativeError("Must be greater than 0") -} - -def factorial(x: PosInt) -> PosInt := match x { - 0 => 1 - n => n * factorial(n - 1) -} -``` - -In short, types allow us to specify the domain and co-domain of functions with regards to the type of input, say, `Int` or `Str`. -During execution, a check is done to verify that the variable does conform to the requirements of the refined type. -If it does not, an exception is raised. - -Type refinement allows us to do some additional things: +Type refinement allows, in the context of object oriented programming, thus allows us to also explicitly name the possible states of an object. +This means that we don't constantly have to check that certain conditions hold. +We can simply ask whether a given object is a certain state by checking whether it is a certain type. -- It allows us to further specify the domain or co-domain of a function -- It allows us to explicitly name the possible states of an object. - This means that we don't constantly have to check that certain conditions hold. - We can simply ask whether a given object is a certain state by checking whether it is a certain type. - -The goal of the compiler becomes: +In general, the goal of the compiler will become: - Limit the amount of checks that need to be done - Detect when it becomes impossible to raise an exception, i.e. if it is impossible to break an invariant then we will never raise an exception. @@ -441,6 +464,8 @@ match my_server.send(message) { } ``` +The `{...}` after `!` is also not necessary if we only match on one exception. + So esentially, we add `!` as a way to shorthand match on exceptions. Currently, we allow both notations, but this comes at the cost of there not being "one way" to handle exceptions. This can lead to similar problems like with Scala where we have multiple ways to do the same thing. @@ -499,6 +524,24 @@ a = a ! # Result[Int, MyErr] => Int, where if error case, an exception is raised print("a has value {a}.") ``` +Finally, we also introduce the `recover` keyword. +The intention is that instead of letting someone else up the stack perform cleanup, we can couple some of the cleanup at this site. +For instance, de-allocation resources which we no longer need. +This is similar to `drop` in Rust, though this applies only to errors/exceptions (as we generally speaking rely on garbage collection). +This is also similar to `finally` in Python, though we don't always run this block, only when we encounter an error. + +The general syntax is ` recover ` +So: + +```mamba +def a: Result[Int, MyErr] := function_may_throw_err() ! { + err: MyOtherErr => print("We have a problem: {err.message}.") +} recover [ + print("cleaning up resource") + some_cleanup_function() +] +``` + ## 💻 The Command Line Interface ``` diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index c8f360f3..8f306c56 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -15,8 +15,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] type-def ::= "type" type [ ":" type ] ( code-block | "when" [ conditions ] ) - conditions ::= "{" condition { newline condition } "}" | condition - condition ::= expression [ "!" expression ] + conditions ::= "{" expression { newline expression } "}" | expression type-tuple ::= "(" [ type ] { "," type } ")" class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( := code-block ) @@ -53,7 +52,8 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). | "_" reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" | ">>=" | "<<=" ) expression - call ::= expression [ ( "." | "?." ) ] id tuple [ "!" ] [ newline match-cases ] + call ::= expression [ ( "." | "?." ) ] id tuple + [ "!" ( match-case | newline "{" match-cases "}" ) [ recover ( expr-or-stmt | "{" expr-or-statement { newline expr-or-statement } "}" ) ] ] raise ::= "!" id { "," id } collection ::= tuple | set | list | map diff --git a/docs/spec/keywords.md b/docs/spec/keywords.md index 4fbd2be6..fbdd86c4 100644 --- a/docs/spec/keywords.md +++ b/docs/spec/keywords.md @@ -59,10 +59,11 @@ Keyword | Use Keyword | Use ---|--- -`if` | Denote start of if expression or statement -`then` | Denote start of then branch of if -`else` | Denote start of else branch of if -`match` | Denote start of a match expression or statement +`if` | Denote start of if expression or statement +`then` | Denote start of then branch of if +`else` | Denote start of else branch of if +`match` | Denote start of a match expression or statement +`recover` | Recover from error, for (partial) local error recovery ## Control Flow Statements From 36427b8e95811b4778cc22062e43e26e78967ce9 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 12:03:40 +0200 Subject: [PATCH 10/44] doc: class is set of statements Also show that we can nest blocks for imperative/sequential execution --- README.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f944326b..e248afbd 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,8 @@ The reason for doing so is domain; Mamba is geared more for mathematical use, for lack of a better term, meaning this design choice follows from the language philosophy. This same philosophy will also influence later how we deal with equality checks between objects in the language and how we copy items in the language, where we favour a pure functional apporach similar to Haskell. +### 📋 Types, Properties, and Classes + Continuing on classes, in Mamba, like Python and Rust, each method in a class has an explicit `self` argument, which gives access to the state of this class instance. However, we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. If we write `self`, it is mutable, whereas if we write `fin self`, it is immutable and we cannot change its fields. @@ -208,7 +210,7 @@ class ServerError(def message: Str): Exception(message) def fin always_the_same_message := "Connected!" -class MyServer(def ip_address: IPv4Address) := [ +class MyServer(def ip_address: IPv4Address) := { def is_connected: Bool := False # We can use constructor arguments in the body of the class def _last_message: Str := "my ip address when I was created was {ip_address}" @@ -225,7 +227,7 @@ class MyServer(def ip_address: IPv4Address) := [ else ! ServerError("Not connected!") def disconnect(self) := self.is_connected := False -] +} ``` Notice how `self` is not mutable in `last_sent`, meaning we can only read variables, whereas in connect `self` is mutable, so we can change properties of `self`. @@ -247,7 +249,7 @@ We can change the relevant parts of the above example to use a class constant: ```mamba from ipaddress import IPv4Address -class MyServer(IP_ADDRESS: IPv4Address) := [ +class MyServer(IP_ADDRESS: IPv4Address) := { # The above IP_ADDRESS is a constant defined within the context of this class # The intial value of ip_address is the value we passed to the constructor, but it may change def ip_address: IPv4Address := IP_ADDRESS @@ -261,7 +263,7 @@ class MyServer(IP_ADDRESS: IPv4Address) := [ # The following would result in a compilation error, we cannot assign to constants! # IP_ADDRESS := new_address ] -] +} ``` ### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) @@ -322,15 +324,23 @@ Lets expand our server example from above, and rewrite it slightly: ```mamba from ipaddress import IPv4Address +import datetime type ConnMyServer: MyServer when self.is_connected type DisConnMyServer: MyServer when not self.is_connected class ServerErr(def message: Str): Exception(message) -class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ +class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := { def is_connected: Bool := False - def _last_message: Str? := None + # we can nest blocks to enforce that certain statements are executed in order + [ + def ip_addr: Str = "my address is {ip_address}" + def date: Str = "The current date is {datetime.datetime.now()}" + + def _welcome_message: Str = "Welcome! {date}, {ip_addr}" + def _last_message: Str? := welcome_message + ] def last_sent(self) -> Str ! ServerErr := if self.last_message != None then self._last_message @@ -341,7 +351,7 @@ class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := [ def send(self: ConnMyServer, message: Str) := self._last_message := message def disconnect(self: ConnMyServer) := self.is_connected := False -] +} ``` Within the then branch of the if statement, we know that `self._last_message` is a `Str`. From 896816825d1e211f27a8ce5a179b404a417970fb Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 12:28:20 +0200 Subject: [PATCH 11/44] doc: add traits to Mamba --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- docs/spec/grammar.md | 4 ++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e248afbd..3189aefd 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ We don't distinguish between a mapping and a function, because a function is (ge We also argue that the above mapping is a representation of some functino with a very small domain (only three items). Therefore, we index indexable collections (mappings and list) using the `collection()` notation. -### 📋 Types, Classes, and Mutability +### 📋 Mutability We introduce first two concepts here, mutability and classes. Classes are similar to classes other object oriented language like Python, Kotlin and to an extent Rust. @@ -266,6 +266,43 @@ class MyServer(IP_ADDRESS: IPv4Address) := { } ``` +Last, `type`s can also serve as interfaces Mamba. +These are similar to interfaces in Java and Kotlin, and similar to traits in Rust. +In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. +For instance, consider example with iterators (which briefly showcases language generics): + +```mamba +type Iterator[T] := { + def next(self) -> T? # syntax sugar for Option[T] + ## Has next can be overriden, but has default implementation already + def has_next(self) -> bool := self.next() != None +} + +class RangeIter(def start: Int, def end: Int) +def Iterator[Int] for RangeIter := { + def current: Int := start + + def next(self) -> Int? := + if self.current >= self.stop then None + else [ + def value := self.current + self.current := self.current + 1 + value + ] +} +``` + +⚠️ `type` thus denotes both traits and type refinement in the language when paired with the `when` keyword. +The intention is that one would not need to mix and match these, but instead: + +- Prefer using a noun when defining a refinement, as this describes some (set of) properties of class and its instances. + The syntax here is `type when ` or `type when { }` +- Prefer using an adjective (e.g. `Iterable`, `Hashable`, `Comparable`) when defining a trait, as this describes something a class and its instances can do. + The syntax here is `type := { for ` + +Lastly, like Rust, types (traits) can also be used as generics. +This would allow, for instance, for defining say a `Hash` trait and enforcing for a hashmap that keys implement said type. + ### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) Mamba also has type refinement features to assign additional properties to types. diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 8f306c56..71e5a401 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -14,11 +14,11 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). file ::= { expr-or-stmt } import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] - type-def ::= "type" type [ ":" type ] ( code-block | "when" [ conditions ] ) + type-def ::= "type" type [ ":" type ] ( ":=" "{" code-block "}" | "when" [ conditions ] ) | conditions ::= "{" expression { newline expression } "}" | expression type-tuple ::= "(" [ type ] { "," type } ")" - class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( := code-block ) + class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( ":=" code-block ) generics ::= "[" id { "," id } "]" id ::= { character } From afbbb5d01ab663295d8db2df0d9d1c93e2e60a04 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 12:59:17 +0200 Subject: [PATCH 12/44] doc: add trait as separate language construct --- README.md | 50 ++++++++++++++++++++++++------------------- docs/spec/grammar.md | 6 ++++-- docs/spec/keywords.md | 9 ++++---- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3189aefd..eac5a56d 100644 --- a/README.md +++ b/README.md @@ -266,42 +266,47 @@ class MyServer(IP_ADDRESS: IPv4Address) := { } ``` -Last, `type`s can also serve as interfaces Mamba. +Last, we have `trait`s can also serve as interfaces Mamba. These are similar to interfaces in Java and Kotlin, and similar to traits in Rust. In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. For instance, consider example with iterators (which briefly showcases language generics): ```mamba -type Iterator[T] := { +trait Iterator[T] := { + def has_next(self) -> Bool def next(self) -> T? # syntax sugar for Option[T] - ## Has next can be overriden, but has default implementation already - def has_next(self) -> bool := self.next() != None } -class RangeIter(def start: Int, def end: Int) +class RangeIter(def _start: Int, def _end: Int) := { + def _current: Int := _start +} + def Iterator[Int] for RangeIter := { - def current: Int := start - - def next(self) -> Int? := - if self.current >= self.stop then None - else [ - def value := self.current - self.current := self.current + 1 - value - ] + def has_next(self) -> Bool := self._current < self._stop + + def next(self) -> Int? := if self.has_next() then [ + def value := self._current + self._current := self._current + 1 + value + ] else None } ``` -⚠️ `type` thus denotes both traits and type refinement in the language when paired with the `when` keyword. -The intention is that one would not need to mix and match these, but instead: - -- Prefer using a noun when defining a refinement, as this describes some (set of) properties of class and its instances. - The syntax here is `type when ` or `type when { }` -- Prefer using an adjective (e.g. `Iterable`, `Hashable`, `Comparable`) when defining a trait, as this describes something a class and its instances can do. - The syntax here is `type := { for ` +Prefer using an adjective (e.g. `Iterable`, `Hashable`, `Comparable`) when defining a trait, as this describes something a class and its instances can do. +The syntax here is `trait := { for `. Lastly, like Rust, types (traits) can also be used as generics. This would allow, for instance, for defining say a `Hash` trait and enforcing for a hashmap that keys implement said type. +We can also compose traits, which means that when we define the composite trait for a class we have to implement all definitions at once. +The syntax is very similar to inheritance for classes: + +E.g. + +```mamba +trait Ordered[T]: Equality, Comparable { + def less_than(self, other: T) -> Bool +} +``` ### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) @@ -310,7 +315,7 @@ Having this as a first-class language feature and incorporating it into the gram Arguably, it might detract from the elegance of the type system as well; A different solution could be to just have a dedicated interface baked into the standard library for this purpose. -The general syntax is `type MyType [: OtherType] when `, where specifying `OtherType` is optional. +The general syntax is `type MyType: MainType when `. The expression can be of any form (and size), but **must** evaluate to a boolean. ```mamba @@ -435,6 +440,7 @@ It is similar to "design by contract", though it should hopefully also create a Mamba has features to ensure that functions are pure, meaning that if `x = y`, for a pure function `f`, `f(x) = f(y)`. `=` is the equality operator in Mamba, which checks for structural equality and not whether this is the same object in memory (with the same address). This is inspired originally by pure functions in proof assistant tools. +For use to be able to compare two instances, the instance must implement the `Equality` trait (which we showed above). By default, functions are not pure. When we mark a function `pure`, restrictions are enforced by the language: diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 71e5a401..0b5a6554 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -14,7 +14,8 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). file ::= { expr-or-stmt } import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] - type-def ::= "type" type [ ":" type ] ( ":=" "{" code-block "}" | "when" [ conditions ] ) | + type-def ::= "type" type ":" type ( ":=" "{" code-block "}" | "when" [ conditions ] ) + trait-def ::= "trait" type ( ":" type { "," type } ) ":=" ( expression-or-statement | "{" code-block "}" ) conditions ::= "{" expression { newline expression } "}" | expression type-tuple ::= "(" [ type ] { "," type } ")" @@ -66,7 +67,8 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). slice ::= expression ( "::" | "::=" ) expression range ::= expression ( ".." | "..=" ) expression - definition ::= "def" ( variable-def | fun-def | operator-def ) + definition ::= "def" ( variable-def | fun-def | operator-def ) | special-def + special-def ::= type-def | trait-def | class-def variable-def ::= [ "fin" ] ( id-maybe-type | collection ) [ ":=" expression ] [ forward ] operator-def ::= [ "pure" ] overridable-op [ "(" [ id-maybe-type ] ")" ] "->" type diff --git a/docs/spec/keywords.md b/docs/spec/keywords.md index fbdd86c4..a797054e 100644 --- a/docs/spec/keywords.md +++ b/docs/spec/keywords.md @@ -18,10 +18,11 @@ Keyword | Use Keyword | Use ---|--- -`type` | When constructing an interface (abstract base class) or type alias -`class` | Denote a class -`isa` | Check whether an object is instance of a class -`when` | Conditional types +`type` | Denote start of type defintion or type alias +`when` | Conditionals of a type definition +`class` | Denote start of class definition +`trait` | Denote start of trait definition +`isa` | Check whether an object is instance of a class, or more generally type ## Classes and Utils From 6ce42ebe7a49d8d7128406245bb6c5fc8014f673 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 14:28:05 +0200 Subject: [PATCH 13/44] doc: describe total functions --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eac5a56d..1eb83ba5 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ We don't distinguish between a mapping and a function, because a function is (ge We also argue that the above mapping is a representation of some functino with a very small domain (only three items). Therefore, we index indexable collections (mappings and list) using the `collection()` notation. -### 📋 Mutability +### ✏️🖊️ Mutability We introduce first two concepts here, mutability and classes. Classes are similar to classes other object oriented language like Python, Kotlin and to an extent Rust. @@ -377,10 +377,10 @@ class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := { def is_connected: Bool := False # we can nest blocks to enforce that certain statements are executed in order [ - def ip_addr: Str = "my address is {ip_address}" - def date: Str = "The current date is {datetime.datetime.now()}" + def ip_addr: Str := "my address is {ip_address}" + def date: Str := "The current date is {datetime.datetime.now()}" - def _welcome_message: Str = "Welcome! {date}, {ip_addr}" + def _welcome_message: Str := "Welcome! {date}, {ip_addr}" def _last_message: Str? := welcome_message ] @@ -475,6 +475,60 @@ def pure sin(x: Int) -> Int := [ ] ``` +### 🤚 Total functions (🇻 0.5+) + +A function may also be total, which means: + +1. It is defined for possible values of its domain +2. It will halt on all such inputs + +The second property is interesting, because that would imply that the compiler can prove that an arbitrary function can halt. +To build such a compiler, we would need to solve the halting problem (which is impossible). +Instead, we place heavy restrictions on total functions, enforcing that they are weakly normalizing: + +1. We may only call total functions +2. Within the _call tree_ of a function, all arguments to nodes in the tree must be _strictly decreasing_ compared to the first parent of a node which is equal to said node. +3. Potentially non-terminating loops, which includes `while`, are not allowed +4. For may only be called over collections which implement `SizedIterator`, which is also implemented by the built-in: + - `RangeToInclusive` : `..=b` + - `RangeTo` : `..b` + - `Range` : `a..b` + - `RangeInclusive` : `a..=b` + +Take for instance this naive implementation of the Fibbonaci sequence: + +```mamba +## fibbonaci, implemented using recursion and not dynamic programming +def total pure fibbonaci(x: PosInt) -> Int := match x { + 0 => 0 + 1 => 1 + n => fibbonaci(n - 1) + fibbonaci(n - 2) +} +``` + +This would, with some substitution magic, give the following _call tree_: + +``` + fibbonaci(x) + / \ +fibbonaci(x - 1) fibbonaci(x - 2) +``` + +Thus, this function has the property of a final function, and we may thus mark it as `total` if we so choose. +The reason why we above state "compared to the first parent of a node which is equal to said node." is that we can have situations where we call other total functions which have recursive calls to self. +This allows us to call other recursive functions without having to strictly decrease the value of the input, but still enfroce that calls to self (and more generally recursive calls to the same function) again are strictly decreasing. +This strictly decreasing property is likely difficult to prove, so we will limit this to a set of primtives at first. + +In general: + +- If a function is `pure`, it has no side effects. +- If a function is `total`, it will terminate for all possible inputs. + +One does not imply the other, so you need both keywords if you want to say a function is total and pure. + +The intended use-case is a bit more niche, likely mostly functions in the standard library, to show that they halt on all possible inputs. +But we can imagine that library writers might find these useful if they wish to be more thorough. + ### ⚠ Error handling Unlike Python, Mamba does not have `try` `except` and `finally` (or `try` `catch` as it is sometimes known). From 034b3d25e46789e1d47ab81586c6bb054175cb4d Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 9 Jul 2025 15:23:42 +0200 Subject: [PATCH 14/44] doc: add constant functions This clashes a bit with language philosophy, but is a necessary evil --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++-- docs/spec/grammar.md | 2 +- docs/spec/keywords.md | 2 ++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1eb83ba5..043a61b5 100644 --- a/README.md +++ b/README.md @@ -489,7 +489,7 @@ Instead, we place heavy restrictions on total functions, enforcing that they are 1. We may only call total functions 2. Within the _call tree_ of a function, all arguments to nodes in the tree must be _strictly decreasing_ compared to the first parent of a node which is equal to said node. 3. Potentially non-terminating loops, which includes `while`, are not allowed -4. For may only be called over collections which implement `SizedIterator`, which is also implemented by the built-in: +4. For loops may only be called over collections which implement `SizedIterator`, which is also implemented by the built-in: - `RangeToInclusive` : `..=b` - `RangeTo` : `..b` - `Range` : `a..b` @@ -517,7 +517,52 @@ fibbonaci(x - 1) fibbonaci(x - 2) Thus, this function has the property of a final function, and we may thus mark it as `total` if we so choose. The reason why we above state "compared to the first parent of a node which is equal to said node." is that we can have situations where we call other total functions which have recursive calls to self. This allows us to call other recursive functions without having to strictly decrease the value of the input, but still enfroce that calls to self (and more generally recursive calls to the same function) again are strictly decreasing. -This strictly decreasing property is likely difficult to prove, so we will limit this to a set of primtives at first. + +We provide the `StrictlyDecreases` trait so users can define if something is strictly decreasing. +The compiler enforces that this is defined for each argument. +However, this is ripe for abuse, so instead, we require that each argument implements the trait `Measure`. + +```mamba +trait Measure { + # returns a value which must be ordered, i.e. Int + def const measure(self) -> ConstOrdered +} + +trait def StrictlyDecreases: Measure { + # if we implement strictly decreasing, we must implement measure + # decreases has a non-overridable method which uses this measure + def fin const decreases(self, other: Self) -> self.measure() < other.measure() +} +``` + +This avoids abuse of `decreases`. +Instead, ordering is reduced to numeric ordering, which is verifiable and depends on the output of a pure function. +It is for instance defined for the built-in primtive `Int`. + +```mamba +def Measure for Int { + # measure for int just returns self + def const measure(self) -> ConstOrdered := self +} + +# This is an example for how we would define it for string. +# We also show that implementing the StrictlyDecreases trait just results in us only havign to implement measure. +def StrictlyDecreases for Str { + def const measure(self) -> ConstOrdered := self.len() +} +``` + +Both of the above return an `Int`, which is part of the library and implements the `ConstOrdered` trait: + +```mamba +def ConstOrdered for Int { + def const less_than(self, other: Int) := self < other +} +``` + +At compilation time, the compiler then runs this trait and verifies that this property holds. +_Note: this means that we need to evaluate code during compilation time!_ +Only constant functions can be evaluated at compile time, see the section on constant functions below. In general: @@ -529,6 +574,32 @@ One does not imply the other, so you need both keywords if you want to say a fun The intended use-case is a bit more niche, likely mostly functions in the standard library, to show that they halt on all possible inputs. But we can imagine that library writers might find these useful if they wish to be more thorough. +### Constant functions (🇻 0.5+) + +The above also highlights constant functions in the language, which is a necessary evil. +Evil because it does clash a bit with the more "math focussed" aspect of the language. +Constant functions are functions which can be evaluated at compile time. +These functions have two constraints: + +- These may not call non-constant functions (including total and pure functions). +- A constant function is also pure. +- A constant function is not enforced to be total, but it is recommended that it is! +- We may well place additional constraints on constant functions in future. + +The most important use-case of constant functions is for the total functions above. +A constant function is defined as `def const my_function() := ...`. + +Because the function `less_than` returns objects which implement the `ConstOrdered` trait, the `less_than` must necessarily also be a constant function: + +```mamba +trait ConstOrdered[T] { + def const less_than(self, other: T) -> Bool +} +``` + +We differentiate this from the `Ordered` trait so that we don't enforce everyone must implement `less_than` as a constant function, which is rarely necessary. + + ### ⚠ Error handling Unlike Python, Mamba does not have `try` `except` and `finally` (or `try` `catch` as it is sometimes known). diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 0b5a6554..3d2b5c7e 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -74,7 +74,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). operator-def ::= [ "pure" ] overridable-op [ "(" [ id-maybe-type ] ")" ] "->" type [ ":=" ( expr-or-stmt | code-block ) ] - fun-def ::= [ "pure" ] id fun-args [ "->" type ] [ raise ] + fun-def ::= ( [ "const" ] | [ "total" ] [ "pure" ] ) id fun-args [ "->" type ] [ raise ] [ ":=" ( expr-or-stmt | code-block ) ] fun-args ::= "(" [ fun-arg ] { "," fun-arg } ")" fun-arg ::= id-maybe-type [ ":=" expression ] diff --git a/docs/spec/keywords.md b/docs/spec/keywords.md index a797054e..e8546b71 100644 --- a/docs/spec/keywords.md +++ b/docs/spec/keywords.md @@ -39,6 +39,8 @@ Keyword | Use `def` | Denote definition `fin` | Denote defined variable is immutable `pure` | Denote function is pure +`total` | Denote a function is total +`const` | Denote a function is constant (evaluated during compile time) ## Boolean operators From 63c978326dfa9ee527e227fc1e2f1eb5dfa58fb9 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Thu, 10 Jul 2025 10:41:37 +0200 Subject: [PATCH 15/44] doc: Use linear algebra for examples Also cleanup language spec a bit --- README.md | 222 ++++++++++++++++++++++--------------------- docs/spec/grammar.md | 69 ++++++-------- 2 files changed, 142 insertions(+), 149 deletions(-) diff --git a/README.md b/README.md index 043a61b5..54f976be 100644 --- a/README.md +++ b/README.md @@ -177,9 +177,7 @@ Therefore, we index indexable collections (mappings and list) using the `collect ### ✏️🖊️ Mutability -We introduce first two concepts here, mutability and classes. -Classes are similar to classes other object oriented language like Python, Kotlin and to an extent Rust. -Mutability gives us the power to modify an object in the language after it is created (we consider everything to be an object, though we don't consider Mamba to be strictly object-oriented). +Mutability gives us the power to modify an instance in the language after it is created. So for instance ``` @@ -197,48 +195,55 @@ This same philosophy will also influence later how we deal with equality checks ### 📋 Types, Properties, and Classes -Continuing on classes, in Mamba, like Python and Rust, each method in a class has an explicit `self` argument, which gives access to the state of this class instance. +Next, we introduce the concept of a class. +A class is essentially a blueprint for the behaviour of instances of that class. + +In Mamba, like Python and Rust, each method in a class has an explicit `self` argument, which gives access to the state of this instance. However, we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. If we write `self`, it is mutable, whereas if we write `fin self`, it is immutable and we cannot change its fields. We can do the same for any field. -We showcase this using a simple dummy `Server` object. -```mamba -from ipaddress import IPv4Address +We showcase this using a simple dummy `Matrix` object. +You will also see some "pure" functions, these will be explained later. -class ServerError(def message: Str): Exception(message) +```mamba +class MatrixErr(def message: Str): Exception(message) -def fin always_the_same_message := "Connected!" +class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { + # Accessor for matrix contents + def contents(fin self) -> List[Int] := [a, b, c, d] -class MyServer(def ip_address: IPv4Address) := { - def is_connected: Bool := False - # We can use constructor arguments in the body of the class - def _last_message: Str := "my ip address when I was created was {ip_address}" + # Trace of the matrix (a + d) + def pure trace(fin self) -> Int := a + d - def last_sent(fin self) -> Str ! ServerError := self._last_message + # Determinant recomputation (pure function) + def pure determinant(fin self) -> Int := a * d - b * c - def connect(self) := [ - self.is_connected := True - print(always_the_same_message) + def scale(self, factor: Int) := [ + self.a := self.a * factor + self.b := self.b * factor + self.c := self.c * factor + self.d := self.d * factor ] - def send(self, message: Str) ! ServerError := - if self.is_connected then self._last_message := message - else ! ServerError("Not connected!") - - def disconnect(self) := self.is_connected := False + def reset(self) := [ + self.a := 1 + self.b := 0 + self.c := 0 + self.d := 1 + ] } ``` -Notice how `self` is not mutable in `last_sent`, meaning we can only read variables, whereas in connect `self` is mutable, so we can change properties of `self`. +Notice how `self` is not mutable in `trace`, meaning we can only read variables, whereas in `scale`, `self` is mutable so we can change properties of `self`. In general, the notation of a class is: `class MyClass() := []` -Though the body is optional. +The body of the class is optional, i.e. one can create "just" a data class. As for constructor arguments: -- If they are prefixed with `def`, then they are immediately accessible (e.g. `my_server.ip_address`). +- If they are prefixed with `def`, then they are immediately accessible (e.g. `matrix.a`). - If they are **not** prefixed with `def`, then they are only constructor arguments. This means that they are a class-constant, a constant which is defined in the context of a class. This means that they may be used in any part of the class (body, functions, methods). @@ -247,27 +252,29 @@ As for constructor arguments: We can change the relevant parts of the above example to use a class constant: ```mamba -from ipaddress import IPv4Address - -class MyServer(IP_ADDRESS: IPv4Address) := { - # The above IP_ADDRESS is a constant defined within the context of this class - # The intial value of ip_address is the value we passed to the constructor, but it may change - def ip_address: IPv4Address := IP_ADDRESS - - def change_ip(self, new_address: IPv4Address) := [ - print("When we first created this server, the address was {IP_ADDRESS}") - print("In the meantime, our address is {self.ip_address}") - print("And now we will change our address to {new_address}") +class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { + # ORIGIN_X and ORIGIN_Y are constructor constants — they cannot be changed + # current x and y are mutable fields initialized from the constants + def x: Int := ORIGIN_X + def y: Int := ORIGIN_Y + + def move(self, dx: Int, dy: Int) := [ + self.x := self.x + dx + self.y := self.y + dy + ] - self.ip_address := new_address - # The following would result in a compilation error, we cannot assign to constants! - # IP_ADDRESS := new_address + def reset(self) := [ + self.x := ORIGIN_X + self.y := ORIGIN_Y ] + + def info(fin self) -> Str := + "Currently at ({self.x}, {self.y}), originally from ({ORIGIN_X}, {ORIGIN_Y})" } ``` -Last, we have `trait`s can also serve as interfaces Mamba. -These are similar to interfaces in Java and Kotlin, and similar to traits in Rust. +Last, we have `trait`s, which in Mamba are more fine-grained building blocks to describe the behaviour of instances. +These are similar to interfaces in Java and Kotlin, and almost identical to traits in Rust. In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. For instance, consider example with iterators (which briefly showcases language generics): @@ -362,37 +369,28 @@ else ``` In short, types allow us to specify the domain and co-domain of functions with regards to the type of input, say, `Int` or `Str`. -Lets expand our server example from above, and rewrite it slightly: +Lets expand our matrix example from above, and rewrite it slightly: ```mamba -from ipaddress import IPv4Address -import datetime +type InvertibleMatrix: Matrix when self.determinant() != 0.0 -type ConnMyServer: MyServer when self.is_connected -type DisConnMyServer: MyServer when not self.is_connected +class MatrixErr(def message: Str): Exception(message) -class ServerErr(def message: Str): Exception(message) - -class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) := { - def is_connected: Bool := False - # we can nest blocks to enforce that certain statements are executed in order - [ - def ip_addr: Str := "my address is {ip_address}" - def date: Str := "The current date is {datetime.datetime.now()}" - - def _welcome_message: Str := "Welcome! {date}, {ip_addr}" - def _last_message: Str? := welcome_message - ] +## Matrix, which now takes floats as argument +class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := { + def _last_op: Str? := None - def last_sent(self) -> Str ! ServerErr := - if self.last_message != None then self._last_message - else ! ServerError("No last message!") + def determinant(fin self) -> Float := self.a * self.d - self.b * self.c - def connect(self: DisConnMyServer) := self.is_connected := True + def inverse(self: InvertibleMatrix) -> Matrix := + def det := self.determinant() + self._last_op := "inverse" - def send(self: ConnMyServer, message: Str) := self._last_message := message + Matrix(self.d / det, -self.b / det, -self.c / det, self.a / det) - def disconnect(self: ConnMyServer) := self.is_connected := False + def last_op(fin self) -> Str ! MatrixErr := + if self._last_op != None then self._last_op + else ! MatrixErr("No operation performed") } ``` @@ -404,23 +402,17 @@ Each type effectively denotes another state that `self` can be in. For each type, we use `when` to show that it is a type refinement, which certain conditions. ```mamba -import ipaddress -from server import MyServer +def m := Matrix(1.0, 2.0, 3.0, 4.0) -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) - -# The default state of http_server is DisconnectedHTTPServer, so we don't need to check that here -http_server.connect() - -# We check the state -if my_server isa ConnMyServer then [ - # http_server is a Connected Server if the above is true - my_server.send("Hello World!") -] +if m isa InvertibleMatrix then + def m_inv := m.inverse() + print("Original matrix: {m}") + print("Inverse: {m_inv}") +else + print("Matrix is singular (not invertible).") -print("last message sent before disconnect: \"{my_server.last_sent}\".") -if my_server isa ConnectedMyServer then my_server.disconnect() +def last_op = m.last_op()! +print("Last operation was: {last_op}") ``` Type refinement allows, in the context of object oriented programming, thus allows us to also explicitly name the possible states of an object. @@ -433,7 +425,8 @@ In general, the goal of the compiler will become: - Detect when it becomes impossible to raise an exception, i.e. if it is impossible to break an invariant then we will never raise an exception. Overall, the goal of type refinement it to allow us to express in greater detail the expected behaviour of functions in a more concise manner. -It is similar to "design by contract", though it should hopefully also create a clearer mental model of domains and codomains of functions. +This is somewhat similar to "design by contract", though baked more into the language itself. +This should help us to express more clearly domains and codomains of functions. ### 🔒 Pure functions (🇻 0.4.1+) @@ -445,8 +438,11 @@ For use to be able to compare two instances, the instance must implement the `Eq By default, functions are not pure. When we mark a function `pure`, restrictions are enforced by the language: -- Read non-final properties of `self` (if this is a method). - This means that its output depends only on the direct input, and input to the constructor of the class. +- `self` **must** be final (if this is a method). + This means that it cannot mutate the values of self. + It should be noted that if we mutate self and call a method again, then the output might be different. + But, this makes sense! + Self is just another argument to the function, and by mutating the instance we call the same function again but with a different instance, conceptually speaking. - Call impure functions. Some additional rules hold for calling and assigning to passed arguments to uphold the pure property (meaning, no side-effects): @@ -506,11 +502,13 @@ def total pure fibbonaci(x: PosInt) -> Int := match x { } ``` -This would, with some substitution magic, give the following _call tree_: +This would, with some substitution magic, give the following _call tree_ (showing only the important parts): ``` fibbonaci(x) - / \ + | + + # addition operator + / \ fibbonaci(x - 1) fibbonaci(x - 2) ``` @@ -535,7 +533,7 @@ trait def StrictlyDecreases: Measure { } ``` -This avoids abuse of `decreases`. +This avoids abuse of `decreases` (i.e. one could write `def fin const decreases(self, other: Self) := True`). Instead, ordering is reduced to numeric ordering, which is verifiable and depends on the output of a pure function. It is for instance defined for the built-in primtive `Int`. @@ -560,7 +558,7 @@ def ConstOrdered for Int { } ``` -At compilation time, the compiler then runs this trait and verifies that this property holds. +At compilation time, the compiler then evaluates `measure` and `less_than` and verifies that these properties holds. _Note: this means that we need to evaluate code during compilation time!_ Only constant functions can be evaluated at compile time, see the section on constant functions below. @@ -577,16 +575,24 @@ But we can imagine that library writers might find these useful if they wish to ### Constant functions (🇻 0.5+) The above also highlights constant functions in the language, which is a necessary evil. -Evil because it does clash a bit with the more "math focussed" aspect of the language. +Evil because it does clash a bit with the more "math-focused" aspect of the language. Constant functions are functions which can be evaluated at compile time. These functions have two constraints: - These may not call non-constant functions (including total and pure functions). - A constant function is also pure. + +Additionally: + - A constant function is not enforced to be total, but it is recommended that it is! + This is because for the compiler to prove a function is constant, it must compile the application. + But since we are already compiling, that is not an option (unless we have a meta-compiler, but that would require a meta-meta compiler, which would require...). - We may well place additional constraints on constant functions in future. -The most important use-case of constant functions is for the total functions above. +**Essentially, the main reason for Mamba having constant functions is to serve as the logical bedrock for provable total functions**. +One other benefit is that compiled functions are evaluated at compile time and not runtime, potentially offering significant speed benefits. +This is useful when one wants to document how one derived a constant in the form of code, without re-calculating it each time at runtime. + A constant function is defined as `def const my_function() := ...`. Because the function `less_than` returns objects which implement the `ConstOrdered` trait, the `less_than` must necessarily also be a constant function: @@ -597,8 +603,7 @@ trait ConstOrdered[T] { } ``` -We differentiate this from the `Ordered` trait so that we don't enforce everyone must implement `less_than` as a constant function, which is rarely necessary. - +In this case, we differentiate this from the `Ordered` trait so that we don't enforce everyone must implement `less_than` as a constant function, which is rarely necessary. ### ⚠ Error handling @@ -609,41 +614,40 @@ Again, this represents a trade-off between elegancy of the type system and simpl Arguably it may be easier to just use Monads, similar to how Rust's solution. But, we are operating in a different domain, so that may be overly verbose for our purposes. -Lets continue with our Server example. -We modify the above script such that we don't check whether the server is connected or not. -In that case, we must handle the case where `my_server` throws a `ServerErr`: +Lets continue with our matrix example. +Before, we simply discarded the error by appending `!` to `last_op`. +Instead, we now handle the error on-site: ```mamba -import ipaddress -from server import MyServer +def m := Matrix(1.0, 2.0, 3.0, 4.0) -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) +if m isa InvertibleMatrix then + def inv := m.inverse() +else + print("Matrix is singular (not invertible).") -def message := "Hello World!" -my_server.send(message) ! { - err: ServerErr => print("Error while sending message: \"{message}\": {err}") +def last_op = m.last_op() ! { + err: MatrixErr(message) => [ + print("Error when getting last op: \"{message}\"") + "N/A" # optionally we can also return, but here we assign default value + ] } -if my_server isa ConnectedMyServer then my_server.disconnect() +print("Last operation was: {last_op}") ``` -In the above script, we will always print the error since we forgot to actually connect to the server. +In the above script, we will always print an error (gracefully) and assign some other value to `last_op`. Here we showcase how we try to handle errors on-site instead of in a (large) `try` block. -This means that we don't need a `finally` block: -We aim to deal with the error where it happens and then continue executing the remaining code. This also prevents us from wrapping large code blocks in a `try`, where it might not be clear what statement or expression might throw what error. -`my_server.send(message) ! { ... }` is syntax sugar for +`m.last_op() ! { ... }` is syntax sugar for ```mamba -match my_server.send(message) { - err: Exception(ServerErr) => print("Error while sending message: \"{message}\": {err}") +match m.last_op() { + err: MatrixErr(message) => print("Error when getting last op: \"{message}\"") } ``` -The `{...}` after `!` is also not necessary if we only match on one exception. - So esentially, we add `!` as a way to shorthand match on exceptions. Currently, we allow both notations, but this comes at the cost of there not being "one way" to handle exceptions. This can lead to similar problems like with Scala where we have multiple ways to do the same thing. diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 3d2b5c7e..beea9d92 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -14,12 +14,11 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). file ::= { expr-or-stmt } import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] - type-def ::= "type" type ":" type ( ":=" "{" code-block "}" | "when" [ conditions ] ) - trait-def ::= "trait" type ( ":" type { "," type } ) ":=" ( expression-or-statement | "{" code-block "}" ) - conditions ::= "{" expression { newline expression } "}" | expression + type-def ::= "type" type ":" type ( ":=" "{" code-set "}" | "when" [ code-set ] ) + trait-def ::= "trait" type ( ":" type { "," type } ) [ ":=" code-set ] type-tuple ::= "(" [ type ] { "," type } ")" - class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] ( ":=" code-block ) + class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] [ ":=" code-set ] generics ::= "[" id { "," id } "]" id ::= { character } @@ -28,16 +27,14 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). type ::= ( id [ generics ] | type-tuple ) [ "->" type ] type-tuple ::= "(" [ type { "," type } ] ")" - expr-or-stmt ::= ( statement | expression ) [ comment ] + expr-or-stmt ::= ( statement | expression ) statement ::= control-flow-stmt | definition | reassignment | type-def - | "retry" | "pass" | class | type-def - | comment | import expression ::= "(" expression ")" | expression "?or" expression @@ -52,35 +49,27 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). | call | "_" - reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" | ">>=" | "<<=" ) expression - call ::= expression [ ( "." | "?." ) ] id tuple - [ "!" ( match-case | newline "{" match-cases "}" ) [ recover ( expr-or-stmt | "{" expr-or-statement { newline expr-or-statement } "}" ) ] ] + reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" | ">>=" | "<<=" ) code-block + call ::= code-block [ ( "." | "?." ) ] id tuple [ "!" match-cases [ recover code-block ] ] raise ::= "!" id { "," id } collection ::= tuple | set | list | map - tuple ::= "(" { expression } ")" - set ::= "{" { expression } "}" | set-builder + tuple ::= "(" code-block { "," code-block } ")" + set ::= "{" code-block { "," code-block } "}" | set-builder set-builder ::= "{" expression "|" expression { "," expression } "}" - list ::= "[" { expression } "]" | list-builder + list ::= "[" code-block { "," code-block } "]" | list-builder list-builder ::= "[" expression "|" expression { "," expression } "]" - slice ::= expression ( "::" | "::=" ) expression - range ::= expression ( ".." | "..=" ) expression + slice ::= code-block ( "::" | "::=" ) code-block + range ::= code-block ( ".." | "..=" ) code-block - definition ::= "def" ( variable-def | fun-def | operator-def ) | special-def - special-def ::= type-def | trait-def | class-def + definition ::= "def" ( variable-def | fun-def ) | type-def | trait-def | class-def - variable-def ::= [ "fin" ] ( id-maybe-type | collection ) [ ":=" expression ] [ forward ] - operator-def ::= [ "pure" ] overridable-op [ "(" [ id-maybe-type ] ")" ] "->" type - [ ":=" ( expr-or-stmt | code-block ) ] - - fun-def ::= ( [ "const" ] | [ "total" ] [ "pure" ] ) id fun-args [ "->" type ] [ raise ] - [ ":=" ( expr-or-stmt | code-block ) ] + variable-def ::= [ "fin" ] ( id-maybe-type | collection ) [ ":=" code-block ] [ forward ] + fun-def ::= ( [ "const" ] | [ "total" ] [ "pure" ] ) ( id | overridable-op ) fun-args [ "->" type ] [ raise ] [ ":=" code-block ] fun-args ::= "(" [ fun-arg ] { "," fun-arg } ")" - fun-arg ::= id-maybe-type [ ":=" expression ] - forward ::= "forward" id { "," id } - - anon-fun ::= "\" [ id-maybe-type { "," id-maybe-type } ] ":=" expression + fun-arg ::= id-maybe-type [ ":=" code-block ] + anon-fun ::= "\" [ id-maybe-type { "," id-maybe-type } ] ":=" code-block operation ::= relation [ ( equality | instance-eq | boolean-logic ) relation ] relation ::= arithmetic [ comparison relation ] @@ -106,24 +95,24 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). e-notation ::= ( integer | real ) "E" [ "-" ] integer string ::= """ { character } """ - code-block ::= "[" expr-or-statement "]" - | expr-or-stmt - | "[" newline expr-or-statement { newline expr-or-statement } "]" - one-or-more-expr ::= expression { "," expression } + code-block ::= expr-or-stmt + | "[" expr-or-stmt "]" + | "[" newline expr-or-stmt { newline expr-or-stmt } "]" + code-set ::= expr-or-stmt + | "{" expr-or-stmt "}" + | "{" newline expr-or-stmt { newline expr-or-stmt } "}" control-flow-expr::= if | match - if ::= "if" one-or-more-expr "then" code-block [ "else" code-block ] - match ::= "match" one-or-more-expr "with" newline match-cases - match-cases ::= match-case | "{" match-case { newline match-case } "}" - match-case ::= expression "=>" expr-or-stmt + if ::= "if" code-block "then" code-block [ "else" code-block ] + match ::= "match" code-block "with" match-cases + match-cases ::= "{" match-case "}" | "{" newline match-case { newline match-case } "}" + match-case ::= expression "=>" code-block control-flow-stmt::= while | foreach | "break" | "continue" - while ::= "while" one-or-more-expr "do" code-block - foreach ::= "for" one-or-more-expr "in" expression "do" code-block + while ::= "while" code-block "do" code-block + foreach ::= "for" code-block "in" code-block "do" code-block - newline ::= newline-char - newline-char ::= \n | \r\n - comment ::= "#" { character } newline + newline ::= ``` ## Notes From 39535f29624fa71bc9a235a701de9574826b71f6 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Thu, 10 Jul 2025 13:52:28 +0200 Subject: [PATCH 16/44] doc: expand interweave total recursive calls --- README.md | 60 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 54f976be..d0578481 100644 --- a/README.md +++ b/README.md @@ -484,6 +484,13 @@ Instead, we place heavy restrictions on total functions, enforcing that they are 1. We may only call total functions 2. Within the _call tree_ of a function, all arguments to nodes in the tree must be _strictly decreasing_ compared to the first parent of a node which is equal to said node. + + a. If in the _call tree_ we call a different total function, the argument does not have to be strictly decreasing. + b. However, it should still be globally decreasing, meaning that we amend the above: + _"compared to the first parent of the node which is equal to said node, summing over all intermediate nodes" + This does mean that we must be able to perform basic arithmetic on the types of the function for this (logic) system to work! + **In some sense, basic (integer) arithmetic forms the logical bedrock of our system** + 3. Potentially non-terminating loops, which includes `while`, are not allowed 4. For loops may only be called over collections which implement `SizedIterator`, which is also implemented by the built-in: - `RangeToInclusive` : `..=b` @@ -491,6 +498,8 @@ Instead, we place heavy restrictions on total functions, enforcing that they are - `Range` : `a..b` - `RangeInclusive` : `a..=b` +Put another way, we sidestep the issue by ensuring that our system is still sound, but incomplete by acknoweldging that we cannot prove termination for arbitary functions! + Take for instance this naive implementation of the Fibbonaci sequence: ```mamba @@ -518,18 +527,18 @@ This allows us to call other recursive functions without having to strictly decr We provide the `StrictlyDecreases` trait so users can define if something is strictly decreasing. The compiler enforces that this is defined for each argument. -However, this is ripe for abuse, so instead, we require that each argument implements the trait `Measure`. +However, this is ripe for abuse, so instead, we require that each argument implements the trait `Measurable`. ```mamba -trait Measure { - # returns a value which must be ordered, i.e. Int - def const measure(self) -> ConstOrdered -} - -trait def StrictlyDecreases: Measure { - # if we implement strictly decreasing, we must implement measure - # decreases has a non-overridable method which uses this measure - def fin const decreases(self, other: Self) -> self.measure() < other.measure() +# if we implement strictly decreasing, we must implement measure +# These are non-overridable method which uses this measure +trait def StrictlyDecreases: Measurable { + def fin const decreases(self, other: Self) -> Bool := self.measure() < other.measure() + def fin const =(self, other: Self) -> Bool := self.measure() = other.measure() + def fin const difference(self, other: Self) -> Measurable := self.measure() - other.measure() + + # this we must implement + def const measure(self) -> Measurable } ``` @@ -538,27 +547,40 @@ Instead, ordering is reduced to numeric ordering, which is verifiable and depend It is for instance defined for the built-in primtive `Int`. ```mamba -def Measure for Int { - # measure for int just returns self - def const measure(self) -> ConstOrdered := self +# Measure for int just returns self +def StrictlyDecreases for Int { + def const measure(self) -> Measurable := self } -# This is an example for how we would define it for string. -# We also show that implementing the StrictlyDecreases trait just results in us only havign to implement measure. +# For string, we as an example use the length of the string (Which is also an integer) def StrictlyDecreases for Str { - def const measure(self) -> ConstOrdered := self.len() + def const measure(self) -> Measurable := self.len() } ``` -Both of the above return an `Int`, which is part of the library and implements the `ConstOrdered` trait: +Both of the above return an `Int`, which is part of the library and implements the `Measured` trait: ```mamba -def ConstOrdered for Int { - def const less_than(self, other: Int) := self < other +# Trait measurable lives at the heart of this system, and by extension Mamba +trait Measurable: ConstAdd, ConstUnarySub, ConstEq { + def const less_than(self, other: Self) -> Bool + def const unary_sub(self, other: Self) -> Self + def const add(self, other: Self) -> Self + def const equal(self, other: Self) -> Bool +} + +# Built in to the standard library +# The idea is that this allows performing arithmetic not just at runtime but at compile-time. +def Measurable for Int { + def const less_than(self, other: Int) -> Bool := self < other + def const unary_sub(self) -> Int := -other + def const add(self, other: Int) -> Int := self + other + def const equal(self, other: Int) -> Bool := self = other } ``` At compilation time, the compiler then evaluates `measure` and `less_than` and verifies that these properties holds. +We require that the measured item implements basic arithmetic so that we can add and subtract as we traverse those trees where we interweave recursive calls. _Note: this means that we need to evaluate code during compilation time!_ Only constant functions can be evaluated at compile time, see the section on constant functions below. From a80a9ac17f2b2dcd6d0aa72591d21603b1ad3aa7 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 11 Jul 2025 09:47:00 +0200 Subject: [PATCH 17/44] doc: rewrite const to meta functions I think this terminology fits better what we are trying to achieve. --- README.md | 84 ++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index d0578481..614d2fa5 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,7 @@ class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { self.d := self.d * factor ] + # Reset turns this matrix into an 2x2 identity matrix, regardless of the intial value. def reset(self) := [ self.a := 1 self.b := 0 @@ -263,6 +264,7 @@ class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { self.y := self.y + dy ] + # Unlike the matrix before, reset resets this point to the value it was when it was instantiated. def reset(self) := [ self.x := ORIGIN_X self.y := ORIGIN_Y @@ -471,7 +473,7 @@ def pure sin(x: Int) -> Int := [ ] ``` -### 🤚 Total functions (🇻 0.5+) +### 🤚 Total functions (🇻 x+) A function may also be total, which means: @@ -533,56 +535,54 @@ However, this is ripe for abuse, so instead, we require that each argument imple # if we implement strictly decreasing, we must implement measure # These are non-overridable method which uses this measure trait def StrictlyDecreases: Measurable { - def fin const decreases(self, other: Self) -> Bool := self.measure() < other.measure() - def fin const =(self, other: Self) -> Bool := self.measure() = other.measure() - def fin const difference(self, other: Self) -> Measurable := self.measure() - other.measure() + def fin meta decreases(self, other: Self) -> Bool := self.measure() < other.measure() + def fin meta equal(self, other: Self) -> Bool := self.measure() = other.measure() + def fin meta subtract(self, other: Self) -> Measurable := self.measure() - other.measure() # this we must implement - def const measure(self) -> Measurable + def meta measure(self) -> Measurable } ``` -This avoids abuse of `decreases` (i.e. one could write `def fin const decreases(self, other: Self) := True`). +This avoids abuse of `decreases` (i.e. one could write `def fin meta decreases(self, other: Self) := True`). Instead, ordering is reduced to numeric ordering, which is verifiable and depends on the output of a pure function. It is for instance defined for the built-in primtive `Int`. ```mamba # Measure for int just returns self def StrictlyDecreases for Int { - def const measure(self) -> Measurable := self + def meta measure(self) -> Measurable := self } # For string, we as an example use the length of the string (Which is also an integer) def StrictlyDecreases for Str { - def const measure(self) -> Measurable := self.len() + def meta measure(self) -> Measurable := self.len() } ``` -Both of the above return an `Int`, which is part of the library and implements the `Measured` trait: +Both of the above return an `Int`, which is part of the library and implements the `Measured` trait. +This is a special built-in trait of the language, which as of writing cannot be implemented for custom types. +This is because this forms the logical bedrock of our system of proving that functions are total, but in future we may relax this constraint. ```mamba -# Trait measurable lives at the heart of this system, and by extension Mamba -trait Measurable: ConstAdd, ConstUnarySub, ConstEq { - def const less_than(self, other: Self) -> Bool - def const unary_sub(self, other: Self) -> Self - def const add(self, other: Self) -> Self - def const equal(self, other: Self) -> Bool -} +# Trait measurable lives at the heart of this system, and by extension Mamba. +# If a trait is marked as meta, then all functions within must be meta. +@builtin +meta trait Measurable: Add, Sub, Eq, Comparable # Built in to the standard library # The idea is that this allows performing arithmetic not just at runtime but at compile-time. def Measurable for Int { def const less_than(self, other: Int) -> Bool := self < other def const unary_sub(self) -> Int := -other - def const add(self, other: Int) -> Int := self + other - def const equal(self, other: Int) -> Bool := self = other + def const add(self, other: Int) -> Int := self + other + def const equal(self, other: Int) -> Bool := self = other } ``` -At compilation time, the compiler then evaluates `measure` and `less_than` and verifies that these properties holds. We require that the measured item implements basic arithmetic so that we can add and subtract as we traverse those trees where we interweave recursive calls. -_Note: this means that we need to evaluate code during compilation time!_ -Only constant functions can be evaluated at compile time, see the section on constant functions below. +_Paeno arithmetic, essentially, forms the logical bedrock of the system which proves functions are total._ +Only meta functions can be evaluated at compile time, see the section on meta functions below. In general: @@ -594,38 +594,34 @@ One does not imply the other, so you need both keywords if you want to say a fun The intended use-case is a bit more niche, likely mostly functions in the standard library, to show that they halt on all possible inputs. But we can imagine that library writers might find these useful if they wish to be more thorough. -### Constant functions (🇻 0.5+) +### Meta functions (🇻 x+) -The above also highlights constant functions in the language, which is a necessary evil. -Evil because it does clash a bit with the more "math-focused" aspect of the language. -Constant functions are functions which can be evaluated at compile time. +The above also highlights meta functions in the language, which is a necessary evil. +Meta functions are functions which can be evaluated at compile time. +This is somewhat similar to macro's in say C++ (or Rust, whose implementation is arguably far superior). +However, the goal of meta functions and traits is to prove properties of variables at compile time. These functions have two constraints: -- These may not call non-constant functions (including total and pure functions). -- A constant function is also pure. +- These may not call non-meta functions (including total and pure functions) or values. +- A meta function is also pure; they have no side-effects. + As this is always implied, we omit the need for the `pure` keyword. Additionally: -- A constant function is not enforced to be total, but it is recommended that it is! - This is because for the compiler to prove a function is constant, it must compile the application. - But since we are already compiling, that is not an option (unless we have a meta-compiler, but that would require a meta-meta compiler, which would require...). -- We may well place additional constraints on constant functions in future. +- A meta function is not enforced to be total, but it is recommended that it is! + This is because for the compiler to prove a function is meta, it must compile the application first. + Thus we have a circular dependency; + We are already compiling, so this is not an option (unless we have a meta-compiler, but that would require a meta-meta compiler, and so forth...). +- We may well place additional constraints on meta functions in future. -**Essentially, the main reason for Mamba having constant functions is to serve as the logical bedrock for provable total functions**. +**Essentially, the main reason for Mamba having meta functions is to serve as the logical bedrock for provable total functions**. One other benefit is that compiled functions are evaluated at compile time and not runtime, potentially offering significant speed benefits. -This is useful when one wants to document how one derived a constant in the form of code, without re-calculating it each time at runtime. - -A constant function is defined as `def const my_function() := ...`. - -Because the function `less_than` returns objects which implement the `ConstOrdered` trait, the `less_than` must necessarily also be a constant function: - -```mamba -trait ConstOrdered[T] { - def const less_than(self, other: T) -> Bool -} -``` +This is useful when one wants to document how one derived a meta in the form of code, without re-calculating it each time at runtime. -In this case, we differentiate this from the `Ordered` trait so that we don't enforce everyone must implement `less_than` as a constant function, which is rarely necessary. +- A meta function is defined as `def meta my_function() := ...`. +- A meta variable is defined `def meta my_var: MyType := ...`, with type annotations being non-optional. +- A meta trait is defined as `meta trait MyTrait ...`. + Within a meta trait, all definitions are also meta. ### ⚠ Error handling From bdd658826a31a3854f57ef05ab1080df542d3608 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 11 Jul 2025 10:21:21 +0200 Subject: [PATCH 18/44] test: add tests for README examples These tests are ignored, the intention is that: 1) We can parse all of the examples in the README 2) We can type check a subset of the examples in README. Examples which we cannot check should throw check, not parse error. --- README.md | 18 +++++----- tests/check/valid.rs | 35 ++++++++++++++----- tests/parse/valid.rs | 26 ++++++++++++++ .../valid/readme_example/builtin_trait.mamba | 13 +++++++ .../resource/valid/readme_example/class.mamba | 27 ++++++++++++++ .../readme_example/class_with_constants.mamba | 20 +++++++++++ .../valid/readme_example/error_handling.mamba | 27 +++++++------- .../error_handling_as_expression.mamba | 12 +++++++ .../readme_example/error_handling_check.py | 18 ---------- .../error_handling_desyntax_sugared.mamba | 3 ++ .../error_handling_early_exit.mamba | 3 ++ .../error_handling_handle_subset.mamba | 10 ++++++ .../error_handling_recover.mamba | 6 ++++ .../valid/readme_example/factorial.mamba | 8 +++-- .../valid/readme_example/factorial_check.py | 14 -------- .../readme_example/factorial_dynamic.mamba | 6 ++-- .../readme_example/factorial_dynamic_check.py | 9 ----- .../valid/readme_example/handle.mamba | 17 --------- .../valid/readme_example/handle_check.py | 31 ---------------- .../valid/readme_example/impl_trait.mamba | 9 +++++ .../valid/readme_example/list_shorthand.mamba | 2 ++ .../resource/valid/readme_example/lists.mamba | 8 +++++ .../valid/readme_example/mutability.mamba | 5 +++ .../valid/readme_example/pos_int.mamba | 6 ---- .../valid/readme_example/pure_functions.mamba | 5 +-- .../valid/readme_example/server_class.mamba | 24 ------------- .../readme_example/server_class_check.py | 30 ---------------- .../valid/readme_example/sets_maps.mamba | 14 ++++++++ .../readme_example/total_functions.mamba | 6 ++++ .../valid/readme_example/trait_fin_meta.mamba | 10 ++++++ .../readme_example/trait_inheritance.mamba | 3 ++ .../valid/readme_example/traits.mamba | 18 ++++++++++ .../readme_example/type_refinement.mamba | 27 -------------- .../type_refinement_call_site.mamba | 11 ++++++ .../type_refinement_in_fun.mamba | 9 +++++ .../type_refinement_matrix.mamba | 20 +++++++++++ .../type_refinement_on_matrix.mamba | 11 ++++++ .../readme_example/type_refinement_set.mamba | 4 +++ .../readme_example/type_refinement_use.mamba | 16 --------- .../valid/readme_example/use_server.mamba | 16 --------- .../valid/readme_example/use_server_check.py | 23 ------------ 41 files changed, 310 insertions(+), 270 deletions(-) create mode 100644 tests/resource/valid/readme_example/builtin_trait.mamba create mode 100644 tests/resource/valid/readme_example/class.mamba create mode 100644 tests/resource/valid/readme_example/class_with_constants.mamba create mode 100644 tests/resource/valid/readme_example/error_handling_as_expression.mamba delete mode 100644 tests/resource/valid/readme_example/error_handling_check.py create mode 100644 tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba create mode 100644 tests/resource/valid/readme_example/error_handling_early_exit.mamba create mode 100644 tests/resource/valid/readme_example/error_handling_handle_subset.mamba create mode 100644 tests/resource/valid/readme_example/error_handling_recover.mamba delete mode 100644 tests/resource/valid/readme_example/factorial_check.py delete mode 100644 tests/resource/valid/readme_example/factorial_dynamic_check.py delete mode 100644 tests/resource/valid/readme_example/handle.mamba delete mode 100644 tests/resource/valid/readme_example/handle_check.py create mode 100644 tests/resource/valid/readme_example/impl_trait.mamba create mode 100644 tests/resource/valid/readme_example/list_shorthand.mamba create mode 100644 tests/resource/valid/readme_example/lists.mamba create mode 100644 tests/resource/valid/readme_example/mutability.mamba delete mode 100644 tests/resource/valid/readme_example/pos_int.mamba delete mode 100644 tests/resource/valid/readme_example/server_class.mamba delete mode 100644 tests/resource/valid/readme_example/server_class_check.py create mode 100644 tests/resource/valid/readme_example/sets_maps.mamba create mode 100644 tests/resource/valid/readme_example/total_functions.mamba create mode 100644 tests/resource/valid/readme_example/trait_fin_meta.mamba create mode 100644 tests/resource/valid/readme_example/trait_inheritance.mamba create mode 100644 tests/resource/valid/readme_example/traits.mamba delete mode 100644 tests/resource/valid/readme_example/type_refinement.mamba create mode 100644 tests/resource/valid/readme_example/type_refinement_call_site.mamba create mode 100644 tests/resource/valid/readme_example/type_refinement_in_fun.mamba create mode 100644 tests/resource/valid/readme_example/type_refinement_matrix.mamba create mode 100644 tests/resource/valid/readme_example/type_refinement_on_matrix.mamba create mode 100644 tests/resource/valid/readme_example/type_refinement_set.mamba delete mode 100644 tests/resource/valid/readme_example/type_refinement_use.mamba delete mode 100644 tests/resource/valid/readme_example/use_server.mamba delete mode 100644 tests/resource/valid/readme_example/use_server_check.py diff --git a/README.md b/README.md index 614d2fa5..7f38d4e1 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Below are some code examples to showcase the features of Mamba. We can write a simple script that computes the factorial of a value given by the user. ```mamba -## Factorial of x +# Factorial of x def factorial(x: Int) -> Int := match x { 0 => 1 n => n * factorial(n - 1) @@ -128,7 +128,7 @@ Lists make use of square brackets: # lists def a := [0, 2, 51] def b := ["list", "of", "strings"] -# lists of tuples, buidler syntax +# lists of tuples, builder syntax def ab := [(x, y) | x in a, x > 0, y in b, b != "of" ] # Indexing is done using curly brackets! @@ -572,12 +572,14 @@ meta trait Measurable: Add, Sub, Eq, Comparable # Built in to the standard library # The idea is that this allows performing arithmetic not just at runtime but at compile-time. -def Measurable for Int { - def const less_than(self, other: Int) -> Bool := self < other - def const unary_sub(self) -> Int := -other - def const add(self, other: Int) -> Int := self + other - def const equal(self, other: Int) -> Bool := self = other -} +def Measurable for Int +# The following is already defined for Int, but for the sake of our example: +# { +# def meta less_than(self, other: Int) -> Bool := self < other +# def meta unary_sub(self) -> Int := -other +# def meta add(self, other: Int) -> Int := self + other +# def meta equal(self, other: Int) -> Bool := self = other +# } ``` We require that the measured item implements basic arithmetic so that we can add and subtract as we traverse those trees where we interweave recursive calls. diff --git a/tests/check/valid.rs b/tests/check/valid.rs index d390423a..a0762c70 100644 --- a/tests/check/valid.rs +++ b/tests/check/valid.rs @@ -123,15 +123,32 @@ use test_case::test_case; #[test_case("operation", "boolean")] #[test_case("operation", "equality_different_types")] #[test_case("operation", "type_alias_primitive" => ignore["investigate whether this should in fact, pass"])] -#[test_case("readme_example", "error_handling" => ignore["mileston 0.5"])] -#[test_case("readme_example", "factorial")] -#[test_case("readme_example", "factorial_dynamic")] -#[test_case("readme_example", "handle")] -#[test_case("readme_example", "pos_int" => ignore["mileston 0.6 (type refinement)"])] -#[test_case("readme_example", "pure_functions" => ignore["mileston 0.4.1"])] -#[test_case("readme_example", "server_class")] -#[test_case("readme_example", "type_refinement" => ignore["mileston 0.6"])] -#[test_case("readme_example", "use_server" => ignore["milestone 0.5"])] +#[test_case("reamde_example", "builtin_trait" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "class_with_constants" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "class" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "error_handling_as_expression" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "error_handling_desyntax_sugared" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "error_handling_early_exit" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "error_handling_handle_subset" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "error_handling_recover" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "error_handling" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "factorial_dynamic" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "factorial" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "impl_trait" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "list_shorthand" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "lists" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "mutability" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "pure_functions" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "sets_maps" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "total_functions" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "trait_fin_meta" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "trait_inheritance" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "traits" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "type_refinement_call_site" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "type_refinement_in_fun" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "type_refinement_matrix" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "type_refinement_on_matrix" => ignore["incorporate new AST in checker"])] +#[test_case("reamde_example", "type_refinement_set" => ignore["incorporate new AST in checker"])] fn to_python(input_dir: &str, file_name: &str) -> OutTestRet { tests_util::test_directory(true, &[input_dir], &[input_dir, "target"], file_name) } diff --git a/tests/parse/valid.rs b/tests/parse/valid.rs index d5d56726..7cb3dd3f 100644 --- a/tests/parse/valid.rs +++ b/tests/parse/valid.rs @@ -21,6 +21,32 @@ use mamba::parse::result::ParseResult; #[test_case("collection", "tuple")] #[test_case("class", "types")] #[test_case("class", "import")] +#[test_case("reamde_example", "builtin_trait" => ignore["rewrite parser"])] +#[test_case("reamde_example", "class_with_constants" => ignore["rewrite parser"])] +#[test_case("reamde_example", "class" => ignore["rewrite parser"])] +#[test_case("reamde_example", "error_handling_as_expression" => ignore["rewrite parser"])] +#[test_case("reamde_example", "error_handling_desyntax_sugared" => ignore["rewrite parser"])] +#[test_case("reamde_example", "error_handling_early_exit" => ignore["rewrite parser"])] +#[test_case("reamde_example", "error_handling_handle_subset" => ignore["rewrite parser"])] +#[test_case("reamde_example", "error_handling_recover" => ignore["rewrite parser"])] +#[test_case("reamde_example", "error_handling" => ignore["rewrite parser"])] +#[test_case("reamde_example", "factorial_dynamic" => ignore["rewrite parser"])] +#[test_case("reamde_example", "factorial" => ignore["rewrite parser"])] +#[test_case("reamde_example", "impl_trait" => ignore["rewrite parser"])] +#[test_case("reamde_example", "list_shorthand" => ignore["rewrite parser"])] +#[test_case("reamde_example", "lists" => ignore["rewrite parser"])] +#[test_case("reamde_example", "mutability" => ignore["rewrite parser"])] +#[test_case("reamde_example", "pure_functions" => ignore["rewrite parser"])] +#[test_case("reamde_example", "sets_maps" => ignore["rewrite parser"])] +#[test_case("reamde_example", "total_functions" => ignore["rewrite parser"])] +#[test_case("reamde_example", "trait_fin_meta" => ignore["rewrite parser"])] +#[test_case("reamde_example", "trait_inheritance" => ignore["rewrite parser"])] +#[test_case("reamde_example", "traits" => ignore["rewrite parser"])] +#[test_case("reamde_example", "type_refinement_call_site" => ignore["rewrite parser"])] +#[test_case("reamde_example", "type_refinement_in_fun" => ignore["rewrite parser"])] +#[test_case("reamde_example", "type_refinement_matrix" => ignore["rewrite parser"])] +#[test_case("reamde_example", "type_refinement_on_matrix" => ignore["rewrite parser"])] +#[test_case("reamde_example", "type_refinement_set" => ignore["rewrite parser"])] fn syntax(input_dir: &str, file_name: &str) -> ParseResult<()> { let file_name = format!("{file_name}.mamba"); let source = resource_content(true, &[input_dir], &file_name).unwrap(); diff --git a/tests/resource/valid/readme_example/builtin_trait.mamba b/tests/resource/valid/readme_example/builtin_trait.mamba new file mode 100644 index 00000000..c7f37b3b --- /dev/null +++ b/tests/resource/valid/readme_example/builtin_trait.mamba @@ -0,0 +1,13 @@ +# Trait measurable lives at the heart of this system, and by extension Mamba. +# If a trait is marked as meta, then all functions within must be meta. +@builtin +meta trait Measurable: Add, Sub, Eq, Comparable + +# Built in to the standard library +# The idea is that this allows performing arithmetic not just at runtime but at compile-time. +def Measurable for Int { + def const less_than(self, other: Int) -> Bool := self < other + def const unary_sub(self) -> Int := -other + def const add(self, other: Int) -> Int := self + other + def const equal(self, other: Int) -> Bool := self = other +} diff --git a/tests/resource/valid/readme_example/class.mamba b/tests/resource/valid/readme_example/class.mamba new file mode 100644 index 00000000..310e376e --- /dev/null +++ b/tests/resource/valid/readme_example/class.mamba @@ -0,0 +1,27 @@ +class MatrixErr(def message: Str): Exception(message) + +class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { + # Accessor for matrix contents + def contents(fin self) -> List[Int] := [a, b, c, d] + + # Trace of the matrix (a + d) + def pure trace(fin self) -> Int := a + d + + # Determinant recomputation (pure function) + def pure determinant(fin self) -> Int := a * d - b * c + + def scale(self, factor: Int) := [ + self.a := self.a * factor + self.b := self.b * factor + self.c := self.c * factor + self.d := self.d * factor + ] + + # Reset turns this matrix into an 2x2 identity matrix, regardless of the intial value. + def reset(self) := [ + self.a := 1 + self.b := 0 + self.c := 0 + self.d := 1 + ] +} diff --git a/tests/resource/valid/readme_example/class_with_constants.mamba b/tests/resource/valid/readme_example/class_with_constants.mamba new file mode 100644 index 00000000..c4f6be44 --- /dev/null +++ b/tests/resource/valid/readme_example/class_with_constants.mamba @@ -0,0 +1,20 @@ +class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { + # ORIGIN_X and ORIGIN_Y are constructor constants — they cannot be changed + # current x and y are mutable fields initialized from the constants + def x: Int := ORIGIN_X + def y: Int := ORIGIN_Y + + def move(self, dx: Int, dy: Int) := [ + self.x := self.x + dx + self.y := self.y + dy + ] + + # Unlike the matrix before, reset resets this point to the value it was when it was instantiated. + def reset(self) := [ + self.x := ORIGIN_X + self.y := ORIGIN_Y + ] + + def info(fin self) -> Str := + "Currently at ({self.x}, {self.y}), originally from ({ORIGIN_X}, {ORIGIN_Y})" +} diff --git a/tests/resource/valid/readme_example/error_handling.mamba b/tests/resource/valid/readme_example/error_handling.mamba index a7b6f28d..5b3c7216 100644 --- a/tests/resource/valid/readme_example/error_handling.mamba +++ b/tests/resource/valid/readme_example/error_handling.mamba @@ -1,18 +1,15 @@ -from ipaddress import IPv4Address +def m := Matrix(1.0, 2.0, 3.0, 4.0) -class ServerErr: Exception -class MyServer(def ip_address: IPv4Address): - def send(self, message: Str) := - pass - def disconnect() := pass +if m isa InvertibleMatrix then + def inv := m.inverse() +else + print("Matrix is singular (not invertible).") -class ConnectedMyServer: MyServer +def last_op = m.last_op() ! { + err: MatrixErr(message) => [ + print("Error when getting last op: \"{message}\"") + "N/A" # optionally we can also return, but here we assign default value + ] +} -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) - -def message := "Hello World!" -my_server.send(message) handle - err: ServerErr => print("Error while sending message: \"{message}\": {err}") - -if my_server isa ConnectedMyServer then my_server.disconnect() +print("Last operation was: {last_op}") diff --git a/tests/resource/valid/readme_example/error_handling_as_expression.mamba b/tests/resource/valid/readme_example/error_handling_as_expression.mamba new file mode 100644 index 00000000..baef47ef --- /dev/null +++ b/tests/resource/valid/readme_example/error_handling_as_expression.mamba @@ -0,0 +1,12 @@ +def a: Int := function_may_throw_err() ! { + err: MyErr => [ + print("We have a problem: {err.message}.") + return # we return, halting execution + ] + err: MyOtherErr => [ + print("We have another problem: {err.message}.") + 0 # ... or we assign default value 0 to a + ] +} + +print("a has value {a}.") diff --git a/tests/resource/valid/readme_example/error_handling_check.py b/tests/resource/valid/readme_example/error_handling_check.py deleted file mode 100644 index 755ed000..00000000 --- a/tests/resource/valid/readme_example/error_handling_check.py +++ /dev/null @@ -1,18 +0,0 @@ -from ipaddress import IPv4Address -from server import MyServer - -class MyServer: - def __init__(self, ip_address: IPv4Address): - self.ip_address = ip_address - -fin some_ip = ipaddress.ip_address("151.101.193.140") -my_server = MyServer(some_ip) - -def message = "Hello World!" -try: - my_server.send(message) -except ServerErr as err: - print(f"Error while sending message: \"{message}\": {err}") - -if isinstance(my_server, ConnectedMyServer): - my_server.disconnect() diff --git a/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba b/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba new file mode 100644 index 00000000..f1379e94 --- /dev/null +++ b/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba @@ -0,0 +1,3 @@ +match m.last_op() { + err: MatrixErr(message) => print("Error when getting last op: \"{message}\"") +} diff --git a/tests/resource/valid/readme_example/error_handling_early_exit.mamba b/tests/resource/valid/readme_example/error_handling_early_exit.mamba new file mode 100644 index 00000000..cc15d783 --- /dev/null +++ b/tests/resource/valid/readme_example/error_handling_early_exit.mamba @@ -0,0 +1,3 @@ +def a := function_may_throw_err() ! +# if `function_may_throw_err` returned an exception, we will never reach this point +print("a has value {a}.") diff --git a/tests/resource/valid/readme_example/error_handling_handle_subset.mamba b/tests/resource/valid/readme_example/error_handling_handle_subset.mamba new file mode 100644 index 00000000..80f9b034 --- /dev/null +++ b/tests/resource/valid/readme_example/error_handling_handle_subset.mamba @@ -0,0 +1,10 @@ +def a: Result[Int, MyErr] := function_may_throw_err() ! { + err: MyOtherErr => [ + print("We have another problem: {err.message}.") + 0 # ... or we assign default value 0 to a + ] +} + +a = a ! # Result[Int, MyErr] => Int, where if error case, an exception is raised. + +print("a has value {a}.") diff --git a/tests/resource/valid/readme_example/error_handling_recover.mamba b/tests/resource/valid/readme_example/error_handling_recover.mamba new file mode 100644 index 00000000..50e0671a --- /dev/null +++ b/tests/resource/valid/readme_example/error_handling_recover.mamba @@ -0,0 +1,6 @@ +def a: Result[Int, MyErr] := function_may_throw_err() ! { + err: MyOtherErr => print("We have a problem: {err.message}.") +} recover [ + print("cleaning up resource") + some_cleanup_function() +] diff --git a/tests/resource/valid/readme_example/factorial.mamba b/tests/resource/valid/readme_example/factorial.mamba index d41f1f31..124a8bbf 100644 --- a/tests/resource/valid/readme_example/factorial.mamba +++ b/tests/resource/valid/readme_example/factorial.mamba @@ -1,10 +1,12 @@ -def factorial(x: Int) -> Int := match x +# Factorial of x +def factorial(x: Int) -> Int := match x { 0 => 1 n => n * factorial(n - 1) +} def num := input("Compute factorial: ") -if num.is_digit() then +if num.is_digit() then [ def result := factorial(Int(num)) print("Factorial {num} is: {result}.") -else +] else print("Input was not an integer.") diff --git a/tests/resource/valid/readme_example/factorial_check.py b/tests/resource/valid/readme_example/factorial_check.py deleted file mode 100644 index 2668207f..00000000 --- a/tests/resource/valid/readme_example/factorial_check.py +++ /dev/null @@ -1,14 +0,0 @@ -def factorial(x: int) -> int: - match x: - case 0: - return 1 - case n: - return n * factorial(n - 1) - -num: str = input("Compute factorial: ") - -if num.is_digit(): - result = factorial(int(num)) - print(f"Factorial {num} is: {result}.") -else: - print("Input was not an integer.") diff --git a/tests/resource/valid/readme_example/factorial_dynamic.mamba b/tests/resource/valid/readme_example/factorial_dynamic.mamba index d20a2449..bf9f6fee 100644 --- a/tests/resource/valid/readme_example/factorial_dynamic.mamba +++ b/tests/resource/valid/readme_example/factorial_dynamic.mamba @@ -1,6 +1,8 @@ -def factorial(x: Int) -> Int := match x +def factorial(x: Int) -> Int := match x { 0 => 1 - n => + n => [ def ans := 1 for i in 1 ..= n do ans := ans * i ans + ] +} diff --git a/tests/resource/valid/readme_example/factorial_dynamic_check.py b/tests/resource/valid/readme_example/factorial_dynamic_check.py deleted file mode 100644 index 178f5a9f..00000000 --- a/tests/resource/valid/readme_example/factorial_dynamic_check.py +++ /dev/null @@ -1,9 +0,0 @@ -def factorial(x: int) -> int: - match x: - case 0: - return 1 - case n: - ans = 1 - for i in range(1, n, 1): - ans = ans * i - return ans diff --git a/tests/resource/valid/readme_example/handle.mamba b/tests/resource/valid/readme_example/handle.mamba deleted file mode 100644 index 97bb7bce..00000000 --- a/tests/resource/valid/readme_example/handle.mamba +++ /dev/null @@ -1,17 +0,0 @@ -class MyErr(def message: Str): Exception -class MyOtherErr(def message: Str): Exception - -def function_may_throw_err() -> Int ! MyErr := 10 - -def g() := - def a := function_may_throw_err() - err: MyErr => - print("We have a problem: {err.message}.") - return # we return, halting execution - err: MyOtherErr => - print("We have another problem: {err.message}.") - 0 # ... or we assign default value 0 to a - - print("a has value {a}.") - -g() diff --git a/tests/resource/valid/readme_example/handle_check.py b/tests/resource/valid/readme_example/handle_check.py deleted file mode 100644 index cc13ad49..00000000 --- a/tests/resource/valid/readme_example/handle_check.py +++ /dev/null @@ -1,31 +0,0 @@ -class MyErr(Exception): - def __init__(self, message: str): - Exception.__init__(self) - self.message = message - - -class MyOtherErr(Exception): - def __init__(self, message: str): - Exception.__init__(self) - self.message = message - - -def function_may_throw_err() -> int: - return 10 - - -def g(): - a: int = None - try: - a: int = function_may_throw_err() - except MyErr as err: - print(f"We have a problem: {err.message}.") - return None - except MyOtherErr as err: - print(f"We have another problem: {err.message}.") - a = 0 - - print(f"a has value {a}.") - - -g() diff --git a/tests/resource/valid/readme_example/impl_trait.mamba b/tests/resource/valid/readme_example/impl_trait.mamba new file mode 100644 index 00000000..9c18769a --- /dev/null +++ b/tests/resource/valid/readme_example/impl_trait.mamba @@ -0,0 +1,9 @@ +# Measure for int just returns self +def StrictlyDecreases for Int { + def meta measure(self) -> Measurable := self +} + +# For string, we as an example use the length of the string (Which is also an integer) +def StrictlyDecreases for Str { + def meta measure(self) -> Measurable := self.len() +} diff --git a/tests/resource/valid/readme_example/list_shorthand.mamba b/tests/resource/valid/readme_example/list_shorthand.mamba new file mode 100644 index 00000000..85352d5d --- /dev/null +++ b/tests/resource/valid/readme_example/list_shorthand.mamba @@ -0,0 +1,2 @@ +def numbers := [32, 504, 59] +def numbers := { 0 => 32, 1 => 504, 2 => 59 } diff --git a/tests/resource/valid/readme_example/lists.mamba b/tests/resource/valid/readme_example/lists.mamba new file mode 100644 index 00000000..b29d3fbf --- /dev/null +++ b/tests/resource/valid/readme_example/lists.mamba @@ -0,0 +1,8 @@ +# lists +def a := [0, 2, 51] +def b := ["list", "of", "strings"] +# lists of tuples, buidler syntax +def ab := [(x, y) | x in a, x > 0, y in b, b != "of" ] + +# Indexing is done using curly brackets! +print(a(0)) # prints '0' diff --git a/tests/resource/valid/readme_example/mutability.mamba b/tests/resource/valid/readme_example/mutability.mamba new file mode 100644 index 00000000..668c376d --- /dev/null +++ b/tests/resource/valid/readme_example/mutability.mamba @@ -0,0 +1,5 @@ +def a := 10 # we may modify a +def fin b := 20 # we may not modify b + +a := a + 2 # allowed +# b := b + 2 # compilation error diff --git a/tests/resource/valid/readme_example/pos_int.mamba b/tests/resource/valid/readme_example/pos_int.mamba deleted file mode 100644 index 9623e05b..00000000 --- a/tests/resource/valid/readme_example/pos_int.mamba +++ /dev/null @@ -1,6 +0,0 @@ -type PosInt: Int when - self >= 0 else "Must be greater than 0" - -def factorial(x: PosInt) -> PosInt := match x - 0 => 1 - n => n * factorial(n - 1) diff --git a/tests/resource/valid/readme_example/pure_functions.mamba b/tests/resource/valid/readme_example/pure_functions.mamba index 7ff16553..296897f5 100644 --- a/tests/resource/valid/readme_example/pure_functions.mamba +++ b/tests/resource/valid/readme_example/pure_functions.mamba @@ -2,8 +2,9 @@ def fin taylor := 7 # the sin function is pure, its output depends solely on the input -def pure sin(x: Int) := +def pure sin(x: Int) -> Int := [ def ans := x - for i in 1 ..= taylor .. 2 do + for i in (1 ..= taylor).step(2) do ans := ans + (x ^ (i + 2)) / (factorial (i + 2)) ans +] diff --git a/tests/resource/valid/readme_example/server_class.mamba b/tests/resource/valid/readme_example/server_class.mamba deleted file mode 100644 index 3a1522fb..00000000 --- a/tests/resource/valid/readme_example/server_class.mamba +++ /dev/null @@ -1,24 +0,0 @@ -from ipaddress import IPv4Address - -class ServerError(def message: Str): Exception(message) - -def fin always_the_same_message := "Connected!" - -class MyServer(def ip_address: IPv4Address) - def is_connected: Bool := False - def _last_message: Str := "temp" - - def last_sent(fin self) -> Str ! ServerError := - self._last_message - - def connect(self) := - self.is_connected := True - print(always_the_same_message) - - def send(self, message: Str) ! ServerError := - if self.is_connected then - self._last_message := message - else - ! ServerError("Not connected!") - - def disconnect(self) := self.is_connected := False diff --git a/tests/resource/valid/readme_example/server_class_check.py b/tests/resource/valid/readme_example/server_class_check.py deleted file mode 100644 index 0b27be85..00000000 --- a/tests/resource/valid/readme_example/server_class_check.py +++ /dev/null @@ -1,30 +0,0 @@ -from ipaddress import IPv4Address - -class ServerError(Exception): - def __init__(self, message:str): - Exception.__init__(self, message) - -always_the_same_message: str = "Connected!" - -class MyServer: - is_connected: bool = False - _last_message: str = "temp" - - def __init__(self, ip_address: IPv4Address): - self.ip_address = ip_address - - def last_sent(self) -> str: - return self._last_message - - def connect(self): - self.is_connected = True - print(always_the_same_message) - - def send(self, message: str): - if self.is_connected: - self._last_message = message - else: - raise ServerError("Not connected!") - - def disconnect(self): - self.is_connected = False diff --git a/tests/resource/valid/readme_example/sets_maps.mamba b/tests/resource/valid/readme_example/sets_maps.mamba new file mode 100644 index 00000000..43334ec2 --- /dev/null +++ b/tests/resource/valid/readme_example/sets_maps.mamba @@ -0,0 +1,14 @@ +# sets +def c := { 10, 20 } +def d := { 3 } +# sets, builder syntax +def cd := { x ^ y | x in c, y in d } + +# maps +def e := { "do" => 1, "ree" => 2, "meee" => 3 } +# maps, builder syntax +def ef := { x => y - 2 | x in e, y = x.len() } + +# indexing works for lists and maps/mappings (sets cannot be indexed because these are unordered) +print(ab(2)) # prints '(2, "list")' +print(ef(1)) # prints '1' diff --git a/tests/resource/valid/readme_example/total_functions.mamba b/tests/resource/valid/readme_example/total_functions.mamba new file mode 100644 index 00000000..760f2a7c --- /dev/null +++ b/tests/resource/valid/readme_example/total_functions.mamba @@ -0,0 +1,6 @@ +## fibbonaci, implemented using recursion and not dynamic programming +def total pure fibbonaci(x: PosInt) -> Int := match x { + 0 => 0 + 1 => 1 + n => fibbonaci(n - 1) + fibbonaci(n - 2) +} diff --git a/tests/resource/valid/readme_example/trait_fin_meta.mamba b/tests/resource/valid/readme_example/trait_fin_meta.mamba new file mode 100644 index 00000000..19e9e60c --- /dev/null +++ b/tests/resource/valid/readme_example/trait_fin_meta.mamba @@ -0,0 +1,10 @@ +# if we implement strictly decreasing, we must implement measure +# These are non-overridable method which uses this measure +trait def StrictlyDecreases: Measurable { + def fin meta decreases(self, other: Self) -> Bool := self.measure() < other.measure() + def fin meta equal(self, other: Self) -> Bool := self.measure() = other.measure() + def fin meta subtract(self, other: Self) -> Measurable := self.measure() - other.measure() + + # this we must implement + def meta measure(self) -> Measurable +} diff --git a/tests/resource/valid/readme_example/trait_inheritance.mamba b/tests/resource/valid/readme_example/trait_inheritance.mamba new file mode 100644 index 00000000..5e077af5 --- /dev/null +++ b/tests/resource/valid/readme_example/trait_inheritance.mamba @@ -0,0 +1,3 @@ +trait Ordered[T]: Equality, Comparable { + def less_than(self, other: T) -> Bool +} diff --git a/tests/resource/valid/readme_example/traits.mamba b/tests/resource/valid/readme_example/traits.mamba new file mode 100644 index 00000000..01e4bbf1 --- /dev/null +++ b/tests/resource/valid/readme_example/traits.mamba @@ -0,0 +1,18 @@ +trait Iterator[T] := { + def has_next(self) -> Bool + def next(self) -> T? # syntax sugar for Option[T] +} + +class RangeIter(def _start: Int, def _end: Int) := { + def _current: Int := _start +} + +def Iterator[Int] for RangeIter := { + def has_next(self) -> Bool := self._current < self._stop + + def next(self) -> Int? := if self.has_next() then [ + def value := self._current + self._current := self._current + 1 + value + ] else None +} diff --git a/tests/resource/valid/readme_example/type_refinement.mamba b/tests/resource/valid/readme_example/type_refinement.mamba deleted file mode 100644 index b67f7fbc..00000000 --- a/tests/resource/valid/readme_example/type_refinement.mamba +++ /dev/null @@ -1,27 +0,0 @@ -from ipaddress import IPv4Address - -type Server - def ip_address: IPv4Address - - def connect() -> () raise ServerErr - def send(String) -> () raise ServerErr - def disconnect() -> () - -type ConnMyServer: MyServer when self.is_connected -type DisConnMyServer: MyServer when not self.is_connected - -class ServerErr(def message: Str): Exception(message) - -class MyServer(self: DisConnMyServer, def ip_address: IPv4Address): Server - def is_connected: Bool := False - def _last_message: String? := None - - def last_sent(self) -> Str ! ServerErr := if self.last_message /= None - then self._last_message - else ! ServerError("No last message!") - - def connect(self: DisConnMyServer) := self.is_connected := True - - def send(self: ConnMyServer, message: String) := self._last_message := message - - def disconnect(self: ConnMyServer) := self.is_connected := False diff --git a/tests/resource/valid/readme_example/type_refinement_call_site.mamba b/tests/resource/valid/readme_example/type_refinement_call_site.mamba new file mode 100644 index 00000000..a5dca89b --- /dev/null +++ b/tests/resource/valid/readme_example/type_refinement_call_site.mamba @@ -0,0 +1,11 @@ +def x := -42 # some value + +# currently this is a compilation error, x is type Int +# we cannot yet evaluate refined types at compile time, only runtime +# factorial(x) # error: 'x' is type Int, but signature is factorial(PosInt) + +if x isa PosInt then + print(factorial(x)) +else + print("x must be positive") + \ No newline at end of file diff --git a/tests/resource/valid/readme_example/type_refinement_in_fun.mamba b/tests/resource/valid/readme_example/type_refinement_in_fun.mamba new file mode 100644 index 00000000..7452b337 --- /dev/null +++ b/tests/resource/valid/readme_example/type_refinement_in_fun.mamba @@ -0,0 +1,9 @@ +# we list the conditions below, which are a list of boolean expressions. +# this first-class language feature desugars to an list of checks which are done at the call site. +# we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. +type PosInt: Int when self >= 0 + +def factorial(x: PosInt) -> PosInt := match x { + 0 => 1 + n => n * factorial(n - 1) +} diff --git a/tests/resource/valid/readme_example/type_refinement_matrix.mamba b/tests/resource/valid/readme_example/type_refinement_matrix.mamba new file mode 100644 index 00000000..28ed10c3 --- /dev/null +++ b/tests/resource/valid/readme_example/type_refinement_matrix.mamba @@ -0,0 +1,20 @@ +type InvertibleMatrix: Matrix when self.determinant() != 0.0 + +class MatrixErr(def message: Str): Exception(message) + +## Matrix, which now takes floats as argument +class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := { + def _last_op: Str? := None + + def determinant(fin self) -> Float := self.a * self.d - self.b * self.c + + def inverse(self: InvertibleMatrix) -> Matrix := + def det := self.determinant() + self._last_op := "inverse" + + Matrix(self.d / det, -self.b / det, -self.c / det, self.a / det) + + def last_op(fin self) -> Str ! MatrixErr := + if self._last_op != None then self._last_op + else ! MatrixErr("No operation performed") +} diff --git a/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba b/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba new file mode 100644 index 00000000..4e2bde0f --- /dev/null +++ b/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba @@ -0,0 +1,11 @@ +def m := Matrix(1.0, 2.0, 3.0, 4.0) + +if m isa InvertibleMatrix then + def m_inv := m.inverse() + print("Original matrix: {m}") + print("Inverse: {m_inv}") +else + print("Matrix is singular (not invertible).") + +def last_op = m.last_op()! +print("Last operation was: {last_op}") diff --git a/tests/resource/valid/readme_example/type_refinement_set.mamba b/tests/resource/valid/readme_example/type_refinement_set.mamba new file mode 100644 index 00000000..739d8984 --- /dev/null +++ b/tests/resource/valid/readme_example/type_refinement_set.mamba @@ -0,0 +1,4 @@ +type SpecialInt: Int when { + self >= 0 + self <= 100 or self mod 2 = 0 +} diff --git a/tests/resource/valid/readme_example/type_refinement_use.mamba b/tests/resource/valid/readme_example/type_refinement_use.mamba deleted file mode 100644 index e7dba375..00000000 --- a/tests/resource/valid/readme_example/type_refinement_use.mamba +++ /dev/null @@ -1,16 +0,0 @@ -import ipaddress -from server import MyServer - -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) - -# The default state of http_server is DisconnectedHTTPServer, so we don't need to check that here -http_server.connect() - -# We check the state -if my_server isa ConnMyServer then - # http_server is a Connected Server if the above is true - my_server.send("Hello World!") - -print("last message sent before disconnect: \"{my_server.last_sent}\".") -if my_server isa ConnectedMyServer then my_server.disconnect() diff --git a/tests/resource/valid/readme_example/use_server.mamba b/tests/resource/valid/readme_example/use_server.mamba deleted file mode 100644 index 9a0919fd..00000000 --- a/tests/resource/valid/readme_example/use_server.mamba +++ /dev/null @@ -1,16 +0,0 @@ -from ipaddress import IPv4Address - -class MyServer(def ip_address: IPv4Address): - def is_connected: Bool := True - def last_sent() -> String := "dummy" - -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) - -http_server.connect() -if my_server.is_connected then http_server.send("Hello World!") - -# This statement may raise an error, but for now de simply leave it as-is -# See the error handling section for more detail -print("last message sent before disconnect: \"{my_server.last_sent()}\".") -my_server.disconnect() diff --git a/tests/resource/valid/readme_example/use_server_check.py b/tests/resource/valid/readme_example/use_server_check.py deleted file mode 100644 index 09dfac49..00000000 --- a/tests/resource/valid/readme_example/use_server_check.py +++ /dev/null @@ -1,23 +0,0 @@ -from ipaddress import IPv4Address -from server import MyServer - -class MyServer: - is_connected: bool = true - - def __init__(self, ip_address: IPv4Address): - self.ip_address = ip_address - - def last_sent() -> str: - "dummy" - -some_ip = ipaddress.ip_address("151.101.193.140") -my_server = MyServer(some_ip) - -http_server.connect() -if my_server.is_connected: - http_server.send("Hello World!") - -# This statement may raise an error, but for now de simply leave it as-is -# See the error handling section for more detail -print(f"last message sent before disconnect: \"{my_server.last_sent()}\".") -my_server.disconnect() From 4cb0e64e3368c1add3b7633eb860ee92ab68de2f Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 11 Jul 2025 11:13:47 +0200 Subject: [PATCH 19/44] feat: simplify lex, remove comment token --- src/parse/lex/token.rs | 2 -- src/parse/lex/tokenize.rs | 8 +++----- src/parse/mod.rs | 9 +-------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index 514c0f32..8fbff3d0 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -127,7 +127,6 @@ pub enum Token { Question, Pass, - Comment(String), Eof, } @@ -253,7 +252,6 @@ impl fmt::Display for Token { Token::When => write!(f, "when"), Token::Pass => write!(f, "pass"), - Token::Comment(comment) => write!(f, "#{comment}"), Token::Eof => write!(f, ""), } diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index d3c4970b..5f1ddbac 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -81,12 +81,10 @@ pub fn into_tokens(c: char, it: &mut Peekable, state: &mut State) -> LexR _ => create(state, Token::Eq), }, '#' => { - let mut comment = String::new(); - while it.peek().is_some() && *it.peek().unwrap() != '\n' && *it.peek().unwrap() != '\r' - { - comment.push(it.next().unwrap()); + while it.peek().is_some() && *it.peek().unwrap() != '\n' { + it.next().unwrap(); } - create(state, Token::Comment(comment)) + Ok(vec![]) } '!' => match it.peek() { Some('=') => next_and_create(it, state, Token::Neq), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b4fd78ab..4d928dce 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -31,14 +31,7 @@ impl FromStr for AST { type Err = Box; fn from_str(input: &str) -> ParseResult { - let tokens: Vec = tokenize(input) - .map(|tokens| { - tokens - .into_iter() - .filter(|t| !matches!(t.token, Token::Comment(_))) - .collect() - }) - .map_err(ParseErr::from)?; + let tokens: Vec = tokenize(input).map_err(ParseErr::from)?; let mut iterator = LexIterator::new(tokens.iter().peekable()); let statements = block::parse_statements(&mut iterator)?; From 5a6d6f237095c0eb6e1e2c58c8c324923f60afff Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 11 Jul 2025 11:24:32 +0200 Subject: [PATCH 20/44] feat: remove bitwise operators from grammar I think this clashes with the language philosophy. Instead, we favour having this be part of the standard lib --- docs/spec/characters.md | 13 -- docs/spec/grammar.md | 4 +- docs/spec/keywords.md | 2 +- src/check/constrain/generate/call.rs | 8 - src/generate/ast/node.rs | 2 - src/parse/ast/node_op.rs | 4 - src/parse/call.rs | 4 +- src/parse/expr_or_stmt.rs | 4 +- src/parse/expression.rs | 5 +- src/parse/lex/mod.rs | 4 +- src/parse/lex/token.rs | 18 -- src/parse/lex/tokenize.rs | 23 +-- src/parse/operation.rs | 191 ++---------------- src/parse/statement.rs | 10 - tests/check/valid.rs | 1 - .../class/assign_types_double_nested.mamba | 3 - .../class/assign_types_double_nested_check.py | 3 - .../valid/class/assign_types_nested.mamba | 3 - .../valid/class/assign_types_nested_check.py | 3 - .../valid/operation/assign_types.mamba | 3 - .../valid/operation/assign_types_check.py | 3 - .../valid/operation/assign_types_nested.mamba | 3 - .../operation/assign_types_nested_check.py | 3 - tests/resource/valid/operation/bitwise.mamba | 17 -- .../resource/valid/operation/bitwise_check.py | 17 -- 25 files changed, 23 insertions(+), 328 deletions(-) delete mode 100644 tests/resource/valid/operation/bitwise.mamba delete mode 100644 tests/resource/valid/operation/bitwise_check.py diff --git a/docs/spec/characters.md b/docs/spec/characters.md index 91126bbf..128faffc 100644 --- a/docs/spec/characters.md +++ b/docs/spec/characters.md @@ -46,17 +46,6 @@ Symbol | Use `=` | Structurally equal `!=` | Structurally not equal -## Binary operators - -Symbol | Use ----|--- -`&&` | And operator -`||` | Or operator -`!|` | Exclusive or operator -`!!` | Negation operator -`<<` | Left shift -`>>` | Right shift - ## Assignment and Functions Symbol | Use @@ -78,8 +67,6 @@ Symbol | Use `*=` | Multiply value with variable and assign to variable `/=` | Divide variable by value and assign to variable `^=` | Raise variable by value and assign to variable -`>>=` | Binary shift variable to the right by value -`<<=` | Binary shift variable to the left by value ## Context Dependent diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index beea9d92..9f88b6a0 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -49,7 +49,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). | call | "_" - reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" | ">>=" | "<<=" ) code-block + reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" ) code-block call ::= code-block [ ( "." | "?." ) ] id tuple [ "!" match-cases [ recover code-block ] ] raise ::= "!" id { "," id } @@ -66,7 +66,7 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). definition ::= "def" ( variable-def | fun-def ) | type-def | trait-def | class-def variable-def ::= [ "fin" ] ( id-maybe-type | collection ) [ ":=" code-block ] [ forward ] - fun-def ::= ( [ "const" ] | [ "total" ] [ "pure" ] ) ( id | overridable-op ) fun-args [ "->" type ] [ raise ] [ ":=" code-block ] + fun-def ::= ( [ "meta" ] | [ "total" ] [ "pure" ] ) ( id | overridable-op ) fun-args [ "->" type ] [ raise ] [ ":=" code-block ] fun-args ::= "(" [ fun-arg ] { "," fun-arg } ")" fun-arg ::= id-maybe-type [ ":=" code-block ] anon-fun ::= "\" [ id-maybe-type { "," id-maybe-type } ] ":=" code-block diff --git a/docs/spec/keywords.md b/docs/spec/keywords.md index e8546b71..bc40b127 100644 --- a/docs/spec/keywords.md +++ b/docs/spec/keywords.md @@ -40,7 +40,7 @@ Keyword | Use `fin` | Denote defined variable is immutable `pure` | Denote function is pure `total` | Denote a function is total -`const` | Denote a function is constant (evaluated during compile time) +`meta` | Denote a meta function (evaluated during compile time) ## Boolean operators diff --git a/src/check/constrain/generate/call.rs b/src/check/constrain/generate/call.rs index 80082511..e0676052 100644 --- a/src/check/constrain/generate/call.rs +++ b/src/check/constrain/generate/call.rs @@ -353,14 +353,6 @@ fn reassign_op( left: left.clone(), right, }, - NodeOp::BLShift => Node::BLShift { - left: left.clone(), - right, - }, - NodeOp::BRShift => Node::BRShift { - left: left.clone(), - right, - }, other => { let msg = format!("Cannot reassign using operator '{other}'"); return Err(vec![TypeErr::new(ast.pos, &msg)]); diff --git a/src/generate/ast/node.rs b/src/generate/ast/node.rs index 8258b1ca..939b5910 100644 --- a/src/generate/ast/node.rs +++ b/src/generate/ast/node.rs @@ -330,8 +330,6 @@ impl TryFrom<(&ASTTy, &NodeOp)> for CoreOp { NodeOp::Mul => Ok(CoreOp::MulAssign), NodeOp::Div => Ok(CoreOp::DivAssign), NodeOp::Pow => Ok(CoreOp::PowAssign), - NodeOp::BLShift => Ok(CoreOp::BLShiftAssign), - NodeOp::BRShift => Ok(CoreOp::BRShiftAssign), NodeOp::Assign => Ok(CoreOp::Assign), op => Err(UnimplementedErr::new(ast, &format!("Reassign with {op}"))), } diff --git a/src/parse/ast/node_op.rs b/src/parse/ast/node_op.rs index 4c6386b5..36532912 100644 --- a/src/parse/ast/node_op.rs +++ b/src/parse/ast/node_op.rs @@ -17,8 +17,6 @@ pub enum NodeOp { Eq, Le, Ge, - BLShift, - BRShift, } impl Display for NodeOp { @@ -36,8 +34,6 @@ impl Display for NodeOp { NodeOp::Eq => write!(f, "{EQ}"), NodeOp::Le => write!(f, "{LE}"), NodeOp::Ge => write!(f, "{GE}"), - NodeOp::BLShift => write!(f, "<<"), - NodeOp::BRShift => write!(f, ">>"), } } } diff --git a/src/parse/call.rs b/src/parse/call.rs index 01360710..332ba7ea 100644 --- a/src/parse/call.rs +++ b/src/parse/call.rs @@ -79,7 +79,7 @@ mod test { #[test] fn op_assign() { - let source = String::from("a:=1\nb+=2\nc-=3\nd*=4\ne/=5\nf^=6\ng<<=7\nh>>=8\n"); + let source = String::from("a:=1\nb+=2\nc-=3\nd*=4\ne/=5\nf^=6\n"); let statements = parse_direct(&source).unwrap(); let ops: Vec = statements @@ -96,8 +96,6 @@ mod test { assert_eq!(ops[3], NodeOp::Mul); assert_eq!(ops[4], NodeOp::Div); assert_eq!(ops[5], NodeOp::Pow); - assert_eq!(ops[6], NodeOp::BLShift); - assert_eq!(ops[7], NodeOp::BRShift); } #[test] diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index 7c75c2e4..44bff060 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -46,9 +46,7 @@ pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { | Token::SubAssign | Token::MulAssign | Token::DivAssign - | Token::PowAssign - | Token::BLShiftAssign - | Token::BRShiftAssign => parse_reassignment(&expr_or_stmt, it), + | Token::PowAssign => parse_reassignment(&expr_or_stmt, it), _ => Ok(expr_or_stmt.clone()), }, Ok(expr_or_stmt.clone()), diff --git a/src/parse/expression.rs b/src/parse/expression.rs index a69637e5..09497e4e 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -38,7 +38,6 @@ pub fn parse_inner_expression(it: &mut LexIterator) -> ParseResult { Token::Add, Token::Id(String::new()), Token::Sub, - Token::BOneCmpl, Token::BSlash, ]; @@ -75,9 +74,7 @@ pub fn parse_inner_expression(it: &mut LexIterator) -> ParseResult { Ok(Box::from(AST::new(start.union(end), node))) } - Token::Not | Token::Sqrt | Token::Add | Token::Sub | Token::BOneCmpl => { - parse_expression(it) - } + Token::Not | Token::Sqrt | Token::Add | Token::Sub => parse_expression(it), Token::BSlash => parse_anon_fun(it), diff --git a/src/parse/lex/mod.rs b/src/parse/lex/mod.rs index 3f511a35..4f02f2de 100644 --- a/src/parse/lex/mod.rs +++ b/src/parse/lex/mod.rs @@ -100,7 +100,7 @@ mod tests { #[test] fn assign_operations() { - let source = String::from(":= += -= *= /= ^= >>= <<="); + let source = String::from(":= += -= *= /= ^="); let tokens = tokenize(&source).unwrap(); assert_eq!( tokens.iter().map(|l| l.token.clone()).collect_vec(), @@ -111,8 +111,6 @@ mod tests { Token::MulAssign, Token::DivAssign, Token::PowAssign, - Token::BRShiftAssign, - Token::BLShiftAssign, Token::Eof, ] ); diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index 8fbff3d0..0afacc85 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -50,8 +50,6 @@ pub enum Token { MulAssign, DivAssign, PowAssign, - BLShiftAssign, - BRShiftAssign, Def, Real(String), @@ -74,13 +72,6 @@ pub enum Token { Mod, Sqrt, - BAnd, - BOr, - BXOr, - BOneCmpl, - BLShift, - BRShift, - Ge, Geq, Le, @@ -174,8 +165,6 @@ impl fmt::Display for Token { Token::MulAssign => write!(f, "*="), Token::PowAssign => write!(f, "^="), Token::DivAssign => write!(f, "/="), - Token::BLShiftAssign => write!(f, "<<="), - Token::BRShiftAssign => write!(f, ">>="), Token::Def => write!(f, "def"), Token::Id(id) => write!(f, "{id}"), @@ -199,13 +188,6 @@ impl fmt::Display for Token { Token::Mod => write!(f, "mod"), Token::Sqrt => write!(f, "sqrt"), - Token::BAnd => write!(f, "_and_"), - Token::BOr => write!(f, "_or_"), - Token::BXOr => write!(f, "_xor_"), - Token::BOneCmpl => write!(f, "_not_"), - Token::BLShift => write!(f, "<<"), - Token::BRShift => write!(f, ">>"), - Token::Ge => write!(f, ">"), Token::Geq => write!(f, ">="), Token::Le => write!(f, "<"), diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index 5f1ddbac..6062e33c 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -25,10 +25,7 @@ pub fn into_tokens(c: char, it: &mut Peekable, state: &mut State) -> LexR ']' => create(state, Token::RSBrack), '{' => create(state, Token::LCBrack), '}' => create(state, Token::RCBrack), - '|' => match it.peek() { - Some('|') => next_and_create(it, state, Token::BOr), - _ => create(state, Token::Ver), - }, + '|' => create(state, Token::Ver), '\n' => create(state, Token::NL), '.' => match it.peek() { Some('.') => match (it.next(), it.peek()) { @@ -38,18 +35,10 @@ pub fn into_tokens(c: char, it: &mut Peekable, state: &mut State) -> LexR _ => create(state, Token::Point), }, '<' => match it.peek() { - Some('<') => match (it.next(), it.peek()) { - (_, Some('=')) => next_and_create(it, state, Token::BLShiftAssign), - _ => next_and_create(it, state, Token::BLShift), - }, Some('=') => next_and_create(it, state, Token::Leq), _ => create(state, Token::Le), }, '>' => match it.peek() { - Some('>') => match (it.next(), it.peek()) { - (_, Some('=')) => next_and_create(it, state, Token::BRShiftAssign), - _ => next_and_create(it, state, Token::BRShift), - }, Some('=') => next_and_create(it, state, Token::Geq), _ => create(state, Token::Ge), }, @@ -88,18 +77,8 @@ pub fn into_tokens(c: char, it: &mut Peekable, state: &mut State) -> LexR } '!' => match it.peek() { Some('=') => next_and_create(it, state, Token::Neq), - Some('!') => next_and_create(it, state, Token::BOneCmpl), - Some('|') => next_and_create(it, state, Token::BXOr), _ => create(state, Token::Raise), }, - '&' => match it.peek() { - Some('&') => next_and_create(it, state, Token::BAnd), - _ => Err(Box::new(LexErr::new( - state.pos, - None, - "Is this supposed to be a binary and operator?", - ))), - }, '?' => create(state, Token::Question), '0'..='9' => { let mut number = c.to_string(); diff --git a/src/parse/operation.rs b/src/parse/operation.rs index 279406b6..ab2a0ae9 100644 --- a/src/parse/operation.rs +++ b/src/parse/operation.rs @@ -30,31 +30,11 @@ macro_rules! inner_bin_op { /// 7. and, or, question or /// 8. postfix calls pub fn parse_expression(it: &mut LexIterator) -> ParseResult { - parse_level_7(it) -} - -fn parse_level_7(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("operation (7)")?; - let arithmetic = it.parse(&parse_level_6, "operation", start)?; - macro_rules! bin_op { - ($it:expr, $fun:path, $ast:ident, $arithmetic:expr, $msg:expr) => {{ - inner_bin_op!($it, start, $fun, $ast, $arithmetic, $msg) - }}; - } - - it.peek( - &|it, lex| match lex.token { - Token::And => bin_op!(it, parse_level_7, And, arithmetic.clone(), "and"), - Token::Or => bin_op!(it, parse_level_7, Or, arithmetic.clone(), "or"), - Token::Question => bin_op!(it, parse_level_7, Question, arithmetic.clone(), "question"), - _ => Ok(arithmetic.clone()), - }, - Ok(arithmetic.clone()), - ) + parse_level_6(it) } fn parse_level_6(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("operation (6)")?; + let start = it.start_pos("operation (7)")?; let arithmetic = it.parse(&parse_level_5, "operation", start)?; macro_rules! bin_op { ($it:expr, $fun:path, $ast:ident, $arithmetic:expr, $msg:expr) => {{ @@ -64,15 +44,9 @@ fn parse_level_6(it: &mut LexIterator) -> ParseResult { it.peek( &|it, lex| match lex.token { - Token::Ge => bin_op!(it, parse_level_6, Ge, arithmetic.clone(), "greater"), - Token::Geq => bin_op!(it, parse_level_6, Geq, arithmetic.clone(), "greater, equal"), - Token::Le => bin_op!(it, parse_level_6, Le, arithmetic.clone(), "less"), - Token::Leq => bin_op!(it, parse_level_6, Leq, arithmetic.clone(), "less, equal"), - Token::Eq => bin_op!(it, parse_level_6, Eq, arithmetic.clone(), "equal"), - Token::Neq => bin_op!(it, parse_level_6, Neq, arithmetic.clone(), "not equal"), - Token::Is => bin_op!(it, parse_level_6, Is, arithmetic.clone(), "is"), - Token::IsA => bin_op!(it, parse_level_6, IsA, arithmetic.clone(), "is a"), - Token::In => bin_op!(it, parse_level_6, In, arithmetic.clone(), "in"), + Token::And => bin_op!(it, parse_level_6, And, arithmetic.clone(), "and"), + Token::Or => bin_op!(it, parse_level_6, Or, arithmetic.clone(), "or"), + Token::Question => bin_op!(it, parse_level_6, Question, arithmetic.clone(), "question"), _ => Ok(arithmetic.clone()), }, Ok(arithmetic.clone()), @@ -80,7 +54,7 @@ fn parse_level_6(it: &mut LexIterator) -> ParseResult { } fn parse_level_5(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("operation (5)")?; + let start = it.start_pos("operation (6)")?; let arithmetic = it.parse(&parse_level_4, "operation", start)?; macro_rules! bin_op { ($it:expr, $fun:path, $ast:ident, $arithmetic:expr, $msg:expr) => {{ @@ -90,23 +64,15 @@ fn parse_level_5(it: &mut LexIterator) -> ParseResult { it.peek( &|it, lex| match lex.token { - Token::BLShift => bin_op!( - it, - parse_level_5, - BLShift, - arithmetic.clone(), - "bitwise left shift" - ), - Token::BRShift => bin_op!( - it, - parse_level_5, - BRShift, - arithmetic.clone(), - "bitwise right shift" - ), - Token::BAnd => bin_op!(it, parse_level_5, BAnd, arithmetic.clone(), "bitwise and"), - Token::BOr => bin_op!(it, parse_level_5, BOr, arithmetic.clone(), "bitwise or"), - Token::BXOr => bin_op!(it, parse_level_5, BXOr, arithmetic.clone(), "bitwise xor"), + Token::Ge => bin_op!(it, parse_level_5, Ge, arithmetic.clone(), "greater"), + Token::Geq => bin_op!(it, parse_level_5, Geq, arithmetic.clone(), "greater, equal"), + Token::Le => bin_op!(it, parse_level_5, Le, arithmetic.clone(), "less"), + Token::Leq => bin_op!(it, parse_level_5, Leq, arithmetic.clone(), "less, equal"), + Token::Eq => bin_op!(it, parse_level_5, Eq, arithmetic.clone(), "equal"), + Token::Neq => bin_op!(it, parse_level_5, Neq, arithmetic.clone(), "not equal"), + Token::Is => bin_op!(it, parse_level_5, Is, arithmetic.clone(), "is"), + Token::IsA => bin_op!(it, parse_level_5, IsA, arithmetic.clone(), "is a"), + Token::In => bin_op!(it, parse_level_5, In, arithmetic.clone(), "in"), _ => Ok(arithmetic.clone()), }, Ok(arithmetic.clone()), @@ -199,14 +165,6 @@ fn parse_level_2(it: &mut LexIterator) -> ParseResult { un_op!(it, parse_expression, Sqrt, Sqrt, "square root") } else if it.eat_if(&Token::Not).is_some() { un_op!(it, parse_expression, Not, Not, "not") - } else if it.eat_if(&Token::BOneCmpl).is_some() { - un_op!( - it, - parse_expression, - BOneCmpl, - BOneCmpl, - "bitwise ones compliment" - ) } else { parse_level_1(it) } @@ -666,125 +624,6 @@ mod test { ); } - #[test] - fn b_and_verify() { - let source = String::from("one && three"); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(BAnd, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("one") - } - ); - assert_eq!( - right.node, - Node::Id { - lit: String::from("three") - } - ); - } - - #[test] - fn b_or_verify() { - let source = String::from("one || \"asdf\""); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(BOr, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("one") - } - ); - assert_eq!( - right.node, - Node::Str { - lit: String::from("asdf"), - expressions: vec![] - } - ); - } - - #[test] - fn b_xor_verify() { - let source = String::from("one !| \"asdf\""); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(BXOr, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("one") - } - ); - assert_eq!( - right.node, - Node::Str { - lit: String::from("asdf"), - expressions: vec![] - } - ); - } - - #[test] - fn b_ones_complement_verify() { - let source = String::from("!! \"asdf\""); - let ast = parse_direct(&source).unwrap(); - - let expr = verify_is_un_operation!(BOneCmpl, ast); - assert_eq!( - expr.node, - Node::Str { - lit: String::from("asdf"), - expressions: vec![] - } - ); - } - - #[test] - fn b_lshift_verify() { - let source = String::from("one << \"asdf\""); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(BLShift, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("one") - } - ); - assert_eq!( - right.node, - Node::Str { - lit: String::from("asdf"), - expressions: vec![] - } - ); - } - - #[test] - fn brshift_verify() { - let source = String::from("one >> \"asdf\""); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(BRShift, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("one") - } - ); - assert_eq!( - right.node, - Node::Str { - lit: String::from("asdf"), - expressions: vec![] - } - ); - } - #[test] fn addition_missing_factor() { let source = String::from("a +"); diff --git a/src/parse/statement.rs b/src/parse/statement.rs index ba1df9ff..77f31ca4 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -112,8 +112,6 @@ pub fn parse_reassignment(pre: &AST, it: &mut LexIterator) -> ParseResult { Token::MulAssign, Token::DivAssign, Token::PowAssign, - Token::BLShiftAssign, - Token::BRShiftAssign, ]; let (token, op) = if let Some(token) = it.peek_next() { @@ -142,14 +140,6 @@ pub fn parse_reassignment(pre: &AST, it: &mut LexIterator) -> ParseResult { token: Token::PowAssign, .. } => (Token::PowAssign, NodeOp::Pow), - Lex { - token: Token::BLShiftAssign, - .. - } => (Token::BLShiftAssign, NodeOp::BLShift), - Lex { - token: Token::BRShiftAssign, - .. - } => (Token::BRShiftAssign, NodeOp::BRShift), lex => { return Err(Box::from(expected_one_of(&expect, lex, "reassignment"))); } diff --git a/tests/check/valid.rs b/tests/check/valid.rs index a0762c70..563712f7 100644 --- a/tests/check/valid.rs +++ b/tests/check/valid.rs @@ -119,7 +119,6 @@ use test_case::test_case; #[test_case("operation", "in_set_is_bool")] #[test_case("operation", "multiply_other_int")] #[test_case("operation", "primitives")] -#[test_case("operation", "bitwise")] #[test_case("operation", "boolean")] #[test_case("operation", "equality_different_types")] #[test_case("operation", "type_alias_primitive" => ignore["investigate whether this should in fact, pass"])] diff --git a/tests/resource/valid/class/assign_types_double_nested.mamba b/tests/resource/valid/class/assign_types_double_nested.mamba index 39539855..18f922a4 100644 --- a/tests/resource/valid/class/assign_types_double_nested.mamba +++ b/tests/resource/valid/class/assign_types_double_nested.mamba @@ -14,6 +14,3 @@ x.y.a := x.y.a * 6 x.y.a := x.y.a / 7 x.y.a := x.y.a ^ 2 - -x.y.a := x.y.a << 10 -x.y.a := x.y.a >> 5 diff --git a/tests/resource/valid/class/assign_types_double_nested_check.py b/tests/resource/valid/class/assign_types_double_nested_check.py index 335d53f8..02137c52 100644 --- a/tests/resource/valid/class/assign_types_double_nested_check.py +++ b/tests/resource/valid/class/assign_types_double_nested_check.py @@ -18,6 +18,3 @@ def __init__(self, a: float): x.y.a = x.y.a / 7 x.y.a = x.y.a ** 2 - -x.y.a = x.y.a << 10 -x.y.a = x.y.a >> 5 diff --git a/tests/resource/valid/class/assign_types_nested.mamba b/tests/resource/valid/class/assign_types_nested.mamba index 61909e4d..507c4ae6 100644 --- a/tests/resource/valid/class/assign_types_nested.mamba +++ b/tests/resource/valid/class/assign_types_nested.mamba @@ -8,6 +8,3 @@ x.a := x.a * 6 x.a := x.a / 7 x.a := x.a ^ 2 - -x.a := x.a << 10 -x.a := x.a >> 5 diff --git a/tests/resource/valid/class/assign_types_nested_check.py b/tests/resource/valid/class/assign_types_nested_check.py index 07ce66ab..a14310ec 100644 --- a/tests/resource/valid/class/assign_types_nested_check.py +++ b/tests/resource/valid/class/assign_types_nested_check.py @@ -10,6 +10,3 @@ def __init__(self, a: float): x.a = x.a / 7 x.a = x.a ** 2 - -x.a = x.a << 10 -x.a = x.a >> 5 diff --git a/tests/resource/valid/operation/assign_types.mamba b/tests/resource/valid/operation/assign_types.mamba index bba089e6..903082ce 100644 --- a/tests/resource/valid/operation/assign_types.mamba +++ b/tests/resource/valid/operation/assign_types.mamba @@ -6,6 +6,3 @@ a *= 6 a /= 7 a ^= 2 - -a <<= 10 -a >>= 5 diff --git a/tests/resource/valid/operation/assign_types_check.py b/tests/resource/valid/operation/assign_types_check.py index f83bde3f..2a46d623 100644 --- a/tests/resource/valid/operation/assign_types_check.py +++ b/tests/resource/valid/operation/assign_types_check.py @@ -6,6 +6,3 @@ a /= 7 a **= 2 - -a <<= 10 -a >>= 5 diff --git a/tests/resource/valid/operation/assign_types_nested.mamba b/tests/resource/valid/operation/assign_types_nested.mamba index 83d1b13b..874f71a8 100644 --- a/tests/resource/valid/operation/assign_types_nested.mamba +++ b/tests/resource/valid/operation/assign_types_nested.mamba @@ -8,6 +8,3 @@ x.a *= 6 x.a /= 7 x.a ^= 2 - -x.a <<= 10 -x.a >>= 5 diff --git a/tests/resource/valid/operation/assign_types_nested_check.py b/tests/resource/valid/operation/assign_types_nested_check.py index 002a42ad..df94dd21 100644 --- a/tests/resource/valid/operation/assign_types_nested_check.py +++ b/tests/resource/valid/operation/assign_types_nested_check.py @@ -10,6 +10,3 @@ def __init__(self, a: float): x.a /= 7 x.a **= 2 - -x.a <<= 10 -x.a >>= 5 diff --git a/tests/resource/valid/operation/bitwise.mamba b/tests/resource/valid/operation/bitwise.mamba deleted file mode 100644 index f84bd6a7..00000000 --- a/tests/resource/valid/operation/bitwise.mamba +++ /dev/null @@ -1,17 +0,0 @@ -def a := 10 -def b := 20 -def c := 30 -def d := 40 -def e := 50 -def f := 60 -def g := 70 -def h := 80 -def i := 90 -def j := 100 - -!! a -a << b -c >> d -e && f -g || h -i !| j diff --git a/tests/resource/valid/operation/bitwise_check.py b/tests/resource/valid/operation/bitwise_check.py deleted file mode 100644 index daef5b9e..00000000 --- a/tests/resource/valid/operation/bitwise_check.py +++ /dev/null @@ -1,17 +0,0 @@ -a: int = 10 -b: int = 20 -c: int = 30 -d: int = 40 -e: int = 50 -f: int = 60 -g: int = 70 -h: int = 80 -i: int = 90 -j: int = 100 - -~a -a << b -c >> d -e & f -g | h -i ^ j From 03a8a8849945df5787092202458e607e9738ba14 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 16 Jul 2025 08:57:56 +0200 Subject: [PATCH 21/44] [ci skip] test: rewrite test input The goal now is to make all tests pass. If a certain feature is not yet supported as of yet, an appropriate type error must be thrown. It is assumed that the parser can be rewritten in one go. --- README.md | 14 +++++++------- docs/spec/grammar.md | 3 ++- tests/check/invalid.rs | 1 + .../invalid/syntax/assign_and_while.mamba | 3 ++- .../invalid/syntax/top_lvl_class_access.mamba | 3 ++- .../invalid/type/access/access_int.mamba | 2 +- .../type/access/access_list_with_string.mamba | 2 +- .../invalid/type/access/access_set.mamba | 2 +- .../type/access/access_slice_using_range.mamba | 2 +- .../access/access_string_dict_with_int.mamba | 2 +- .../type/access/slice_begin_wrong_type.mamba | 2 +- .../type/access/slice_end_wrong_type.mamba | 2 +- .../type/access/slice_step_wrong_type.mamba | 2 +- .../type/class/access_field_wrong_type.mamba | 2 +- .../type/class/access_function_wrong_type.mamba | 2 +- .../type/class/access_unassigned_class_var.mamba | 3 ++- .../type/class/access_unassigned_field.mamba | 3 ++- .../invalid/type/class/args_and_init.mamba | 2 +- .../assign_to_inner_inner_not_allowed.mamba | 7 ++++--- .../type/class/assign_to_inner_not_allowed.mamba | 4 ++-- .../type/class/assign_to_non_existent_self.mamba | 3 ++- .../invalid/type/class/compound_field.mamba | 3 ++- .../type/class/generic_unknown_type.mamba | 2 +- .../type/class/incompat_parent_generics.mamba | 2 +- .../invalid/type/class/no_generic_arg.mamba | 2 +- .../type/class/one_tuple_not_assigned_to.mamba | 3 ++- .../invalid/type/class/parent_is_class.mamba | 5 +++++ .../invalid/type/class/reassign_function.mamba | 2 +- .../type/class/reassign_non_existent.mamba | 2 +- .../class/reassign_to_unassigned_class_var.mamba | 3 ++- .../invalid/type/class/reassign_wrong_type.mamba | 2 +- .../invalid/type/class/same_parent_twice.mamba | 2 +- .../class/top_level_class_not_assigned_to.mamba | 3 ++- .../invalid/type/class/wrong_generic_type.mamba | 2 +- .../dictionary_assume_not_optional.mamba | 2 +- .../dictionary_in_fun_wrong_ret_ty.mamba | 2 +- .../collection/dictionary_not_sliceable.mamba | 2 +- .../dictionary_use_value_as_other_type.mamba | 2 +- .../collection/dictionary_wrong_key_type.mamba | 2 +- .../access_match_arms_variable.mamba | 6 +++--- .../class_field_assigned_to_only_else.mamba | 9 ++++----- ...ss_field_assigned_to_only_one_arm_match.mamba | 6 ++++-- .../class_field_assigned_to_only_then.mamba | 3 ++- .../control_flow/different_type_shadow.mamba | 3 ++- .../type/control_flow/if_not_boolean.mamba | 2 +- .../undefined_var_in_match_arm.mamba | 3 ++- .../definition/assign_to_function_call.mamba | 5 +++-- .../definition/assign_to_inner_non_mut.mamba | 5 +++-- .../definition/assign_to_inner_non_mut2.mamba | 7 ++++--- .../definition/assign_to_inner_non_mut3.mamba | 7 ++++--- .../function_ret_in_class_not_super.mamba | 3 ++- .../type/definition/nested_non_mut_field.mamba | 4 ++-- .../definition/non_mutable_in_call_chain.mamba | 8 ++++---- .../type/definition/reassign_non_mut.mamba | 2 +- .../type/definition/reassign_non_mut_field.mamba | 2 +- .../type/definition/tuple_modify_inner_mut.mamba | 2 +- .../type/definition/tuple_modify_mut.mamba | 2 +- .../definition/tuple_modify_mut_entire.mamba | 2 +- .../invalid/type/error/handle_only_id.mamba | 9 ++++++--- .../invalid/type/error/unhandled_exception.mamba | 16 ++++++++-------- .../type/function/call_mut_function.mamba | 8 ++++---- .../function/call_mut_function_on_non_mut.mamba | 7 +++---- .../{access_string_check.py => access_string.py} | 0 ...nary_access_check.py => dictionary_access.py} | 0 .../valid/access/index_via_function.mamba | 8 ++++---- .../resource/valid/access/index_via_function.py | 12 ++++++++++++ .../valid/access/index_via_function_check.py | 12 ------------ tests/resource/valid/access/simple_index.mamba | 1 - .../{simple_index_check.py => simple_index.py} | 1 - .../valid/access/simple_list_access.mamba | 4 ++-- ...ist_access_check.py => simple_list_access.py} | 2 +- ...s_child_check.py => call_with_class_child.py} | 0 tests/resource/valid/call/input.mamba | 5 ++--- .../valid/call/{input_check.py => input.py} | 0 .../valid/class/assign_to_nullable_field.mamba | 3 ++- ...ield_check.py => assign_to_nullable_field.py} | 0 .../valid/class/assign_types_double_nested.mamba | 8 +++----- ...ed_check.py => assign_types_double_nested.py} | 0 ...es_nested_check.py => assign_types_nested.py} | 0 .../valid/class/class_super_one_line_init.mamba | 9 ++++----- ...nit_check.py => class_super_one_line_init.py} | 0 .../{doc_strings_check.py => doc_strings.py} | 0 .../valid/class/fun_with_body_in_interface.mamba | 4 ++-- ...ce_check.py => fun_with_body_in_interface.py} | 0 .../class/generic_unknown_type_unused.mamba | 2 +- ...d_check.py => generic_unknown_type_unused.py} | 0 .../class/{generics_check.py => generics.py} | 0 tests/resource/valid/class/import.mamba | 6 +++--- .../valid/class/{import_check.py => import.py} | 0 tests/resource/valid/class/multiple_parent.mamba | 7 ++++--- ...ltiple_parent_check.py => multiple_parent.py} | 0 tests/resource/valid/class/parent.mamba | 9 +++++++-- .../valid/class/{parent_check.py => parent.py} | 0 .../valid/class/print_types_double_nested.mamba | 8 +++----- ...ted_check.py => print_types_double_nested.py} | 0 .../valid/class/same_var_different_type.mamba | 6 ++++-- ..._type_check.py => same_var_different_type.py} | 0 tests/resource/valid/class/shadow.mamba | 11 ++++++----- .../valid/class/{shadow_check.py => shadow.py} | 0 tests/resource/valid/class/simple_class.mamba | 2 +- tests/resource/valid/class/simple_class.py | 5 +++++ ...p_level_tuple_check.py => top_level_tuple.py} | 0 .../top_level_unassigned_but_nullable.mamba | 8 ++++---- ...k.py => top_level_unassigned_but_nullable.py} | 0 ...tuple_as_class_check.py => tuple_as_class.py} | 0 tests/resource/valid/class/types.mamba | 12 ++++++------ .../valid/class/{types_check.py => types.py} | 0 .../class/unassigned_tuple_second_nullable.mamba | 8 +++----- ...ck.py => unassigned_tuple_second_nullable.py} | 0 .../valid/class/var_from_outside_class.mamba | 5 ++--- ..._class_check.py => var_from_outside_class.py} | 0 .../{with_generics_check.py => with_generics.py} | 0 ...llection_type_check.py => collection_type.py} | 0 .../{dictionary_check.py => dictionary.py} | 0 ...ry_builder_check.py => dictionary_builder.py} | 0 ...nary_in_fun_check.py => dictionary_in_fun.py} | 0 ...on_type_check.py => infer_collection_type.py} | 0 ...check.py => infer_collection_type_for_fun.py} | 0 .../valid/collection/{list_check.py => list.py} | 0 ...t_builder_check.py => nested_list_builder.py} | 0 ...et_builder_check.py => nested_set_builder.py} | 0 .../valid/collection/{set_check.py => set.py} | 1 + .../{set_union_check.py => set_union.py} | 1 + .../{assign_if_check.py => assign_if.py} | 0 .../{assign_match_check.py => assign_match.py} | 0 ... class_field_assigned_to_both_branches_if.py} | 0 ... class_field_assigned_to_exhaustive_match.py} | 0 ...le_assign_if_check.py => double_assign_if.py} | 0 ..._check.py => for_over_collection_of_tuple.py} | 0 ...func_check.py => for_over_range_from_func.py} | 0 ...ype_union_check.py => for_over_type_union.py} | 0 ...for_statements_check.py => for_statements.py} | 0 .../{handle_in_if_check.py => handle_in_if.py} | 0 ...if_in_for_loop_check.py => if_in_for_loop.py} | 0 .../{if_in_if_cond_check.py => if_in_if_cond.py} | 0 .../{if_two_types_check.py => if_two_types.py} | 0 ...ed_check.py => match_dont_remove_shadowed.py} | 0 .../{match_stmt_check.py => match_stmt.py} | 0 .../{matches_in_if_check.py => matches_in_if.py} | 0 ..._in_if_arms_check.py => shadow_in_if_arms.py} | 0 ...s_else_check.py => shadow_in_if_arms_else.py} | 0 ...s_then_check.py => shadow_in_if_arms_then.py} | 0 .../control_flow/{while_check.py => while.py} | 0 .../definition/all_mutable_in_call_chain.mamba | 8 ++++---- .../definition/all_mutable_in_call_chain.py | 14 ++++++++++++++ .../valid/definition/assign_to_inner_mut.mamba | 9 +++++---- .../valid/definition/assign_to_inner_mut.py | 12 ++++++++++++ ...heck.py => assign_to_nullable_in_function.py} | 0 .../{assign_tuples_check.py => assign_tuples.py} | 0 ...assign_with_if_check.py => assign_with_if.py} | 0 ...heck.py => assign_with_if_different_types.py} | 0 .../valid/definition/assign_with_match.mamba | 3 ++- ..._with_match_check.py => assign_with_match.py} | 0 .../assign_with_match_different_types.mamba | 3 ++- ...k.py => assign_with_match_different_types.py} | 0 .../assign_with_match_type_annotation.mamba | 3 ++- ...k.py => assign_with_match_type_annotation.py} | 0 .../valid/definition/assign_with_nested_if.mamba | 4 ++-- ...sted_if_check.py => assign_with_nested_if.py} | 0 .../definition/assign_with_try_except.mamba | 3 ++- ...except_check.py => assign_with_try_except.py} | 0 ...rings_check.py => collection_in_f_strings.py} | 0 .../{f_strings_check.py => f_strings.py} | 0 ..._type_check.py => function_no_return_type.py} | 0 ..._ret_super_check.py => function_ret_super.py} | 0 .../definition/function_ret_super_in_class.mamba | 3 ++- ...s_check.py => function_ret_super_in_class.py} | 0 ...tion_with_if_check.py => function_with_if.py} | 0 ...se_check.py => function_with_if_and_raise.py} | 0 .../valid/definition/function_with_match.mamba | 4 +++- ...ith_match_check.py => function_with_match.py} | 0 .../definition/function_with_nested_if.mamba | 4 ++-- ...ed_if_check.py => function_with_nested_if.py} | 0 .../{long_f_string_check.py => long_f_string.py} | 0 .../valid/definition/nested_function.mamba | 6 +++--- .../resource/valid/definition/nested_function.py | 12 ++++++++++++ .../valid/definition/nested_mut_field.mamba | 4 ++-- ...ed_mut_field_check.py => nested_mut_field.py} | 0 .../definition/{ternary_check.py => ternary.py} | 0 .../valid/definition/tuple_modify_mut.mamba | 2 +- ...e_modify_mut_check.py => tuple_modify_mut.py} | 0 .../definition/tuple_modify_outer_mut.mamba | 2 +- ...er_mut_check.py => tuple_modify_outer_mut.py} | 0 .../definition/tuple_non_lit_modify_mut.mamba | 2 +- ..._mut_check.py => tuple_non_lit_modify_mut.py} | 0 tests/resource/valid/{doc_check.py => doc.py} | 0 .../valid/{empty_file_check.py => empty_file.py} | 0 .../error/{exception_check.py => exception.py} | 0 .../resource/valid/error/exception_in_fun.mamba | 6 +++--- ...ption_in_fun_check.py => exception_in_fun.py} | 0 .../valid/error/exception_in_fun_super.mamba | 9 ++++----- ..._super_check.py => exception_in_fun_super.py} | 0 tests/resource/valid/error/handle.mamba | 3 ++- .../valid/error/{handle_check.py => handle.py} | 0 tests/resource/valid/error/handle_only_id.mamba | 11 ++++++----- ...handle_only_id_check.py => handle_only_id.py} | 0 .../valid/error/handle_var_usable_after.mamba | 7 ++++--- ...after_check.py => handle_var_usable_after.py} | 0 .../resource/valid/error/nested_exception.mamba | 6 +++--- ...ed_exception_check.py => nested_exception.py} | 0 .../valid/error/{raise_check.py => raise.py} | 0 .../valid/error/{with_check.py => with.py} | 0 ...d_exception_check.py => allowed_exception.py} | 0 .../{allowed_pass_check.py => allowed_pass.py} | 0 ...able_fun_arg_check.py => callable_fun_arg.py} | 0 .../valid/function/{calls_check.py => calls.py} | 0 tests/resource/valid/function/definition.mamba | 6 ++++-- .../{definition_check.py => definition.py} | 0 ...n_and_type_check.py => exception_and_type.py} | 0 ...se_super_check.py => function_raise_super.py} | 0 ...faults_check.py => function_with_defaults.py} | 0 .../resource/valid/function/match_function.mamba | 10 +++++----- ...match_function_check.py => match_function.py} | 0 .../{print_string_check.py => print_string.py} | 0 .../valid/function/return_last_expression.mamba | 3 ++- ...ession_check.py => return_last_expression.py} | 0 ...on_call_check.py => ternary_function_call.py} | 0 .../{arithmetic_check.py => arithmetic.py} | 0 .../{assign_types_check.py => assign_types.py} | 0 ...es_nested_check.py => assign_types_nested.py} | 0 ...on_check.py => assign_types_no_annotation.py} | 0 .../operation/{boolean_check.py => boolean.py} | 0 .../operation/equality_different_types.mamba | 3 ++- ...ypes_check.py => equality_different_types.py} | 0 .../valid/operation/greater_than_int.mamba | 2 +- ...ter_than_int_check.py => greater_than_int.py} | 0 .../valid/operation/greater_than_other_int.mamba | 2 +- ...er_int_check.py => greater_than_other_int.py} | 0 ...in_set_is_bool_check.py => in_set_is_bool.py} | 0 .../valid/operation/multiply_other_int.mamba | 2 +- ..._other_int_check.py => multiply_other_int.py} | 0 .../{primitives_check.py => primitives.py} | 0 ...rimitive_check.py => type_alias_primitive.py} | 0 .../{std_functions_check.py => std_functions.py} | 0 tests_util/src/lib.rs | 6 +----- 235 files changed, 323 insertions(+), 241 deletions(-) create mode 100644 tests/resource/invalid/type/class/parent_is_class.mamba rename tests/resource/valid/access/{access_string_check.py => access_string.py} (100%) rename tests/resource/valid/access/{dictionary_access_check.py => dictionary_access.py} (100%) create mode 100644 tests/resource/valid/access/index_via_function.py delete mode 100644 tests/resource/valid/access/index_via_function_check.py rename tests/resource/valid/access/{simple_index_check.py => simple_index.py} (72%) rename tests/resource/valid/access/{simple_list_access_check.py => simple_list_access.py} (73%) rename tests/resource/valid/call/{call_with_class_child_check.py => call_with_class_child.py} (100%) rename tests/resource/valid/call/{input_check.py => input.py} (100%) rename tests/resource/valid/class/{assign_to_nullable_field_check.py => assign_to_nullable_field.py} (100%) rename tests/resource/valid/class/{assign_types_double_nested_check.py => assign_types_double_nested.py} (100%) rename tests/resource/valid/class/{assign_types_nested_check.py => assign_types_nested.py} (100%) rename tests/resource/valid/class/{class_super_one_line_init_check.py => class_super_one_line_init.py} (100%) rename tests/resource/valid/class/{doc_strings_check.py => doc_strings.py} (100%) rename tests/resource/valid/class/{fun_with_body_in_interface_check.py => fun_with_body_in_interface.py} (100%) rename tests/resource/valid/class/{generic_unknown_type_unused_check.py => generic_unknown_type_unused.py} (100%) rename tests/resource/valid/class/{generics_check.py => generics.py} (100%) rename tests/resource/valid/class/{import_check.py => import.py} (100%) rename tests/resource/valid/class/{multiple_parent_check.py => multiple_parent.py} (100%) rename tests/resource/valid/class/{parent_check.py => parent.py} (100%) rename tests/resource/valid/class/{print_types_double_nested_check.py => print_types_double_nested.py} (100%) rename tests/resource/valid/class/{same_var_different_type_check.py => same_var_different_type.py} (100%) rename tests/resource/valid/class/{shadow_check.py => shadow.py} (100%) create mode 100644 tests/resource/valid/class/simple_class.py rename tests/resource/valid/class/{top_level_tuple_check.py => top_level_tuple.py} (100%) rename tests/resource/valid/class/{top_level_unassigned_but_nullable_check.py => top_level_unassigned_but_nullable.py} (100%) rename tests/resource/valid/class/{tuple_as_class_check.py => tuple_as_class.py} (100%) rename tests/resource/valid/class/{types_check.py => types.py} (100%) rename tests/resource/valid/class/{unassigned_tuple_second_nullable_check.py => unassigned_tuple_second_nullable.py} (100%) rename tests/resource/valid/class/{var_from_outside_class_check.py => var_from_outside_class.py} (100%) rename tests/resource/valid/class/{with_generics_check.py => with_generics.py} (100%) rename tests/resource/valid/collection/{collection_type_check.py => collection_type.py} (100%) rename tests/resource/valid/collection/{dictionary_check.py => dictionary.py} (100%) rename tests/resource/valid/collection/{dictionary_builder_check.py => dictionary_builder.py} (100%) rename tests/resource/valid/collection/{dictionary_in_fun_check.py => dictionary_in_fun.py} (100%) rename tests/resource/valid/collection/{infer_collection_type_check.py => infer_collection_type.py} (100%) rename tests/resource/valid/collection/{infer_collection_type_for_fun_check.py => infer_collection_type_for_fun.py} (100%) rename tests/resource/valid/collection/{list_check.py => list.py} (100%) rename tests/resource/valid/collection/{nested_list_builder_check.py => nested_list_builder.py} (100%) rename tests/resource/valid/collection/{nested_set_builder_check.py => nested_set_builder.py} (100%) rename tests/resource/valid/collection/{set_check.py => set.py} (99%) rename tests/resource/valid/collection/{set_union_check.py => set_union.py} (98%) rename tests/resource/valid/control_flow/{assign_if_check.py => assign_if.py} (100%) rename tests/resource/valid/control_flow/{assign_match_check.py => assign_match.py} (100%) rename tests/resource/valid/control_flow/{class_field_assigned_to_both_branches_if_check.py => class_field_assigned_to_both_branches_if.py} (100%) rename tests/resource/valid/control_flow/{class_field_assigned_to_exhaustive_match_check.py => class_field_assigned_to_exhaustive_match.py} (100%) rename tests/resource/valid/control_flow/{double_assign_if_check.py => double_assign_if.py} (100%) rename tests/resource/valid/control_flow/{for_over_collection_of_tuple_check.py => for_over_collection_of_tuple.py} (100%) rename tests/resource/valid/control_flow/{for_over_range_from_func_check.py => for_over_range_from_func.py} (100%) rename tests/resource/valid/control_flow/{for_over_type_union_check.py => for_over_type_union.py} (100%) rename tests/resource/valid/control_flow/{for_statements_check.py => for_statements.py} (100%) rename tests/resource/valid/control_flow/{handle_in_if_check.py => handle_in_if.py} (100%) rename tests/resource/valid/control_flow/{if_in_for_loop_check.py => if_in_for_loop.py} (100%) rename tests/resource/valid/control_flow/{if_in_if_cond_check.py => if_in_if_cond.py} (100%) rename tests/resource/valid/control_flow/{if_two_types_check.py => if_two_types.py} (100%) rename tests/resource/valid/control_flow/{match_dont_remove_shadowed_check.py => match_dont_remove_shadowed.py} (100%) rename tests/resource/valid/control_flow/{match_stmt_check.py => match_stmt.py} (100%) rename tests/resource/valid/control_flow/{matches_in_if_check.py => matches_in_if.py} (100%) rename tests/resource/valid/control_flow/{shadow_in_if_arms_check.py => shadow_in_if_arms.py} (100%) rename tests/resource/valid/control_flow/{shadow_in_if_arms_else_check.py => shadow_in_if_arms_else.py} (100%) rename tests/resource/valid/control_flow/{shadow_in_if_arms_then_check.py => shadow_in_if_arms_then.py} (100%) rename tests/resource/valid/control_flow/{while_check.py => while.py} (100%) create mode 100644 tests/resource/valid/definition/all_mutable_in_call_chain.py create mode 100644 tests/resource/valid/definition/assign_to_inner_mut.py rename tests/resource/valid/definition/{assign_to_nullable_in_function_check.py => assign_to_nullable_in_function.py} (100%) rename tests/resource/valid/definition/{assign_tuples_check.py => assign_tuples.py} (100%) rename tests/resource/valid/definition/{assign_with_if_check.py => assign_with_if.py} (100%) rename tests/resource/valid/definition/{assign_with_if_different_types_check.py => assign_with_if_different_types.py} (100%) rename tests/resource/valid/definition/{assign_with_match_check.py => assign_with_match.py} (100%) rename tests/resource/valid/definition/{assign_with_match_different_types_check.py => assign_with_match_different_types.py} (100%) rename tests/resource/valid/definition/{assign_with_match_type_annotation_check.py => assign_with_match_type_annotation.py} (100%) rename tests/resource/valid/definition/{assign_with_nested_if_check.py => assign_with_nested_if.py} (100%) rename tests/resource/valid/definition/{assign_with_try_except_check.py => assign_with_try_except.py} (100%) rename tests/resource/valid/definition/{collection_in_f_strings_check.py => collection_in_f_strings.py} (100%) rename tests/resource/valid/definition/{f_strings_check.py => f_strings.py} (100%) rename tests/resource/valid/definition/{function_no_return_type_check.py => function_no_return_type.py} (100%) rename tests/resource/valid/definition/{function_ret_super_check.py => function_ret_super.py} (100%) rename tests/resource/valid/definition/{function_ret_super_in_class_check.py => function_ret_super_in_class.py} (100%) rename tests/resource/valid/definition/{function_with_if_check.py => function_with_if.py} (100%) rename tests/resource/valid/definition/{function_with_if_and_raise_check.py => function_with_if_and_raise.py} (100%) rename tests/resource/valid/definition/{function_with_match_check.py => function_with_match.py} (100%) rename tests/resource/valid/definition/{function_with_nested_if_check.py => function_with_nested_if.py} (100%) rename tests/resource/valid/definition/{long_f_string_check.py => long_f_string.py} (100%) create mode 100644 tests/resource/valid/definition/nested_function.py rename tests/resource/valid/definition/{nested_mut_field_check.py => nested_mut_field.py} (100%) rename tests/resource/valid/definition/{ternary_check.py => ternary.py} (100%) rename tests/resource/valid/definition/{tuple_modify_mut_check.py => tuple_modify_mut.py} (100%) rename tests/resource/valid/definition/{tuple_modify_outer_mut_check.py => tuple_modify_outer_mut.py} (100%) rename tests/resource/valid/definition/{tuple_non_lit_modify_mut_check.py => tuple_non_lit_modify_mut.py} (100%) rename tests/resource/valid/{doc_check.py => doc.py} (100%) rename tests/resource/valid/{empty_file_check.py => empty_file.py} (100%) rename tests/resource/valid/error/{exception_check.py => exception.py} (100%) rename tests/resource/valid/error/{exception_in_fun_check.py => exception_in_fun.py} (100%) rename tests/resource/valid/error/{exception_in_fun_super_check.py => exception_in_fun_super.py} (100%) rename tests/resource/valid/error/{handle_check.py => handle.py} (100%) rename tests/resource/valid/error/{handle_only_id_check.py => handle_only_id.py} (100%) rename tests/resource/valid/error/{handle_var_usable_after_check.py => handle_var_usable_after.py} (100%) rename tests/resource/valid/error/{nested_exception_check.py => nested_exception.py} (100%) rename tests/resource/valid/error/{raise_check.py => raise.py} (100%) rename tests/resource/valid/error/{with_check.py => with.py} (100%) rename tests/resource/valid/function/{allowed_exception_check.py => allowed_exception.py} (100%) rename tests/resource/valid/function/{allowed_pass_check.py => allowed_pass.py} (100%) rename tests/resource/valid/function/{callable_fun_arg_check.py => callable_fun_arg.py} (100%) rename tests/resource/valid/function/{calls_check.py => calls.py} (100%) rename tests/resource/valid/function/{definition_check.py => definition.py} (100%) rename tests/resource/valid/function/{exception_and_type_check.py => exception_and_type.py} (100%) rename tests/resource/valid/function/{function_raise_super_check.py => function_raise_super.py} (100%) rename tests/resource/valid/function/{function_with_defaults_check.py => function_with_defaults.py} (100%) rename tests/resource/valid/function/{match_function_check.py => match_function.py} (100%) rename tests/resource/valid/function/{print_string_check.py => print_string.py} (100%) rename tests/resource/valid/function/{return_last_expression_check.py => return_last_expression.py} (100%) rename tests/resource/valid/function/{ternary_function_call_check.py => ternary_function_call.py} (100%) rename tests/resource/valid/operation/{arithmetic_check.py => arithmetic.py} (100%) rename tests/resource/valid/operation/{assign_types_check.py => assign_types.py} (100%) rename tests/resource/valid/operation/{assign_types_nested_check.py => assign_types_nested.py} (100%) rename tests/resource/valid/operation/{assign_types_no_annotation_check.py => assign_types_no_annotation.py} (100%) rename tests/resource/valid/operation/{boolean_check.py => boolean.py} (100%) rename tests/resource/valid/operation/{equality_different_types_check.py => equality_different_types.py} (100%) rename tests/resource/valid/operation/{greater_than_int_check.py => greater_than_int.py} (100%) rename tests/resource/valid/operation/{greater_than_other_int_check.py => greater_than_other_int.py} (100%) rename tests/resource/valid/operation/{in_set_is_bool_check.py => in_set_is_bool.py} (100%) rename tests/resource/valid/operation/{multiply_other_int_check.py => multiply_other_int.py} (100%) rename tests/resource/valid/operation/{primitives_check.py => primitives.py} (100%) rename tests/resource/valid/operation/{type_alias_primitive_check.py => type_alias_primitive.py} (100%) rename tests/resource/valid/{std_functions_check.py => std_functions.py} (100%) diff --git a/README.md b/README.md index 7f38d4e1..146b2679 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Lists make use of square brackets: # lists def a := [0, 2, 51] def b := ["list", "of", "strings"] +def empty_list = [] # lists of tuples, builder syntax def ab := [(x, y) | x in a, x > 0, y in b, b != "of" ] @@ -143,11 +144,13 @@ def c := { 10, 20 } def d := { 3 } # sets, builder syntax def cd := { x ^ y | x in c, y in d } +def empty_set := {,} # empty sets must have comma to distinguish from code block # maps def e := { "do" => 1, "ree" => 2, "meee" => 3 } # maps, builder syntax def ef := { x => y - 2 | x in e, y = x.len() } +def empty_mapping := {=>} # indexing works for lists and maps/mappings (sets cannot be indexed because these are unordered) print(ab(2)) # prints '(2, "list")' @@ -211,13 +214,13 @@ class MatrixErr(def message: Str): Exception(message) class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { # Accessor for matrix contents - def contents(fin self) -> List[Int] := [a, b, c, d] + def contents(fin self) -> List[Int] := [self.a, self.b, self.c, self.d] # Trace of the matrix (a + d) - def pure trace(fin self) -> Int := a + d + def pure trace(fin self) -> Int := self.a + self.d # Determinant recomputation (pure function) - def pure determinant(fin self) -> Int := a * d - b * c + def pure determinant(fin self) -> Int := self.a * self.d - self.b * self.c def scale(self, factor: Int) := [ self.a := self.a * factor @@ -246,16 +249,13 @@ As for constructor arguments: - If they are prefixed with `def`, then they are immediately accessible (e.g. `matrix.a`). - If they are **not** prefixed with `def`, then they are only constructor arguments. - This means that they are a class-constant, a constant which is defined in the context of a class. - This means that they may be used in any part of the class (body, functions, methods). + They may be used at any point in the class, but they are (1) invariant and (2) may not be accessed from outside the class. - The body of the class is evaluated for each object we created, effectively making this the constructor body. We can change the relevant parts of the above example to use a class constant: ```mamba class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { - # ORIGIN_X and ORIGIN_Y are constructor constants — they cannot be changed - # current x and y are mutable fields initialized from the constants def x: Int := ORIGIN_X def y: Int := ORIGIN_Y diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 9f88b6a0..4406f9ad 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -12,7 +12,8 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). ```ebnf file ::= { expr-or-stmt } - import ::= [ "from" id ] "import" id { "," id } [ as id { "," id } ] + import ::= "import" id [ "::" id ] ( "::" ( "{" import-as { "," import-as } "}" | import-as ) ) + import-as ::= id ( "as" id ) type-def ::= "type" type ":" type ( ":=" "{" code-set "}" | "when" [ code-set ] ) trait-def ::= "trait" type ( ":" type { "," type } ) [ ":=" code-set ] diff --git a/tests/check/invalid.rs b/tests/check/invalid.rs index 52eca0d0..5240c79f 100644 --- a/tests/check/invalid.rs +++ b/tests/check/invalid.rs @@ -39,6 +39,7 @@ use mamba::parse::ast::AST; #[test_case("class", "same_parent_twice" => matches Err(_))] #[test_case("class", "top_level_class_not_assigned_to" => matches Err(_))] #[test_case("class", "wrong_generic_type" => matches Err(_))] +#[test_case("class", "parent_is_class" => matches Err(_))] #[test_case("collection", "dictionary_assume_not_optional" => ignore["type checker incorrectly assumes access always non-optional"])] #[test_case("collection", "dictionary_in_fun_wrong_ret_ty" => matches Err(_))] #[test_case("collection", "dictionary_not_sliceable" => matches Err(_))] diff --git a/tests/resource/invalid/syntax/assign_and_while.mamba b/tests/resource/invalid/syntax/assign_and_while.mamba index 4c33b27f..50bc50f5 100644 --- a/tests/resource/invalid/syntax/assign_and_while.mamba +++ b/tests/resource/invalid/syntax/assign_and_while.mamba @@ -4,8 +4,9 @@ def c := 4.2E2 def illegal := e * def other def d := 0 -while e < f, b do +while e < f, b do [ def g := h * i j := k mod 2 + "300" +] while l < m do print n diff --git a/tests/resource/invalid/syntax/top_lvl_class_access.mamba b/tests/resource/invalid/syntax/top_lvl_class_access.mamba index 5802a380..ad33dce1 100644 --- a/tests/resource/invalid/syntax/top_lvl_class_access.mamba +++ b/tests/resource/invalid/syntax/top_lvl_class_access.mamba @@ -1,2 +1,3 @@ -class X +class X := { def y.y: Int := 10 +} diff --git a/tests/resource/invalid/type/access/access_int.mamba b/tests/resource/invalid/type/access/access_int.mamba index f395168d..a9f59496 100644 --- a/tests/resource/invalid/type/access/access_int.mamba +++ b/tests/resource/invalid/type/access/access_int.mamba @@ -1,3 +1,3 @@ def x := 10 -print(x[10]) +print(x(10)) diff --git a/tests/resource/invalid/type/access/access_list_with_string.mamba b/tests/resource/invalid/type/access/access_list_with_string.mamba index f3663d13..e800e6e7 100644 --- a/tests/resource/invalid/type/access/access_list_with_string.mamba +++ b/tests/resource/invalid/type/access/access_list_with_string.mamba @@ -1,2 +1,2 @@ x := [1, 2, 3] -x["idx"] +x("idx") diff --git a/tests/resource/invalid/type/access/access_set.mamba b/tests/resource/invalid/type/access/access_set.mamba index 10ae09aa..17cd3227 100644 --- a/tests/resource/invalid/type/access/access_set.mamba +++ b/tests/resource/invalid/type/access/access_set.mamba @@ -1,3 +1,3 @@ def my_set := { 2, 3, 4 } -my_set[0] +my_set(0) diff --git a/tests/resource/invalid/type/access/access_slice_using_range.mamba b/tests/resource/invalid/type/access/access_slice_using_range.mamba index 9a5e1e18..a48477b4 100644 --- a/tests/resource/invalid/type/access/access_slice_using_range.mamba +++ b/tests/resource/invalid/type/access/access_slice_using_range.mamba @@ -1,2 +1,2 @@ x := [1, 2, 3] -x[1 ..= 3 .. 2] +x(1 ..= 3) diff --git a/tests/resource/invalid/type/access/access_string_dict_with_int.mamba b/tests/resource/invalid/type/access/access_string_dict_with_int.mamba index 2c54e67a..61958aa2 100644 --- a/tests/resource/invalid/type/access/access_string_dict_with_int.mamba +++ b/tests/resource/invalid/type/access/access_string_dict_with_int.mamba @@ -1,3 +1,3 @@ x := { "asd" => 10, "fgh" => 20} -x[10] +x(10) diff --git a/tests/resource/invalid/type/access/slice_begin_wrong_type.mamba b/tests/resource/invalid/type/access/slice_begin_wrong_type.mamba index bd385d6b..7b4be562 100644 --- a/tests/resource/invalid/type/access/slice_begin_wrong_type.mamba +++ b/tests/resource/invalid/type/access/slice_begin_wrong_type.mamba @@ -1,2 +1,2 @@ x := [1, 2, 3] -x["a" :: 2 :: 4] +x("a" :: 2) diff --git a/tests/resource/invalid/type/access/slice_end_wrong_type.mamba b/tests/resource/invalid/type/access/slice_end_wrong_type.mamba index 418e12cd..bd8c426e 100644 --- a/tests/resource/invalid/type/access/slice_end_wrong_type.mamba +++ b/tests/resource/invalid/type/access/slice_end_wrong_type.mamba @@ -1,4 +1,4 @@ x := [1, 2, 3] -x[1 :: "a" :: 2] +x(1 :: "a") diff --git a/tests/resource/invalid/type/access/slice_step_wrong_type.mamba b/tests/resource/invalid/type/access/slice_step_wrong_type.mamba index a06ae4f1..b3158339 100644 --- a/tests/resource/invalid/type/access/slice_step_wrong_type.mamba +++ b/tests/resource/invalid/type/access/slice_step_wrong_type.mamba @@ -1,2 +1,2 @@ x := [1, 2, 3] -x[1 :: 3 :: "c"] +x(1 :: 3) diff --git a/tests/resource/invalid/type/class/access_field_wrong_type.mamba b/tests/resource/invalid/type/class/access_field_wrong_type.mamba index e8be745a..73c86960 100644 --- a/tests/resource/invalid/type/class/access_field_wrong_type.mamba +++ b/tests/resource/invalid/type/class/access_field_wrong_type.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def something: String := "my string" diff --git a/tests/resource/invalid/type/class/access_function_wrong_type.mamba b/tests/resource/invalid/type/class/access_function_wrong_type.mamba index c4230fb1..3e152931 100644 --- a/tests/resource/invalid/type/class/access_function_wrong_type.mamba +++ b/tests/resource/invalid/type/class/access_function_wrong_type.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def something(self) -> String := return "my string" diff --git a/tests/resource/invalid/type/class/access_unassigned_class_var.mamba b/tests/resource/invalid/type/class/access_unassigned_class_var.mamba index f8bf3540..6e248d7b 100644 --- a/tests/resource/invalid/type/class/access_unassigned_class_var.mamba +++ b/tests/resource/invalid/type/class/access_unassigned_class_var.mamba @@ -1,5 +1,6 @@ -class X +class X := { def z: Int def __init__(self) := self.z +} diff --git a/tests/resource/invalid/type/class/access_unassigned_field.mamba b/tests/resource/invalid/type/class/access_unassigned_field.mamba index 38edbcf8..2b15d204 100644 --- a/tests/resource/invalid/type/class/access_unassigned_field.mamba +++ b/tests/resource/invalid/type/class/access_unassigned_field.mamba @@ -1,5 +1,6 @@ -class X +class X := { def z: Int def __init__(self) := def z: Int := self.z + 5 +} diff --git a/tests/resource/invalid/type/class/args_and_init.mamba b/tests/resource/invalid/type/class/args_and_init.mamba index 6626fe29..db195fa5 100644 --- a/tests/resource/invalid/type/class/args_and_init.mamba +++ b/tests/resource/invalid/type/class/args_and_init.mamba @@ -1,2 +1,2 @@ -class MyClass(arg1: SomeType) +class MyClass(arg1: SomeType) := def __init__(self) := print("cannot have two constructors") diff --git a/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba b/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba index ec96077b..51a8786a 100644 --- a/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba +++ b/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba @@ -1,11 +1,12 @@ class Y(x: Int) -class X +class X := { def y: Y def __init__(a: Int) := self.y = Y(a) +} -x = X(10) +def x := X(10) # now assign a float -x.x = 2.5 +x.x := 2.5 diff --git a/tests/resource/invalid/type/class/assign_to_inner_not_allowed.mamba b/tests/resource/invalid/type/class/assign_to_inner_not_allowed.mamba index 73558671..94091562 100644 --- a/tests/resource/invalid/type/class/assign_to_inner_not_allowed.mamba +++ b/tests/resource/invalid/type/class/assign_to_inner_not_allowed.mamba @@ -1,6 +1,6 @@ class X(x: Int) -x = X(10) +def x := X(10) # now assign a float -x.x = 2.5 +x.x := 2.5 diff --git a/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba b/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba index 9fc6231d..34982df1 100644 --- a/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba +++ b/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba @@ -1,5 +1,6 @@ -class X +class X := { def z: Int def __init__(self, x: Int) := self.y := x +} diff --git a/tests/resource/invalid/type/class/compound_field.mamba b/tests/resource/invalid/type/class/compound_field.mamba index 6ae1c512..a6044713 100644 --- a/tests/resource/invalid/type/class/compound_field.mamba +++ b/tests/resource/invalid/type/class/compound_field.mamba @@ -1,5 +1,6 @@ -class MyClass +class MyClass := { def a: Int := 10 def b: Int := self.a + 1 +} def my_class := MyClass() diff --git a/tests/resource/invalid/type/class/generic_unknown_type.mamba b/tests/resource/invalid/type/class/generic_unknown_type.mamba index 8f9fc6b3..1c958e14 100644 --- a/tests/resource/invalid/type/class/generic_unknown_type.mamba +++ b/tests/resource/invalid/type/class/generic_unknown_type.mamba @@ -1,4 +1,4 @@ -class MyClass[A: UnknownType] +class MyClass[A: UnknownType] := def f() -> Int := 10 # should error because suddenly we are using an ill-typed class. diff --git a/tests/resource/invalid/type/class/incompat_parent_generics.mamba b/tests/resource/invalid/type/class/incompat_parent_generics.mamba index a0acab18..70a87ce5 100644 --- a/tests/resource/invalid/type/class/incompat_parent_generics.mamba +++ b/tests/resource/invalid/type/class/incompat_parent_generics.mamba @@ -1,4 +1,4 @@ class SuperClass[B: Int] -class MyClass[A: String]: SuperClass[A] +class MyClass[A: String]: SuperClass[A] := def my_fun(x: A) -> A := x diff --git a/tests/resource/invalid/type/class/no_generic_arg.mamba b/tests/resource/invalid/type/class/no_generic_arg.mamba index e08581d3..b42f6e8d 100644 --- a/tests/resource/invalid/type/class/no_generic_arg.mamba +++ b/tests/resource/invalid/type/class/no_generic_arg.mamba @@ -1,4 +1,4 @@ -class MyClass[A] +class MyClass[A] := def my_fun(x: A) -> A := x def my_class := MyClass() diff --git a/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba b/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba index 2eb539a9..d92915ee 100644 --- a/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba +++ b/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba @@ -1,5 +1,6 @@ -class X +class X := { def (a, b): (Int, Int) def __init__(self, x: Int) := self.a = x +} diff --git a/tests/resource/invalid/type/class/parent_is_class.mamba b/tests/resource/invalid/type/class/parent_is_class.mamba new file mode 100644 index 00000000..2cf91998 --- /dev/null +++ b/tests/resource/invalid/type/class/parent_is_class.mamba @@ -0,0 +1,5 @@ +class MyType[A: MyGeneric, C](def super_field: Str) + +class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") := { + def some_function(self) -> Int := 10 +} diff --git a/tests/resource/invalid/type/class/reassign_function.mamba b/tests/resource/invalid/type/class/reassign_function.mamba index d3748e6b..81336845 100644 --- a/tests/resource/invalid/type/class/reassign_function.mamba +++ b/tests/resource/invalid/type/class/reassign_function.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def something() -> Int := 10 diff --git a/tests/resource/invalid/type/class/reassign_non_existent.mamba b/tests/resource/invalid/type/class/reassign_non_existent.mamba index 010f1e25..778cd984 100644 --- a/tests/resource/invalid/type/class/reassign_non_existent.mamba +++ b/tests/resource/invalid/type/class/reassign_non_existent.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def something: Int := 10 diff --git a/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba b/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba index d2e083a2..fa395b22 100644 --- a/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba +++ b/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba @@ -1,5 +1,6 @@ -class X +class X := { def z: Int def __init__(self, x: Int) := self.z += 10 +} diff --git a/tests/resource/invalid/type/class/reassign_wrong_type.mamba b/tests/resource/invalid/type/class/reassign_wrong_type.mamba index 683df41f..8281c09a 100644 --- a/tests/resource/invalid/type/class/reassign_wrong_type.mamba +++ b/tests/resource/invalid/type/class/reassign_wrong_type.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def something: Int := 10 diff --git a/tests/resource/invalid/type/class/same_parent_twice.mamba b/tests/resource/invalid/type/class/same_parent_twice.mamba index 6054ebd3..f4c089ae 100644 --- a/tests/resource/invalid/type/class/same_parent_twice.mamba +++ b/tests/resource/invalid/type/class/same_parent_twice.mamba @@ -1,4 +1,4 @@ class MyType(a: String) -class MyClass1: MyType("asdf"), MyType("qwerty") +class MyClass1: MyType("asdf"), MyType("qwerty") := def other: Int diff --git a/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba b/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba index 8aa16ab9..9bc01129 100644 --- a/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba +++ b/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba @@ -1,6 +1,7 @@ -class X +class X := { def y: Int def z: Int def __init__(self, x: Int) := self.y := x +} diff --git a/tests/resource/invalid/type/class/wrong_generic_type.mamba b/tests/resource/invalid/type/class/wrong_generic_type.mamba index d7c7476b..a129b1c9 100644 --- a/tests/resource/invalid/type/class/wrong_generic_type.mamba +++ b/tests/resource/invalid/type/class/wrong_generic_type.mamba @@ -1,4 +1,4 @@ -class MyClass[A: String] +class MyClass[A: String] := def my_fun(x: A) -> A := x def my_class := MyClass[Int]() diff --git a/tests/resource/invalid/type/collection/dictionary_assume_not_optional.mamba b/tests/resource/invalid/type/collection/dictionary_assume_not_optional.mamba index c3a0ec8a..9a4e3bec 100644 --- a/tests/resource/invalid/type/collection/dictionary_assume_not_optional.mamba +++ b/tests/resource/invalid/type/collection/dictionary_assume_not_optional.mamba @@ -1,3 +1,3 @@ def a := { 20 => "asdf", 30 => "asdf" } -def f() -> Str := a[40] +def f() -> Str := a(40) diff --git a/tests/resource/invalid/type/collection/dictionary_in_fun_wrong_ret_ty.mamba b/tests/resource/invalid/type/collection/dictionary_in_fun_wrong_ret_ty.mamba index a9d436bc..552d4bdb 100644 --- a/tests/resource/invalid/type/collection/dictionary_in_fun_wrong_ret_ty.mamba +++ b/tests/resource/invalid/type/collection/dictionary_in_fun_wrong_ret_ty.mamba @@ -1,3 +1,3 @@ def j := { 10 => 200, 30 => 5 } -def f(key: Int) -> Str := j[key] +def f(key: Int) -> Str := j(key) diff --git a/tests/resource/invalid/type/collection/dictionary_not_sliceable.mamba b/tests/resource/invalid/type/collection/dictionary_not_sliceable.mamba index ca481a29..512bcd0a 100644 --- a/tests/resource/invalid/type/collection/dictionary_not_sliceable.mamba +++ b/tests/resource/invalid/type/collection/dictionary_not_sliceable.mamba @@ -1,3 +1,3 @@ def a := { 20 => "asdf", 30 => "asdf" } -a[20::30] +a(20::30) diff --git a/tests/resource/invalid/type/collection/dictionary_use_value_as_other_type.mamba b/tests/resource/invalid/type/collection/dictionary_use_value_as_other_type.mamba index 56e39b56..b1b7ac2a 100644 --- a/tests/resource/invalid/type/collection/dictionary_use_value_as_other_type.mamba +++ b/tests/resource/invalid/type/collection/dictionary_use_value_as_other_type.mamba @@ -1,3 +1,3 @@ def a := { 20 => "asdf", 30 => "asdf" } -def i: Int := a[20] +def i: Int := a(20) diff --git a/tests/resource/invalid/type/collection/dictionary_wrong_key_type.mamba b/tests/resource/invalid/type/collection/dictionary_wrong_key_type.mamba index bc633d2a..aa931deb 100644 --- a/tests/resource/invalid/type/collection/dictionary_wrong_key_type.mamba +++ b/tests/resource/invalid/type/collection/dictionary_wrong_key_type.mamba @@ -1,3 +1,3 @@ def a := { 20 => "asdf", 30 => "asdf" } -a["asdf"] +a("asdf") diff --git a/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba b/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba index 14ae8f57..cfb32c7c 100644 --- a/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba +++ b/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba @@ -1,5 +1,5 @@ -match 10 - n => - print("10") +match 10 { + n => print("10") +} print(n) diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba index d30bf997..5f74ac09 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba @@ -1,8 +1,7 @@ -class MyClass +class MyClass := { def x: Int def __init__(self) := - if False then - print("something") - else - self.x := 10 + if False then print("something") + else self.x := 10 +} diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba index 705178a5..4990514a 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba @@ -1,10 +1,12 @@ -class MyClass +class MyClass := { def x: Int def __init__(self) := - match 10 + match 10 { 2 => self.x := 2 3 => self.x := 3 4 => print("o") 5 => print("o") _ => print("p") + } +} diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba index ad0c580c..7e050719 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := { def x: Int def __init__(self) := @@ -6,3 +6,4 @@ class MyClass self.x := 10 else print("something") +} diff --git a/tests/resource/invalid/type/control_flow/different_type_shadow.mamba b/tests/resource/invalid/type/control_flow/different_type_shadow.mamba index 73acc202..4b3e4e19 100644 --- a/tests/resource/invalid/type/control_flow/different_type_shadow.mamba +++ b/tests/resource/invalid/type/control_flow/different_type_shadow.mamba @@ -1,3 +1,4 @@ -match 10 +match 10 { n => print(x) +} diff --git a/tests/resource/invalid/type/control_flow/if_not_boolean.mamba b/tests/resource/invalid/type/control_flow/if_not_boolean.mamba index c49e1e96..3dc74fb0 100644 --- a/tests/resource/invalid/type/control_flow/if_not_boolean.mamba +++ b/tests/resource/invalid/type/control_flow/if_not_boolean.mamba @@ -1 +1 @@ -if 0::10 then print("hello world") +if 0..10 then print("hello world") diff --git a/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba b/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba index 73acc202..4b3e4e19 100644 --- a/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba +++ b/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba @@ -1,3 +1,4 @@ -match 10 +match 10 { n => print(x) +} diff --git a/tests/resource/invalid/type/definition/assign_to_function_call.mamba b/tests/resource/invalid/type/definition/assign_to_function_call.mamba index 7640549f..9f55aee0 100644 --- a/tests/resource/invalid/type/definition/assign_to_function_call.mamba +++ b/tests/resource/invalid/type/definition/assign_to_function_call.mamba @@ -1,9 +1,10 @@ -class A +class A := def c: C -class C +class C := { def fin my_field: Int := 10 def my_field_accessor(self) -> Int := self.my_field +} def a := A() a.c.my_field_accessor() := 20 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba index 93f856d4..f15c6cc9 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba @@ -1,9 +1,10 @@ -class A +class A := def c: C -class C +class C := { def my_class: D := D() def my_field_accessor(self) -> D := self.my_class +} class D def fin my_field: Int := 10 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba index 49a0511f..14f32807 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba @@ -1,11 +1,12 @@ -class A +class A := def c: C -class C +class C := { def my_class: D := D() def my_field_accessor(self) -> D := self.my_class +} -class D +class D := def fin my_field: Int := 10 def a := A() diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba index 70fe6342..6a964934 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba @@ -1,11 +1,12 @@ -class A +class A := def c: C -class C +class C := { def fin my_class: D := D() def my_field_accessor(self) -> D := self.my_class +} -class D +class D := def my_field: Int := 10 def a := A() diff --git a/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba b/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba index ad99a104..2ea7b630 100644 --- a/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba +++ b/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba @@ -1,3 +1,4 @@ -class X +class X := { def some_higher_order(self, fun: Int -> Int) -> Int? := return fun(10) def fancy(self) -> Int := return self.some_higher_order(\x: Int := x * 2) +} diff --git a/tests/resource/invalid/type/definition/nested_non_mut_field.mamba b/tests/resource/invalid/type/definition/nested_non_mut_field.mamba index bbe218f7..bad417f7 100644 --- a/tests/resource/invalid/type/definition/nested_non_mut_field.mamba +++ b/tests/resource/invalid/type/definition/nested_non_mut_field.mamba @@ -1,7 +1,7 @@ -class B +class B := def my_num: Int := 10 -class A +class A := def fin b: B diff --git a/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba b/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba index e54997f1..9bf64755 100644 --- a/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba +++ b/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba @@ -1,13 +1,13 @@ -class A +class A := def b: B := B() -class B +class B := def fin c: C := C() -class C +class C := def d: D := D() -class D +class D :+ def e: Int := 100 def a := A() diff --git a/tests/resource/invalid/type/definition/reassign_non_mut.mamba b/tests/resource/invalid/type/definition/reassign_non_mut.mamba index 5f8cf72f..ee2a0654 100644 --- a/tests/resource/invalid/type/definition/reassign_non_mut.mamba +++ b/tests/resource/invalid/type/definition/reassign_non_mut.mamba @@ -1,4 +1,4 @@ -class A +class A := def my_num: Int := 10 diff --git a/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba b/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba index b025f161..bdd44a35 100644 --- a/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba +++ b/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba @@ -1,4 +1,4 @@ -class A +class A := def fin my_num: Int := 10 diff --git a/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba b/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba index d7770a91..4050c3dd 100644 --- a/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba +++ b/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_field: Int := 10 diff --git a/tests/resource/invalid/type/definition/tuple_modify_mut.mamba b/tests/resource/invalid/type/definition/tuple_modify_mut.mamba index ae45897c..5216cbaa 100644 --- a/tests/resource/invalid/type/definition/tuple_modify_mut.mamba +++ b/tests/resource/invalid/type/definition/tuple_modify_mut.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_field: Int := 10 diff --git a/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba b/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba index 0a936dde..e044ba49 100644 --- a/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba +++ b/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_field: Int := 10 diff --git a/tests/resource/invalid/type/error/handle_only_id.mamba b/tests/resource/invalid/type/error/handle_only_id.mamba index 790e6c89..49035cbb 100644 --- a/tests/resource/invalid/type/error/handle_only_id.mamba +++ b/tests/resource/invalid/type/error/handle_only_id.mamba @@ -3,10 +3,13 @@ class MyErr2(msg: String): Exception(msg) def f(x: Int) -> Int ! { MyErr1, MyErr2 } := x -def a := f(10) - MyErr1 => +def a := f(10) ! { + MyErr1 => [ print("Something went wrong") -1 - MyErr2 => + ] + MyErr2 => [ print("Something else went wrong") -2 + ] +} diff --git a/tests/resource/invalid/type/error/unhandled_exception.mamba b/tests/resource/invalid/type/error/unhandled_exception.mamba index f804c2a4..a69ce2e3 100644 --- a/tests/resource/invalid/type/error/unhandled_exception.mamba +++ b/tests/resource/invalid/type/error/unhandled_exception.mamba @@ -1,14 +1,14 @@ class MyException1(msg: String): Exception(msg) class MyException2(msg: String): Exception(msg) -def f(x: Int) -> Int ! { MyException1, MyException2 } := - match x - 0 => 20 - 1 => ! MyException1() - 2 => ! MyException2() +def f(x: Int) -> Int ! { MyException1, MyException2 } := match x { + 0 => 20 + 1 => ! MyException1() + 2 => ! MyException2() +} -def g() -> Int := - f(2) - err: MyException1 => print("a") +def g() -> Int := f(2) ! { + err: MyException1 => print("a") +} g() diff --git a/tests/resource/invalid/type/function/call_mut_function.mamba b/tests/resource/invalid/type/function/call_mut_function.mamba index 5f13a5b3..6f532a52 100644 --- a/tests/resource/invalid/type/function/call_mut_function.mamba +++ b/tests/resource/invalid/type/function/call_mut_function.mamba @@ -1,9 +1,9 @@ -class MyClass +class MyClass := { def a: Int := 20 - def f(fin self, x: Int) -> Int := - # not allowed, self is fin! - self.a := x + 20 + # not allowed, self is fin! + def f(fin self, x: Int) -> Int := self.a := x + 20 +} def fin my_class := MyClass() my_class.f(10) diff --git a/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba b/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba index 2117152f..ed3df670 100644 --- a/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba +++ b/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba @@ -1,8 +1,7 @@ -class MyClass +class MyClass := { def a: Int := 20 - def f(self, x: Int) -> Int := - self.a := x + 20 - + def f(self, x: Int) -> Int := self.a := x + 20 +} def fin my_class := MyClass() # should not be allowed, self is fin! diff --git a/tests/resource/valid/access/access_string_check.py b/tests/resource/valid/access/access_string.py similarity index 100% rename from tests/resource/valid/access/access_string_check.py rename to tests/resource/valid/access/access_string.py diff --git a/tests/resource/valid/access/dictionary_access_check.py b/tests/resource/valid/access/dictionary_access.py similarity index 100% rename from tests/resource/valid/access/dictionary_access_check.py rename to tests/resource/valid/access/dictionary_access.py diff --git a/tests/resource/valid/access/index_via_function.mamba b/tests/resource/valid/access/index_via_function.mamba index af7ac723..a0f19231 100644 --- a/tests/resource/valid/access/index_via_function.mamba +++ b/tests/resource/valid/access/index_via_function.mamba @@ -1,8 +1,8 @@ -def f() -> Slice := 0 :: 2 :: 3 -def g() -> Slice := 0 ::= 2 :: 3 +def f() -> Slice := 0 :: 2 +def g() -> Slice := 0 ::= 2 -def i() -> Range := 0 .. 2 .. 3 -def j() -> Range := 0 ..= 2 .. 3 +def i() -> Range := 0 .. 2 +def j() -> Range := 0 ..= 2 def x := [1, 2, 3] x[f()] diff --git a/tests/resource/valid/access/index_via_function.py b/tests/resource/valid/access/index_via_function.py new file mode 100644 index 00000000..702091c2 --- /dev/null +++ b/tests/resource/valid/access/index_via_function.py @@ -0,0 +1,12 @@ +def f() -> slice: + return slice(0, 2 - 1) +def g() -> slice: + return slice(0, 2) + +def i() -> range: + return range(0, 2) +def j() -> range: + return range(0, 2 + 1) + +x: list[int] = [1, 2, 3] +x[f()] diff --git a/tests/resource/valid/access/index_via_function_check.py b/tests/resource/valid/access/index_via_function_check.py deleted file mode 100644 index 555b1265..00000000 --- a/tests/resource/valid/access/index_via_function_check.py +++ /dev/null @@ -1,12 +0,0 @@ -def f() -> slice: - return slice(0, 2 - 1, 3) -def g() -> slice: - return slice(0, 2, 3) - -def i() -> range: - return range(0, 2, 3) -def j() -> range: - return range(0, 2 + 1, 3) - -x: list[int] = [1, 2, 3] -x[f()] diff --git a/tests/resource/valid/access/simple_index.mamba b/tests/resource/valid/access/simple_index.mamba index 2d592d32..34f556a6 100644 --- a/tests/resource/valid/access/simple_index.mamba +++ b/tests/resource/valid/access/simple_index.mamba @@ -2,4 +2,3 @@ def x := [1, 2, 3] print(x[3]) print(x[0 ::= 2]) -print(x[0 ::= 2 :: 1]) diff --git a/tests/resource/valid/access/simple_index_check.py b/tests/resource/valid/access/simple_index.py similarity index 72% rename from tests/resource/valid/access/simple_index_check.py rename to tests/resource/valid/access/simple_index.py index 8ef20e11..387c0ba3 100644 --- a/tests/resource/valid/access/simple_index_check.py +++ b/tests/resource/valid/access/simple_index.py @@ -2,4 +2,3 @@ print(x[3]) print(x[slice(0,2,1)]) -print(x[slice(0,2,1)]) diff --git a/tests/resource/valid/access/simple_list_access.mamba b/tests/resource/valid/access/simple_list_access.mamba index 7f448fb5..82b48b6d 100644 --- a/tests/resource/valid/access/simple_list_access.mamba +++ b/tests/resource/valid/access/simple_list_access.mamba @@ -1,5 +1,5 @@ def x := [1, 2, 3] x[2] -x[1 :: 2 :: 1] -x[1 ::= 2 :: 1] +x[1 :: 2] +x[1 ::= 2] diff --git a/tests/resource/valid/access/simple_list_access_check.py b/tests/resource/valid/access/simple_list_access.py similarity index 73% rename from tests/resource/valid/access/simple_list_access_check.py rename to tests/resource/valid/access/simple_list_access.py index 59449482..2c10700c 100644 --- a/tests/resource/valid/access/simple_list_access_check.py +++ b/tests/resource/valid/access/simple_list_access.py @@ -2,4 +2,4 @@ x[2] x[slice(1,2 - 1,1)] -x[slice(1,2,1)] +x[slice(1, 2, 1)] diff --git a/tests/resource/valid/call/call_with_class_child_check.py b/tests/resource/valid/call/call_with_class_child.py similarity index 100% rename from tests/resource/valid/call/call_with_class_child_check.py rename to tests/resource/valid/call/call_with_class_child.py diff --git a/tests/resource/valid/call/input.mamba b/tests/resource/valid/call/input.mamba index 9f13b3c3..3e4d86fc 100644 --- a/tests/resource/valid/call/input.mamba +++ b/tests/resource/valid/call/input.mamba @@ -1,7 +1,6 @@ def num := input("Compute factorial: ") -if num.is_digit() then +if num.is_digit() then [ def result := Int(num) print("Factorial {num} is: {result}.") -else - print("Input was not an integer.") +] else print("Input was not an integer.") diff --git a/tests/resource/valid/call/input_check.py b/tests/resource/valid/call/input.py similarity index 100% rename from tests/resource/valid/call/input_check.py rename to tests/resource/valid/call/input.py diff --git a/tests/resource/valid/class/assign_to_nullable_field.mamba b/tests/resource/valid/class/assign_to_nullable_field.mamba index 47c79545..d3a5e304 100644 --- a/tests/resource/valid/class/assign_to_nullable_field.mamba +++ b/tests/resource/valid/class/assign_to_nullable_field.mamba @@ -1,5 +1,6 @@ -class MyServer() +class MyServer() := { def _message: Str? := None def send(self, x: Str) := self._message := x +} diff --git a/tests/resource/valid/class/assign_to_nullable_field_check.py b/tests/resource/valid/class/assign_to_nullable_field.py similarity index 100% rename from tests/resource/valid/class/assign_to_nullable_field_check.py rename to tests/resource/valid/class/assign_to_nullable_field.py diff --git a/tests/resource/valid/class/assign_types_double_nested.mamba b/tests/resource/valid/class/assign_types_double_nested.mamba index 18f922a4..fb5e9674 100644 --- a/tests/resource/valid/class/assign_types_double_nested.mamba +++ b/tests/resource/valid/class/assign_types_double_nested.mamba @@ -1,10 +1,8 @@ class Y(def a: Float) -class X - def y: Y - - def __init__(self, a: Float) := - self.y := Y(a) +class X(a: Float) := { + def y: Y := Y(a) +} def x := X(10) diff --git a/tests/resource/valid/class/assign_types_double_nested_check.py b/tests/resource/valid/class/assign_types_double_nested.py similarity index 100% rename from tests/resource/valid/class/assign_types_double_nested_check.py rename to tests/resource/valid/class/assign_types_double_nested.py diff --git a/tests/resource/valid/class/assign_types_nested_check.py b/tests/resource/valid/class/assign_types_nested.py similarity index 100% rename from tests/resource/valid/class/assign_types_nested_check.py rename to tests/resource/valid/class/assign_types_nested.py diff --git a/tests/resource/valid/class/class_super_one_line_init.mamba b/tests/resource/valid/class/class_super_one_line_init.mamba index 7779b503..0f955b1a 100644 --- a/tests/resource/valid/class/class_super_one_line_init.mamba +++ b/tests/resource/valid/class/class_super_one_line_init.mamba @@ -1,7 +1,6 @@ -class MyType(def super_field: Str) +trait MyType -class MyClass2: MyType("the quick brown fox jumped over the slow donkey") +class MyClass2(other_field: Int, z: Int) := { def fin z_modified: Str := "asdf" - def other_field: Int := 10 - - def __init__(self, other_field: Int, z: Int) := self.other_field := z + other_field + def other_field: Int := z + other_field +} diff --git a/tests/resource/valid/class/class_super_one_line_init_check.py b/tests/resource/valid/class/class_super_one_line_init.py similarity index 100% rename from tests/resource/valid/class/class_super_one_line_init_check.py rename to tests/resource/valid/class/class_super_one_line_init.py diff --git a/tests/resource/valid/class/doc_strings_check.py b/tests/resource/valid/class/doc_strings.py similarity index 100% rename from tests/resource/valid/class/doc_strings_check.py rename to tests/resource/valid/class/doc_strings.py diff --git a/tests/resource/valid/class/fun_with_body_in_interface.mamba b/tests/resource/valid/class/fun_with_body_in_interface.mamba index 48260f63..0fd577e5 100644 --- a/tests/resource/valid/class/fun_with_body_in_interface.mamba +++ b/tests/resource/valid/class/fun_with_body_in_interface.mamba @@ -1,4 +1,4 @@ -type MyType +trait MyType := { def abstract_fun(my_arg: Int) -> Str - def concrete_fun(x: Int) -> Int := return x + 10 +} diff --git a/tests/resource/valid/class/fun_with_body_in_interface_check.py b/tests/resource/valid/class/fun_with_body_in_interface.py similarity index 100% rename from tests/resource/valid/class/fun_with_body_in_interface_check.py rename to tests/resource/valid/class/fun_with_body_in_interface.py diff --git a/tests/resource/valid/class/generic_unknown_type_unused.mamba b/tests/resource/valid/class/generic_unknown_type_unused.mamba index 3918e96f..0b5c1fbe 100644 --- a/tests/resource/valid/class/generic_unknown_type_unused.mamba +++ b/tests/resource/valid/class/generic_unknown_type_unused.mamba @@ -1,2 +1,2 @@ -class MyClass[A: UnknownType] +class MyClass[A: UnknownType] := def f() -> Int := 10 diff --git a/tests/resource/valid/class/generic_unknown_type_unused_check.py b/tests/resource/valid/class/generic_unknown_type_unused.py similarity index 100% rename from tests/resource/valid/class/generic_unknown_type_unused_check.py rename to tests/resource/valid/class/generic_unknown_type_unused.py diff --git a/tests/resource/valid/class/generics_check.py b/tests/resource/valid/class/generics.py similarity index 100% rename from tests/resource/valid/class/generics_check.py rename to tests/resource/valid/class/generics.py diff --git a/tests/resource/valid/class/import.mamba b/tests/resource/valid/class/import.mamba index f732ff07..a4339762 100644 --- a/tests/resource/valid/class/import.mamba +++ b/tests/resource/valid/class/import.mamba @@ -1,4 +1,4 @@ -from a import b,c as c,d -from b import c +import a::{b as c,c as d} +import b::c import d -import d as dd +import d::dd diff --git a/tests/resource/valid/class/import_check.py b/tests/resource/valid/class/import.py similarity index 100% rename from tests/resource/valid/class/import_check.py rename to tests/resource/valid/class/import.py diff --git a/tests/resource/valid/class/multiple_parent.mamba b/tests/resource/valid/class/multiple_parent.mamba index 70ec513f..ef7613df 100644 --- a/tests/resource/valid/class/multiple_parent.mamba +++ b/tests/resource/valid/class/multiple_parent.mamba @@ -1,5 +1,6 @@ -class MyType(a: Str) -class MyType2(b: Str) +trait MyType +trait MyType2 -class MyClass1: MyType("asdf"), MyType2("qwerty") +class MyClass1: { MyType, MyType2 } := { def other: Int +} diff --git a/tests/resource/valid/class/multiple_parent_check.py b/tests/resource/valid/class/multiple_parent.py similarity index 100% rename from tests/resource/valid/class/multiple_parent_check.py rename to tests/resource/valid/class/multiple_parent.py diff --git a/tests/resource/valid/class/parent.mamba b/tests/resource/valid/class/parent.mamba index 6be9fefc..9be95f5e 100644 --- a/tests/resource/valid/class/parent.mamba +++ b/tests/resource/valid/class/parent.mamba @@ -1,6 +1,11 @@ -type MyType +trait MyType := { def fun_a(self) def factorial(self, x: Int) -> Int +} -class MyClass1: MyType("asdf") +class MyClass1: MyType := { def other: Int + + def fun_a(self) := print("hello) + def factorial(self, x: Int) -> Int := x * 1 +} diff --git a/tests/resource/valid/class/parent_check.py b/tests/resource/valid/class/parent.py similarity index 100% rename from tests/resource/valid/class/parent_check.py rename to tests/resource/valid/class/parent.py diff --git a/tests/resource/valid/class/print_types_double_nested.mamba b/tests/resource/valid/class/print_types_double_nested.mamba index aaaebe84..ad682e7c 100644 --- a/tests/resource/valid/class/print_types_double_nested.mamba +++ b/tests/resource/valid/class/print_types_double_nested.mamba @@ -1,10 +1,8 @@ class Y(def a: Float) -class X - def y: Y - - def __init__(self, a: Float) := - self.y := Y(a) +class X(a: Float) := { + def y: Y := Y(a) +} def x := X(10) diff --git a/tests/resource/valid/class/print_types_double_nested_check.py b/tests/resource/valid/class/print_types_double_nested.py similarity index 100% rename from tests/resource/valid/class/print_types_double_nested_check.py rename to tests/resource/valid/class/print_types_double_nested.py diff --git a/tests/resource/valid/class/same_var_different_type.mamba b/tests/resource/valid/class/same_var_different_type.mamba index 7e92e556..913a689b 100644 --- a/tests/resource/valid/class/same_var_different_type.mamba +++ b/tests/resource/valid/class/same_var_different_type.mamba @@ -1,7 +1,9 @@ -class C1 +class C1 := { def a: Str := "str" def f(self) -> Str := self.a +} -class C2 +class C2 := { def a: Int := 10 def f(self) -> Str := self.a +} diff --git a/tests/resource/valid/class/same_var_different_type_check.py b/tests/resource/valid/class/same_var_different_type.py similarity index 100% rename from tests/resource/valid/class/same_var_different_type_check.py rename to tests/resource/valid/class/same_var_different_type.py diff --git a/tests/resource/valid/class/shadow.mamba b/tests/resource/valid/class/shadow.mamba index 13cfce15..f6cc68a0 100644 --- a/tests/resource/valid/class/shadow.mamba +++ b/tests/resource/valid/class/shadow.mamba @@ -1,10 +1,10 @@ -class MyClass1 +class MyClass1 := def f1(self) := print("1") -class MyClass2 +class MyClass2 := def f2(self) := print("2") -class MyClass3 +class MyClass3 := def f3(self) := print("3") -class MyClass4 +class MyClass4 := def f4(self) := print("4") def x := MyClass1() @@ -13,7 +13,7 @@ x.f1() def x := MyClass2() x.f2() -class MyClass +class MyClass := { def x: MyClass3 := MyClass3() def g() := @@ -21,3 +21,4 @@ class MyClass def f(x: MyClass4) := x.f4() +} diff --git a/tests/resource/valid/class/shadow_check.py b/tests/resource/valid/class/shadow.py similarity index 100% rename from tests/resource/valid/class/shadow_check.py rename to tests/resource/valid/class/shadow.py diff --git a/tests/resource/valid/class/simple_class.mamba b/tests/resource/valid/class/simple_class.mamba index 49c756f0..ef8d07b1 100644 --- a/tests/resource/valid/class/simple_class.mamba +++ b/tests/resource/valid/class/simple_class.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_function() -> Int := 10 def my_class := MyClass() diff --git a/tests/resource/valid/class/simple_class.py b/tests/resource/valid/class/simple_class.py new file mode 100644 index 00000000..7af66742 --- /dev/null +++ b/tests/resource/valid/class/simple_class.py @@ -0,0 +1,5 @@ +class MyClass : + def my_function() -> int: 10 + +my_class = MyClass() +print(my_class.my_function()) diff --git a/tests/resource/valid/class/top_level_tuple_check.py b/tests/resource/valid/class/top_level_tuple.py similarity index 100% rename from tests/resource/valid/class/top_level_tuple_check.py rename to tests/resource/valid/class/top_level_tuple.py diff --git a/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba b/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba index b41accc8..b3d8d840 100644 --- a/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba +++ b/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba @@ -1,5 +1,5 @@ -class X +class X := { def y: Int? - - def __init__(self) := - print("No assignments here!") + # typically, statements are evaluated in order, but Mamba makes no guarantees about this behaviour! + print("No assignments here!") +} diff --git a/tests/resource/valid/class/top_level_unassigned_but_nullable_check.py b/tests/resource/valid/class/top_level_unassigned_but_nullable.py similarity index 100% rename from tests/resource/valid/class/top_level_unassigned_but_nullable_check.py rename to tests/resource/valid/class/top_level_unassigned_but_nullable.py diff --git a/tests/resource/valid/class/tuple_as_class_check.py b/tests/resource/valid/class/tuple_as_class.py similarity index 100% rename from tests/resource/valid/class/tuple_as_class_check.py rename to tests/resource/valid/class/tuple_as_class.py diff --git a/tests/resource/valid/class/types.mamba b/tests/resource/valid/class/types.mamba index c84d33c8..4e965d21 100644 --- a/tests/resource/valid/class/types.mamba +++ b/tests/resource/valid/class/types.mamba @@ -1,21 +1,21 @@ class MyGeneric: Str -class MyType(def some_field: Str) type SomeState: MyClass when self.private_field > 2 -type OtherState: MyClass when +type OtherState: MyClass when { self.private_field > 10 self.private_field < 200 self.required_field < 50 +} -type SuperInterface +trait SuperInterface := def bar: Int -type MyInterface: SuperInterface +trait MyInterface: SuperInterface := def required_field: Int - def higher_order(self) -> int + def higher_order(self) -> Int # some class -class MyClass(def my_field: Int, other_field: Str := "Hello"): MyType(other_field), MyInterface +class MyClass(def my_field: Int, other_field: Str := "Hello"): MyInterface def required_field: Int := 100 def private_field: Int := 20 diff --git a/tests/resource/valid/class/types_check.py b/tests/resource/valid/class/types.py similarity index 100% rename from tests/resource/valid/class/types_check.py rename to tests/resource/valid/class/types.py diff --git a/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba b/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba index 60d0ce1a..c47441db 100644 --- a/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba +++ b/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba @@ -1,5 +1,3 @@ -class X - def (y, z): (Int, Int?) - - def __init__(self) := - self.y := 10 +class X := { + def (y, z): (Int, Int?) := (10, None) +} diff --git a/tests/resource/valid/class/unassigned_tuple_second_nullable_check.py b/tests/resource/valid/class/unassigned_tuple_second_nullable.py similarity index 100% rename from tests/resource/valid/class/unassigned_tuple_second_nullable_check.py rename to tests/resource/valid/class/unassigned_tuple_second_nullable.py diff --git a/tests/resource/valid/class/var_from_outside_class.mamba b/tests/resource/valid/class/var_from_outside_class.mamba index 6ffba234..8dd89735 100644 --- a/tests/resource/valid/class/var_from_outside_class.mamba +++ b/tests/resource/valid/class/var_from_outside_class.mamba @@ -1,5 +1,4 @@ def message := "may be mutable, for now" -class MyClass - def f(self) := - print(message) +class MyClass := + def f(self) := print(message) diff --git a/tests/resource/valid/class/var_from_outside_class_check.py b/tests/resource/valid/class/var_from_outside_class.py similarity index 100% rename from tests/resource/valid/class/var_from_outside_class_check.py rename to tests/resource/valid/class/var_from_outside_class.py diff --git a/tests/resource/valid/class/with_generics_check.py b/tests/resource/valid/class/with_generics.py similarity index 100% rename from tests/resource/valid/class/with_generics_check.py rename to tests/resource/valid/class/with_generics.py diff --git a/tests/resource/valid/collection/collection_type_check.py b/tests/resource/valid/collection/collection_type.py similarity index 100% rename from tests/resource/valid/collection/collection_type_check.py rename to tests/resource/valid/collection/collection_type.py diff --git a/tests/resource/valid/collection/dictionary_check.py b/tests/resource/valid/collection/dictionary.py similarity index 100% rename from tests/resource/valid/collection/dictionary_check.py rename to tests/resource/valid/collection/dictionary.py diff --git a/tests/resource/valid/collection/dictionary_builder_check.py b/tests/resource/valid/collection/dictionary_builder.py similarity index 100% rename from tests/resource/valid/collection/dictionary_builder_check.py rename to tests/resource/valid/collection/dictionary_builder.py diff --git a/tests/resource/valid/collection/dictionary_in_fun_check.py b/tests/resource/valid/collection/dictionary_in_fun.py similarity index 100% rename from tests/resource/valid/collection/dictionary_in_fun_check.py rename to tests/resource/valid/collection/dictionary_in_fun.py diff --git a/tests/resource/valid/collection/infer_collection_type_check.py b/tests/resource/valid/collection/infer_collection_type.py similarity index 100% rename from tests/resource/valid/collection/infer_collection_type_check.py rename to tests/resource/valid/collection/infer_collection_type.py diff --git a/tests/resource/valid/collection/infer_collection_type_for_fun_check.py b/tests/resource/valid/collection/infer_collection_type_for_fun.py similarity index 100% rename from tests/resource/valid/collection/infer_collection_type_for_fun_check.py rename to tests/resource/valid/collection/infer_collection_type_for_fun.py diff --git a/tests/resource/valid/collection/list_check.py b/tests/resource/valid/collection/list.py similarity index 100% rename from tests/resource/valid/collection/list_check.py rename to tests/resource/valid/collection/list.py diff --git a/tests/resource/valid/collection/nested_list_builder_check.py b/tests/resource/valid/collection/nested_list_builder.py similarity index 100% rename from tests/resource/valid/collection/nested_list_builder_check.py rename to tests/resource/valid/collection/nested_list_builder.py diff --git a/tests/resource/valid/collection/nested_set_builder_check.py b/tests/resource/valid/collection/nested_set_builder.py similarity index 100% rename from tests/resource/valid/collection/nested_set_builder_check.py rename to tests/resource/valid/collection/nested_set_builder.py diff --git a/tests/resource/valid/collection/set_check.py b/tests/resource/valid/collection/set.py similarity index 99% rename from tests/resource/valid/collection/set_check.py rename to tests/resource/valid/collection/set.py index f7336ce7..d56b5344 100644 --- a/tests/resource/valid/collection/set_check.py +++ b/tests/resource/valid/collection/set.py @@ -1,4 +1,5 @@ from typing import Any, Tuple + a: set[Any] = {} b: set[int] = { 10, 20 } c: set[set[Any]] = { {}, {} } diff --git a/tests/resource/valid/collection/set_union_check.py b/tests/resource/valid/collection/set_union.py similarity index 98% rename from tests/resource/valid/collection/set_union_check.py rename to tests/resource/valid/collection/set_union.py index ccdff723..8f48ab33 100644 --- a/tests/resource/valid/collection/set_union_check.py +++ b/tests/resource/valid/collection/set_union.py @@ -1,2 +1,3 @@ from typing import Union + b: set[Union[bool, int, str]] = { 10, "string", True } diff --git a/tests/resource/valid/control_flow/assign_if_check.py b/tests/resource/valid/control_flow/assign_if.py similarity index 100% rename from tests/resource/valid/control_flow/assign_if_check.py rename to tests/resource/valid/control_flow/assign_if.py diff --git a/tests/resource/valid/control_flow/assign_match_check.py b/tests/resource/valid/control_flow/assign_match.py similarity index 100% rename from tests/resource/valid/control_flow/assign_match_check.py rename to tests/resource/valid/control_flow/assign_match.py diff --git a/tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if_check.py b/tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if.py similarity index 100% rename from tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if_check.py rename to tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if.py diff --git a/tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match_check.py b/tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match.py similarity index 100% rename from tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match_check.py rename to tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match.py diff --git a/tests/resource/valid/control_flow/double_assign_if_check.py b/tests/resource/valid/control_flow/double_assign_if.py similarity index 100% rename from tests/resource/valid/control_flow/double_assign_if_check.py rename to tests/resource/valid/control_flow/double_assign_if.py diff --git a/tests/resource/valid/control_flow/for_over_collection_of_tuple_check.py b/tests/resource/valid/control_flow/for_over_collection_of_tuple.py similarity index 100% rename from tests/resource/valid/control_flow/for_over_collection_of_tuple_check.py rename to tests/resource/valid/control_flow/for_over_collection_of_tuple.py diff --git a/tests/resource/valid/control_flow/for_over_range_from_func_check.py b/tests/resource/valid/control_flow/for_over_range_from_func.py similarity index 100% rename from tests/resource/valid/control_flow/for_over_range_from_func_check.py rename to tests/resource/valid/control_flow/for_over_range_from_func.py diff --git a/tests/resource/valid/control_flow/for_over_type_union_check.py b/tests/resource/valid/control_flow/for_over_type_union.py similarity index 100% rename from tests/resource/valid/control_flow/for_over_type_union_check.py rename to tests/resource/valid/control_flow/for_over_type_union.py diff --git a/tests/resource/valid/control_flow/for_statements_check.py b/tests/resource/valid/control_flow/for_statements.py similarity index 100% rename from tests/resource/valid/control_flow/for_statements_check.py rename to tests/resource/valid/control_flow/for_statements.py diff --git a/tests/resource/valid/control_flow/handle_in_if_check.py b/tests/resource/valid/control_flow/handle_in_if.py similarity index 100% rename from tests/resource/valid/control_flow/handle_in_if_check.py rename to tests/resource/valid/control_flow/handle_in_if.py diff --git a/tests/resource/valid/control_flow/if_in_for_loop_check.py b/tests/resource/valid/control_flow/if_in_for_loop.py similarity index 100% rename from tests/resource/valid/control_flow/if_in_for_loop_check.py rename to tests/resource/valid/control_flow/if_in_for_loop.py diff --git a/tests/resource/valid/control_flow/if_in_if_cond_check.py b/tests/resource/valid/control_flow/if_in_if_cond.py similarity index 100% rename from tests/resource/valid/control_flow/if_in_if_cond_check.py rename to tests/resource/valid/control_flow/if_in_if_cond.py diff --git a/tests/resource/valid/control_flow/if_two_types_check.py b/tests/resource/valid/control_flow/if_two_types.py similarity index 100% rename from tests/resource/valid/control_flow/if_two_types_check.py rename to tests/resource/valid/control_flow/if_two_types.py diff --git a/tests/resource/valid/control_flow/match_dont_remove_shadowed_check.py b/tests/resource/valid/control_flow/match_dont_remove_shadowed.py similarity index 100% rename from tests/resource/valid/control_flow/match_dont_remove_shadowed_check.py rename to tests/resource/valid/control_flow/match_dont_remove_shadowed.py diff --git a/tests/resource/valid/control_flow/match_stmt_check.py b/tests/resource/valid/control_flow/match_stmt.py similarity index 100% rename from tests/resource/valid/control_flow/match_stmt_check.py rename to tests/resource/valid/control_flow/match_stmt.py diff --git a/tests/resource/valid/control_flow/matches_in_if_check.py b/tests/resource/valid/control_flow/matches_in_if.py similarity index 100% rename from tests/resource/valid/control_flow/matches_in_if_check.py rename to tests/resource/valid/control_flow/matches_in_if.py diff --git a/tests/resource/valid/control_flow/shadow_in_if_arms_check.py b/tests/resource/valid/control_flow/shadow_in_if_arms.py similarity index 100% rename from tests/resource/valid/control_flow/shadow_in_if_arms_check.py rename to tests/resource/valid/control_flow/shadow_in_if_arms.py diff --git a/tests/resource/valid/control_flow/shadow_in_if_arms_else_check.py b/tests/resource/valid/control_flow/shadow_in_if_arms_else.py similarity index 100% rename from tests/resource/valid/control_flow/shadow_in_if_arms_else_check.py rename to tests/resource/valid/control_flow/shadow_in_if_arms_else.py diff --git a/tests/resource/valid/control_flow/shadow_in_if_arms_then_check.py b/tests/resource/valid/control_flow/shadow_in_if_arms_then.py similarity index 100% rename from tests/resource/valid/control_flow/shadow_in_if_arms_then_check.py rename to tests/resource/valid/control_flow/shadow_in_if_arms_then.py diff --git a/tests/resource/valid/control_flow/while_check.py b/tests/resource/valid/control_flow/while.py similarity index 100% rename from tests/resource/valid/control_flow/while_check.py rename to tests/resource/valid/control_flow/while.py diff --git a/tests/resource/valid/definition/all_mutable_in_call_chain.mamba b/tests/resource/valid/definition/all_mutable_in_call_chain.mamba index 276983cd..1eb49144 100644 --- a/tests/resource/valid/definition/all_mutable_in_call_chain.mamba +++ b/tests/resource/valid/definition/all_mutable_in_call_chain.mamba @@ -1,13 +1,13 @@ -class A +class A := def b: B := B() -class B +class B := def c: C := C() -class C +class C := def d: D := D() -class D +class D := def e: Int := 100 def a := A() diff --git a/tests/resource/valid/definition/all_mutable_in_call_chain.py b/tests/resource/valid/definition/all_mutable_in_call_chain.py new file mode 100644 index 00000000..edf1a189 --- /dev/null +++ b/tests/resource/valid/definition/all_mutable_in_call_chain.py @@ -0,0 +1,14 @@ +class A : + b: B = B() + +class B : + c: C = C() + +class C : + d: D = D() + +class D : + e: int = 100 + +a = A() +a.b.c.d.e = 20 diff --git a/tests/resource/valid/definition/assign_to_inner_mut.mamba b/tests/resource/valid/definition/assign_to_inner_mut.mamba index cd212ecc..d0658e45 100644 --- a/tests/resource/valid/definition/assign_to_inner_mut.mamba +++ b/tests/resource/valid/definition/assign_to_inner_mut.mamba @@ -1,11 +1,12 @@ -class A +class A := def c: C -class C +class C := { def my_class: D := D() - def my_field_accessor(self) -> D => self.my_class + def my_field_accessor(self) -> D := self.my_class +} -class D +class D := def my_field: Int := 10 def a := A() diff --git a/tests/resource/valid/definition/assign_to_inner_mut.py b/tests/resource/valid/definition/assign_to_inner_mut.py new file mode 100644 index 00000000..3668c326 --- /dev/null +++ b/tests/resource/valid/definition/assign_to_inner_mut.py @@ -0,0 +1,12 @@ +class A: + c: C + +class C: + my_class: D = D() + def my_field_accessor(self) -> D: self.my_class + +class D: + my_field: int = 10 + +a = A() +a.c.my_field_accessor().my_field = 20 diff --git a/tests/resource/valid/definition/assign_to_nullable_in_function_check.py b/tests/resource/valid/definition/assign_to_nullable_in_function.py similarity index 100% rename from tests/resource/valid/definition/assign_to_nullable_in_function_check.py rename to tests/resource/valid/definition/assign_to_nullable_in_function.py diff --git a/tests/resource/valid/definition/assign_tuples_check.py b/tests/resource/valid/definition/assign_tuples.py similarity index 100% rename from tests/resource/valid/definition/assign_tuples_check.py rename to tests/resource/valid/definition/assign_tuples.py diff --git a/tests/resource/valid/definition/assign_with_if_check.py b/tests/resource/valid/definition/assign_with_if.py similarity index 100% rename from tests/resource/valid/definition/assign_with_if_check.py rename to tests/resource/valid/definition/assign_with_if.py diff --git a/tests/resource/valid/definition/assign_with_if_different_types_check.py b/tests/resource/valid/definition/assign_with_if_different_types.py similarity index 100% rename from tests/resource/valid/definition/assign_with_if_different_types_check.py rename to tests/resource/valid/definition/assign_with_if_different_types.py diff --git a/tests/resource/valid/definition/assign_with_match.mamba b/tests/resource/valid/definition/assign_with_match.mamba index bc141f88..fcbef27c 100644 --- a/tests/resource/valid/definition/assign_with_match.mamba +++ b/tests/resource/valid/definition/assign_with_match.mamba @@ -1,4 +1,5 @@ -def a := match 40 +def a := match 40 { 2 => 3 4 => 30 _ => 300 +} diff --git a/tests/resource/valid/definition/assign_with_match_check.py b/tests/resource/valid/definition/assign_with_match.py similarity index 100% rename from tests/resource/valid/definition/assign_with_match_check.py rename to tests/resource/valid/definition/assign_with_match.py diff --git a/tests/resource/valid/definition/assign_with_match_different_types.mamba b/tests/resource/valid/definition/assign_with_match_different_types.mamba index f3eacf01..7ba92041 100644 --- a/tests/resource/valid/definition/assign_with_match_different_types.mamba +++ b/tests/resource/valid/definition/assign_with_match_different_types.mamba @@ -2,7 +2,8 @@ class MyClass class MyClass1 class MyClass2 -def a:= match 40 +def a:= match 40 { 2 => MyClass() 4 => MyClass1() _ => MyClass2() +} diff --git a/tests/resource/valid/definition/assign_with_match_different_types_check.py b/tests/resource/valid/definition/assign_with_match_different_types.py similarity index 100% rename from tests/resource/valid/definition/assign_with_match_different_types_check.py rename to tests/resource/valid/definition/assign_with_match_different_types.py diff --git a/tests/resource/valid/definition/assign_with_match_type_annotation.mamba b/tests/resource/valid/definition/assign_with_match_type_annotation.mamba index 5b962175..6b93275f 100644 --- a/tests/resource/valid/definition/assign_with_match_type_annotation.mamba +++ b/tests/resource/valid/definition/assign_with_match_type_annotation.mamba @@ -1,4 +1,5 @@ -def a: Int := match 40 +def a: Int := match 40 { 2 => 3 4 => 30 _ => 300 +} diff --git a/tests/resource/valid/definition/assign_with_match_type_annotation_check.py b/tests/resource/valid/definition/assign_with_match_type_annotation.py similarity index 100% rename from tests/resource/valid/definition/assign_with_match_type_annotation_check.py rename to tests/resource/valid/definition/assign_with_match_type_annotation.py diff --git a/tests/resource/valid/definition/assign_with_nested_if.mamba b/tests/resource/valid/definition/assign_with_nested_if.mamba index ca47c092..ec887c4c 100644 --- a/tests/resource/valid/definition/assign_with_nested_if.mamba +++ b/tests/resource/valid/definition/assign_with_nested_if.mamba @@ -1,8 +1,8 @@ def x := 20 def a: Int := if x > 10 then - if x > 30 then + if x > 30 then [ print("string") - x + 1 + x + 1 ] else x - 1 else diff --git a/tests/resource/valid/definition/assign_with_nested_if_check.py b/tests/resource/valid/definition/assign_with_nested_if.py similarity index 100% rename from tests/resource/valid/definition/assign_with_nested_if_check.py rename to tests/resource/valid/definition/assign_with_nested_if.py diff --git a/tests/resource/valid/definition/assign_with_try_except.mamba b/tests/resource/valid/definition/assign_with_try_except.mamba index a4dfcea7..55a2859e 100644 --- a/tests/resource/valid/definition/assign_with_try_except.mamba +++ b/tests/resource/valid/definition/assign_with_try_except.mamba @@ -1,5 +1,6 @@ def g() -> Int ! Exception := ! Exception("A") -def a := g() +def a := g() ! { err: Exception => 10 +} diff --git a/tests/resource/valid/definition/assign_with_try_except_check.py b/tests/resource/valid/definition/assign_with_try_except.py similarity index 100% rename from tests/resource/valid/definition/assign_with_try_except_check.py rename to tests/resource/valid/definition/assign_with_try_except.py diff --git a/tests/resource/valid/definition/collection_in_f_strings_check.py b/tests/resource/valid/definition/collection_in_f_strings.py similarity index 100% rename from tests/resource/valid/definition/collection_in_f_strings_check.py rename to tests/resource/valid/definition/collection_in_f_strings.py diff --git a/tests/resource/valid/definition/f_strings_check.py b/tests/resource/valid/definition/f_strings.py similarity index 100% rename from tests/resource/valid/definition/f_strings_check.py rename to tests/resource/valid/definition/f_strings.py diff --git a/tests/resource/valid/definition/function_no_return_type_check.py b/tests/resource/valid/definition/function_no_return_type.py similarity index 100% rename from tests/resource/valid/definition/function_no_return_type_check.py rename to tests/resource/valid/definition/function_no_return_type.py diff --git a/tests/resource/valid/definition/function_ret_super_check.py b/tests/resource/valid/definition/function_ret_super.py similarity index 100% rename from tests/resource/valid/definition/function_ret_super_check.py rename to tests/resource/valid/definition/function_ret_super.py diff --git a/tests/resource/valid/definition/function_ret_super_in_class.mamba b/tests/resource/valid/definition/function_ret_super_in_class.mamba index 2187eaf2..660a24d3 100644 --- a/tests/resource/valid/definition/function_ret_super_in_class.mamba +++ b/tests/resource/valid/definition/function_ret_super_in_class.mamba @@ -1,6 +1,7 @@ -class X: +class X := { def some_higher_order(self, fun: Int -> Int) -> Int := fun(10) def fancy(self) -> Int? := self.some_higher_order(\x: Int := x * 2) +} def x := X() x.fancy() diff --git a/tests/resource/valid/definition/function_ret_super_in_class_check.py b/tests/resource/valid/definition/function_ret_super_in_class.py similarity index 100% rename from tests/resource/valid/definition/function_ret_super_in_class_check.py rename to tests/resource/valid/definition/function_ret_super_in_class.py diff --git a/tests/resource/valid/definition/function_with_if_check.py b/tests/resource/valid/definition/function_with_if.py similarity index 100% rename from tests/resource/valid/definition/function_with_if_check.py rename to tests/resource/valid/definition/function_with_if.py diff --git a/tests/resource/valid/definition/function_with_if_and_raise_check.py b/tests/resource/valid/definition/function_with_if_and_raise.py similarity index 100% rename from tests/resource/valid/definition/function_with_if_and_raise_check.py rename to tests/resource/valid/definition/function_with_if_and_raise.py diff --git a/tests/resource/valid/definition/function_with_match.mamba b/tests/resource/valid/definition/function_with_match.mamba index 89fd717b..9b8fb183 100644 --- a/tests/resource/valid/definition/function_with_match.mamba +++ b/tests/resource/valid/definition/function_with_match.mamba @@ -1,5 +1,7 @@ def f(x: Int) -> Int := - match x + match x { 2 => 3 4 => 30 _ => 300 + } + \ No newline at end of file diff --git a/tests/resource/valid/definition/function_with_match_check.py b/tests/resource/valid/definition/function_with_match.py similarity index 100% rename from tests/resource/valid/definition/function_with_match_check.py rename to tests/resource/valid/definition/function_with_match.py diff --git a/tests/resource/valid/definition/function_with_nested_if.mamba b/tests/resource/valid/definition/function_with_nested_if.mamba index 014f52ab..eaba8c3d 100644 --- a/tests/resource/valid/definition/function_with_nested_if.mamba +++ b/tests/resource/valid/definition/function_with_nested_if.mamba @@ -1,8 +1,8 @@ def f(x: Int) -> Int := if x > 10 then - if x > 30 then + if x > 30 then [ print("string") - x + 1 + x + 1 ] else x - 1 else diff --git a/tests/resource/valid/definition/function_with_nested_if_check.py b/tests/resource/valid/definition/function_with_nested_if.py similarity index 100% rename from tests/resource/valid/definition/function_with_nested_if_check.py rename to tests/resource/valid/definition/function_with_nested_if.py diff --git a/tests/resource/valid/definition/long_f_string_check.py b/tests/resource/valid/definition/long_f_string.py similarity index 100% rename from tests/resource/valid/definition/long_f_string_check.py rename to tests/resource/valid/definition/long_f_string.py diff --git a/tests/resource/valid/definition/nested_function.mamba b/tests/resource/valid/definition/nested_function.mamba index 00922aaa..1d7ad8ba 100644 --- a/tests/resource/valid/definition/nested_function.mamba +++ b/tests/resource/valid/definition/nested_function.mamba @@ -1,10 +1,10 @@ -class A +class A := def b: B -class B +class B := def my_c(self) -> C := C() -class C +class C := def fin my_field: Int := 10 def a := A() diff --git a/tests/resource/valid/definition/nested_function.py b/tests/resource/valid/definition/nested_function.py new file mode 100644 index 00000000..7684083b --- /dev/null +++ b/tests/resource/valid/definition/nested_function.py @@ -0,0 +1,12 @@ +class A: + b: B + +class B: + def my_c(self) -> C: + C() + +class C: + my_field: Int = 10 + +a = A() +print(a.b.my_c().my_field) diff --git a/tests/resource/valid/definition/nested_mut_field.mamba b/tests/resource/valid/definition/nested_mut_field.mamba index 9e04d2eb..b37332b4 100644 --- a/tests/resource/valid/definition/nested_mut_field.mamba +++ b/tests/resource/valid/definition/nested_mut_field.mamba @@ -1,7 +1,7 @@ -class B +class B := def my_num: Int := 10 -class A +class A := def b: B diff --git a/tests/resource/valid/definition/nested_mut_field_check.py b/tests/resource/valid/definition/nested_mut_field.py similarity index 100% rename from tests/resource/valid/definition/nested_mut_field_check.py rename to tests/resource/valid/definition/nested_mut_field.py diff --git a/tests/resource/valid/definition/ternary_check.py b/tests/resource/valid/definition/ternary.py similarity index 100% rename from tests/resource/valid/definition/ternary_check.py rename to tests/resource/valid/definition/ternary.py diff --git a/tests/resource/valid/definition/tuple_modify_mut.mamba b/tests/resource/valid/definition/tuple_modify_mut.mamba index adb2bd0f..7964e55b 100644 --- a/tests/resource/valid/definition/tuple_modify_mut.mamba +++ b/tests/resource/valid/definition/tuple_modify_mut.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_field: Int := 10 diff --git a/tests/resource/valid/definition/tuple_modify_mut_check.py b/tests/resource/valid/definition/tuple_modify_mut.py similarity index 100% rename from tests/resource/valid/definition/tuple_modify_mut_check.py rename to tests/resource/valid/definition/tuple_modify_mut.py diff --git a/tests/resource/valid/definition/tuple_modify_outer_mut.mamba b/tests/resource/valid/definition/tuple_modify_outer_mut.mamba index adb2bd0f..7964e55b 100644 --- a/tests/resource/valid/definition/tuple_modify_outer_mut.mamba +++ b/tests/resource/valid/definition/tuple_modify_outer_mut.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_field: Int := 10 diff --git a/tests/resource/valid/definition/tuple_modify_outer_mut_check.py b/tests/resource/valid/definition/tuple_modify_outer_mut.py similarity index 100% rename from tests/resource/valid/definition/tuple_modify_outer_mut_check.py rename to tests/resource/valid/definition/tuple_modify_outer_mut.py diff --git a/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba b/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba index 57181c36..b163df63 100644 --- a/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba +++ b/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass := def my_field: Int := 10 def f() -> (MyClass, Int) := diff --git a/tests/resource/valid/definition/tuple_non_lit_modify_mut_check.py b/tests/resource/valid/definition/tuple_non_lit_modify_mut.py similarity index 100% rename from tests/resource/valid/definition/tuple_non_lit_modify_mut_check.py rename to tests/resource/valid/definition/tuple_non_lit_modify_mut.py diff --git a/tests/resource/valid/doc_check.py b/tests/resource/valid/doc.py similarity index 100% rename from tests/resource/valid/doc_check.py rename to tests/resource/valid/doc.py diff --git a/tests/resource/valid/empty_file_check.py b/tests/resource/valid/empty_file.py similarity index 100% rename from tests/resource/valid/empty_file_check.py rename to tests/resource/valid/empty_file.py diff --git a/tests/resource/valid/error/exception_check.py b/tests/resource/valid/error/exception.py similarity index 100% rename from tests/resource/valid/error/exception_check.py rename to tests/resource/valid/error/exception.py diff --git a/tests/resource/valid/error/exception_in_fun.mamba b/tests/resource/valid/error/exception_in_fun.mamba index abcb9587..4084511d 100644 --- a/tests/resource/valid/error/exception_in_fun.mamba +++ b/tests/resource/valid/error/exception_in_fun.mamba @@ -1,6 +1,6 @@ def g() -> Int ! Exception := ! Exception("A") -def f(x: Int) -> Int := - g() - err: Exception => x + 10 +def f(x: Int) -> Int := g() ! { + err: Exception => x + 10 +} diff --git a/tests/resource/valid/error/exception_in_fun_check.py b/tests/resource/valid/error/exception_in_fun.py similarity index 100% rename from tests/resource/valid/error/exception_in_fun_check.py rename to tests/resource/valid/error/exception_in_fun.py diff --git a/tests/resource/valid/error/exception_in_fun_super.mamba b/tests/resource/valid/error/exception_in_fun_super.mamba index b73e9881..7d4db65c 100644 --- a/tests/resource/valid/error/exception_in_fun_super.mamba +++ b/tests/resource/valid/error/exception_in_fun_super.mamba @@ -1,8 +1,7 @@ class MyException(msg: Str): Exception(msg) -def g() -> Int ! MyException := - ! MyException("A") +def g() -> Int ! MyException := ! MyException("A") -def f(x: Int) -> Int := - g() - err: Exception => x + 10 +def f(x: Int) -> Int := g() ! { + err: Exception => x + 10 +} diff --git a/tests/resource/valid/error/exception_in_fun_super_check.py b/tests/resource/valid/error/exception_in_fun_super.py similarity index 100% rename from tests/resource/valid/error/exception_in_fun_super_check.py rename to tests/resource/valid/error/exception_in_fun_super.py diff --git a/tests/resource/valid/error/handle.mamba b/tests/resource/valid/error/handle.mamba index 6a5db48a..81a0b495 100644 --- a/tests/resource/valid/error/handle.mamba +++ b/tests/resource/valid/error/handle.mamba @@ -10,10 +10,11 @@ def f(x: Int) -> Int ! { MyErr1, MyErr2 } := else return x + 2 -def a := f(10) +def a := f(10) ! { err: MyErr1 => print("Something went wrong") -1 err: MyErr2 => print("Something else went wrong") -2 +} diff --git a/tests/resource/valid/error/handle_check.py b/tests/resource/valid/error/handle.py similarity index 100% rename from tests/resource/valid/error/handle_check.py rename to tests/resource/valid/error/handle.py diff --git a/tests/resource/valid/error/handle_only_id.mamba b/tests/resource/valid/error/handle_only_id.mamba index daae8988..433e76c7 100644 --- a/tests/resource/valid/error/handle_only_id.mamba +++ b/tests/resource/valid/error/handle_only_id.mamba @@ -3,10 +3,11 @@ class MyErr2(msg: Str): Exception(msg) def f(x: Int) -> Int ! { MyErr1, MyErr2 } := x -def a := f(10) - _ : MyErr1 => +def a := f(10) ! { + _ : MyErr1 => [ print("Something went wrong") - -1 - _: MyErr2 => + -1] + _: MyErr2 => [ print("Something else went wrong") - -2 + -2] +} diff --git a/tests/resource/valid/error/handle_only_id_check.py b/tests/resource/valid/error/handle_only_id.py similarity index 100% rename from tests/resource/valid/error/handle_only_id_check.py rename to tests/resource/valid/error/handle_only_id.py diff --git a/tests/resource/valid/error/handle_var_usable_after.mamba b/tests/resource/valid/error/handle_var_usable_after.mamba index 14c7e1b1..b18f4a6d 100644 --- a/tests/resource/valid/error/handle_var_usable_after.mamba +++ b/tests/resource/valid/error/handle_var_usable_after.mamba @@ -2,9 +2,10 @@ class MyErr(def message: Str): Exception def function_may_throw_err() -> Int ! MyErr := 10 -def a := function_may_throw_err() - err: MyErr => +def a := function_may_throw_err() ! { + err: MyErr => [ print("We have a problem: {err.message}.") - 20 + 20] +} print("a has value {a}.") diff --git a/tests/resource/valid/error/handle_var_usable_after_check.py b/tests/resource/valid/error/handle_var_usable_after.py similarity index 100% rename from tests/resource/valid/error/handle_var_usable_after_check.py rename to tests/resource/valid/error/handle_var_usable_after.py diff --git a/tests/resource/valid/error/nested_exception.mamba b/tests/resource/valid/error/nested_exception.mamba index 2c37c225..4385efe5 100644 --- a/tests/resource/valid/error/nested_exception.mamba +++ b/tests/resource/valid/error/nested_exception.mamba @@ -2,13 +2,13 @@ class MyException1(msg: Str): Exception(msg) class MyException2(msg: Str): Exception(msg) def f(x: Int) -> Int ! { MyException1, MyException2 } := - match x + match x { 0 => 20 1 => ! MyException1() 2 => ! MyException2() + } def g() -> Int ! MyException2 := - f(2) - err: MyException1 => 10 + f(2) ! { err: MyException1 => 10 } g() diff --git a/tests/resource/valid/error/nested_exception_check.py b/tests/resource/valid/error/nested_exception.py similarity index 100% rename from tests/resource/valid/error/nested_exception_check.py rename to tests/resource/valid/error/nested_exception.py diff --git a/tests/resource/valid/error/raise_check.py b/tests/resource/valid/error/raise.py similarity index 100% rename from tests/resource/valid/error/raise_check.py rename to tests/resource/valid/error/raise.py diff --git a/tests/resource/valid/error/with_check.py b/tests/resource/valid/error/with.py similarity index 100% rename from tests/resource/valid/error/with_check.py rename to tests/resource/valid/error/with.py diff --git a/tests/resource/valid/function/allowed_exception_check.py b/tests/resource/valid/function/allowed_exception.py similarity index 100% rename from tests/resource/valid/function/allowed_exception_check.py rename to tests/resource/valid/function/allowed_exception.py diff --git a/tests/resource/valid/function/allowed_pass_check.py b/tests/resource/valid/function/allowed_pass.py similarity index 100% rename from tests/resource/valid/function/allowed_pass_check.py rename to tests/resource/valid/function/allowed_pass.py diff --git a/tests/resource/valid/function/callable_fun_arg_check.py b/tests/resource/valid/function/callable_fun_arg.py similarity index 100% rename from tests/resource/valid/function/callable_fun_arg_check.py rename to tests/resource/valid/function/callable_fun_arg.py diff --git a/tests/resource/valid/function/calls_check.py b/tests/resource/valid/function/calls.py similarity index 100% rename from tests/resource/valid/function/calls_check.py rename to tests/resource/valid/function/calls.py diff --git a/tests/resource/valid/function/definition.mamba b/tests/resource/valid/function/definition.mamba index 097eff9c..e56c20c2 100644 --- a/tests/resource/valid/function/definition.mamba +++ b/tests/resource/valid/function/definition.mamba @@ -1,10 +1,11 @@ -def fun_a() -> Int? := +def fun_a() -> Int? := [ print(11) if True and True then print("hello") if False or True then print("world") def a := None ? 11 if True then return 10 else return None +] def fun_b(b: Int) := print(b) @@ -19,10 +20,11 @@ def fun_e(m: Int, o: (Str, Str), r: (Int, (Str, Str)) -> Int) -> Int := return r def fun_v(y: Str, ab: Str -> Str -> Bool) -> Str -> Bool := return ab(y) class MyClass(def a: Int, def b: Int) - def some_function(self, c: Int) -> Int := + def some_function(self, c: Int) -> Int := [ def d := 20 d := 10 + 30 return c + 20 + d + ] def +(self, other: MyClass) -> MyClass := return MyClass(self.a + self.b + other.some_function(self.a), self.b) def -(self, other: MyClass) -> MyClass := return self + other diff --git a/tests/resource/valid/function/definition_check.py b/tests/resource/valid/function/definition.py similarity index 100% rename from tests/resource/valid/function/definition_check.py rename to tests/resource/valid/function/definition.py diff --git a/tests/resource/valid/function/exception_and_type_check.py b/tests/resource/valid/function/exception_and_type.py similarity index 100% rename from tests/resource/valid/function/exception_and_type_check.py rename to tests/resource/valid/function/exception_and_type.py diff --git a/tests/resource/valid/function/function_raise_super_check.py b/tests/resource/valid/function/function_raise_super.py similarity index 100% rename from tests/resource/valid/function/function_raise_super_check.py rename to tests/resource/valid/function/function_raise_super.py diff --git a/tests/resource/valid/function/function_with_defaults_check.py b/tests/resource/valid/function/function_with_defaults.py similarity index 100% rename from tests/resource/valid/function/function_with_defaults_check.py rename to tests/resource/valid/function/function_with_defaults.py diff --git a/tests/resource/valid/function/match_function.mamba b/tests/resource/valid/function/match_function.mamba index ac8aaec9..83a4fd3e 100644 --- a/tests/resource/valid/function/match_function.mamba +++ b/tests/resource/valid/function/match_function.mamba @@ -1,5 +1,5 @@ -def f(x: Int) -> Str := - match x - 1 => "One" - 2 => "Two" - _ => "Three" +def f(x: Int) -> Str := match x { + 1 => "One" + 2 => "Two" + _ => "Three" +} diff --git a/tests/resource/valid/function/match_function_check.py b/tests/resource/valid/function/match_function.py similarity index 100% rename from tests/resource/valid/function/match_function_check.py rename to tests/resource/valid/function/match_function.py diff --git a/tests/resource/valid/function/print_string_check.py b/tests/resource/valid/function/print_string.py similarity index 100% rename from tests/resource/valid/function/print_string_check.py rename to tests/resource/valid/function/print_string.py diff --git a/tests/resource/valid/function/return_last_expression.mamba b/tests/resource/valid/function/return_last_expression.mamba index 4c42df8f..d5e52a45 100644 --- a/tests/resource/valid/function/return_last_expression.mamba +++ b/tests/resource/valid/function/return_last_expression.mamba @@ -1,5 +1,6 @@ -def my_fun() -> Str := +def my_fun() -> Str := [ print("a statement") "a" +] print(my_fun()) diff --git a/tests/resource/valid/function/return_last_expression_check.py b/tests/resource/valid/function/return_last_expression.py similarity index 100% rename from tests/resource/valid/function/return_last_expression_check.py rename to tests/resource/valid/function/return_last_expression.py diff --git a/tests/resource/valid/function/ternary_function_call_check.py b/tests/resource/valid/function/ternary_function_call.py similarity index 100% rename from tests/resource/valid/function/ternary_function_call_check.py rename to tests/resource/valid/function/ternary_function_call.py diff --git a/tests/resource/valid/operation/arithmetic_check.py b/tests/resource/valid/operation/arithmetic.py similarity index 100% rename from tests/resource/valid/operation/arithmetic_check.py rename to tests/resource/valid/operation/arithmetic.py diff --git a/tests/resource/valid/operation/assign_types_check.py b/tests/resource/valid/operation/assign_types.py similarity index 100% rename from tests/resource/valid/operation/assign_types_check.py rename to tests/resource/valid/operation/assign_types.py diff --git a/tests/resource/valid/operation/assign_types_nested_check.py b/tests/resource/valid/operation/assign_types_nested.py similarity index 100% rename from tests/resource/valid/operation/assign_types_nested_check.py rename to tests/resource/valid/operation/assign_types_nested.py diff --git a/tests/resource/valid/operation/assign_types_no_annotation_check.py b/tests/resource/valid/operation/assign_types_no_annotation.py similarity index 100% rename from tests/resource/valid/operation/assign_types_no_annotation_check.py rename to tests/resource/valid/operation/assign_types_no_annotation.py diff --git a/tests/resource/valid/operation/boolean_check.py b/tests/resource/valid/operation/boolean.py similarity index 100% rename from tests/resource/valid/operation/boolean_check.py rename to tests/resource/valid/operation/boolean.py diff --git a/tests/resource/valid/operation/equality_different_types.mamba b/tests/resource/valid/operation/equality_different_types.mamba index 3166bb08..12461cf7 100644 --- a/tests/resource/valid/operation/equality_different_types.mamba +++ b/tests/resource/valid/operation/equality_different_types.mamba @@ -1,6 +1,7 @@ -class MyClass +class MyClass := { def __eq__(self, other: MyOtherClass) -> Bool := True def __ne__(self, other: MyOtherClass) -> Bool := False +} class MyOtherClass diff --git a/tests/resource/valid/operation/equality_different_types_check.py b/tests/resource/valid/operation/equality_different_types.py similarity index 100% rename from tests/resource/valid/operation/equality_different_types_check.py rename to tests/resource/valid/operation/equality_different_types.py diff --git a/tests/resource/valid/operation/greater_than_int.mamba b/tests/resource/valid/operation/greater_than_int.mamba index 2e96a5d4..1f986f95 100644 --- a/tests/resource/valid/operation/greater_than_int.mamba +++ b/tests/resource/valid/operation/greater_than_int.mamba @@ -1,4 +1,4 @@ -class MyClass(def a: Int) +class MyClass(def a: Int) := def f(self) -> Bool := self.a > 10 def a := MyClass(10) diff --git a/tests/resource/valid/operation/greater_than_int_check.py b/tests/resource/valid/operation/greater_than_int.py similarity index 100% rename from tests/resource/valid/operation/greater_than_int_check.py rename to tests/resource/valid/operation/greater_than_int.py diff --git a/tests/resource/valid/operation/greater_than_other_int.mamba b/tests/resource/valid/operation/greater_than_other_int.mamba index 0cf7c56e..99e16bf8 100644 --- a/tests/resource/valid/operation/greater_than_other_int.mamba +++ b/tests/resource/valid/operation/greater_than_other_int.mamba @@ -1,4 +1,4 @@ -class MyClass(def a: Int) +class MyClass(def a: Int) := def f(self, other: MyClass) -> Bool := self.a > other.a def a := MyClass(10) diff --git a/tests/resource/valid/operation/greater_than_other_int_check.py b/tests/resource/valid/operation/greater_than_other_int.py similarity index 100% rename from tests/resource/valid/operation/greater_than_other_int_check.py rename to tests/resource/valid/operation/greater_than_other_int.py diff --git a/tests/resource/valid/operation/in_set_is_bool_check.py b/tests/resource/valid/operation/in_set_is_bool.py similarity index 100% rename from tests/resource/valid/operation/in_set_is_bool_check.py rename to tests/resource/valid/operation/in_set_is_bool.py diff --git a/tests/resource/valid/operation/multiply_other_int.mamba b/tests/resource/valid/operation/multiply_other_int.mamba index 3695b792..caed9d4a 100644 --- a/tests/resource/valid/operation/multiply_other_int.mamba +++ b/tests/resource/valid/operation/multiply_other_int.mamba @@ -1,4 +1,4 @@ -class MyClass(def a: Int) +class MyClass(def a: Int) := def f(self, other: MyClass) -> Int := self.a * other.a def a := MyClass(10) diff --git a/tests/resource/valid/operation/multiply_other_int_check.py b/tests/resource/valid/operation/multiply_other_int.py similarity index 100% rename from tests/resource/valid/operation/multiply_other_int_check.py rename to tests/resource/valid/operation/multiply_other_int.py diff --git a/tests/resource/valid/operation/primitives_check.py b/tests/resource/valid/operation/primitives.py similarity index 100% rename from tests/resource/valid/operation/primitives_check.py rename to tests/resource/valid/operation/primitives.py diff --git a/tests/resource/valid/operation/type_alias_primitive_check.py b/tests/resource/valid/operation/type_alias_primitive.py similarity index 100% rename from tests/resource/valid/operation/type_alias_primitive_check.py rename to tests/resource/valid/operation/type_alias_primitive.py diff --git a/tests/resource/valid/std_functions_check.py b/tests/resource/valid/std_functions.py similarity index 100% rename from tests/resource/valid/std_functions_check.py rename to tests/resource/valid/std_functions.py diff --git a/tests_util/src/lib.rs b/tests_util/src/lib.rs index 819052b9..7b817d38 100644 --- a/tests_util/src/lib.rs +++ b/tests_util/src/lib.rs @@ -124,11 +124,7 @@ pub fn fallable( let cmd1 = Command::new(PYTHON) .arg("-m") .arg("py_compile") - .arg(&resource_path( - valid, - input, - &format!("{}_check.py", file_name), - )) + .arg(&resource_path(valid, input, &format!("{file_name}.py"))) .output() .expect("Could not run Python command."); From ca9775cebb87d2e939b302221383699b7b450c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Abrahams?= Date: Fri, 11 Jul 2025 15:27:05 +0200 Subject: [PATCH 22/44] doc: elaborate notation --- README.md | 61 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 146b2679..79c7fb67 100644 --- a/README.md +++ b/README.md @@ -32,19 +32,19 @@ Mamba is similar to Python, but with a few key features: - Null safety - Explicit error handling - A distinction between mutability and immutability -- Pure functions, or, functions without side effects +- Pure functions, or, functions without side effects +- Meta functions, for reasoning about the language itself See [docs](/docs/) for a more extensive overview of the language philosophy. This is a transpiler, written in [Rust](https://www.rust-lang.org/), which converts Mamba source files to Python source files. -Mamba code should therefore be interoperable with Python code. -Functions written in Python can be called in Mamba and vice versa (from the generated Python files). -This interoparability is still a work in progress. +There therefore exists some interopability with Python code. +Currently we compile down to Python, in future we may compile down to Python bytecode, for instance. The below README: -- Gives a quickstart for developer +- Gives a quickstart for developers - Give a short overview of most of the syntax and language features in quick succession, as well as the occasional reasoning behind them. ## 🧑‍💻 Quickstart for developers 👨‍💻 @@ -132,7 +132,7 @@ def empty_list = [] # lists of tuples, builder syntax def ab := [(x, y) | x in a, x > 0, y in b, b != "of" ] -# Indexing is done using curly brackets! +# Indexing is done using round brackets! print(a(0)) # prints '0' ``` @@ -164,24 +164,22 @@ So: def numbers := [32, 504, 59] ``` -Is just shorthand for +Is essentially just shorthand for ```mamba def numbers := { 0 => 32, 1 => 504, 2 => 59 } -``` +``` + +Where we iterate over the list in the order of the keys. Unlike C-style languages (which is nearly the whole world at this point), we index collections using `collection()`. -The main reason for doing so is that we see collections which can be indexed as mappings. -Consider the above argument that a list is just another type of mapping. - -We don't distinguish between a mapping and a function, because a function is (generally speaking) also a type of mapping. -We also argue that the above mapping is a representation of some functino with a very small domain (only three items). +We namely don't distinguish between a mapping and a function, because a function is (generally speaking) also a type of mapping. +The above mapping, for instance, is a representation of some functino with a very small domain (only three items). Therefore, we index indexable collections (mappings and list) using the `collection()` notation. ### ✏️🖊️ Mutability -Mutability gives us the power to modify an instance in the language after it is created. -So for instance +Mutability gives us the power to modify an instance in the language after it is created: ``` def a := 10 # we may modify a @@ -194,17 +192,17 @@ a := a + 2 # allowed We opt to make mutability the default (unlike say in Rust, where you have to use the `mut` keyword to make something mutable). The reason for doing so is domain; Mamba is geared more for mathematical use, for lack of a better term, meaning this design choice follows from the language philosophy. -This same philosophy will also influence later how we deal with equality checks between objects in the language and how we copy items in the language, where we favour a pure functional apporach similar to Haskell. ### 📋 Types, Properties, and Classes Next, we introduce the concept of a class. -A class is essentially a blueprint for the behaviour of instances of that class. +A class is essentially a blueprint for the behaviour of an instance. -In Mamba, like Python and Rust, each method in a class has an explicit `self` argument, which gives access to the state of this instance. -However, we can for each function state whether we can write to `self` or not by stating whether it is mutable or not. -If we write `self`, it is mutable, whereas if we write `fin self`, it is immutable and we cannot change its fields. -We can do the same for any field. +In Mamba, like Python and Rust, each function in a class has an explicit `self` argument, which gives access to the state of this instance. +Such a function is called a method. +We can for each method state whether we can modify the state of `self` by stating whether it is mutable or not. +If we write `self`, it is mutable, whereas if we write `fin self`, it is immutable and we cannot change its state. +We can do the same for any argument to a function, for that matter. We showcase this using a simple dummy `Matrix` object. You will also see some "pure" functions, these will be explained later. @@ -239,7 +237,7 @@ class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { } ``` -Notice how `self` is not mutable in `trace`, meaning we can only read variables, whereas in `scale`, `self` is mutable so we can change properties of `self`. +Notice how `self` is not mutable in `trace`, meaning we can only read variables, whereas in `scale`, `self` is mutable, so we can change properties of `self`. In general, the notation of a class is: `class MyClass() := []` @@ -276,9 +274,11 @@ class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { ``` Last, we have `trait`s, which in Mamba are more fine-grained building blocks to describe the behaviour of instances. -These are similar to interfaces in Java and Kotlin, and almost identical to traits in Rust. -In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. -For instance, consider example with iterators (which briefly showcases language generics): +These are similar to interfaces in Java and Kotlin, and near identical to traits in Rust. +In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. +For those familiar with object oriented programming, we favour a trait based system over inheritance (like Rust, Mamba doesn't have inheritance). + +Consider example with iterators (which briefly showcases language generics): ```mamba trait Iterator[T] := { @@ -305,22 +305,21 @@ Prefer using an adjective (e.g. `Iterable`, `Hashable`, `Comparable`) when defin The syntax here is `trait := { for `. Lastly, like Rust, types (traits) can also be used as generics. -This would allow, for instance, for defining say a `Hash` trait and enforcing for a hashmap that keys implement said type. +This would allow, for instance, for defining a `Hash` trait and enforcing for a hashmap that keys implement said trait. We can also compose traits, which means that when we define the composite trait for a class we have to implement all definitions at once. The syntax is very similar to inheritance for classes: E.g. ```mamba -trait Ordered[T]: Equality, Comparable { - def less_than(self, other: T) -> Bool -} +trait Ordered[T]: Equality, Comparable ``` ### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) -Mamba also has type refinement features to assign additional properties to types. -Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the comlexit of the language. +Mamba also has type refinement features to assign additional properties to types. + +Note: Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the comlexit of the language. Arguably, it might detract from the elegance of the type system as well; A different solution could be to just have a dedicated interface baked into the standard library for this purpose. From 93b422f7b1252ff801aaf6a0e6e248401aff62fd Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 21 Jul 2025 10:03:58 +0200 Subject: [PATCH 23/44] feat: remove unused tokens --- docs/spec/keywords.md | 2 - src/parse/block.rs | 96 ++++++----- src/parse/call.rs | 8 +- src/parse/class.rs | 18 +- src/parse/collection.rs | 6 +- src/parse/control_flow_expr.rs | 6 +- src/parse/definition.rs | 8 +- src/parse/expr_or_stmt.rs | 14 +- src/parse/expression.rs | 23 +-- src/parse/iterator.rs | 2 +- src/parse/lex/mod.rs | 163 +++++++----------- src/parse/lex/state.rs | 14 -- src/parse/lex/token.rs | 13 -- src/parse/lex/tokenize.rs | 37 ++-- src/parse/mod.rs | 9 +- src/parse/operation.rs | 42 ----- src/parse/statement.rs | 25 ++- src/parse/ty.rs | 20 +-- .../resource/valid/class/with_generics.mamba | 4 +- 19 files changed, 183 insertions(+), 327 deletions(-) diff --git a/docs/spec/keywords.md b/docs/spec/keywords.md index bc40b127..ed227c95 100644 --- a/docs/spec/keywords.md +++ b/docs/spec/keywords.md @@ -22,7 +22,6 @@ Keyword | Use `when` | Conditionals of a type definition `class` | Denote start of class definition `trait` | Denote start of trait definition -`isa` | Check whether an object is instance of a class, or more generally type ## Classes and Utils @@ -49,7 +48,6 @@ Keyword | Use `not` | Negation of a boolean value `and` | And operator `or` | Or operator -`is` | Check whether an instance is another instance ## Mathematical Operators diff --git a/src/parse/block.rs b/src/parse/block.rs index 2a0f8ca3..946f8826 100644 --- a/src/parse/block.rs +++ b/src/parse/block.rs @@ -11,61 +11,71 @@ pub fn parse_statements(it: &mut LexIterator) -> ParseResult> { let start = it.start_pos("statements")?; let mut statements: Vec = Vec::new(); - it.peek_while_not_tokens( - &[Token::Dedent, Token::Eof], - &mut |it, lex| match &lex.token { - Token::NL => it.eat(&Token::NL, "statements").map(|_| ()), + it.peek_while_not_tokens(&[], &mut |it, lex| match &lex.token { + Token::NL => it.eat(&Token::NL, "statements").map(|_| ()), - Token::Import | Token::From => { - statements.push(*it.parse(&parse_import, "file", start)?); + Token::Import | Token::From => { + statements.push(*it.parse(&parse_import, "file", start)?); + Ok(()) + } + Token::Type => { + statements.push(*it.parse(&parse_type_def, "file", start)?); + Ok(()) + } + Token::Class => { + statements.push(*it.parse(&parse_class, "file", start)?); + Ok(()) + } + Token::DocStr(doc_str) => { + let end = it.eat(&Token::DocStr(doc_str.clone()), "statements")?; + let node = Node::DocStr { + lit: doc_str.clone(), + }; + statements.push(AST::new(lex.pos.union(end), node)); + Ok(()) + } + _ => { + statements.push(*it.parse(&parse_expr_or_stmt, "statements", start)?); + if it.peek_if(&|lex| lex.token != Token::NL) { + Err(Box::from(expected_one_of( + &[Token::NL], + lex, + "end of statement", + ))) + } else { Ok(()) } - Token::Type => { - statements.push(*it.parse(&parse_type_def, "file", start)?); - Ok(()) - } - Token::Class => { - statements.push(*it.parse(&parse_class, "file", start)?); - Ok(()) - } - Token::DocStr(doc_str) => { - let end = it.eat(&Token::DocStr(doc_str.clone()), "statements")?; - let node = Node::DocStr { - lit: doc_str.clone(), - }; - statements.push(AST::new(lex.pos.union(end), node)); - Ok(()) - } - _ => { - statements.push(*it.parse(&parse_expr_or_stmt, "statements", start)?); - if it.peek_if(&|lex| { - lex.token != Token::NL && lex.token != Token::Dedent && lex.token != Token::Eof - }) { - Err(Box::from(expected_one_of( - &[Token::NL, Token::Dedent, Token::Eof], - lex, - "end of statement", - ))) - } else { - Ok(()) - } - } - }, - )?; + } + })?; Ok(statements) } /// Parse block, and consumes any newlines preceding it. -pub fn parse_block(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("block")?; +pub fn parse_code_block(it: &mut LexIterator) -> ParseResult { + let start = it.start_pos("block block")?; + it.eat_while(&Token::NL); + + it.eat(&Token::LSBrack, "block block")?; + let statements = it.parse_vec(&parse_statements, "block block", start)?; + let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); + + it.eat(&Token::RSBrack, "block block")?; + Ok(Box::from(AST::new( + start.union(end), + Node::Block { statements }, + ))) +} + +pub fn parse_code_set(it: &mut LexIterator) -> ParseResult { + let start = it.start_pos("block set")?; it.eat_while(&Token::NL); - it.eat(&Token::Indent, "block")?; - let statements = it.parse_vec(&parse_statements, "block", start)?; + it.eat(&Token::LCBrack, "block set")?; + let statements = it.parse_vec(&parse_statements, "block set", start)?; let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); - it.eat(&Token::Dedent, "block")?; + it.eat(&Token::RCBrack, "block set")?; Ok(Box::from(AST::new( start.union(end), Node::Block { statements }, diff --git a/src/parse/call.rs b/src/parse/call.rs index 332ba7ea..dcd5e5d7 100644 --- a/src/parse/call.rs +++ b/src/parse/call.rs @@ -39,8 +39,8 @@ pub fn parse_call(pre: &AST, it: &mut LexIterator) -> ParseResult { }; Ok(Box::from(AST::new(pre.pos.union(property.pos), node))) } - Token::LRBrack => { - it.eat(&Token::LRBrack, "direct call")?; + Token::LSBrack => { + it.eat(&Token::LSBrack, "direct call")?; let args = it.parse_vec(&parse_arguments, "direct call", pre.pos)?; let end = it.eat(&Token::RRBrack, "direct call")?; let node = Node::FunctionCall { @@ -50,12 +50,12 @@ pub fn parse_call(pre: &AST, it: &mut LexIterator) -> ParseResult { Ok(Box::from(AST::new(pre.pos.union(end), node))) } _ => Err(Box::from(expected_one_of( - &[Token::Point, Token::LRBrack], + &[Token::Point, Token::LSBrack], ast, "function call", ))), }, - &[Token::Point, Token::LRBrack], + &[Token::Point, Token::LSBrack], "function call", ) } diff --git a/src/parse/class.rs b/src/parse/class.rs index a2b07c0b..4e162a15 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -1,9 +1,9 @@ use crate::parse::ast::Node; use crate::parse::ast::AST; -use crate::parse::block::parse_block; +use crate::parse::block::parse_code_block; use crate::parse::definition::{parse_definition, parse_fun_arg}; use crate::parse::iterator::LexIterator; -use crate::parse::lex::token::Token; +use crate::parse::lex::token::{Lex, Token}; use crate::parse::operation::parse_expression; use crate::parse::result::ParseResult; use crate::parse::result::{custom, expected, expected_one_of}; @@ -16,7 +16,7 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { let ty = it.parse(&parse_type, "class", start)?; let mut args = vec![]; - if it.eat_if(&Token::LRBrack).is_some() { + if it.eat_if(&Token::LSBrack).is_some() { it.peek_while_not_token(&Token::RRBrack, &mut |it, lex| match lex.token { Token::Def => { args.push(*it.parse(&parse_definition, "constructor argument", start)?); @@ -35,7 +35,7 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { let mut parents = vec![]; if it.eat_if(&Token::DoublePoint).is_some() { it.peek_while_not_token(&Token::NL, &mut |it, lex| match lex.token { - Token::Id(_) | Token::LRBrack => { + Token::Id(_) | Token::LSBrack => { parents.push(*it.parse(&parse_parent, "parents", start)?); it.eat_if(&Token::Comma); Ok(()) @@ -48,8 +48,8 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { })?; } - let (body, pos) = if it.peek_if_followed_by(&Token::NL, &Token::Indent) { - let body = it.parse(&parse_block, "class", start)?; + let (body, pos) = if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) { + let body = it.parse(&parse_code_block, "class", start)?; (Some(body.clone()), start.union(body.pos)) } else { (None, start) @@ -69,7 +69,7 @@ pub fn parse_parent(it: &mut LexIterator) -> ParseResult { let ty = it.parse(&parse_type, "parent", start)?; let mut args = vec![]; - let end = if it.eat_if(&Token::LRBrack).is_some() { + let end = if it.eat_if(&Token::LSBrack).is_some() { it.peek_while_not_token(&Token::RRBrack, &mut |it, lex| match &lex.token { Token::Id { .. } => { args.push(*it.parse(&parse_id, "parent arguments", start)?); @@ -126,9 +126,9 @@ pub fn parse_type_def(it: &mut LexIterator) -> ParseResult { }; Ok(Box::from(AST::new(start.union(end), node))) } - _ if it.peek_if_followed_by(&Token::NL, &Token::Indent) => { + _ if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) => { it.eat_if(&Token::NL); - let body = it.parse(&parse_block, "type definition", start)?; + let body = it.parse(&parse_code_block, "type definition", start)?; let isa = isa.clone(); let node = Node::TypeDef { ty: ty.clone(), diff --git a/src/parse/collection.rs b/src/parse/collection.rs index 5c811776..a07e9ba8 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -15,19 +15,19 @@ pub fn parse_collection(it: &mut LexIterator) -> ParseResult { Token::LSBrack => parse_list(it), Token::LCBrack => parse_set_or_dict(it), _ => Err(Box::from(expected_one_of( - &[Token::LRBrack, Token::LSBrack, Token::LCBrack], + &[Token::LSBrack, Token::LSBrack, Token::LCBrack], lex, "collection", ))), }, - &[Token::LRBrack, Token::LSBrack, Token::LCBrack], + &[Token::LSBrack, Token::LSBrack, Token::LCBrack], "collection", ) } pub fn parse_tuple(it: &mut LexIterator) -> ParseResult { let start = it.start_pos("tuple")?; - it.eat(&Token::LRBrack, "tuple")?; + it.eat(&Token::LSBrack, "tuple")?; let elements = it.parse_vec(&parse_expressions, "tuple", start)?; let end = it.eat(&Token::RRBrack, "tuple")?; diff --git a/src/parse/control_flow_expr.rs b/src/parse/control_flow_expr.rs index 9060dcf1..641fd5f5 100644 --- a/src/parse/control_flow_expr.rs +++ b/src/parse/control_flow_expr.rs @@ -63,15 +63,15 @@ fn parse_match(it: &mut LexIterator) -> ParseResult { } pub fn parse_match_cases(it: &mut LexIterator) -> ParseResult> { - let start = it.eat(&Token::Indent, "match cases")?; + let start = it.eat(&Token::LCBrack, "match cases")?; let mut cases = vec![]; - it.peek_while_not_token(&Token::Dedent, &mut |it, _| { + it.peek_while_not_token(&Token::RCBrack, &mut |it, _| { cases.push(*it.parse(&parse_match_case, "match case", start)?); it.eat_if(&Token::NL); Ok(()) })?; - it.eat(&Token::Dedent, "match cases")?; + it.eat(&Token::RCBrack, "match cases")?; Ok(cases) } diff --git a/src/parse/definition.rs b/src/parse/definition.rs index e739a5f0..67330580 100644 --- a/src/parse/definition.rs +++ b/src/parse/definition.rs @@ -29,7 +29,7 @@ pub fn parse_definition(it: &mut LexIterator) -> ParseResult { let res = it.peek_or_err( &|it, lex| match lex.token { - Token::LRBrack | Token::LCBrack | Token::LSBrack if !pure => parse_variable_def(it), + Token::LSBrack | Token::LCBrack | Token::LSBrack if !pure => parse_variable_def(it), Token::Add => op!(it, Add), Token::Sub => op!(it, Sub), @@ -46,7 +46,7 @@ pub fn parse_definition(it: &mut LexIterator) -> ParseResult { }, &[ Token::Id(String::new()), - Token::LRBrack, + Token::LSBrack, Token::LCBrack, Token::LSBrack, Token::Add, @@ -84,7 +84,7 @@ fn parse_var_or_fun_def(it: &mut LexIterator, pure: bool) -> ParseResult { } Node::ExpressionType { expr, ty, mutable } if ty.is_none() => it.peek( &|it, lex| match lex.token { - Token::LRBrack => parse_fun_def(&id, pure, it), + Token::LSBrack => parse_fun_def(&id, pure, it), _ if !pure => parse_variable_def_id(&id, it), _ => { let msg = format!("Definition cannot have {} identifier", Token::Pure); @@ -173,7 +173,7 @@ pub fn parse_raises(it: &mut LexIterator) -> ParseResult> { } pub fn parse_fun_args(it: &mut LexIterator) -> ParseResult> { - let start = it.eat(&Token::LRBrack, "function arguments")?; + let start = it.eat(&Token::LSBrack, "function arguments")?; let mut args = vec![]; it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { args.push(*it.parse(&parse_fun_arg, "function arguments", start)?); diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index 44bff060..2737ad79 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -1,8 +1,8 @@ use crate::parse::ast::{Node, AST}; -use crate::parse::block::parse_block; +use crate::parse::block::{parse_code_block, parse_code_set}; use crate::parse::control_flow_expr::parse_match_cases; use crate::parse::iterator::LexIterator; -use crate::parse::lex::token::Token; +use crate::parse::lex::token::{Lex, Token}; use crate::parse::operation::parse_expression; use crate::parse::result::ParseResult; use crate::parse::statement::parse_statement; @@ -11,10 +11,8 @@ use crate::parse::statement::{is_start_statement, parse_reassignment}; pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { let expr_or_stmt = it.peek_or_err( &|it, lex| match &lex.token { - Token::NL => { - it.eat(&Token::NL, "expression or statement")?; - it.parse(&parse_block, "expression or statement", lex.pos) - } + Token::LSBrack => it.parse(&parse_code_block, "expression", lex.pos), + Token::LCBrack => it.parse(&parse_code_set, "statement", lex.pos), token if is_start_statement(token) => parse_statement(it), _ => parse_expression(it), }, @@ -23,9 +21,7 @@ pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { )?; // if expression/statement followed by newline and indent, we are dealing with a handle block - if it.peek_if_followed_by(&Token::NL, &Token::Indent) { - it.eat(&Token::NL, "internal error in parsing call")?; // peek covers this - + if it.peek_if(&|lex: &Lex| lex.token == Token::Raise) { // parse handle cases if indentation block after let cases = it.parse_vec(&parse_match_cases, "handle cases", expr_or_stmt.pos)?; let end = cases.last().map_or(expr_or_stmt.pos, |stmt| stmt.pos); diff --git a/src/parse/expression.rs b/src/parse/expression.rs index 09497e4e..ea640fd9 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -26,7 +26,7 @@ pub fn parse_inner_expression(it: &mut LexIterator) -> ParseResult { let expected = [ Token::If, Token::Match, - Token::LRBrack, + Token::LSBrack, Token::LSBrack, Token::LCBrack, Token::Underscore, @@ -44,7 +44,7 @@ pub fn parse_inner_expression(it: &mut LexIterator) -> ParseResult { let result = it.peek_or_err( &|it, lex| match &lex.token { Token::If | Token::Match => parse_cntrl_flow_expr(it), - Token::LRBrack | Token::LSBrack | Token::LCBrack => parse_collection(it), + Token::LSBrack | Token::LRBrack | Token::LCBrack => parse_collection(it), Token::Underscore => parse_underscore(it), Token::Id(_) => parse_id(it), Token::Real(real) => literal!(it, real.to_string(), Real), @@ -99,14 +99,10 @@ fn parse_underscore(it: &mut LexIterator) -> ParseResult { fn parse_post_expr(pre: &AST, it: &mut LexIterator) -> ParseResult { it.peek( &|it, lex| match lex.token { - Token::LRBrack | Token::Point => { + Token::LSBrack | Token::Point => { let res = parse_call(pre, it)?; parse_post_expr(&res, it) } - Token::LSBrack => { - let res = parse_index(pre, it)?; - parse_post_expr(&res, it) - } _ if is_start_expression_exclude_unary(lex) => { let res = parse_call(pre, it)?; parse_post_expr(&res, it) @@ -117,25 +113,14 @@ fn parse_post_expr(pre: &AST, it: &mut LexIterator) -> ParseResult { ) } -fn parse_index(pre: &AST, it: &mut LexIterator) -> ParseResult { - it.eat(&Token::LSBrack, "index")?; - - let item = Box::from(pre.clone()); - let range = it.parse(&parse_expression, "index", pre.pos)?; - - let node = Node::Index { item, range }; - let end = it.eat(&Token::RSBrack, "index")?; - Ok(Box::from(AST::new(pre.pos.union(end), node))) -} - /// Excluding unary addition and subtraction pub fn is_start_expression_exclude_unary(tp: &Lex) -> bool { matches!( tp.token, Token::If | Token::Match - | Token::LRBrack | Token::LSBrack + | Token::LRBrack | Token::LCBrack | Token::Underscore | Token::BSlash diff --git a/src/parse/iterator.rs b/src/parse/iterator.rs index 54f8732b..f9381303 100644 --- a/src/parse/iterator.rs +++ b/src/parse/iterator.rs @@ -194,7 +194,7 @@ impl<'a> LexIterator<'a> { loop_fn: &mut dyn FnMut(&mut LexIterator, &Lex) -> ParseResult<()>, ) -> ParseResult<()> { while let Some(&lex) = self.it.peek() { - if !check_fn(lex) || lex.token == Token::Eof { + if !check_fn(lex) { break; } loop_fn(self, lex)?; diff --git a/src/parse/lex/mod.rs b/src/parse/lex/mod.rs index 4f02f2de..79c65a28 100644 --- a/src/parse/lex/mod.rs +++ b/src/parse/lex/mod.rs @@ -1,8 +1,6 @@ -use crate::common::position::CaretPos; use crate::parse::lex::pass::pass; use crate::parse::lex::result::LexResult; use crate::parse::lex::state::State; -use crate::parse::lex::token::{Lex, Token}; use crate::parse::lex::tokenize::into_tokens; pub mod result; @@ -31,15 +29,6 @@ pub fn tokenize(input: &str) -> LexResult { while let Some(c) = it.next() { tokens.append(&mut into_tokens(c, &mut it, &mut state)?); } - tokens.append(&mut state.flush_indents()); - tokens.push(Lex::new( - if let Some(lex) = tokens.last() { - lex.pos.end.offset_pos(1) - } else { - CaretPos::start() - }, - Token::Eof, - )); let tokens = pass(&tokens); Ok(tokens) @@ -53,7 +42,6 @@ fn tokenize_direct(input: &str) -> LexResult { while let Some(c) = it.next() { tokens.append(&mut into_tokens(c, &mut it, &mut state)?); } - tokens.append(&mut state.flush_indents()); let tokens = pass(&tokens); Ok(tokens) @@ -90,10 +78,6 @@ mod tests { pos: Position::new(CaretPos::new(1, 15), CaretPos::new(1, 16)), token: Token::Id(String::from("b")), }, - Lex { - pos: Position::new(CaretPos::new(1, 17), CaretPos::new(1, 17)), - token: Token::Eof, - }, ] ); } @@ -111,7 +95,6 @@ mod tests { Token::MulAssign, Token::DivAssign, Token::PowAssign, - Token::Eof, ] ); } @@ -155,10 +138,6 @@ mod tests { pos: Position::new(CaretPos::new(1, 20), CaretPos::new(1, 21)), token: Token::Id(String::from("i")), }, - Lex { - pos: Position::new(CaretPos::new(1, 22), CaretPos::new(1, 22)), - token: Token::Eof, - }, ] ); } @@ -194,18 +173,10 @@ mod tests { pos: Position::new(CaretPos::new(1, 13), CaretPos::new(1, 15)), token: Token::Neq, }, - Lex { - pos: Position::new(CaretPos::new(1, 16), CaretPos::new(1, 18)), - token: Token::Is, - }, Lex { pos: Position::new(CaretPos::new(1, 19), CaretPos::new(1, 20)), token: Token::Id(String::from("i")), }, - Lex { - pos: Position::new(CaretPos::new(1, 21), CaretPos::new(1, 21)), - token: Token::Eof, - }, ] ); } @@ -216,22 +187,16 @@ mod tests { let tokens = tokenize(&source).unwrap(); assert_eq!( tokens, - vec![ - Lex { - pos: Position::new(CaretPos::new(1, 1), CaretPos::new(1, 21)), - token: Token::Str( - String::from("my string {my_var}"), - vec![vec![Lex { - pos: Position::new(CaretPos::new(1, 13), CaretPos::new(1, 19)), - token: Token::Id(String::from("my_var")), - }]], - ), - }, - Lex { - pos: Position::new(CaretPos::new(1, 22), CaretPos::new(1, 22)), - token: Token::Eof, - }, - ] + vec![Lex { + pos: Position::new(CaretPos::new(1, 1), CaretPos::new(1, 21)), + token: Token::Str( + String::from("my string {my_var}"), + vec![vec![Lex { + pos: Position::new(CaretPos::new(1, 13), CaretPos::new(1, 19)), + token: Token::Id(String::from("my_var")), + }]], + ), + },] ); } @@ -241,40 +206,34 @@ mod tests { let tokens = tokenize(&source).unwrap(); assert_eq!( tokens, - vec![ - Lex { - pos: Position::new(CaretPos::new(1, 1), CaretPos::new(1, 11)), - token: Token::Str( - String::from("{{a, b}}"), - vec![vec![ - Lex { - pos: Position::new(CaretPos::new(1, 3), CaretPos::new(1, 4)), - token: Token::LCBrack, - }, - Lex { - pos: Position::new(CaretPos::new(1, 4), CaretPos::new(1, 5)), - token: Token::Id(String::from("a")), - }, - Lex { - pos: Position::new(CaretPos::new(1, 5), CaretPos::new(1, 6)), - token: Token::Comma, - }, - Lex { - pos: Position::new(CaretPos::new(1, 7), CaretPos::new(1, 8)), - token: Token::Id(String::from("b")), - }, - Lex { - pos: Position::new(CaretPos::new(1, 8), CaretPos::new(1, 9)), - token: Token::RCBrack, - }, - ]], - ), - }, - Lex { - pos: Position::new(CaretPos::new(1, 12), CaretPos::new(1, 12)), - token: Token::Eof, - }, - ] + vec![Lex { + pos: Position::new(CaretPos::new(1, 1), CaretPos::new(1, 11)), + token: Token::Str( + String::from("{{a, b}}"), + vec![vec![ + Lex { + pos: Position::new(CaretPos::new(1, 3), CaretPos::new(1, 4)), + token: Token::LCBrack, + }, + Lex { + pos: Position::new(CaretPos::new(1, 4), CaretPos::new(1, 5)), + token: Token::Id(String::from("a")), + }, + Lex { + pos: Position::new(CaretPos::new(1, 5), CaretPos::new(1, 6)), + token: Token::Comma, + }, + Lex { + pos: Position::new(CaretPos::new(1, 7), CaretPos::new(1, 8)), + token: Token::Id(String::from("b")), + }, + Lex { + pos: Position::new(CaretPos::new(1, 8), CaretPos::new(1, 9)), + token: Token::RCBrack, + }, + ]], + ), + },] ); } @@ -284,32 +243,26 @@ mod tests { let tokens = tokenize(&source).unwrap(); assert_eq!( tokens, - vec![ - Lex { - pos: Position::new(CaretPos::new(1, 1), CaretPos::new(1, 10)), - token: Token::Str( - String::from("{a + b}"), - vec![vec![ - Lex { - pos: Position::new(CaretPos::new(1, 3), CaretPos::new(1, 4)), - token: Token::Id(String::from("a")), - }, - Lex { - pos: Position::new(CaretPos::new(1, 5), CaretPos::new(1, 6)), - token: Token::Add, - }, - Lex { - pos: Position::new(CaretPos::new(1, 7), CaretPos::new(1, 8)), - token: Token::Id(String::from("b")), - }, - ]], - ), - }, - Lex { - pos: Position::new(CaretPos::new(1, 11), CaretPos::new(1, 11)), - token: Token::Eof, - }, - ] + vec![Lex { + pos: Position::new(CaretPos::new(1, 1), CaretPos::new(1, 10)), + token: Token::Str( + String::from("{a + b}"), + vec![vec![ + Lex { + pos: Position::new(CaretPos::new(1, 3), CaretPos::new(1, 4)), + token: Token::Id(String::from("a")), + }, + Lex { + pos: Position::new(CaretPos::new(1, 5), CaretPos::new(1, 6)), + token: Token::Add, + }, + Lex { + pos: Position::new(CaretPos::new(1, 7), CaretPos::new(1, 8)), + token: Token::Id(String::from("b")), + }, + ]], + ), + },] ); } } diff --git a/src/parse/lex/state.rs b/src/parse/lex/state.rs index e67e6ae7..d8499d4b 100644 --- a/src/parse/lex/state.rs +++ b/src/parse/lex/state.rs @@ -23,12 +23,6 @@ impl State { } } - pub fn flush_indents(&mut self) -> Vec { - let amount = ((self.cur_indent) / 4) as usize; - self.cur_indent = 1; - vec![Lex::new(self.pos, Token::Dedent); amount] - } - /// Change state depending on given [Token](lexer::token::Token) and return /// [TokenPos](lexer::token::TokenPos) with current line and position /// (1-indexed). @@ -46,14 +40,6 @@ impl State { self.token_this_line = true; let mut res = self.newlines.pop().map_or(vec![], |nl| vec![nl]); - if self.line_indent >= self.cur_indent { - let amount = ((self.line_indent - self.cur_indent) / 4) as usize; - res.append(&mut vec![Lex::new(self.pos, Token::Indent); amount]); - } else { - let amount = ((self.cur_indent - self.line_indent) / 4) as usize; - res.append(&mut vec![Lex::new(self.pos, Token::Dedent); amount]); - res.push(Lex::new(self.pos, Token::NL)); - } res.append(&mut self.newlines); res.push(Lex::new(self.pos, token.clone())); diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index 0afacc85..3031568e 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -31,7 +31,6 @@ pub enum Token { Type, Class, Pure, - IsA, As, Import, @@ -78,7 +77,6 @@ pub enum Token { Leq, Eq, - Is, Neq, And, Or, @@ -95,8 +93,6 @@ pub enum Token { BTo, NL, - Indent, - Dedent, Underscore, Raise, @@ -116,10 +112,7 @@ pub enum Token { With, Question, - Pass, - - Eof, } impl Token { @@ -147,7 +140,6 @@ impl fmt::Display for Token { Token::Pure => write!(f, "pure"), Token::Type => write!(f, "type"), Token::Class => write!(f, "class"), - Token::IsA => write!(f, "isa"), Token::As => write!(f, "as"), Token::Import => write!(f, "import"), @@ -194,7 +186,6 @@ impl fmt::Display for Token { Token::Leq => write!(f, "<="), Token::Eq => write!(f, "="), - Token::Is => write!(f, "is"), Token::Neq => write!(f, "!="), Token::And => write!(f, "and"), Token::Or => write!(f, "or"), @@ -211,8 +202,6 @@ impl fmt::Display for Token { Token::BTo => write!(f, "=>"), Token::NL => write!(f, ""), - Token::Indent => write!(f, " "), - Token::Dedent => write!(f, ""), Token::Underscore => write!(f, "_"), Token::While => write!(f, "while"), @@ -234,8 +223,6 @@ impl fmt::Display for Token { Token::When => write!(f, "when"), Token::Pass => write!(f, "pass"), - - Token::Eof => write!(f, ""), } } } diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index 6062e33c..9bca006d 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -19,7 +19,7 @@ pub fn into_tokens(c: char, it: &mut Peekable, state: &mut State) -> LexR Some('=') => next_and_create(it, state, Token::Assign), _ => create(state, Token::DoublePoint), }, - '(' => create(state, Token::LRBrack), + '(' => create(state, Token::LSBrack), ')' => create(state, Token::RRBrack), '[' => create(state, Token::LSBrack), ']' => create(state, Token::RSBrack), @@ -246,8 +246,6 @@ fn as_op_or_id(string: String) -> Token { "and" => Token::And, "or" => Token::Or, "not" => Token::Not, - "is" => Token::Is, - "isa" => Token::IsA, "mod" => Token::Mod, "sqrt" => Token::Sqrt, "while" => Token::While, @@ -285,7 +283,7 @@ mod test { assert_eq!(tokens[0].token, Token::Def); assert_eq!(tokens[1].token, Token::Id(String::from("f"))); - assert_eq!(tokens[2].token, Token::LRBrack); + assert_eq!(tokens[2].token, Token::LSBrack); assert_eq!(tokens[3].token, Token::Id(String::from("x"))); assert_eq!(tokens[4].token, Token::DoublePoint); assert_eq!(tokens[5].token, Token::Id(String::from("Int"))); @@ -294,9 +292,7 @@ mod test { assert_eq!(tokens[8].token, Token::Id(String::from("Int"))); assert_eq!(tokens[9].token, Token::Assign); assert_eq!(tokens[10].token, Token::NL); - assert_eq!(tokens[11].token, Token::Indent); - assert_eq!(tokens[12].token, Token::Ret); - assert_eq!(tokens[13].token, Token::Dedent); + assert_eq!(tokens[11].token, Token::Ret); Ok(()) } @@ -310,16 +306,14 @@ mod test { assert_eq!(tokens[0].token, Token::Class); assert_eq!(tokens[1].token, Token::Id(String::from("MyClass"))); assert_eq!(tokens[2].token, Token::NL); - assert_eq!(tokens[3].token, Token::Indent); - assert_eq!(tokens[4].token, Token::Def); - assert_eq!(tokens[5].token, Token::Id(String::from("var"))); - assert_eq!(tokens[6].token, Token::Assign); - assert_eq!(tokens[7].token, Token::Int(String::from("10"))); + assert_eq!(tokens[3].token, Token::Def); + assert_eq!(tokens[4].token, Token::Id(String::from("var"))); + assert_eq!(tokens[5].token, Token::Assign); + assert_eq!(tokens[6].token, Token::Int(String::from("10"))); + assert_eq!(tokens[7].token, Token::NL); assert_eq!(tokens[8].token, Token::NL); - assert_eq!(tokens[9].token, Token::Dedent); - assert_eq!(tokens[10].token, Token::NL); - assert_eq!(tokens[11].token, Token::Class); - assert_eq!(tokens[12].token, Token::Id(String::from("MyClass1"))); + assert_eq!(tokens[9].token, Token::Class); + assert_eq!(tokens[10].token, Token::Id(String::from("MyClass1"))); Ok(()) } @@ -334,15 +328,12 @@ mod test { assert_eq!(tokens[1].token, Token::Id(String::from("a"))); assert_eq!(tokens[2].token, Token::Then); assert_eq!(tokens[3].token, Token::NL); - assert_eq!(tokens[4].token, Token::Indent); - assert_eq!(tokens[5].token, Token::Id(String::from("b"))); + assert_eq!(tokens[4].token, Token::Id(String::from("b"))); + assert_eq!(tokens[5].token, Token::NL); assert_eq!(tokens[6].token, Token::NL); - assert_eq!(tokens[7].token, Token::Dedent); + assert_eq!(tokens[7].token, Token::Else); assert_eq!(tokens[8].token, Token::NL); - assert_eq!(tokens[9].token, Token::Else); - assert_eq!(tokens[10].token, Token::NL); - assert_eq!(tokens[11].token, Token::Indent); - assert_eq!(tokens[12].token, Token::Id(String::from("c"))); + assert_eq!(tokens[9].token, Token::Id(String::from("c"))); Ok(()) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 4d928dce..d57f0971 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use crate::common::position::Position; use crate::parse::ast::{Node, AST}; use crate::parse::iterator::LexIterator; -use crate::parse::lex::token::{Lex, Token}; +use crate::parse::lex::token::Lex; use crate::parse::lex::tokenize; -use crate::parse::result::{expected, ParseErr, ParseResult}; +use crate::parse::result::{ParseErr, ParseResult}; pub mod ast; @@ -35,11 +35,6 @@ impl FromStr for AST { let mut iterator = LexIterator::new(tokens.iter().peekable()); let statements = block::parse_statements(&mut iterator)?; - if iterator.peek_if(&|lex| lex.token != Token::Eof) { - if let Some(lex) = iterator.peek_next() { - return Err(Box::from(expected(&Token::Eof, &lex, "end of file"))); - } - } let start = statements .first() diff --git a/src/parse/operation.rs b/src/parse/operation.rs index ab2a0ae9..1346e23d 100644 --- a/src/parse/operation.rs +++ b/src/parse/operation.rs @@ -70,8 +70,6 @@ fn parse_level_5(it: &mut LexIterator) -> ParseResult { Token::Leq => bin_op!(it, parse_level_5, Leq, arithmetic.clone(), "less, equal"), Token::Eq => bin_op!(it, parse_level_5, Eq, arithmetic.clone(), "equal"), Token::Neq => bin_op!(it, parse_level_5, Neq, arithmetic.clone(), "not equal"), - Token::Is => bin_op!(it, parse_level_5, Is, arithmetic.clone(), "is"), - Token::IsA => bin_op!(it, parse_level_5, IsA, arithmetic.clone(), "is a"), Token::In => bin_op!(it, parse_level_5, In, arithmetic.clone(), "in"), _ => Ok(arithmetic.clone()), }, @@ -395,46 +393,6 @@ mod test { ); } - #[test] - fn is_verify() { - let source = String::from("p is q"); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(Is, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("p") - } - ); - assert_eq!( - right.node, - Node::Id { - lit: String::from("q") - } - ); - } - - #[test] - fn isa_verify() { - let source = String::from("lizard isa animal"); - let ast = parse_direct(&source).unwrap(); - - let (left, right) = verify_is_operation!(IsA, ast); - assert_eq!( - left.node, - Node::Id { - lit: String::from("lizard") - } - ); - assert_eq!( - right.node, - Node::Id { - lit: String::from("animal") - } - ); - } - #[test] fn equality_verify() { let source = String::from("i = s"); diff --git a/src/parse/statement.rs b/src/parse/statement.rs index 77f31ca4..fa696d97 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -4,6 +4,7 @@ use crate::parse::ast::AST; use crate::parse::control_flow_stmt::parse_cntrl_flow_stmt; use crate::parse::definition::parse_definition; use crate::parse::expr_or_stmt::parse_expr_or_stmt; +use crate::parse::expression::is_start_expression; use crate::parse::iterator::LexIterator; use crate::parse::lex::token::{Lex, Token}; use crate::parse::operation::parse_expression; @@ -187,23 +188,19 @@ pub fn parse_with(it: &mut LexIterator) -> ParseResult { pub fn parse_return(it: &mut LexIterator) -> ParseResult { let start = it.start_pos("return")?; - it.eat(&Token::Ret, "return")?; + let end = it.eat(&Token::Ret, "return")?; - if let Some(end) = it.eat_if(&Token::NL) { - let node = Node::ReturnEmpty; - return Ok(Box::from(AST::new(start.union(end), node))); - } else if it.peek_if(&|lex| lex.token == Token::Dedent || lex.token == Token::Eof) - || it.peek_next().is_none() - { - let node = Node::ReturnEmpty; - return Ok(Box::from(AST::new(start, node))); + if let Some(next) = it.peek_next() { + if is_start_expression(&next) { + let expr = it.parse(&parse_expression, "return", start)?; + return Ok(Box::from(AST::new( + start.union(expr.pos), + Node::Return { expr }, + ))); + } } - let expr = it.parse(&parse_expression, "return", start)?; - Ok(Box::from(AST::new( - start.union(expr.pos), - Node::Return { expr }, - ))) + Ok(Box::from(AST::new(start.union(end), Node::ReturnEmpty))) } pub fn is_start_statement(tp: &Token) -> bool { diff --git a/src/parse/ty.rs b/src/parse/ty.rs index 41263a14..68cc6b52 100644 --- a/src/parse/ty.rs +++ b/src/parse/ty.rs @@ -15,9 +15,9 @@ pub fn parse_id(it: &mut LexIterator) -> ParseResult { let end = it.eat(&Token::Id(id.clone()), "identifier")?; Ok(Box::from(AST::new(end, Node::Id { lit: id.clone() }))) } - Token::LRBrack => { + Token::LSBrack => { let mut elements = vec![]; - let start = it.eat(&Token::LRBrack, "identifier tuple")?; + let start = it.eat(&Token::LSBrack, "identifier tuple")?; it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { elements.push(*it.parse(&parse_expr_no_type, "identifier", start)?); it.eat_if(&Token::Comma); @@ -28,7 +28,7 @@ pub fn parse_id(it: &mut LexIterator) -> ParseResult { Ok(Box::from(AST::new(end, Node::Tuple { elements }))) } _ => Err(Box::from(expected_one_of( - &[Token::Id(String::new()), Token::LRBrack], + &[Token::Id(String::new()), Token::LSBrack], lex, "identifier", ))), @@ -77,15 +77,15 @@ pub fn parse_type(it: &mut LexIterator) -> ParseResult { let node = Node::Type { id, generics }; Ok(Box::from(AST::new(start.union(end), node))) } - Token::LRBrack => it.parse(&parse_type_tuple, "type", start), + Token::LSBrack => it.parse(&parse_type_tuple, "type", start), Token::LCBrack => it.parse(&parse_type_set, "type", start), _ => Err(Box::from(expected_one_of( - &[Token::Id(String::new()), Token::LRBrack, Token::LCBrack], + &[Token::Id(String::new()), Token::LSBrack, Token::LCBrack], &lex.clone(), "type", ))), }, - &[Token::Id(String::new()), Token::LRBrack], + &[Token::Id(String::new()), Token::LSBrack], "type", )?; @@ -129,13 +129,13 @@ pub fn parse_conditions(it: &mut LexIterator) -> ParseResult> { let mut conditions = vec![]; if it.eat_if(&Token::NL).is_some() { - it.eat(&Token::Indent, "conditions")?; - it.peek_while_not_token(&Token::Dedent, &mut |it, _| { + it.eat(&Token::LRBrack, "conditions")?; + it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { conditions.push(*it.parse(&parse_condition, "conditions", start)?); it.eat_if(&Token::NL); Ok(()) })?; - it.eat(&Token::Dedent, "conditions")?; + it.eat(&Token::RRBrack, "conditions")?; } else { let start = it.start_pos("conditions")?; conditions.push(*it.parse(&parse_condition, "conditions", start)?); @@ -172,7 +172,7 @@ pub fn parse_type_set(it: &mut LexIterator) -> ParseResult { pub fn parse_type_tuple(it: &mut LexIterator) -> ParseResult { let start = it.start_pos("type tuple")?; - it.eat(&Token::LRBrack, "type tuple")?; + it.eat(&Token::LSBrack, "type tuple")?; let mut types = vec![]; it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { diff --git a/tests/resource/valid/class/with_generics.mamba b/tests/resource/valid/class/with_generics.mamba index 8d06b9c0..d40e30e5 100644 --- a/tests/resource/valid/class/with_generics.mamba +++ b/tests/resource/valid/class/with_generics.mamba @@ -1,7 +1,7 @@ -class InnerClass[A] +class InnerClass[A] := def inner(a: A) -> A := a -class OuterClass[B] +class OuterClass[B] := def inner_class: InnerClass[B] def outer_class := OuterClass() From d5bd6216a9cc346b9c86cc44ca1cd5614da6d6ea Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Sun, 27 Jul 2025 10:10:15 +0200 Subject: [PATCH 24/44] doc: refine grammar further --- docs/spec/README.md | 4 +- docs/spec/grammar.md | 112 ++++++++++------------- docs/spec/keywords.md | 85 ----------------- docs/spec/{characters.md => reserved.md} | 85 ++++++++++++++++- docs/spec/std/README.md | 3 + tests/resource/valid/class/import.mamba | 6 +- 6 files changed, 141 insertions(+), 154 deletions(-) delete mode 100644 docs/spec/keywords.md rename docs/spec/{characters.md => reserved.md} (54%) create mode 100644 docs/spec/std/README.md diff --git a/docs/spec/README.md b/docs/spec/README.md index e31b0bbb..c7669003 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -6,6 +6,6 @@ ### [3.1 Grammar](grammar.md) -### [3.2 Keywords](keywords.md) +### [3.2 Special Characters and Symbols](reserved.md) -### [3.3 Special Characters](characters.md) +### [3.3 Standar Library](std/characters.md) diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 4406f9ad..0c3c26b7 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -12,118 +12,104 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). ```ebnf file ::= { expr-or-stmt } - import ::= "import" id [ "::" id ] ( "::" ( "{" import-as { "," import-as } "}" | import-as ) ) + import ::= [ "from" id ] "import" + ( import-as | "{" import-as "," "}" | "{" import-as "," import-as { "," import-as } "}" ) import-as ::= id ( "as" id ) - type-def ::= "type" type ":" type ( ":=" "{" code-set "}" | "when" [ code-set ] ) - trait-def ::= "trait" type ( ":" type { "," type } ) [ ":=" code-set ] - type-tuple ::= "(" [ type ] { "," type } ")" - - class ::= "class" id [ fun-args ] [ ":" ( type | type-tuple ) ] [ ":=" code-set ] - generics ::= "[" id { "," id } "]" + type-def ::= "type" type-not-fun ":" type-not-fun "when" ( expression | code-set ) + trait-def ::= "trait" type-not-fun ( ":" type-not-fun { "," type-not-fun } ) [ ":=" statement ] + class-def ::= "class" type-not-fun [ fun-args ] [ ":" type-not-fun ] [ ":=" statement ] id ::= { character } id-maybe-type ::= id [ ":" type ] - type ::= ( id [ generics ] | type-tuple ) [ "->" type ] - type-tuple ::= "(" [ type { "," type } ] ")" + type-not-fun ::= id [ generics ] + type ::= id [ generics ] [ "->" type ] + generics ::= "[" id { "," id } "]" expr-or-stmt ::= ( statement | expression ) statement ::= control-flow-stmt | definition | reassignment | type-def - | "pass" - | class - | type-def + | trait-def + | class-def | import - expression ::= "(" expression ")" - | expression "?or" expression | "return" [ expression ] - | expression "as" id - | control-flow-expr - | code-block + | code-set + expression ::= control-flow-expr | collection - | key-value | operation | anon-fun | call | "_" + | code-block - reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" ) code-block - call ::= code-block [ ( "." | "?." ) ] id tuple [ "!" match-cases [ recover code-block ] ] + reassignment ::= expression ( ":=" | "+=" | "-=" | "*=" | "/=" | "^=" ) expression + call ::= expression [ ( "." | "?." ) ] id tuple [ "!" match-cases [ recover expression ] ] raise ::= "!" id { "," id } - collection ::= tuple | set | list | map - tuple ::= "(" code-block { "," code-block } ")" - set ::= "{" code-block { "," code-block } "}" | set-builder - set-builder ::= "{" expression "|" expression { "," expression } "}" - list ::= "[" code-block { "," code-block } "]" | list-builder - list-builder ::= "[" expression "|" expression { "," expression } "]" - - slice ::= code-block ( "::" | "::=" ) code-block - range ::= code-block ( ".." | "..=" ) code-block + # for all collections, we require one comma at least to avoid ambiguity + collection ::= tuple | set | set-builder | list | list-builder | map | map-builder + tuple ::= "(" "," ")" | "(" expression "," ")" + | "(" expression "," [ newline ] expression { "," [ newline ] expression } ")" + set ::= "{" "," "}" | "{" expression "," "}" + | "{" expression { "," [ newline ] expression } "}" + set-builder ::= "{" expression "|" expression { "," [ newline ] expression } "}" + list ::= "[" "," "]" | "[" expression "," "]" + | "[" expression { "," [ newline ] expression } "]" + list-builder ::= "[" expression "|" expression { "," [ newline ] expression } "]" + map ::= "{" expression "=>" expression "," "}" + | "{" expression "=>" expression { "," [ newline ] expression "=>" expression } "}" + map-builder ::= "{ expression "=>" expression | expression { "," [ newline ] expression } } + + slice ::= expression ( "::" | "::=" ) expression + range ::= expression ( ".." | "..=" ) expression - definition ::= "def" ( variable-def | fun-def ) | type-def | trait-def | class-def + definition ::= variable-def | fun-def | type-def | trait-def | class-def - variable-def ::= [ "fin" ] ( id-maybe-type | collection ) [ ":=" code-block ] [ forward ] - fun-def ::= ( [ "meta" ] | [ "total" ] [ "pure" ] ) ( id | overridable-op ) fun-args [ "->" type ] [ raise ] [ ":=" code-block ] + variable-def ::= "def" [ "fin" ] ( id-maybe-type | collection ) [ ":=" expression ] + # type checker should check for valid combination of meta, total, pure + fun-def ::= "def" [ "meta" ] [ "total" ] [ "pure" ] + ( id | overridable-op ) fun-args [ "->" type ] [ raise ] + [ ":=" expression ] fun-args ::= "(" [ fun-arg ] { "," fun-arg } ")" - fun-arg ::= id-maybe-type [ ":=" code-block ] - anon-fun ::= "\" [ id-maybe-type { "," id-maybe-type } ] ":=" code-block + fun-arg ::= id-maybe-type [ ":=" expression ] + anon-fun ::= "\" [ id-maybe-type { "," id-maybe-type } ] ":=" expression - operation ::= relation [ ( equality | instance-eq | boolean-logic ) relation ] + operation ::= relation [ ( equality | boolean-logic ) relation ] relation ::= arithmetic [ comparison relation ] arithmetic ::= term [ additive arithmetic ] - term ::= inner-term [ ( multiclative | range | slice ) term ] + term ::= inner-term [ ( multiplicative | range | slice ) term ] inner-term ::= factor [ power inner-term ] factor ::= [ unary ] ( literal | id | expression ) - overrideable-op ::= additive | multiplicative | power | "=" | "<" | ">" + overridable-op ::= additive | multiplicative | power | "=" | "<" | ">" unary ::= "not" | additive additive ::= "+" | "-" multiplicative ::= "*" | "/" power ::= "^" | "mod" - instance-eq ::= "is" | "isa" equality ::= "=" | "!=" comparison ::= "<=" | ">=" | "<" | ">" boolean-logic ::= "and" | "or" - literal ::= number | string | "None" + literal ::= number | string number ::= real | integer | e-notation real ::= integer "." integer | "." integer | integer "." integer ::= { digit } e-notation ::= ( integer | real ) "E" [ "-" ] integer string ::= """ { character } """ - code-block ::= expr-or-stmt - | "[" expr-or-stmt "]" - | "[" newline expr-or-stmt { newline expr-or-stmt } "]" - code-set ::= expr-or-stmt - | "{" expr-or-stmt "}" - | "{" newline expr-or-stmt { newline expr-or-stmt } "}" + code-block ::= "[" expr-or-stmt { newline expr-or-stmt } "]" + code-set ::= "{" expr-or-stmt { newline expr-or-stmt } "}" control-flow-expr::= if | match - if ::= "if" code-block "then" code-block [ "else" code-block ] - match ::= "match" code-block "with" match-cases - match-cases ::= "{" match-case "}" | "{" newline match-case { newline match-case } "}" - match-case ::= expression "=>" code-block + if ::= "if" expression "then" expression [ "else" expression ] + match ::= "match" expression "with" map control-flow-stmt::= while | foreach | "break" | "continue" - while ::= "while" code-block "do" code-block - foreach ::= "for" code-block "in" code-block "do" code-block + while ::= "while" expression "do" expression + foreach ::= "for" expression "in" expression "do" expression newline ::= ``` - -## Notes - -An `expression` is used in a situation where an expression is required. -This allows the parser to short-circuit if something is definitely not an expression where it should be. -However, we cannot always know in advance whether something is an expression, e.g. when it is a function call. -Those cases should be verified by the type checker. -An `expr-or-stmt` may be used when it does not matter whether something is an expression or statement, such as the body of a loop. - -We do not systematically desugar multiple delimited by commas, or a single expression, to tuples, as is the case in Python. -This prevents ambiguity in the grammar as specified above, and also prevents confusing situations such as `(0)` and `0` being equal. -Instead, we only do this in specific contexts, such as in the conditional of control flows. diff --git a/docs/spec/keywords.md b/docs/spec/keywords.md deleted file mode 100644 index ed227c95..00000000 --- a/docs/spec/keywords.md +++ /dev/null @@ -1,85 +0,0 @@ -⬅ [🏠 Home](../README.md) - -⬅ [3 📚 Specification](README.md) - -# 3.2 Keywords - -The following is a list of all the keywords in the language. - -## Imports - -Keyword | Use ----|--- -`from` | Specify where to import from -`import`| Specify what to import -`as` | Specify alias of import - -## Classes - -Keyword | Use ----|--- -`type` | Denote start of type defintion or type alias -`when` | Conditionals of a type definition -`class` | Denote start of class definition -`trait` | Denote start of trait definition - -## Classes and Utils - -Keyword | Use ----|--- -`self` | Refer to definitions of this class -`init` | The constructor of the class -`forward` | Forwarding methods of contained class - -## Definitions and Functions - -Keyword | Use ----|--- -`def` | Denote definition -`fin` | Denote defined variable is immutable -`pure` | Denote function is pure -`total` | Denote a function is total -`meta` | Denote a meta function (evaluated during compile time) - -## Boolean operators - -Keyword | Use ----|--- -`not` | Negation of a boolean value -`and` | And operator -`or` | Or operator - -## Mathematical Operators - -Keyword | Use ----|--- -`mod` | Modulus operator -`sqrt` | Square root operator - -## Control flow Expressions - -Keyword | Use ----|--- -`if` | Denote start of if expression or statement -`then` | Denote start of then branch of if -`else` | Denote start of else branch of if -`match` | Denote start of a match expression or statement -`recover` | Recover from error, for (partial) local error recovery - -## Control Flow Statements - -Keyword | Use ----|--- -`while` | Denote start of while statement -`for` | Denote start of for statement -`in` | Specify which collection to iterate over in for statement -`do` | Specify what needs to be done in control flow statement -`continue`| Continue onto next iteration within loop -`break` | Exit loop - -## Statements - -Keyword | Use ----|--- -`return` | Return from a function or method -`pass` | Empty placeholder statement diff --git a/docs/spec/characters.md b/docs/spec/reserved.md similarity index 54% rename from docs/spec/characters.md rename to docs/spec/reserved.md index 128faffc..13471eda 100644 --- a/docs/spec/characters.md +++ b/docs/spec/reserved.md @@ -2,7 +2,90 @@ ⬅ [3 📚 Specification](README.md) -# 3.3 Special Characters and Symbols +# 3.2 Special Characters and Symbols + +## 3.2.1 Keywords + +The following is a list of all the keywords in the language. + +## Imports + +Keyword | Use +---|--- +`from` | Specify where to import from +`import`| Specify what to import +`as` | Specify alias of import + +## Classes + +Keyword | Use +---|--- +`type` | Denote start of type defintion or type alias +`when` | Conditionals of a type definition +`class` | Denote start of class definition +`trait` | Denote start of trait definition + +## Classes and Utils + +Keyword | Use +---|--- +`self` | Refer to definitions of this class +`init` | The constructor of the class +`forward` | Forwarding methods of contained class + +## Definitions and Functions + +Keyword | Use +---|--- +`def` | Denote definition +`fin` | Denote defined variable is immutable +`pure` | Denote function is pure +`total` | Denote a function is total +`meta` | Denote a meta function (evaluated during compile time) + +## Boolean operators + +Keyword | Use +---|--- +`not` | Negation of a boolean value +`and` | And operator +`or` | Or operator + +## Mathematical Operators + +Keyword | Use +---|--- +`mod` | Modulus operator +`sqrt` | Square root operator + +## Control flow Expressions + +Keyword | Use +---|--- +`if` | Denote start of if expression or statement +`then` | Denote start of then branch of if +`else` | Denote start of else branch of if +`match` | Denote start of a match expression or statement +`recover` | Recover from error, for (partial) local error recovery + +## Control Flow Statements + +Keyword | Use +---|--- +`while` | Denote start of while statement +`for` | Denote start of for statement +`in` | Specify which collection to iterate over in for statement +`do` | Specify what needs to be done in control flow statement +`continue`| Continue onto next iteration within loop +`break` | Exit loop + +## Statements + +Keyword | Use +---|--- +`return` | Return from a function or method + +## 3.2.2 Special Characters The following is a list of characters in the language diff --git a/docs/spec/std/README.md b/docs/spec/std/README.md new file mode 100644 index 00000000..ec93c3f2 --- /dev/null +++ b/docs/spec/std/README.md @@ -0,0 +1,3 @@ +# Standard library + +This the location where we will start specifying the behaviour of the standard library. diff --git a/tests/resource/valid/class/import.mamba b/tests/resource/valid/class/import.mamba index a4339762..950dccc7 100644 --- a/tests/resource/valid/class/import.mamba +++ b/tests/resource/valid/class/import.mamba @@ -1,4 +1,4 @@ -import a::{b as c,c as d} -import b::c +from a import {b as c, c as d} +from b import c import d -import d::dd +from d import dd From 3e146445036ec06420b7dc572d915fe0d75ece3e Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Sun, 27 Jul 2025 11:28:05 +0200 Subject: [PATCH 25/44] refactor: simplify parse call --- src/check/constrain/constraint/expected.rs | 5 +- src/check/constrain/constraint/generic.rs | 571 +++++++++++++++++++++ src/check/constrain/generate/mod.rs | 2 +- src/check/constrain/mod.rs | 34 +- src/check/context/clss/generic.rs | 32 +- src/check/context/function/generic.rs | 13 +- src/check/mod.rs | 4 +- src/parse/call.rs | 25 +- src/parse/collection.rs | 35 +- src/parse/control_flow_expr.rs | 46 +- src/parse/control_flow_stmt.rs | 35 +- src/parse/definition.rs | 30 +- src/parse/expr_or_stmt.rs | 42 +- src/parse/expression.rs | 9 +- src/parse/mod.rs | 11 +- src/parse/operation.rs | 45 +- src/parse/statement.rs | 27 +- 17 files changed, 732 insertions(+), 234 deletions(-) create mode 100644 src/check/constrain/constraint/generic.rs diff --git a/src/check/constrain/constraint/expected.rs b/src/check/constrain/constraint/expected.rs index a624bc44..495e7908 100644 --- a/src/check/constrain/constraint/expected.rs +++ b/src/check/constrain/constraint/expected.rs @@ -232,12 +232,11 @@ mod tests { use crate::check::constrain::constraint::expected::{Expect, Expected}; use crate::common::position::{CaretPos, Position}; use crate::parse::ast::{Node, AST}; - use crate::parse::parse_direct; #[test] fn test_expected_from_int_constructor_call() { - let ast = parse_direct("Int(10)").unwrap(); - let expect = Expected::from(&ast[0]); + let ast: AST = "Int(10)".parse().unwrap(); + let expect = Expected::from(&ast); assert_eq!( expect.expect, diff --git a/src/check/constrain/constraint/generic.rs b/src/check/constrain/constraint/generic.rs new file mode 100644 index 00000000..d5502d0f --- /dev/null +++ b/src/check/constrain/constraint/generic.rs @@ -0,0 +1,571 @@ +use std::collections::HashSet; +use std::convert::TryFrom; +use std::hash::{Hash, Hasher}; +use std::iter::FromIterator; +use std::ops::Deref; + +use crate::check::context::arg::generic::{ClassArgument, GenericFunctionArg}; +use crate::check::context::field::generic::{GenericField, GenericFields}; +use crate::check::context::function::generic::GenericFunction; +use crate::check::context::function::python::{INIT, STR}; +use crate::check::context::parent::generic::GenericParent; +use crate::check::context::{arg, clss}; +use crate::check::name::string_name::StringName; +use crate::check::name::{Any, Empty, Name}; +use crate::check::result::{TypeErr, TypeResult}; +use crate::common::position::Position; +use crate::parse::ast::{Node, AST}; + +#[derive(Debug, Clone, Eq)] +pub struct GenericClass { + pub is_py_type: bool, + pub name: StringName, + pub pos: Position, + pub concrete: bool, + pub args: Vec, + pub fields: HashSet, + pub functions: HashSet, + pub parents: HashSet, +} + +impl PartialEq for GenericClass { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Hash for GenericClass { + fn hash(&self, state: &mut H) { + self.name.hash(state) + } +} + +impl GenericClass { + pub fn try_from_id(id: &AST) -> TypeResult { + match &id.node { + Node::Id { lit } => { + Ok(GenericClass { + is_py_type: false, + name: StringName::from(lit.as_str()), + pos: id.pos, + concrete: true, // assume concrete for now + args: vec![], + fields: Default::default(), + functions: Default::default(), + parents: Default::default(), + }) + } + _ => Err(vec![TypeErr::new(id.pos, "Expected class name")]), + } + } + + pub fn all_pure(self, pure: bool) -> TypeResult { + let functions = self + .functions + .iter() + .map(|f| f.clone().pure(pure)) + .collect(); + Ok(GenericClass { functions, ..self }) + } +} + +impl Any for GenericClass { + fn any() -> Self { + let mut functions = HashSet::new(); + functions.insert(GenericFunction { + is_py_type: false, + name: StringName::new(STR, &[]), + pure: false, + pos: Position::invisible(), + arguments: vec![], + raises: Name::empty(), + in_class: None, + ret_ty: None, + }); + + GenericClass { + is_py_type: false, + name: StringName::new(clss::ANY, &[]), + pos: Position::invisible(), + concrete: true, + args: vec![], + fields: Default::default(), + functions, + parents: Default::default(), + } + } +} + +impl TryFrom<&AST> for GenericClass { + type Error = Vec; + + fn try_from(class: &AST) -> TypeResult { + match &class.node { + Node::Class { + ty, + args, + parents, + body, + } => { + let name = StringName::try_from(ty)?; + let statements = if let Some(body) = body { + match &body.node { + Node::Block { statements } => statements.clone(), + _ => return Err(vec![TypeErr::new(class.pos, "Expected block in class")]), + } + } else { + vec![] + }; + + let mut class_args = vec![]; + let mut arg_errs = vec![]; + let mut argument_fields = HashSet::new(); + for arg in args { + match ClassArgument::try_from(arg) { + Err(err) => arg_errs.push(err), + Ok(ClassArgument { field, fun_arg }) => { + if let Some(field) = field { + class_args.push(fun_arg); + argument_fields.insert(field.in_class( + Some(&name), + false, + arg.pos, + )?); + } else { + class_args.push(fun_arg); + } + } + } + } + + if !arg_errs.is_empty() { + return Err(arg_errs.into_iter().flatten().collect()); + } + let mut class_args = if class_args.is_empty() { + class_args + } else { + let mut new_args = vec![GenericFunctionArg { + is_py_type: false, + name: String::from(arg::SELF), + has_default: false, + pos: Position::invisible(), + vararg: false, + mutable: true, + ty: Some(Name::from(&name)), + }]; + new_args.append(&mut class_args); + new_args + }; + + let mut temp_parents: HashSet = HashSet::new(); + let errs: Vec<(Position, String)> = parents + .iter() + .flat_map(|p| match &p.node { + Node::Parent { ty, .. } => match &ty.node { + Node::Type { id, .. } => Some((ty.pos, id.node.clone())), + _ => None, + }, + _ => None, + }) + .flat_map(|(pos, parent)| { + if temp_parents.contains(&parent) { + Some((pos, format!("Duplicate parent: {parent}"))) + } else { + temp_parents.insert(parent); + None + } + }) + .collect(); + + if !errs.is_empty() { + return Err(errs + .iter() + .map(|(pos, msg)| TypeErr::new(*pos, msg)) + .collect()); + } + + let (body_fields, functions) = get_fields_and_functions(&name, &statements, false)?; + if let Some(function) = functions.iter().find(|f| f.name == StringName::from(INIT)) + { + if class_args.is_empty() { + class_args.append(&mut function.arguments.clone()) + } else { + let msg = "Cannot have constructor and class arguments"; + return Err(vec![TypeErr::new(class.pos, msg)]); + } + } + + if class_args.is_empty() { + class_args.push(GenericFunctionArg { + is_py_type: false, + name: String::from(arg::SELF), + pos: Position::invisible(), + has_default: false, + vararg: false, + mutable: true, + ty: Option::from(Name::from(&name)), + }) + } + + let (parents, parent_errs): (Vec<_>, Vec<_>) = parents + .iter() + .map(GenericParent::try_from) + .partition(Result::is_ok); + if !parent_errs.is_empty() { + return Err(parent_errs + .into_iter() + .flat_map(Result::unwrap_err) + .collect()); + } + + Ok(GenericClass { + is_py_type: false, + name, + pos: class.pos, + args: class_args, + concrete: true, + fields: argument_fields.union(&body_fields).cloned().collect(), + functions, + parents: parents.into_iter().map(Result::unwrap).collect(), + }) + } + Node::TypeDef { ty, isa, body, .. } => { + let name = StringName::try_from(ty)?; + let statements = if let Some(body) = body { + match &body.node { + Node::Block { statements } => statements.clone(), + _ => return Err(vec![TypeErr::new(class.pos, "Expected block in class")]), + } + } else { + vec![] + }; + + let parents = if let Some(isa) = isa { + HashSet::from_iter(vec![GenericParent::try_from(isa.deref())?]) + } else { + HashSet::new() + }; + + let (fields, functions) = get_fields_and_functions(&name, &statements, true)?; + Ok(GenericClass { + is_py_type: false, + name, + pos: class.pos, + args: vec![GenericFunctionArg { + is_py_type: false, + name: String::from(arg::SELF), + pos: Position::invisible(), + has_default: false, + vararg: false, + mutable: true, + ty: Option::from(Name::try_from(ty)?), + }], + concrete: false, + fields, + functions, + parents, + }) + } + Node::TypeAlias { ty, isa, .. } => Ok(GenericClass { + is_py_type: false, + name: StringName::try_from(ty)?, + pos: class.pos, + args: vec![GenericFunctionArg { + is_py_type: false, + name: String::from(arg::SELF), + pos: Position::invisible(), + has_default: false, + vararg: false, + mutable: true, + ty: Option::from(Name::try_from(ty)?), + }], + concrete: false, + fields: HashSet::new(), + functions: HashSet::new(), + parents: HashSet::from_iter(vec![GenericParent::try_from(isa.deref())?]), + }), + _ => Err(vec![TypeErr::new( + class.pos, + "Expected class or type definition", + )]), + } + } +} + +fn get_fields_and_functions( + class: &StringName, + statements: &[AST], + type_def: bool, +) -> TypeResult<(HashSet, HashSet)> { + let mut fields = HashSet::new(); + let mut functions = HashSet::new(); + + for statement in statements { + match &statement.node { + Node::FunDef { .. } => { + let function = GenericFunction::try_from(statement)?; + let function = function.in_class(Some(class), type_def, statement.pos)?; + functions.insert(function); + } + Node::VariableDef { .. } => { + let stmt_fields: HashSet = GenericFields::try_from(statement)? + .fields + .into_iter() + .map(|f| f.in_class(Some(class), type_def, statement.pos)) + .collect::>()?; + + for generic_field in &stmt_fields { + if generic_field.ty.is_none() { + let msg = format!( + "Class field '{}' was not assigned a type", + generic_field.name + ); + return Err(vec![TypeErr::new(generic_field.pos, &msg)]); + } + } + + fields = fields.union(&stmt_fields).cloned().collect(); + } + Node::DocStr { .. } => {} + _ => { + let msg = "Expected function or variable definition"; + return Err(vec![TypeErr::new(statement.pos, msg)]); + } + } + } + + Ok((fields, functions)) +} + +#[cfg(test)] +mod test { + use std::convert::TryFrom; + + use itertools::Itertools; + + use crate::check::context::clss::generic::GenericClass; + use crate::check::name::string_name::StringName; + use crate::check::name::true_name::TrueName; + use crate::check::name::Name; + use crate::parse::ast::AST; + use crate::TypeErr; + + #[test] + fn from_class_inline_args() -> Result<(), Vec> { + let source = "class MyClass(def fin a: Int, b: Int): Parent(b)\n def c: Int := a + b\n"; + let ast: AST = source.parse() + .expect("valid class syntax"); + + let generic_class = GenericClass::try_from(&ast)?; + + assert_eq!(generic_class.name, StringName::from("MyClass")); + assert!(!generic_class.is_py_type); + assert!(generic_class.concrete); + + assert_eq!(generic_class.parents.len(), 1); + let parent = generic_class.parents.iter().next().expect("Parent"); + assert_eq!(parent.name, TrueName::from("Parent")); + assert!(!parent.is_py_type); + + assert_eq!(generic_class.args.len(), 3); + assert_eq!(generic_class.args[0].name, String::from("self")); + assert_eq!(generic_class.args[0].ty, Some(Name::from("MyClass"))); + assert!(!generic_class.args[0].is_py_type); + assert!(!generic_class.args[0].vararg); + assert!(generic_class.args[0].mutable); + assert!(!generic_class.args[0].has_default); + + assert_eq!(generic_class.args[1].name, String::from("a")); + assert_eq!(generic_class.args[1].ty, Some(Name::from("Int"))); + assert!(!generic_class.args[1].vararg); + assert!(!generic_class.args[1].mutable); + assert!(!generic_class.args[1].is_py_type); + assert!(!generic_class.args[1].has_default); + + assert_eq!(generic_class.args[2].name, String::from("b")); + assert_eq!(generic_class.args[2].ty, Some(Name::from("Int"))); + assert!(!generic_class.args[2].vararg); + assert!(generic_class.args[2].mutable); + assert!(!generic_class.args[2].is_py_type); + assert!(!generic_class.args[2].has_default); + + assert_eq!(generic_class.fields.len(), 2); + let mut fields = generic_class + .fields + .iter() + .sorted_by_key(|f| f.name.clone()); + + let field = fields.next().expect("Field"); + assert_eq!(field.name, "a"); + assert_eq!(field.in_class, Some(StringName::from("MyClass"))); + assert_eq!(field.ty, Some(Name::from("Int"))); + assert!(!field.is_py_type); + assert!(!field.mutable); + + let field = fields.next().expect("Field"); + assert_eq!(field.name, "c"); + assert_eq!(field.in_class, Some(StringName::from("MyClass"))); + assert_eq!(field.ty, Some(Name::from("Int"))); + assert!(!field.is_py_type); + assert!(field.mutable); + + Ok(()) + } + + #[test] + fn from_class() -> Result<(), Vec> { + let source = "class MyClass\n def c: Int := a + b\n"; + let ast: AST = source.parse() + .expect("valid class syntax"); + + + let generic_class = GenericClass::try_from(&ast)?; + + assert_eq!(generic_class.name, StringName::from("MyClass")); + assert!(!generic_class.is_py_type); + assert!(generic_class.concrete); + + assert!(generic_class.parents.is_empty()); + assert_eq!(generic_class.args.len(), 1); + assert_eq!(generic_class.args[0].name, String::from("self")); + assert_eq!(generic_class.args[0].ty, Some(Name::from("MyClass"))); + assert!(!generic_class.args[0].is_py_type); + assert!(!generic_class.args[0].vararg); + assert!(generic_class.args[0].mutable); + assert!(!generic_class.args[0].has_default); + + assert_eq!(generic_class.fields.len(), 1); + let mut fields = generic_class + .fields + .iter() + .sorted_by_key(|f| f.name.clone()); + + let field = fields.next().expect("Field"); + assert_eq!(field.name, "c"); + assert_eq!(field.in_class, Some(StringName::from("MyClass"))); + assert_eq!(field.ty, Some(Name::from("Int"))); + assert!(!field.is_py_type); + assert!(field.mutable); + + Ok(()) + } + + #[test] + fn from_class_with_generic() -> Result<(), Vec> { + let source = "class MyClass[T]\n def c: T\n"; + let asts: AST = source.parse()(source) + .expect("valid type syntax") + .into_iter() + .next() + .expect("type AST"); + + let generic_class = GenericClass::try_from(&ast)?; + + let name = StringName::new("MyClass", &[Name::from("T")]); + assert_eq!(generic_class.name, name.clone()); + assert!(!generic_class.is_py_type); + assert!(generic_class.concrete); + + assert!(generic_class.parents.is_empty()); + assert_eq!(generic_class.args.len(), 1); + assert_eq!(generic_class.args[0].name, String::from("self")); + assert_eq!(generic_class.args[0].ty, Some(Name::from(&name))); + assert!(!generic_class.args[0].is_py_type); + assert!(!generic_class.args[0].vararg); + assert!(generic_class.args[0].mutable); + assert!(!generic_class.args[0].has_default); + + assert_eq!(generic_class.fields.len(), 1); + let mut fields = generic_class + .fields + .iter() + .sorted_by_key(|f| f.name.clone()); + + let field = fields.next().expect("Field"); + assert_eq!(field.name, "c"); + assert_eq!(field.in_class, Some(name)); + assert_eq!(field.ty, Some(Name::from("T"))); + assert!(!field.is_py_type); + assert!(field.mutable); + + Ok(()) + } + + #[test] + fn from_type_with_generic() -> Result<(), Vec> { + let source = "type MyType[T]\n def c: T\n"; + let ast: AST = source.parse() + .expect("valid type syntax"); + + let generic_class = GenericClass::try_from(&ast)?; + + let name = StringName::new("MyType", &[Name::from("T")]); + assert_eq!(generic_class.name, name.clone()); + assert!(!generic_class.is_py_type); + assert!(!generic_class.concrete); + + assert!(generic_class.parents.is_empty()); + assert_eq!(generic_class.args.len(), 1); + assert_eq!(generic_class.args[0].name, String::from("self")); + assert_eq!(generic_class.args[0].ty, Some(Name::from(&name))); + assert!(!generic_class.args[0].is_py_type); + assert!(!generic_class.args[0].vararg); + assert!(generic_class.args[0].mutable); + assert!(!generic_class.args[0].has_default); + + assert_eq!(generic_class.fields.len(), 1); + let mut fields = generic_class + .fields + .iter() + .sorted_by_key(|f| f.name.clone()); + + let field = fields.next().expect("Field"); + assert_eq!(field.name, "c"); + assert_eq!(field.in_class, Some(name)); + assert_eq!(field.ty, Some(Name::from("T"))); + assert!(!field.is_py_type); + assert!(field.mutable); + + Ok(()) + } + + #[test] + fn from_type_def() -> Result<(), Vec> { + let source = "type MyType\n def c: String\n"; + let asts: AST = source.parse()(source) + .expect("valid type syntax") + .into_iter() + .next() + .expect("type AST"); + + let generic_class = GenericClass::try_from(&ast)?; + + assert_eq!(generic_class.name, StringName::from("MyType")); + assert!(!generic_class.is_py_type); + assert!(!generic_class.concrete); + + assert!(generic_class.parents.is_empty()); + assert_eq!(generic_class.args.len(), 1); + assert_eq!(generic_class.args[0].name, String::from("self")); + assert_eq!(generic_class.args[0].ty, Some(Name::from("MyType"))); + assert!(!generic_class.args[0].is_py_type); + assert!(!generic_class.args[0].vararg); + assert!(generic_class.args[0].mutable); + assert!(!generic_class.args[0].has_default); + + assert_eq!(generic_class.fields.len(), 1); + let mut fields = generic_class + .fields + .iter() + .sorted_by_key(|f| f.name.clone()); + + let field = fields.next().expect("Field"); + assert_eq!(field.name, "c"); + assert_eq!(field.in_class, Some(StringName::from("MyType"))); + assert_eq!(field.ty, Some(Name::from("String"))); + assert!(!field.is_py_type); + assert!(field.mutable); + + Ok(()) + } +} diff --git a/src/check/constrain/generate/mod.rs b/src/check/constrain/generate/mod.rs index fa76a5a8..9fea074e 100644 --- a/src/check/constrain/generate/mod.rs +++ b/src/check/constrain/generate/mod.rs @@ -32,7 +32,7 @@ pub(super) mod env; pub type Constrained = Result>; -pub fn gen_all(ast: &AST, ctx: &Context) -> Constrained> { +pub fn constrain(ast: &AST, ctx: &Context) -> Constrained> { let mut builder = ConstrBuilder::new(); generate(ast, &Environment::default(), ctx, &mut builder)?; diff --git a/src/check/constrain/mod.rs b/src/check/constrain/mod.rs index 1220a3b6..cb4a6a9c 100644 --- a/src/check/constrain/mod.rs +++ b/src/check/constrain/mod.rs @@ -1,4 +1,4 @@ -use crate::check::constrain::generate::gen_all; +use crate::check::constrain::generate::constrain; use crate::check::constrain::unify::finished::Finished; use crate::check::constrain::unify::unify; use crate::check::context::Context; @@ -12,14 +12,19 @@ pub(super) mod unify; pub type Unified = Result>; -pub fn constraints(ast: &AST, ctx: &Context) -> Unified { - let constrained = gen_all(ast, ctx)?; +/// An [AST] is well-typed if: +/// +/// - Using the given [Context], which may include imports and the standar library (which is always imported). +/// - If after generating constraints for the given [AST]. +/// - The constraints successfully unify, meaning no contradictions or ambiguity. +pub fn constrain_and_unify(ast: &AST, ctx: &Context) -> Unified { + let constrained = constrain(ast, ctx)?; unify(&constrained, ctx) } #[cfg(test)] mod tests { - use crate::check::constrain::constraints; + use crate::check::constrain::constrain_and_unify; use crate::check::context::Context; use crate::check::name::{Name, Nullable}; use crate::common::position::{CaretPos, Position}; @@ -29,9 +34,10 @@ mod tests { fn if_stmt_no_type() { let src = "if True then 10 else 20"; let ast = src.parse().unwrap(); - let finished = constraints(&ast, &Context::default().into_with_primitives().unwrap()) - .unwrap() - .pos_to_name; + let finished = + constrain_and_unify(&ast, &Context::default().into_with_primitives().unwrap()) + .unwrap() + .pos_to_name; let pos_bool = Position::new(CaretPos::new(1, 4), CaretPos::new(1, 8)); // is interchangeable since call to __bool__() in Bool @@ -52,9 +58,10 @@ mod tests { fn it_stmt_as_expression() { let src = "def a := if True then 10 else 20"; let ast = src.parse().unwrap(); - let finished = constraints(&ast, &Context::default().into_with_primitives().unwrap()) - .unwrap() - .pos_to_name; + let finished = + constrain_and_unify(&ast, &Context::default().into_with_primitives().unwrap()) + .unwrap() + .pos_to_name; let pos_20 = Position::new(CaretPos::new(1, 31), CaretPos::new(1, 33)); assert_eq!(finished[&pos_20], Name::from("Int")); @@ -80,9 +87,10 @@ mod tests { fn it_stmt_as_expression_none() { let src = "def a := if True then 10 else None"; let ast = src.parse::().unwrap(); - let finished = constraints(&ast, &Context::default().into_with_primitives().unwrap()) - .unwrap() - .pos_to_name; + let finished = + constrain_and_unify(&ast, &Context::default().into_with_primitives().unwrap()) + .unwrap() + .pos_to_name; let pos_none = Position::new(CaretPos::new(1, 31), CaretPos::new(1, 35)); assert_eq!(finished[&pos_none], Name::from("None")); diff --git a/src/check/context/clss/generic.rs b/src/check/context/clss/generic.rs index 325ffcb3..7f558eaa 100644 --- a/src/check/context/clss/generic.rs +++ b/src/check/context/clss/generic.rs @@ -347,17 +347,13 @@ mod test { use crate::check::name::string_name::StringName; use crate::check::name::true_name::TrueName; use crate::check::name::Name; - use crate::parse::parse_direct; + use crate::parse::ast::AST; use crate::TypeErr; #[test] fn from_class_inline_args() -> Result<(), Vec> { let source = "class MyClass(def fin a: Int, b: Int): Parent(b)\n def c: Int := a + b\n"; - let ast = parse_direct(source) - .expect("valid class syntax") - .into_iter() - .next() - .expect("class AST"); + let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -418,11 +414,7 @@ mod test { #[test] fn from_class() -> Result<(), Vec> { let source = "class MyClass\n def c: Int := a + b\n"; - let ast = parse_direct(source) - .expect("valid class syntax") - .into_iter() - .next() - .expect("class AST"); + let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -458,11 +450,7 @@ mod test { #[test] fn from_class_with_generic() -> Result<(), Vec> { let source = "class MyClass[T]\n def c: T\n"; - let ast = parse_direct(source) - .expect("valid type syntax") - .into_iter() - .next() - .expect("type AST"); + let ast: AST = source.parse().expect("valid type syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -499,11 +487,7 @@ mod test { #[test] fn from_type_with_generic() -> Result<(), Vec> { let source = "type MyType[T]\n def c: T\n"; - let ast = parse_direct(source) - .expect("valid type syntax") - .into_iter() - .next() - .expect("type AST"); + let ast: AST = source.parse().expect("valid type syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -540,11 +524,7 @@ mod test { #[test] fn from_type_def() -> Result<(), Vec> { let source = "type MyType\n def c: String\n"; - let ast = parse_direct(source) - .expect("valid type syntax") - .into_iter() - .next() - .expect("type AST"); + let ast: AST = source.parse().expect("valid type syntax"); let generic_class = GenericClass::try_from(&ast)?; diff --git a/src/check/context/function/generic.rs b/src/check/context/function/generic.rs index 26aad979..438c046f 100644 --- a/src/check/context/function/generic.rs +++ b/src/check/context/function/generic.rs @@ -137,7 +137,6 @@ mod test { use crate::check::name::Name; use crate::common::position::Position; use crate::parse::ast::Node; - use crate::parse::parse_direct; use crate::{TypeErr, AST}; #[test] @@ -149,11 +148,7 @@ mod test { #[test] fn from_fundef() -> Result<(), Vec> { let source = "def f(fin a: Int, b: String := \"a\") -> String ! E := pass"; - let ast = parse_direct(source) - .expect("valid function syntax") - .into_iter() - .next() - .expect("function AST"); + let ast: AST = source.parse().expect("valid function syntax"); let generic_function = GenericFunction::try_from(&ast)?; @@ -185,11 +180,7 @@ mod test { #[test] fn from_fundef_no_ret() -> Result<(), Vec> { let source = "def f() := pass"; - let ast = parse_direct(source) - .expect("valid function syntax") - .into_iter() - .next() - .expect("function AST"); + let ast: AST = source.parse().expect("valid function syntax"); let generic_function = GenericFunction::try_from(&ast)?; assert_eq!(generic_function.ret_ty, None); diff --git a/src/check/mod.rs b/src/check/mod.rs index d1db85e8..bd2ea945 100644 --- a/src/check/mod.rs +++ b/src/check/mod.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use log::trace; use crate::check::ast::ASTTy; -use crate::check::constrain::constraints; +use crate::check::constrain::constrain_and_unify; use crate::check::context::Context; use crate::check::result::TypeResult; use crate::parse::ast::AST; @@ -31,7 +31,7 @@ pub fn check(ast: &AST, ctx: &Context) -> TypeResult { ctx.fields.len() ); - let finished = constraints(ast, ctx)?; + let finished = constrain_and_unify(ast, ctx)?; Ok(ASTTy::from((ast, &finished))) } diff --git a/src/parse/call.rs b/src/parse/call.rs index dcd5e5d7..5c03b913 100644 --- a/src/parse/call.rs +++ b/src/parse/call.rs @@ -75,12 +75,16 @@ fn parse_arguments(it: &mut LexIterator) -> ParseResult> { mod test { use crate::parse::ast::node_op::NodeOp; use crate::parse::ast::{Node, AST}; - use crate::parse::parse_direct; #[test] fn op_assign() { let source = String::from("a:=1\nb+=2\nc-=3\nd*=4\ne/=5\nf^=6\n"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); + + let statements = match &ast.node { + Node::Block { statements } => statements, + _ => panic!("Expected multiple statements, got {ast:?}"), + }; let ops: Vec = statements .iter() @@ -101,9 +105,9 @@ mod test { #[test] fn anon_fun_no_args_verify() { let source = String::from("\\ := c"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::AnonFun { args, body } = &statements.first().expect("script empty.").node else { + let Node::AnonFun { args, body } = &ast.node else { panic!("first element script was anon fun.") }; @@ -119,9 +123,9 @@ mod test { #[test] fn anon_fun_verify() { let source = String::from("\\a,b := c"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::AnonFun { args, body } = &statements.first().expect("script empty.").node else { + let Node::AnonFun { args, body } = &ast.node else { panic!("first element script was anon fun.") }; @@ -176,10 +180,9 @@ mod test { #[test] fn direct_call_verify() { let source = String::from("a(b, c)"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::FunctionCall { name, args } = &statements.first().expect("script empty.").node - else { + let Node::FunctionCall { name, args } = &ast.node else { panic!("first element script was anon fun.") }; @@ -207,9 +210,9 @@ mod test { #[test] fn method_call_verify() { let source = String::from("instance.a(b, c)"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (instance, name, args) = match &statements.first().expect("script empty.").node { + let (instance, name, args) = match &ast.node { Node::PropertyCall { instance, property } => match &property.node { Node::FunctionCall { name, args } => (instance.clone(), name.clone(), args.clone()), other => panic!("not function call in property call {other:?}"), diff --git a/src/parse/collection.rs b/src/parse/collection.rs index a07e9ba8..a695324e 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -150,14 +150,13 @@ pub fn parse_expressions(it: &mut LexIterator) -> ParseResult> { #[cfg(test)] mod test { - use crate::parse::ast::Node; - use crate::parse::parse_direct; + use crate::parse::ast::{Node, AST}; #[test] fn tuple_empty_verify() { let source = String::from("()"); - let statements = parse_direct(&source).unwrap(); - let Node::Tuple { elements } = &statements.first().expect("script empty.").node else { + let ast: AST = source.parse().unwrap(); + let Node::Tuple { elements } = &ast.node else { panic!("first element script was not tuple.") }; @@ -167,8 +166,8 @@ mod test { #[test] fn tuple_single_is_expr_verify() { let source = String::from("(a)"); - let statements = parse_direct(&source).unwrap(); - let Node::Id { lit } = &statements.first().expect("script empty.").node else { + let ast: AST = source.parse().unwrap(); + let Node::Id { lit } = &ast.node else { panic!("first element script was not tuple.") }; @@ -178,8 +177,8 @@ mod test { #[test] fn tuple_multiple_verify() { let source = String::from("(d, c)"); - let statements = parse_direct(&source).unwrap(); - let Node::Tuple { elements } = &statements.first().expect("script empty.").node else { + let ast: AST = source.parse().unwrap(); + let Node::Tuple { elements } = &ast.node else { panic!("first element script was not tuple.") }; @@ -201,9 +200,9 @@ mod test { #[test] fn set_verify() { let source = String::from("{a, b}"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::Set { elements } = &statements.first().expect("script empty.").node else { + let Node::Set { elements } = &ast.node else { panic!("first element script was not set.") }; @@ -224,11 +223,9 @@ mod test { #[test] fn set_builder_verify() { let source = String::from("{a | c, d}"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::SetBuilder { item, conditions } = - &statements.first().expect("script empty.").node - else { + let Node::SetBuilder { item, conditions } = &ast.node else { panic!("first element script was not set builder.") }; @@ -257,9 +254,9 @@ mod test { #[test] fn list_verify() { let source = String::from("[a, b]"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::List { elements } = &statements.first().expect("script empty.").node else { + let Node::List { elements } = &ast.node else { panic!("first element script was not list.") }; @@ -280,10 +277,8 @@ mod test { #[test] fn list_builder_verify() { let source = String::from("[a | c, d]"); - let statements = parse_direct(&source).unwrap(); - let Node::ListBuilder { item, conditions } = - &statements.first().expect("script empty.").node - else { + let ast: AST = source.parse().unwrap(); + let Node::ListBuilder { item, conditions } = &ast.node else { panic!("first element script was not list builder.") }; diff --git a/src/parse/control_flow_expr.rs b/src/parse/control_flow_expr.rs index 641fd5f5..020cf88b 100644 --- a/src/parse/control_flow_expr.rs +++ b/src/parse/control_flow_expr.rs @@ -108,16 +108,14 @@ fn parse_expression_maybe_type(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { use crate::parse::ast::{Node, AST}; - use crate::parse::parse_direct; use crate::parse::result::ParseResult; #[test] fn if_else_verify() { let source = String::from("if a then c else d"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::IfElse { cond, then, el } = &statements.first().expect("script empty.").node - else { + let Node::IfElse { cond, then, el } = &ast.node else { panic!("first element script was not if.") }; @@ -144,9 +142,9 @@ mod test { #[test] fn match_verify() { let source = String::from("match a\n a => b\n c => d"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let Node::Match { cond, cases } = &statements.first().expect("script empty.").node else { + let Node::Match { cond, cases } = &ast.node else { panic!("first element script was not match.") }; @@ -215,9 +213,9 @@ mod test { #[test] fn if_expression() -> ParseResult<()> { let source = String::from("if a then\n b\n"); - let ast = parse_direct(&source)?; + let ast: AST = source.parse()?; - let Some(Node::IfElse { cond, then, el }) = ast.first().map(|a| &a.node) else { + let Node::IfElse { cond, then, el } = ast.node else { panic!("Expected if, got {ast:?}") }; @@ -245,9 +243,9 @@ mod test { #[test] fn if_else_expression() -> ParseResult<()> { let source = String::from("if a then\n b\nelse\n c"); - let ast = parse_direct(&source)?; + let ast: AST = source.parse()?; - let Some(Node::IfElse { cond, then, el }) = ast.first().map(|a| &a.node) else { + let Node::IfElse { cond, then, el } = ast.node else { panic!("Expected if, got {ast:?}") }; @@ -281,27 +279,11 @@ mod test { Ok(()) } - #[test] - fn if_then_missing_body() { - let source = String::from("if a then b else"); - parse_direct(&source).unwrap_err(); - } - - #[test] - fn match_missing_condition() { - let source = String::from("match\n a => b"); - parse_direct(&source).unwrap_err(); - } - - #[test] - fn match_missing_arms() { - let source = String::from("match a with\n "); - parse_direct(&source).unwrap_err(); - } - - #[test] - fn match_missing_arms_no_newline() { - let source = String::from("match a"); - parse_direct(&source).unwrap_err(); + #[test_case::test_case("if a then b else"; "if_then_missing_body")] + #[test_case::test_case("match\n a => b"; "match_missing_condition")] + #[test_case::test_case("match a with\n "; "match_missing_arms")] + #[test_case::test_case("match a"; "match_missing_match_missing_arms_no_newlinearms")] + fn invalid(src: &str) { + src.parse::().unwrap_err(); } } diff --git a/src/parse/control_flow_stmt.rs b/src/parse/control_flow_stmt.rs index 7a6aff39..e55187be 100644 --- a/src/parse/control_flow_stmt.rs +++ b/src/parse/control_flow_stmt.rs @@ -65,15 +65,14 @@ fn parse_for(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { - use crate::parse::ast::Node; - use crate::parse::parse_direct; + use crate::parse::ast::{Node, AST}; #[test] fn for_statement_verify() { let source = String::from("for a in c do d"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (expr, collection, body) = match &statements.first().expect("script empty.").node { + let (expr, collection, body) = match &ast.node { Node::For { expr, col, body } => (expr.clone(), col.clone(), body.clone()), _ => panic!("first element script was not for."), }; @@ -101,9 +100,9 @@ mod test { #[test] fn for_range_step_verify() { let source = String::from("for a in c .. d .. e do f"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (expr, col, body) = match &statements.first().expect("script empty.").node { + let (expr, col, body) = match &ast.node { Node::For { expr, col, body } => (expr.clone(), col.clone(), body.clone()), _ => panic!("first element script was not foreach."), }; @@ -155,9 +154,9 @@ mod test { #[test] fn for_range_incl_verify() { let source = String::from("for a in c ..= d do f"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (expr, col, body) = match &statements.first().expect("script empty.").node { + let (expr, col, body) = match &ast.node { Node::For { expr, col, body } => (expr.clone(), col.clone(), body.clone()), _ => panic!("first element script was not foreach."), }; @@ -204,9 +203,9 @@ mod test { #[test] fn if_verify() { let source = String::from("if a then c"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (cond, then, el) = match &statements.first().expect("script empty.").node { + let (cond, then, el) = match &ast.node { Node::IfElse { cond, then, el } => (cond, then, el), _ => panic!("first element script was not if."), }; @@ -229,9 +228,9 @@ mod test { #[test] fn if_with_block_verify() { let source = String::from("if a then\n c\n d"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (cond, then, el) = match &statements.first().expect("script empty.").node { + let (cond, then, el) = match &ast.node { Node::IfElse { cond, then, el } => (cond.clone(), then.clone(), el.clone()), _ => panic!("first element script was not if."), }; @@ -267,9 +266,9 @@ mod test { #[test] fn while_verify() { let source = String::from("while a do d"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (cond, body) = match &statements.first().expect("script empty.").node { + let (cond, body) = match &ast.node { Node::While { cond, body } => (cond.clone(), body.clone()), _ => panic!("first element script was not while."), }; @@ -290,21 +289,21 @@ mod test { #[test] fn for_missing_do() { - parse_direct(&String::from("for a in c d")).unwrap_err(); + "for a in c d".parse::().unwrap_err(); } #[test] fn for_missing_body() { - parse_direct(&String::from("for a in c")).unwrap_err(); + "for a in c".parse::().unwrap_err(); } #[test] fn if_missing_then() { - parse_direct(&String::from("if a b")).unwrap_err(); + "if a b".parse::().unwrap_err(); } #[test] fn if_missing_body() { - parse_direct(&String::from("if a then")).unwrap_err(); + "if a then".parse::().unwrap_err(); } } diff --git a/src/parse/definition.rs b/src/parse/definition.rs index 67330580..fa8a182e 100644 --- a/src/parse/definition.rs +++ b/src/parse/definition.rs @@ -295,12 +295,10 @@ fn parse_variable_def(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { use crate::parse::ast::{Node, AST}; - use crate::parse::parse_direct; macro_rules! unwrap_func_definition { ($ast:expr) => {{ - let definition = $ast.first().expect("script empty.").clone(); - match definition.node { + match $ast.node { Node::FunDef { id, pure, @@ -317,7 +315,7 @@ mod test { macro_rules! unwrap_definition { ($ast:expr) => {{ - let definition = $ast.first().expect("script empty.").node.clone(); + let definition = $ast.node.clone(); match definition { Node::VariableDef { mutable, @@ -334,7 +332,7 @@ mod test { #[test] fn empty_definition_verify() { let source = String::from("def a"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, _type, expression, forward) = unwrap_definition!(ast); assert!(mutable); @@ -352,7 +350,7 @@ mod test { #[test] fn definition_verify() { let source = String::from("def a := 10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, ty, expression, forward) = unwrap_definition!(ast); assert!(mutable); @@ -379,7 +377,7 @@ mod test { #[test] fn mutable_definition_verify() { let source = String::from("def fin a := 10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, ty, expression, forward) = unwrap_definition!(ast); assert!(!mutable); @@ -406,7 +404,7 @@ mod test { #[test] fn private_definition_verify() { let source = String::from("def a := 10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, ty, expression, forward) = unwrap_definition!(ast); assert!(mutable); @@ -433,7 +431,7 @@ mod test { #[test] fn typed_definition_verify() { let source = String::from("def a: Object := 10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, ty, expression, forward) = unwrap_definition!(ast); let type_id = match ty { @@ -473,7 +471,7 @@ mod test { #[test] fn forward_empty_definition_verify() { let source = String::from("def a forward b, c"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, ty, expression, forward) = unwrap_definition!(ast); assert!(mutable); @@ -503,7 +501,7 @@ mod test { #[test] fn forward_definition_verify() { let source = String::from("def a := MyClass forward b, c"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (mutable, id, ty, expression, forward) = unwrap_definition!(ast); assert!(mutable); @@ -538,7 +536,7 @@ mod test { #[test] fn function_definition_verify() { let source = String::from("def f(fin b: Something, c) := d"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (pure, id, fun_args, ret, raises, body) = unwrap_func_definition!(ast); assert!(!pure); @@ -622,7 +620,7 @@ mod test { #[test] fn function_no_args_definition_verify() { let source = String::from("def f() := d"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (pure, id, args, ret, _, body) = unwrap_func_definition!(ast); assert!(!pure); @@ -649,7 +647,7 @@ mod test { #[test] fn function_pure_definition_verify() { let source = String::from("def pure f() := d"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (pure, id, args, ret, _, body) = unwrap_func_definition!(ast); assert!(pure); @@ -676,13 +674,13 @@ mod test { #[test] fn function_no_separator_args() { let source = String::from("def f(x b: Something) := d"); - parse_direct(&source).unwrap_err(); + source.parse::().unwrap_err(); } #[test] fn function_definition_with_literal_verify() { let source = String::from("def f(x, b: Something) := d"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (pure, id, fun_args, ret, _, body) = unwrap_func_definition!(ast); assert!(!pure); diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index 2737ad79..d1cdb502 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -53,14 +53,13 @@ pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { mod test { use crate::parse::ast::node_op::NodeOp; use crate::parse::ast::{Node, AST}; - use crate::parse::parse_direct; #[test] fn range_verify() { let source = String::from("hello .. world"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (from, to, inclusive, step) = match &statements.first().expect("script empty.").node { + let (from, to, inclusive, step) = match &ast.node { Node::Range { from, to, @@ -89,9 +88,9 @@ mod test { #[test] fn range_step_verify() { let source = String::from("hello .. world .. 2"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (from, to, inclusive, step) = match &statements.first().expect("script empty.").node { + let (from, to, inclusive, step) = match &ast.node { Node::Range { from, to, @@ -125,9 +124,9 @@ mod test { #[test] fn range_incl_verify() { let source = String::from("foo ..= bar"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (from, to, inclusive, step) = match &statements.first().expect("script empty.").node { + let (from, to, inclusive, step) = match &ast.node { Node::Range { from, to, @@ -156,9 +155,9 @@ mod test { #[test] fn reassign_verify() { let source = String::from("id := new_value"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let (left, right) = match &statements.first().expect("script empty.").node { + let (left, right) = match &ast.node { Node::Reassign { left, right, op } => { assert_eq!(*op, NodeOp::Assign); (left.clone(), right.clone()) @@ -183,9 +182,9 @@ mod test { #[test] fn return_verify() { let source = String::from("return some_value"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let expr = match &statements.first().expect("script empty.").node { + let expr = match &ast.node { Node::Return { expr } => expr.clone(), _ => panic!("first element script was not reassign."), }; @@ -201,9 +200,9 @@ mod test { #[test] fn return_stmt_with_comment_verify() { let source = String::from("return some_value # comment"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let expr = match &statements.first().expect("script empty.").node { + let expr = match &ast.node { Node::Return { expr } => expr.clone(), _ => panic!("first element script was not reassign."), }; @@ -219,9 +218,9 @@ mod test { #[test] fn literal_expr_with_comment_verify() { let source = String::from("10 # comment"); - let statements = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); - let lit = match &statements.first().expect("script empty.").node { + let lit = match &ast.node { Node::Int { lit } => lit.clone(), _ => panic!("first element script was not reassign."), }; @@ -232,21 +231,10 @@ mod test { #[test] fn underscore_verify() { let source = String::from("_"); - let statements = parse_direct(&source).unwrap(); - - let ast = statements.first().expect("script empty.").clone(); + let ast: AST = source.parse().unwrap(); assert_eq!(ast.node, Node::Underscore); } - #[test] - fn pass_verify() { - let source = String::from("pass"); - let statements = parse_direct(&source).unwrap(); - - let ast = statements.first().expect("script empty.").clone(); - assert_eq!(ast.node, Node::Pass); - } - #[test] fn import_verify() { let source = String::from("import c"); diff --git a/src/parse/expression.rs b/src/parse/expression.rs index ea640fd9..5eebbd86 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -140,17 +140,14 @@ pub fn is_start_expression(tp: &Lex) -> bool { #[cfg(test)] mod test { - use crate::parse::ast::Node; - use crate::parse::parse_direct; + use crate::parse::ast::{Node, AST}; #[test] fn parse_call() { let source = String::from("a.b.c"); - let asts = parse_direct(&source).expect("valid AST"); + let ast: AST = source.parse().expect("valid AST"); - assert_eq!(asts.len(), 1); - let reassignment = asts.first().expect("reassignment"); - let (first, second, third) = match &reassignment.node { + let (first, second, third) = match &ast.node { Node::PropertyCall { instance, property } => match &property.node { Node::PropertyCall { instance: inner, diff --git a/src/parse/mod.rs b/src/parse/mod.rs index d57f0971..e3857737 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -35,6 +35,9 @@ impl FromStr for AST { let mut iterator = LexIterator::new(tokens.iter().peekable()); let statements = block::parse_statements(&mut iterator)?; + if statements.len() == 1 { + return Ok(statements.first().cloned().unwrap()); + } let start = statements .first() @@ -46,11 +49,3 @@ impl FromStr for AST { Ok(AST::new(start.union(end), Node::Block { statements })) } } - -#[cfg(test)] -pub fn parse_direct(input: &str) -> ParseResult> { - match AST::from_str(input)?.node { - Node::Block { statements } => Ok(statements), - _ => Ok(vec![]), - } -} diff --git a/src/parse/operation.rs b/src/parse/operation.rs index 1346e23d..1e253d06 100644 --- a/src/parse/operation.rs +++ b/src/parse/operation.rs @@ -201,11 +201,10 @@ mod test { use crate::parse::ast::{Node, AST}; use crate::parse::lex::token::Token::*; - use crate::parse::parse_direct; macro_rules! verify_is_operation { ($op:ident, $ast:expr) => {{ - match &$ast.first().expect("script empty.").node { + match &$ast.node { Node::$op { left, right } => (left.clone(), right.clone()), other => panic!( "first element script was not op: {}, but was: {:?}", @@ -217,9 +216,9 @@ mod test { macro_rules! verify_is_un_operation { ($op:ident, $ast:expr) => {{ - match &$ast.first().expect("script empty.").node { + match &$ast.node { Node::$op { expr } => expr.clone(), - _ => panic!("first element script was not tuple."), + _ => panic!("was not operator: {:?}", $ast), } }}; } @@ -227,7 +226,7 @@ mod test { #[test] fn addition_verify() { let source = String::from("a + b"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Add, ast); assert_eq!( @@ -247,7 +246,7 @@ mod test { #[test] fn addition_unary_verify() { let source = String::from("+ b"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let expr = verify_is_un_operation!(AddU, ast); assert_eq!( @@ -261,7 +260,7 @@ mod test { #[test] fn subtraction_verify() { let source = String::from("a - False"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Sub, ast); assert_eq!( @@ -281,7 +280,7 @@ mod test { #[test] fn subtraction_unary_verify() { let source = String::from("- c"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let expr = verify_is_un_operation!(SubU, ast); assert_eq!( @@ -295,7 +294,7 @@ mod test { #[test] fn multiplication_verify() { let source = String::from("True * b"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Mul, ast); assert_eq!( @@ -315,7 +314,7 @@ mod test { #[test] fn division_verify() { let source = String::from("10.0 / fgh"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Div, ast); assert_eq!( @@ -335,7 +334,7 @@ mod test { #[test] fn floor_division_verify() { let source = String::from("10.0 // fgh"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(FDiv, ast); assert_eq!( @@ -355,7 +354,7 @@ mod test { #[test] fn power_verify() { let source = String::from("chopin ^ liszt"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Pow, ast); assert_eq!( @@ -375,7 +374,7 @@ mod test { #[test] fn mod_verify() { let source = String::from("chopin mod 3E10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Mod, ast); assert_eq!( @@ -396,7 +395,7 @@ mod test { #[test] fn equality_verify() { let source = String::from("i = s"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Eq, ast); assert_eq!( @@ -416,7 +415,7 @@ mod test { #[test] fn le_verify() { let source = String::from("one < two"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Le, ast); assert_eq!( @@ -436,7 +435,7 @@ mod test { #[test] fn leq_verify() { let source = String::from("two_hundred <= three"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Leq, ast); assert_eq!( @@ -456,7 +455,7 @@ mod test { #[test] fn ge_verify() { let source = String::from("r > 10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Ge, ast); assert_eq!( @@ -476,7 +475,7 @@ mod test { #[test] fn geq_verify() { let source = String::from("4 >= 10"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Geq, ast); assert_eq!( @@ -496,7 +495,7 @@ mod test { #[test] fn in_verify() { let source = String::from("one in my_set"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(In, ast); assert_eq!( @@ -516,7 +515,7 @@ mod test { #[test] fn and_verify() { let source = String::from("one and three"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(And, ast); assert_eq!( @@ -536,7 +535,7 @@ mod test { #[test] fn or_verify() { let source = String::from("one or \"asdf\""); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let (left, right) = verify_is_operation!(Or, ast); assert_eq!( @@ -557,7 +556,7 @@ mod test { #[test] fn not_verify() { let source = String::from("not some_cond"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let expr = verify_is_un_operation!(Not, ast); assert_eq!( @@ -571,7 +570,7 @@ mod test { #[test] fn sqrt_verify() { let source = String::from("sqrt some_num"); - let ast = parse_direct(&source).unwrap(); + let ast: AST = source.parse().unwrap(); let expr = verify_is_un_operation!(Sqrt, ast); assert_eq!( diff --git a/src/parse/statement.rs b/src/parse/statement.rs index fa696d97..a7cb8459 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -221,18 +221,15 @@ pub fn is_start_statement(tp: &Token) -> bool { mod test { use crate::common::position::{CaretPos, Position}; use crate::parse::ast::node_op::NodeOp; - use crate::parse::ast::Node; - use crate::parse::parse_direct; + use crate::parse::ast::{Node, AST}; #[test] fn parse_return() { let source = String::from("return 20"); - let asts = parse_direct(&source).expect("valid AST"); + let ast: AST = source.parse().expect("valid AST"); - assert_eq!(asts.len(), 1); - let ret = asts.first().expect("return"); - let Node::Return { expr } = &ret.node else { - panic!("Expected reassignment, was: {:?}", ret.node) + let Node::Return { expr } = &ast.node else { + panic!("Expected reassignment, was: {ast:?}") }; assert_eq!( @@ -250,12 +247,10 @@ mod test { #[test] fn parse_reassignment() { let source = String::from("a := 1"); - let asts = parse_direct(&source).expect("valid AST"); + let ast: AST = source.parse().expect("valid AST"); - assert_eq!(asts.len(), 1); - let reassignment = asts.first().expect("reassignment"); - let Node::Reassign { left, right, op } = &reassignment.node else { - panic!("Expected reassignment, was {reassignment:?}") + let Node::Reassign { left, right, op } = &ast.node else { + panic!("Expected reassignment, was {ast:?}") }; assert_eq!( @@ -276,12 +271,10 @@ mod test { #[test] fn parse_reassignment_call() { let source = String::from("a.b := 1"); - let asts = parse_direct(&source).expect("valid AST"); + let ast: AST = source.parse().expect("valid AST"); - assert_eq!(asts.len(), 1); - let reassignment = asts.first().expect("reassignment"); - let Node::Reassign { left, right, op } = &reassignment.node else { - panic!("Expected reassignment, was {reassignment:?}") + let Node::Reassign { left, right, op } = &ast.node else { + panic!("Expected reassignment, was {ast:?}") }; let Node::PropertyCall { instance, property } = &left.node else { From ee95da29aef1f9da27f7f7a0434558c04ad69c89 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Sun, 3 Aug 2025 17:47:29 +0200 Subject: [PATCH 26/44] feat: enhance code block,set and list,set parsing --- README.md | 30 +++++--- src/check/constrain/generate/collection.rs | 2 +- src/check/context/clss/generic.rs | 2 +- src/check/context/generic.rs | 68 ++++++++++-------- src/parse/block.rs | 61 +++++++++++------ src/parse/class.rs | 80 ++++++++-------------- src/parse/collection.rs | 48 +++++++++---- src/parse/control_flow_expr.rs | 2 +- src/parse/control_flow_stmt.rs | 2 +- src/parse/expr_or_stmt.rs | 30 ++------ src/parse/lex/mod.rs | 4 +- src/parse/lex/tokenize.rs | 12 ++-- src/parse/statement.rs | 4 +- 13 files changed, 181 insertions(+), 164 deletions(-) diff --git a/README.md b/README.md index 79c7fb67..143017e8 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Mamba is similar to Python, but with a few key features: - Null safety - Explicit error handling - A distinction between mutability and immutability -- Pure functions, or, functions without side effects +- Pure functions, or, functions without side effects - Meta functions, for reasoning about the language itself See [docs](/docs/) for a more extensive overview of the language philosophy. @@ -168,8 +168,8 @@ Is essentially just shorthand for ```mamba def numbers := { 0 => 32, 1 => 504, 2 => 59 } -``` - +``` + Where we iterate over the list in the order of the keys. Unlike C-style languages (which is nearly the whole world at this point), we index collections using `collection()`. @@ -196,13 +196,13 @@ Mamba is geared more for mathematical use, for lack of a better term, meaning th ### 📋 Types, Properties, and Classes Next, we introduce the concept of a class. -A class is essentially a blueprint for the behaviour of an instance. +A class is essentially a blueprint for the behaviour of an instance. -In Mamba, like Python and Rust, each function in a class has an explicit `self` argument, which gives access to the state of this instance. +In Mamba, like Python and Rust, each function in a class has an explicit `self` argument, which gives access to the state of this instance. Such a function is called a method. We can for each method state whether we can modify the state of `self` by stating whether it is mutable or not. If we write `self`, it is mutable, whereas if we write `fin self`, it is immutable and we cannot change its state. -We can do the same for any argument to a function, for that matter. +We can do the same for any argument to a function, for that matter. We showcase this using a simple dummy `Matrix` object. You will also see some "pure" functions, these will be explained later. @@ -238,9 +238,9 @@ class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { ``` Notice how `self` is not mutable in `trace`, meaning we can only read variables, whereas in `scale`, `self` is mutable, so we can change properties of `self`. -In general, the notation of a class is: +_In general_, the notation of a class is: -`class MyClass() := []` +`class MyClass() := {}` The body of the class is optional, i.e. one can create "just" a data class. As for constructor arguments: @@ -250,6 +250,14 @@ As for constructor arguments: They may be used at any point in the class, but they are (1) invariant and (2) may not be accessed from outside the class. - The body of the class is evaluated for each object we created, effectively making this the constructor body. +As for the class body + +- It is denoted using a code set: Using `{` and `}`. + This is because the concept of order is not defined in a class body. +- In future, we may generalize the code-set notation to mean a set of statements which may be executed in arbitrary order, and thus **also in parallel**. + Therefore baking parallel computations into the semantics of the language, as opposed to a library. + However, this idea is still in its infancy. + We can change the relevant parts of the above example to use a class constant: ```mamba @@ -275,9 +283,9 @@ class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { Last, we have `trait`s, which in Mamba are more fine-grained building blocks to describe the behaviour of instances. These are similar to interfaces in Java and Kotlin, and near identical to traits in Rust. -In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. +In Mamba, we aim to have many small traits for a more idiomatic way to express the behaviour of objects/classes. For those familiar with object oriented programming, we favour a trait based system over inheritance (like Rust, Mamba doesn't have inheritance). - + Consider example with iterators (which briefly showcases language generics): ```mamba @@ -317,7 +325,7 @@ trait Ordered[T]: Equality, Comparable ### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) -Mamba also has type refinement features to assign additional properties to types. +Mamba also has type refinement features to assign additional properties to types. Note: Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the comlexit of the language. Arguably, it might detract from the elegance of the type system as well; diff --git a/src/check/constrain/generate/collection.rs b/src/check/constrain/generate/collection.rs index b61f35aa..bcf1f610 100644 --- a/src/check/constrain/generate/collection.rs +++ b/src/check/constrain/generate/collection.rs @@ -307,7 +307,7 @@ mod tests { #[test] fn for_col_variable_ty() { - let src = "def a := 0 ..= 2\nfor i in a do\n print(\"hello\")"; + let src = "def a := 0 ..= 2\nfor i in a do print(\"hello\")"; let ast = src.parse::().unwrap(); let result = check_all(&[ast]).unwrap(); diff --git a/src/check/context/clss/generic.rs b/src/check/context/clss/generic.rs index 7f558eaa..5f510782 100644 --- a/src/check/context/clss/generic.rs +++ b/src/check/context/clss/generic.rs @@ -352,7 +352,7 @@ mod test { #[test] fn from_class_inline_args() -> Result<(), Vec> { - let source = "class MyClass(def fin a: Int, b: Int): Parent(b)\n def c: Int := a + b\n"; + let source = "class MyClass(def fin a: Int, b: Int): Parent(b) := { def c: Int := a + b }"; let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast)?; diff --git a/src/check/context/generic.rs b/src/check/context/generic.rs index 09dcee83..ed2b7e19 100644 --- a/src/check/context/generic.rs +++ b/src/check/context/generic.rs @@ -11,7 +11,7 @@ use crate::check::result::{TypeErr, TypeResult}; use crate::parse::ast::{Node, OptAST, AST}; pub fn generics( - files: &[AST], + asts: &[AST], ) -> TypeResult<( HashSet, HashSet, @@ -21,43 +21,51 @@ pub fn generics( let mut fields = HashSet::new(); let mut functions = HashSet::new(); - for file in files { - match &file.node { + for ast in asts { + match &ast.node { Node::Block { statements } => { - for module in statements { - match &module.node { - Node::Class { .. } | Node::TypeDef { .. } | Node::TypeAlias { .. } => { - types.insert(GenericClass::try_from(module)?); - } - Node::FunDef { .. } => { - functions.insert(GenericFunction::try_from(module)?); - } - Node::VariableDef { .. } => { - GenericFields::try_from(module)? - .fields - .iter() - .for_each(|ty| { - fields.insert(ty.clone()); - }); - } - Node::Import { - from, - import, - alias, - } => from_import(from, import, alias)?.into_iter().for_each(|t| { - types.insert(t); - }), - _ => {} - } + for ast in statements { + single_ast(ast, &mut types, &mut fields, &mut functions)? } } - _ => return Err(vec![TypeErr::new(file.pos, "Expected file")]), - } + _ => single_ast(ast, &mut types, &mut fields, &mut functions)?, + }; } Ok((types, fields, functions)) } +fn single_ast( + ast: &AST, + types: &mut HashSet, + fields: &mut HashSet, + functions: &mut HashSet, +) -> TypeResult<()> { + match &ast.node { + Node::Class { .. } | Node::TypeDef { .. } | Node::TypeAlias { .. } => { + types.insert(GenericClass::try_from(ast)?); + } + Node::FunDef { .. } => { + functions.insert(GenericFunction::try_from(ast)?); + } + Node::VariableDef { .. } => { + GenericFields::try_from(ast)?.fields.iter().for_each(|ty| { + fields.insert(ty.clone()); + }); + } + Node::Import { + from, + import, + alias, + } => from_import(from, import, alias)?.into_iter().for_each(|t| { + types.insert(t); + }), + _ => {} + }; + + Ok(()) +} + /// From import. /// /// A more elaborate import system will extract the signature of the class. diff --git a/src/parse/block.rs b/src/parse/block.rs index 946f8826..47bf7f6a 100644 --- a/src/parse/block.rs +++ b/src/parse/block.rs @@ -1,11 +1,13 @@ use crate::parse::ast::Node; use crate::parse::ast::AST; use crate::parse::class::{parse_class, parse_type_def}; +use crate::parse::collection::{parse_list_partial, parse_set_or_dict_partial}; use crate::parse::expr_or_stmt::parse_expr_or_stmt; +use crate::parse::expression::is_start_expression; use crate::parse::iterator::LexIterator; -use crate::parse::lex::token::Token; -use crate::parse::result::{expected_one_of, ParseResult}; -use crate::parse::statement::parse_import; +use crate::parse::lex::token::{Lex, Token}; +use crate::parse::result::{custom, ParseResult}; +use crate::parse::statement::{is_start_statement, parse_import}; pub fn parse_statements(it: &mut LexIterator) -> ParseResult> { let start = it.start_pos("statements")?; @@ -36,35 +38,35 @@ pub fn parse_statements(it: &mut LexIterator) -> ParseResult> { } _ => { statements.push(*it.parse(&parse_expr_or_stmt, "statements", start)?); - if it.peek_if(&|lex| lex.token != Token::NL) { - Err(Box::from(expected_one_of( - &[Token::NL], - lex, - "end of statement", - ))) - } else { - Ok(()) + if it.peek_if(&is_start_statement) || it.peek_if(&is_start_expression) { + let pos = it.peek_next().map_or(start, |next| next.pos); + return Err(Box::new(custom("statement or expression cannot be immediately followed by another statement or expression", pos))) } + Ok(()) } })?; Ok(statements) } -/// Parse block, and consumes any newlines preceding it. -pub fn parse_code_block(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("block block")?; +/// Similar to parse code block, but used in situations where a list is also allowed (when parsing an expression). +pub fn parse_code_block_or_list(it: &mut LexIterator) -> ParseResult { + let start = it.start_pos("block block or list")?; it.eat_while(&Token::NL); - it.eat(&Token::LSBrack, "block block")?; + it.eat(&Token::LSBrack, "block block or list")?; let statements = it.parse_vec(&parse_statements, "block block", start)?; let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); - it.eat(&Token::RSBrack, "block block")?; - Ok(Box::from(AST::new( - start.union(end), - Node::Block { statements }, - ))) + if statements.len() == 1 && it.peek_if(&|lex: &Lex| lex.token == Token::Comma) { + parse_list_partial(start, statements.first().unwrap(), it) + } else { + it.eat(&Token::RSBrack, "block block or list")?; + Ok(Box::from(AST::new( + start.union(end), + Node::Block { statements }, + ))) + } } pub fn parse_code_set(it: &mut LexIterator) -> ParseResult { @@ -81,3 +83,22 @@ pub fn parse_code_set(it: &mut LexIterator) -> ParseResult { Node::Block { statements }, ))) } + +pub fn parse_code_set_or_set(it: &mut LexIterator) -> ParseResult { + let start = it.start_pos("block set or set")?; + it.eat_while(&Token::NL); + + it.eat(&Token::LCBrack, "block set or set")?; + let statements = it.parse_vec(&parse_statements, "block set", start)?; + let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); + + if statements.len() == 1 && it.peek_if(&|lex: &Lex| lex.token == Token::Comma) { + parse_set_or_dict_partial(start, statements.first().unwrap(), it) + } else { + it.eat(&Token::RCBrack, "block set or set")?; + Ok(Box::from(AST::new( + start.union(end), + Node::Block { statements }, + ))) + } +} diff --git a/src/parse/class.rs b/src/parse/class.rs index 4e162a15..db094f41 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -1,6 +1,6 @@ use crate::parse::ast::Node; use crate::parse::ast::AST; -use crate::parse::block::parse_code_block; +use crate::parse::block::parse_code_set; use crate::parse::definition::{parse_definition, parse_fun_arg}; use crate::parse::iterator::LexIterator; use crate::parse::lex::token::{Lex, Token}; @@ -49,7 +49,7 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { } let (body, pos) = if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) { - let body = it.parse(&parse_code_block, "class", start)?; + let body = it.parse(&parse_code_set, "class", start)?; (Some(body.clone()), start.union(body.pos)) } else { (None, start) @@ -128,7 +128,7 @@ pub fn parse_type_def(it: &mut LexIterator) -> ParseResult { } _ if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) => { it.eat_if(&Token::NL); - let body = it.parse(&parse_code_block, "type definition", start)?; + let body = it.parse(&parse_code_set, "type definition", start)?; let isa = isa.clone(); let node = Node::TypeDef { ty: ty.clone(), @@ -170,18 +170,12 @@ mod test { let ast = source.parse::().unwrap(); let (from, import, alias) = match ast.node { - Node::Block { - statements: modules, - .. - } => match &modules.first().expect("script empty.").node { - Node::Import { - from, - import, - alias, - } => (from.clone(), import.clone(), alias.clone()), - _ => panic!("first element script was not list."), - }, - _ => panic!("ast was not script."), + Node::Import { + from, + import, + alias, + } => (from.clone(), import.clone(), alias.clone()), + _ => panic!("first element script was not list."), }; assert_eq!(from, None); @@ -201,18 +195,12 @@ mod test { let ast = source.parse::().unwrap(); let (from, import, alias) = match ast.node { - Node::Block { - statements: modules, - .. - } => match &modules.first().expect("script empty.").node { - Node::Import { - from, - import, - alias, - } => (from.clone(), import.clone(), alias.clone()), - other => panic!("first element script was not import: {other:?}."), - }, - other => panic!("ast was not script: {other:?}"), + Node::Import { + from, + import, + alias, + } => (from.clone(), import.clone(), alias.clone()), + other => panic!("first element script was not import: {other:?}."), }; assert_eq!(from, None); @@ -238,18 +226,12 @@ mod test { let ast = source.parse::().unwrap(); let (from, import, alias) = match ast.node { - Node::Block { - statements: modules, - .. - } => match &modules.first().expect("script empty.").node { - Node::Import { - from, - import, - alias, - } => (from.clone(), import.clone(), alias.clone()), - other => panic!("first element script was not from: {other:?}."), - }, - other => panic!("ast was not script: {other:?}"), + Node::Import { + from, + import, + alias, + } => (from.clone(), import.clone(), alias.clone()), + other => panic!("first element script was not from: {other:?}."), }; assert_eq!( @@ -292,19 +274,13 @@ mod test { let ast = source.parse::().unwrap(); let (ty, args, parents, body) = match ast.node { - Node::Block { - statements: modules, - .. - } => match &modules.first().expect("script empty.").node { - Node::Class { - ty, - args, - parents, - body, - } => (ty.clone(), args.clone(), parents.clone(), body.clone()), - other => panic!("Was not class: {other:?}."), - }, - other => panic!("Ast was not script: {other:?}"), + Node::Class { + ty, + args, + parents, + body, + } => (ty.clone(), args.clone(), parents.clone(), body.clone()), + other => panic!("Was not class: {other:?}."), }; match ty.node { diff --git a/src/parse/collection.rs b/src/parse/collection.rs index a695324e..1f36dbdc 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -31,11 +31,10 @@ pub fn parse_tuple(it: &mut LexIterator) -> ParseResult { let elements = it.parse_vec(&parse_expressions, "tuple", start)?; let end = it.eat(&Token::RRBrack, "tuple")?; - Ok(Box::from(if elements.len() == 1 { - elements[0].clone() - } else { - AST::new(start.union(end), Node::Tuple { elements }) - })) + Ok(Box::new(AST::new( + start.union(end), + Node::Tuple { elements }, + ))) } fn parse_set_or_dict(it: &mut LexIterator) -> ParseResult { @@ -48,19 +47,31 @@ fn parse_set_or_dict(it: &mut LexIterator) -> ParseResult { } let item = it.parse(&parse_expression, "set", start)?; + parse_set_or_dict_partial(start, &item, it) +} + +/// Parse set where first element is already parsed. +pub fn parse_set_or_dict_partial( + start: Position, + first: &AST, + it: &mut LexIterator, +) -> ParseResult { if it.eat_if(&Token::BTo).is_some() { let to = it.parse(&parse_expression, "dictionary entry to", start)?; - return parse_dict(it, &(*item.clone(), *to), start); + return parse_dict(it, &(first.clone(), *to), start); } if it.eat_if(&Token::Ver).is_some() { let conditions = it.parse_vec(&parse_expressions, "set builder", start)?; let end = it.eat(&Token::RCBrack, "set builder")?; - let node = Node::SetBuilder { item, conditions }; + let node = Node::SetBuilder { + item: Box::new(first.clone()), + conditions, + }; return Ok(Box::from(AST::new(start.union(end), node))); } - let mut elements = vec![*item]; + let mut elements = vec![first.clone()]; elements.append(&mut it.parse_vec_if(&Token::Comma, &parse_expressions, "set", start)?); let end = it.eat(&Token::RCBrack, "set")?; @@ -122,14 +133,21 @@ fn parse_list(it: &mut LexIterator) -> ParseResult { } let item = it.parse(&parse_expression, "list", start)?; + parse_list_partial(start, &item, it) +} + +pub fn parse_list_partial(start: Position, first: &AST, it: &mut LexIterator) -> ParseResult { if it.eat_if(&Token::Ver).is_some() { let conditions = it.parse_vec(&parse_expressions, "list", start)?; let end = it.eat(&Token::RSBrack, "list")?; - let node = Node::ListBuilder { item, conditions }; + let node = Node::ListBuilder { + item: Box::new(first.clone()), + conditions, + }; return Ok(Box::from(AST::new(start.union(end), node))); } - let mut elements = vec![*item]; + let mut elements = vec![first.clone()]; elements.append(&mut it.parse_vec_if(&Token::Comma, &parse_expressions, "list", start)?); let end = it.eat(&Token::RSBrack, "list")?; @@ -167,11 +185,17 @@ mod test { fn tuple_single_is_expr_verify() { let source = String::from("(a)"); let ast: AST = source.parse().unwrap(); - let Node::Id { lit } = &ast.node else { + let Node::Tuple { elements } = &ast.node else { panic!("first element script was not tuple.") }; - assert_eq!(lit.as_str(), "a"); + assert_eq!(elements.len(), 1); + assert_eq!( + elements[0].node, + Node::Id { + lit: String::from("a") + } + ); } #[test] diff --git a/src/parse/control_flow_expr.rs b/src/parse/control_flow_expr.rs index 020cf88b..c17cec22 100644 --- a/src/parse/control_flow_expr.rs +++ b/src/parse/control_flow_expr.rs @@ -141,7 +141,7 @@ mod test { #[test] fn match_verify() { - let source = String::from("match a\n a => b\n c => d"); + let source = String::from("match a\n{ a => b\n c => d}"); let ast: AST = source.parse().unwrap(); let Node::Match { cond, cases } = &ast.node else { diff --git a/src/parse/control_flow_stmt.rs b/src/parse/control_flow_stmt.rs index e55187be..353e08d0 100644 --- a/src/parse/control_flow_stmt.rs +++ b/src/parse/control_flow_stmt.rs @@ -227,7 +227,7 @@ mod test { #[test] fn if_with_block_verify() { - let source = String::from("if a then\n c\n d"); + let source = String::from("if a then\n[c\nd]"); let ast: AST = source.parse().unwrap(); let (cond, then, el) = match &ast.node { diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index d1cdb502..f113e7e6 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -1,5 +1,5 @@ use crate::parse::ast::{Node, AST}; -use crate::parse::block::{parse_code_block, parse_code_set}; +use crate::parse::block::{parse_code_block_or_list, parse_code_set_or_set}; use crate::parse::control_flow_expr::parse_match_cases; use crate::parse::iterator::LexIterator; use crate::parse::lex::token::{Lex, Token}; @@ -11,9 +11,9 @@ use crate::parse::statement::{is_start_statement, parse_reassignment}; pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { let expr_or_stmt = it.peek_or_err( &|it, lex| match &lex.token { - Token::LSBrack => it.parse(&parse_code_block, "expression", lex.pos), - Token::LCBrack => it.parse(&parse_code_set, "statement", lex.pos), - token if is_start_statement(token) => parse_statement(it), + Token::LSBrack => it.parse(&parse_code_block_or_list, "expression", lex.pos), + Token::LCBrack => it.parse(&parse_code_set_or_set, "statement", lex.pos), + _ if is_start_statement(lex) => parse_statement(it), _ => parse_expression(it), }, &[], @@ -240,16 +240,7 @@ mod test { let source = String::from("import c"); let ast = source.parse::().unwrap(); - let imports = match ast.node { - Node::Block { - statements: modules, - .. - } => modules, - _ => panic!("ast was not file."), - }; - - assert_eq!(imports.len(), 1); - let (from, import, alias) = match &imports[0].node { + let (from, import, alias) = match &ast.node { Node::Import { from, import, @@ -273,16 +264,7 @@ mod test { let source = String::from("import a, b as c, d"); let ast = source.parse::().unwrap(); - let imports = match ast.node { - Node::Block { - statements: modules, - .. - } => modules, - _ => panic!("ast was not file."), - }; - - assert_eq!(imports.len(), 1); - let (from, import, alias) = match &imports[0].node { + let (from, import, alias) = match &ast.node { Node::Import { from, import, diff --git a/src/parse/lex/mod.rs b/src/parse/lex/mod.rs index 79c65a28..fc5ff3c6 100644 --- a/src/parse/lex/mod.rs +++ b/src/parse/lex/mod.rs @@ -144,7 +144,7 @@ mod tests { #[test] fn comparison() { - let source = String::from("< > <= >= = != is i"); + let source = String::from("< > <= >= = != i"); let tokens = tokenize(&source).unwrap(); assert_eq!( tokens, @@ -174,7 +174,7 @@ mod tests { token: Token::Neq, }, Lex { - pos: Position::new(CaretPos::new(1, 19), CaretPos::new(1, 20)), + pos: Position::new(CaretPos::new(1, 16), CaretPos::new(1, 17)), token: Token::Id(String::from("i")), }, ] diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index 9bca006d..e65455c1 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -311,9 +311,8 @@ mod test { assert_eq!(tokens[5].token, Token::Assign); assert_eq!(tokens[6].token, Token::Int(String::from("10"))); assert_eq!(tokens[7].token, Token::NL); - assert_eq!(tokens[8].token, Token::NL); - assert_eq!(tokens[9].token, Token::Class); - assert_eq!(tokens[10].token, Token::Id(String::from("MyClass1"))); + assert_eq!(tokens[8].token, Token::Class); + assert_eq!(tokens[9].token, Token::Id(String::from("MyClass1"))); Ok(()) } @@ -330,10 +329,9 @@ mod test { assert_eq!(tokens[3].token, Token::NL); assert_eq!(tokens[4].token, Token::Id(String::from("b"))); assert_eq!(tokens[5].token, Token::NL); - assert_eq!(tokens[6].token, Token::NL); - assert_eq!(tokens[7].token, Token::Else); - assert_eq!(tokens[8].token, Token::NL); - assert_eq!(tokens[9].token, Token::Id(String::from("c"))); + assert_eq!(tokens[6].token, Token::Else); + assert_eq!(tokens[7].token, Token::NL); + assert_eq!(tokens[8].token, Token::Id(String::from("c"))); Ok(()) } diff --git a/src/parse/statement.rs b/src/parse/statement.rs index a7cb8459..24e01dac 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -203,9 +203,9 @@ pub fn parse_return(it: &mut LexIterator) -> ParseResult { Ok(Box::from(AST::new(start.union(end), Node::ReturnEmpty))) } -pub fn is_start_statement(tp: &Token) -> bool { +pub fn is_start_statement(lex: &Lex) -> bool { matches!( - tp, + lex.token, Token::Def | Token::Fin | Token::For From 6ce7f5006e989926757b82ef4323138cf6dcdf38 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 23 Mar 2026 14:00:00 +0100 Subject: [PATCH 27/44] fix: bracket type tokenization --- src/parse/class.rs | 15 +++++++++++++-- src/parse/expression.rs | 6 +++++- src/parse/iterator.rs | 39 +++++++++++++++++++++++++-------------- src/parse/lex/tokenize.rs | 2 +- src/parse/mod.rs | 6 +----- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/parse/class.rs b/src/parse/class.rs index db094f41..e125642f 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -69,7 +69,7 @@ pub fn parse_parent(it: &mut LexIterator) -> ParseResult { let ty = it.parse(&parse_type, "parent", start)?; let mut args = vec![]; - let end = if it.eat_if(&Token::LSBrack).is_some() { + let end = if it.eat_if(&Token::LRBrack).is_some() { it.peek_while_not_token(&Token::RRBrack, &mut |it, lex| match &lex.token { Token::Id { .. } => { args.push(*it.parse(&parse_id, "parent arguments", start)?); @@ -160,9 +160,13 @@ pub fn parse_type_def(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { + use std::str::FromStr; + use crate::common::result::WithSource; use crate::parse::ast::{Node, AST}; - use crate::parse::result::ParseErr; + use crate::parse::class::parse_parent; + use crate::parse::iterator::LexIterator; + use crate::parse::result::{ParseErr, ParseResult}; #[test] fn import_verify() { @@ -376,4 +380,11 @@ mod test { .map_err(Box::new) .map(|_| ()) } + + #[test] + fn parent_with_args() -> ParseResult<()> { + let source = "Parent(\"hello world\")\n"; + let mut it = LexIterator::from_str(source)?; + parse_parent(&mut it).map(|_| ()) + } } diff --git a/src/parse/expression.rs b/src/parse/expression.rs index 5eebbd86..0f4e5767 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -54,7 +54,11 @@ pub fn parse_inner_expression(it: &mut LexIterator) -> ParseResult { let expressions: Vec> = tokens .iter() - .map(|tokens| parse_expression(&mut LexIterator::new(tokens.iter().peekable()))) + .map(|tokens| { + parse_expression(&mut LexIterator::new( + tokens.clone().into_iter().peekable(), + )) + }) .collect::>()?; let node = Node::Str { lit: string.clone(), diff --git a/src/parse/iterator.rs b/src/parse/iterator.rs index f9381303..2016d030 100644 --- a/src/parse/iterator.rs +++ b/src/parse/iterator.rs @@ -1,5 +1,6 @@ use std::iter::Peekable; -use std::slice::Iter; +use std::str::FromStr; +use std::vec::IntoIter; use itertools::multipeek; @@ -7,16 +8,26 @@ use crate::common::position::Position; use crate::common::result::WithCause; use crate::parse::lex::token::Lex; use crate::parse::lex::token::Token; -use crate::parse::result::eof_expected_one_of; +use crate::parse::lex::tokenize; use crate::parse::result::expected; use crate::parse::result::ParseResult; +use crate::parse::result::{eof_expected_one_of, ParseErr}; -pub struct LexIterator<'a> { - it: Peekable>, +pub struct LexIterator { + it: Peekable>, } -impl<'a> LexIterator<'a> { - pub fn new(it: Peekable>) -> LexIterator<'a> { +impl FromStr for LexIterator { + type Err = Box; + + fn from_str(input: &str) -> Result { + let tokens: Vec = tokenize(input).map_err(ParseErr::from)?; + Ok(LexIterator::new(tokens.into_iter().peekable())) + } +} + +impl LexIterator { + pub fn new(it: Peekable>) -> LexIterator { LexIterator { it } } @@ -59,8 +70,8 @@ impl<'a> LexIterator<'a> { pub fn eat(&mut self, token: &Token, err_msg: &str) -> ParseResult { match self.it.next() { - Some(Lex { token: actual, pos }) if Token::same_type(actual, token) => Ok(*pos), - Some(lex) => Err(Box::from(expected(token, lex, err_msg))), + Some(Lex { token: actual, pos }) if Token::same_type(&actual, token) => Ok(pos), + Some(lex) => Err(Box::from(expected(token, &lex, err_msg))), None => Err(Box::from(eof_expected_one_of(&[token.clone()], err_msg))), } } @@ -143,7 +154,7 @@ impl<'a> LexIterator<'a> { ) -> ParseResult { match self.it.peek().cloned() { None => Err(Box::from(eof_expected_one_of(eof_expected, eof_err_msg))), - Some(lex) => match_fun(self, lex), + Some(lex) => match_fun(self, &lex), } } @@ -160,7 +171,7 @@ impl<'a> LexIterator<'a> { #[allow(dead_code)] // Useful method when debugging pub fn peek_next(&mut self) -> Option { - self.it.peek().cloned().cloned() + self.it.peek().cloned() } pub fn peek_while_not_tokens( @@ -193,7 +204,7 @@ impl<'a> LexIterator<'a> { check_fn: &dyn Fn(&Lex) -> bool, loop_fn: &mut dyn FnMut(&mut LexIterator, &Lex) -> ParseResult<()>, ) -> ParseResult<()> { - while let Some(&lex) = self.it.peek() { + while let Some(lex) = self.it.clone().peek() { if !check_fn(lex) { break; } @@ -225,7 +236,7 @@ mod tests { let l2 = Lex::new(CaretPos::start().offset_pos(1), Token::Neq); let l3 = Lex::new(CaretPos::start().offset_pos(2), Token::Eq); let lex = vec![l1, l2, l3]; - let mut it = LexIterator::new(lex.iter().peekable()); + let mut it = LexIterator::new(lex.into_iter().peekable()); assert!(it.peek_if_followed_by(&Token::Neq, &Token::Eq)); assert!(it.peek_if_followed_by(&Token::Neq, &Token::Neq)); @@ -239,8 +250,8 @@ mod tests { fn test_peek_followed_by_leaves_iter_unmodified() { let l1 = Lex::new(CaretPos::start().offset_pos(0), Token::Neq); let l2 = Lex::new(CaretPos::start().offset_pos(1), Token::Eq); - let lex = [l1, l2]; - let mut lex_iter = LexIterator::new(lex.iter().peekable()); + let lex = vec![l1, l2]; + let mut lex_iter = LexIterator::new(lex.into_iter().peekable()); lex_iter.peek_if_followed_by(&Token::Neq, &Token::Eq); assert_eq!( diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index e65455c1..cbc13c2f 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -19,7 +19,7 @@ pub fn into_tokens(c: char, it: &mut Peekable, state: &mut State) -> LexR Some('=') => next_and_create(it, state, Token::Assign), _ => create(state, Token::DoublePoint), }, - '(' => create(state, Token::LSBrack), + '(' => create(state, Token::LRBrack), ')' => create(state, Token::RRBrack), '[' => create(state, Token::LSBrack), ']' => create(state, Token::RSBrack), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index e3857737..426f6480 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -3,8 +3,6 @@ use std::str::FromStr; use crate::common::position::Position; use crate::parse::ast::{Node, AST}; use crate::parse::iterator::LexIterator; -use crate::parse::lex::token::Lex; -use crate::parse::lex::tokenize; use crate::parse::result::{ParseErr, ParseResult}; pub mod ast; @@ -31,9 +29,7 @@ impl FromStr for AST { type Err = Box; fn from_str(input: &str) -> ParseResult { - let tokens: Vec = tokenize(input).map_err(ParseErr::from)?; - - let mut iterator = LexIterator::new(tokens.iter().peekable()); + let mut iterator = LexIterator::from_str(input)?; let statements = block::parse_statements(&mut iterator)?; if statements.len() == 1 { return Ok(statements.first().cloned().unwrap()); From 058b96fbbb75769e89f0eab3df71e249d86ff862 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 23 Mar 2026 14:07:41 +0100 Subject: [PATCH 28/44] fix: inline class def In case class definition is only one line --- src/parse/class.rs | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/parse/class.rs b/src/parse/class.rs index e125642f..ec31f21e 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -34,19 +34,24 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { let mut parents = vec![]; if it.eat_if(&Token::DoublePoint).is_some() { - it.peek_while_not_token(&Token::NL, &mut |it, lex| match lex.token { - Token::Id(_) | Token::LSBrack => { - parents.push(*it.parse(&parse_parent, "parents", start)?); - it.eat_if(&Token::Comma); - Ok(()) - } - _ => Err(Box::from(expected( - &Token::Id(String::new()), - &lex.clone(), - "parents", - ))), - })?; + it.peek_while_not_tokens( + &[Token::NL, Token::Assign], + &mut |it, lex| match lex.token { + Token::Id(_) | Token::LSBrack => { + parents.push(*it.parse(&parse_parent, "parents", start)?); + it.eat_if(&Token::Comma); + Ok(()) + } + _ => Err(Box::from(expected( + &Token::Id(String::new()), + &lex.clone(), + "parents", + ))), + }, + )?; } + it.eat_if(&Token::NL); + it.eat_if(&Token::Assign); let (body, pos) = if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) { let body = it.parse(&parse_code_set, "class", start)?; @@ -160,6 +165,7 @@ pub fn parse_type_def(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { + use super::*; use std::str::FromStr; use crate::common::result::WithSource; @@ -387,4 +393,18 @@ mod test { let mut it = LexIterator::from_str(source)?; parse_parent(&mut it).map(|_| ()) } + + #[test] + fn class_with_parent_with_args() -> ParseResult<()> { + let source = "class Class: Parent(\"hello world\")\n"; + let mut it = LexIterator::from_str(source)?; + parse_class(&mut it).map(|_| ()) + } + + #[test] + fn class_with_parent_with_args_with_body() -> ParseResult<()> { + let source = "class Class: Parent(\"hello world\") := { def var := 10 }"; + let mut it = LexIterator::from_str(source)?; + parse_class(&mut it).map(|_| ()) + } } From b91e5636bdbc05f15552aa358a8e915708f8f905 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 23 Mar 2026 14:10:18 +0100 Subject: [PATCH 29/44] fix: bracket type direct function call --- src/parse/call.rs | 8 ++++---- src/parse/definition.rs | 33 ++++++++++++++++++++++++++++++++- src/parse/lex/tokenize.rs | 2 +- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/parse/call.rs b/src/parse/call.rs index 5c03b913..3fbd1691 100644 --- a/src/parse/call.rs +++ b/src/parse/call.rs @@ -39,8 +39,8 @@ pub fn parse_call(pre: &AST, it: &mut LexIterator) -> ParseResult { }; Ok(Box::from(AST::new(pre.pos.union(property.pos), node))) } - Token::LSBrack => { - it.eat(&Token::LSBrack, "direct call")?; + Token::LRBrack => { + it.eat(&Token::LRBrack, "direct call")?; let args = it.parse_vec(&parse_arguments, "direct call", pre.pos)?; let end = it.eat(&Token::RRBrack, "direct call")?; let node = Node::FunctionCall { @@ -50,12 +50,12 @@ pub fn parse_call(pre: &AST, it: &mut LexIterator) -> ParseResult { Ok(Box::from(AST::new(pre.pos.union(end), node))) } _ => Err(Box::from(expected_one_of( - &[Token::Point, Token::LSBrack], + &[Token::Point, Token::LRBrack], ast, "function call", ))), }, - &[Token::Point, Token::LSBrack], + &[Token::Point, Token::LRBrack], "function call", ) } diff --git a/src/parse/definition.rs b/src/parse/definition.rs index fa8a182e..f83ff3d2 100644 --- a/src/parse/definition.rs +++ b/src/parse/definition.rs @@ -294,6 +294,9 @@ fn parse_variable_def(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { + use std::str::FromStr; + + use super::*; use crate::parse::ast::{Node, AST}; macro_rules! unwrap_func_definition { @@ -329,6 +332,34 @@ mod test { }}; } + #[test] + fn definition_simple() -> ParseResult<()> { + let source = "def a := b + b"; + let mut it = LexIterator::from_str(source)?; + parse_definition(&mut it).map(|_| ()) + } + + #[test] + fn func_definition_simple() -> ParseResult<()> { + let source = "def a() := b + b"; + let mut it = LexIterator::from_str(source)?; + parse_definition(&mut it).map(|_| ()) + } + + #[test] + fn func_definition_one_arg() -> ParseResult<()> { + let source = "def a(b: Int) := b + b"; + let mut it = LexIterator::from_str(source)?; + parse_definition(&mut it).map(|_| ()) + } + + #[test] + fn func_definition_two_arg() -> ParseResult<()> { + let source = "def f(x: Int, b: Something) := d"; + let mut it = LexIterator::from_str(source)?; + parse_definition(&mut it).map(|_| ()) + } + #[test] fn empty_definition_verify() { let source = String::from("def a"); @@ -679,7 +710,7 @@ mod test { #[test] fn function_definition_with_literal_verify() { - let source = String::from("def f(x, b: Something) := d"); + let source = String::from("def f(x: Int, b: Something) := d"); let ast: AST = source.parse().unwrap(); let (pure, id, fun_args, ret, _, body) = unwrap_func_definition!(ast); diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index cbc13c2f..0f8040f4 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -283,7 +283,7 @@ mod test { assert_eq!(tokens[0].token, Token::Def); assert_eq!(tokens[1].token, Token::Id(String::from("f"))); - assert_eq!(tokens[2].token, Token::LSBrack); + assert_eq!(tokens[2].token, Token::LRBrack); assert_eq!(tokens[3].token, Token::Id(String::from("x"))); assert_eq!(tokens[4].token, Token::DoublePoint); assert_eq!(tokens[5].token, Token::Id(String::from("Int"))); From 265f62ad06c65e408d282f7155bec9976aff5de0 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 23 Mar 2026 15:23:03 +0100 Subject: [PATCH 30/44] doc: update year LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7793eb0f..8ffd02dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2025 Joël S. Abrahams +Copyright (c) 2026 Joël S. Abrahams Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5aa2202883b800e2ed628244099f646add11f1fe Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 23 Mar 2026 15:40:24 +0100 Subject: [PATCH 31/44] fix: allow pure in function definition --- src/parse/definition.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parse/definition.rs b/src/parse/definition.rs index f83ff3d2..afe16ca4 100644 --- a/src/parse/definition.rs +++ b/src/parse/definition.rs @@ -84,10 +84,10 @@ fn parse_var_or_fun_def(it: &mut LexIterator, pure: bool) -> ParseResult { } Node::ExpressionType { expr, ty, mutable } if ty.is_none() => it.peek( &|it, lex| match lex.token { - Token::LSBrack => parse_fun_def(&id, pure, it), + Token::LRBrack => parse_fun_def(&id, pure, it), _ if !pure => parse_variable_def_id(&id, it), _ => { - let msg = format!("Definition cannot have {} identifier", Token::Pure); + let msg = format!("Definition cannot have '{}' identifier", Token::Pure); Err(Box::from(custom(&msg, id.pos))) } }, @@ -173,7 +173,7 @@ pub fn parse_raises(it: &mut LexIterator) -> ParseResult> { } pub fn parse_fun_args(it: &mut LexIterator) -> ParseResult> { - let start = it.eat(&Token::LSBrack, "function arguments")?; + let start = it.eat(&Token::LRBrack, "function arguments")?; let mut args = vec![]; it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { args.push(*it.parse(&parse_fun_arg, "function arguments", start)?); @@ -710,7 +710,7 @@ mod test { #[test] fn function_definition_with_literal_verify() { - let source = String::from("def f(x: Int, b: Something) := d"); + let source = String::from("def f(x, b: Something) := d"); let ast: AST = source.parse().unwrap(); let (pure, id, fun_args, ret, _, body) = unwrap_func_definition!(ast); From e2b83d43101b214a3d17e8bc24e6fd11fe34542a Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 23 Mar 2026 15:59:26 +0100 Subject: [PATCH 32/44] test: control flow if stmt parsing --- src/parse/control_flow_expr.rs | 21 +++++++++++++++++++++ src/parse/control_flow_stmt.rs | 11 +++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/parse/control_flow_expr.rs b/src/parse/control_flow_expr.rs index c17cec22..7d29c19c 100644 --- a/src/parse/control_flow_expr.rs +++ b/src/parse/control_flow_expr.rs @@ -107,9 +107,30 @@ fn parse_expression_maybe_type(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { + use std::str::FromStr; + + use super::*; use crate::parse::ast::{Node, AST}; use crate::parse::result::ParseResult; + #[test] + fn if_with_block_verify_no_panic() -> ParseResult<()> { + let mut it = LexIterator::from_str("if a then c")?; + parse_cntrl_flow_expr(&mut it).map(|_| ()) + } + + #[test] + fn if_with_block_verify_block_no_panic() -> ParseResult<()> { + let mut it = LexIterator::from_str("if a then [c]")?; + parse_cntrl_flow_expr(&mut it).map(|_| ()) + } + + #[test] + fn if_with_block_verify_block_ret_list_no_panic() -> ParseResult<()> { + let mut it = LexIterator::from_str("if a then [[c]]")?; + parse_cntrl_flow_expr(&mut it).map(|_| ()) + } + #[test] fn if_else_verify() { let source = String::from("if a then c else d"); diff --git a/src/parse/control_flow_stmt.rs b/src/parse/control_flow_stmt.rs index 353e08d0..7c9679fb 100644 --- a/src/parse/control_flow_stmt.rs +++ b/src/parse/control_flow_stmt.rs @@ -65,7 +65,12 @@ fn parse_for(it: &mut LexIterator) -> ParseResult { #[cfg(test)] mod test { + use super::*; + use std::str::FromStr; + use crate::parse::ast::{Node, AST}; + use crate::parse::iterator::LexIterator; + use crate::parse::result::ParseResult; #[test] fn for_statement_verify() { @@ -97,6 +102,12 @@ mod test { ); } + #[test] + fn for_range_step_no_panic() -> ParseResult<()> { + let mut it = LexIterator::from_str("for a in c .. d .. e do f")?; + parse_cntrl_flow_stmt(&mut it).map(|_| ()) + } + #[test] fn for_range_step_verify() { let source = String::from("for a in c .. d .. e do f"); From 3961147d7eed33ffba6f141cc3409131ee89698f Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 27 Mar 2026 17:30:06 +0100 Subject: [PATCH 33/44] feat: printing of token name --- src/parse/lex/token.rs | 75 ++++++++++++++++++++++++++++++++++++++++++ src/parse/result.rs | 8 +++-- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index 3031568e..e59e1b6b 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -115,6 +115,10 @@ pub enum Token { Pass, } +/// Name structure, used to give a more descriptive name of a token. +/// Useful in debug or error messages, where we don't only want to print the token itself, but instead a description. +pub struct TokenName<'a>(&'a Token); + impl Token { pub fn width(&self) -> usize { self.to_string().len() @@ -131,6 +135,77 @@ impl Token { _ => left == right, } } + + /// Similar to `Display`, except that it writes a more descriptive name. + /// Useful in say error or debug messages, where you don't want to literally print the token but what it is. + /// In many cases, similar to display. + pub fn name<'a>(&'a self) -> TokenName<'a> { + TokenName(self) + } + + /// Quick check to see if token name is not equal to self. + /// Used in context of error message generation. + pub fn equals_name(&self) -> bool { + format!("{self}") == format!("{}", self.name()) + } +} + +impl fmt::Display for TokenName<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Token::Assign => write!(f, "assign"), + Token::AddAssign => write!(f, "add assign"), + Token::SubAssign => write!(f, "sub assign"), + Token::MulAssign => write!(f, "mul assign"), + Token::PowAssign => write!(f, "pow assign"), + Token::DivAssign => write!(f, "div assign"), + + Token::Id(_) => write!(f, "identifier"), + Token::Real(_) => write!(f, "real literal"), + Token::Int(_) => write!(f, "integer literal"), + Token::Str(..) => write!(f, "string literal"), + Token::DocStr(_) => write!(f, "doc string"), + Token::ENum(..) => write!(f, "enum variant"), + + Token::Range => write!(f, "range"), + Token::RangeIncl => write!(f, "range inclusive"), + Token::Slice => write!(f, "slice"), + Token::SliceIncl => write!(f, "slice inclusive"), + + Token::Add => write!(f, "add"), + Token::Sub => write!(f, "sub"), + Token::Mul => write!(f, "mul"), + Token::Div => write!(f, "div"), + Token::FDiv => write!(f, "floor div"), + Token::Pow => write!(f, "pow"), + + Token::Ge => write!(f, "greater than"), + Token::Geq => write!(f, "greater than or equal to"), + Token::Le => write!(f, "less than"), + Token::Leq => write!(f, "less than or equal to"), + + Token::Eq => write!(f, "equal"), + Token::Neq => write!(f, "not equal"), + + Token::LRBrack => write!(f, "left roung bracket"), + Token::RRBrack => write!(f, "right rount bracket"), + Token::LSBrack => write!(f, "left square bracket"), + Token::RSBrack => write!(f, "right square bracket"), + Token::LCBrack => write!(f, "left curly bracket"), + Token::RCBrack => write!(f, "right curly bracket"), + Token::Ver => write!(f, "vertial"), + Token::To => write!(f, "to"), + Token::BTo => write!(f, "broad to"), + + Token::NL => write!(f, "newline"), + Token::Underscore => write!(f, "underscore"), + + Token::Question => write!(f, "question"), + Token::Raise => write!(f, "raise"), + + _ => write!(f, "{}", self.0), + } + } } impl fmt::Display for Token { diff --git a/src/parse/result.rs b/src/parse/result.rs index 32ad4242..3cefffd4 100644 --- a/src/parse/result.rs +++ b/src/parse/result.rs @@ -66,10 +66,14 @@ impl From for ParseErr { pub fn expected_one_of(tokens: &[Token], actual: &Lex, parsing: &str) -> ParseErr { let msg = format!( - "Expected one of [{}] while parsing {}{parsing}, but found token '{}'", + "Expected one of [{}] while parsing {}{parsing}, but found {}", comma_delm(tokens), an_or_a(parsing), - actual.token + if actual.token.equals_name() { + format!("'{}'", actual.token) + } else { + format!("{} ('{}')", actual.token.name(), actual.token) + } ); ParseErr { pos: actual.pos, From f7f68a6431177c20e0075b372e002e3d1fb0ded4 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 27 Mar 2026 17:33:20 +0100 Subject: [PATCH 34/44] fix: right bracket type tuples --- src/parse/collection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parse/collection.rs b/src/parse/collection.rs index 1f36dbdc..9644f85e 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -15,19 +15,19 @@ pub fn parse_collection(it: &mut LexIterator) -> ParseResult { Token::LSBrack => parse_list(it), Token::LCBrack => parse_set_or_dict(it), _ => Err(Box::from(expected_one_of( - &[Token::LSBrack, Token::LSBrack, Token::LCBrack], + &[Token::LRBrack, Token::LSBrack, Token::LCBrack], lex, "collection", ))), }, - &[Token::LSBrack, Token::LSBrack, Token::LCBrack], + &[Token::LRBrack, Token::LSBrack, Token::LCBrack], "collection", ) } pub fn parse_tuple(it: &mut LexIterator) -> ParseResult { let start = it.start_pos("tuple")?; - it.eat(&Token::LSBrack, "tuple")?; + it.eat(&Token::LRBrack, "tuple")?; let elements = it.parse_vec(&parse_expressions, "tuple", start)?; let end = it.eat(&Token::RRBrack, "tuple")?; From cbebe827073e5a2f109fbdcb75dcdc69521b5aa4 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Fri, 27 Mar 2026 18:29:25 +0100 Subject: [PATCH 35/44] test: start introducing new grammar to test suite --- README.md | 189 ++++++++---------- docs/spec/grammar.md | 4 +- docs/spec/reserved.md | 8 +- src/check/context/clss/generic.rs | 3 +- src/check/mod.rs | 8 +- src/parse/block.rs | 53 ++--- src/parse/control_flow_stmt.rs | 2 +- src/parse/expr_or_stmt.rs | 6 +- src/parse/lex/token.rs | 5 + src/parse/lex/tokenize.rs | 3 + src/parse/ty.rs | 10 +- .../invalid/syntax/assign_and_while.mamba | 4 +- .../invalid/syntax/top_lvl_class_access.mamba | 4 +- .../class/access_unassigned_class_var.mamba | 4 +- .../type/class/access_unassigned_field.mamba | 4 +- .../assign_to_inner_inner_not_allowed.mamba | 4 +- .../class/assign_to_non_existent_self.mamba | 4 +- .../invalid/type/class/compound_field.mamba | 4 +- .../class/one_tuple_not_assigned_to.mamba | 4 +- .../invalid/type/class/parent_is_class.mamba | 4 +- .../reassign_to_unassigned_class_var.mamba | 4 +- .../top_level_class_not_assigned_to.mamba | 4 +- .../access_match_arms_variable.mamba | 4 +- .../class_field_assigned_to_only_else.mamba | 4 +- ...field_assigned_to_only_one_arm_match.mamba | 8 +- .../class_field_assigned_to_only_then.mamba | 4 +- .../control_flow/different_type_shadow.mamba | 2 +- .../undefined_var_in_match_arm.mamba | 2 +- .../definition/assign_to_function_call.mamba | 4 +- .../definition/assign_to_inner_non_mut.mamba | 4 +- .../definition/assign_to_inner_non_mut2.mamba | 4 +- .../definition/assign_to_inner_non_mut3.mamba | 4 +- .../function_ret_in_class_not_super.mamba | 4 +- .../invalid/type/error/handle_only_id.mamba | 6 +- .../type/error/unhandled_exception.mamba | 4 +- .../type/function/call_mut_function.mamba | 4 +- .../call_mut_function_on_non_mut.mamba | 4 +- .../class/assign_to_nullable_field.mamba | 4 +- .../class/assign_types_double_nested.mamba | 4 +- .../class/class_super_one_line_init.mamba | 4 +- .../class/fun_with_body_in_interface.mamba | 4 +- .../valid/class/multiple_parent.mamba | 4 +- tests/resource/valid/class/parent.mamba | 8 +- .../class/print_types_double_nested.mamba | 4 +- .../valid/class/same_var_different_type.mamba | 8 +- tests/resource/valid/class/shadow.mamba | 4 +- .../top_level_unassigned_but_nullable.mamba | 4 +- tests/resource/valid/class/types.mamba | 15 +- .../unassigned_tuple_second_nullable.mamba | 4 +- .../valid/control_flow/assign_if.mamba | 5 +- .../valid/control_flow/assign_match.mamba | 3 +- .../valid/control_flow/double_assign_if.mamba | 4 +- .../valid/control_flow/for_statements.mamba | 12 +- .../definition/assign_to_inner_mut.mamba | 4 +- .../valid/definition/assign_with_match.mamba | 2 +- .../assign_with_match_different_types.mamba | 2 +- .../assign_with_match_type_annotation.mamba | 2 +- .../definition/assign_with_try_except.mamba | 2 +- .../function_ret_super_in_class.mamba | 4 +- .../definition/function_with_match.mamba | 2 +- .../valid/error/exception_in_fun.mamba | 4 +- .../valid/error/exception_in_fun_super.mamba | 4 +- tests/resource/valid/error/handle.mamba | 10 +- .../resource/valid/error/handle_only_id.mamba | 14 +- .../valid/error/handle_var_usable_after.mamba | 9 +- .../valid/error/nested_exception.mamba | 6 +- .../resource/valid/function/definition.mamba | 11 +- .../valid/function/match_function.mamba | 4 +- .../function/return_last_expression.mamba | 4 +- .../operation/equality_different_types.mamba | 4 +- .../valid/readme_example/builtin_trait.mamba | 4 +- .../resource/valid/readme_example/class.mamba | 12 +- .../readme_example/class_with_constants.mamba | 14 +- .../valid/readme_example/error_handling.mamba | 10 +- .../error_handling_as_expression.mamba | 12 +- .../error_handling_desyntax_sugared.mamba | 2 +- .../error_handling_handle_subset.mamba | 12 +- .../error_handling_recover.mamba | 6 +- .../valid/readme_example/factorial.mamba | 4 +- .../readme_example/factorial_dynamic.mamba | 8 +- .../valid/readme_example/impl_trait.mamba | 8 +- .../valid/readme_example/pure_functions.mamba | 4 +- .../readme_example/total_functions.mamba | 4 +- .../valid/readme_example/trait_fin_meta.mamba | 4 +- .../readme_example/trait_inheritance.mamba | 4 +- .../valid/readme_example/traits.mamba | 12 +- .../type_refinement_in_fun.mamba | 6 +- .../type_refinement_matrix.mamba | 7 +- .../type_refinement_on_matrix.mamba | 4 +- .../readme_example/type_refinement_set.mamba | 4 +- 90 files changed, 352 insertions(+), 365 deletions(-) diff --git a/README.md b/README.md index 143017e8..fd23f5b5 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,13 @@ See [docs](/docs/) for a more extensive overview of the language philosophy. This is a transpiler, written in [Rust](https://www.rust-lang.org/), which converts Mamba source files to Python source files. -There therefore exists some interopability with Python code. +There therefore exists some interoperability with Python code. Currently we compile down to Python, in future we may compile down to Python bytecode, for instance. -The below README: +This README: - Gives a quickstart for developers -- Give a short overview of most of the syntax and language features in quick succession, as well as the occasional reasoning behind them. +- Gives a short overview of the syntax and language features in quick succession, as well as the occasional reasoning behind them. ## 🧑‍💻 Quickstart for developers 👨‍💻 @@ -80,16 +80,16 @@ We can write a simple script that computes the factorial of a value given by the ```mamba # Factorial of x -def factorial(x: Int) -> Int := match x { +def factorial(x: Int) -> Int := match x where 0 => 1 n => n * factorial(n - 1) -} +end def num := input("Compute factorial: ") -if num.is_digit() then [ +if num.is_digit() then do def result := factorial(Int(num)) print("Factorial {num} is: {result}.") -] else +end else print("Input was not an integer.") ``` @@ -98,25 +98,23 @@ This is part of the signature of the function, and is required (it cannot be inf This means that the compiler will check for us that factorial is only used with integers as argument. Also note that: -- Code blocks are denoted using `[` and `]` because this is a list of statements and expressions that gets executed _in order_. -- For a match expression or statement, each case is denoted using `{` and `}`, as this is a _set_ of cases which we match on. - You you can read `match x {}` , where we read this as "match `x` on this set of conditions in `{...}`", though we omit the "on" as to not introduce another keyword. +- Code blocks are denoted using `do` and `end` because this is a list of statements and expressions that gets executed _in order_. +- For a match expression or statement, each case is denoted using `where` and `end`, as this is a _set_ of cases which we match on. + You you can read `match x where ... end` , where we read this as "match `x` on this set of conditions in `where ... end`", though we omit the "on" as to not introduce another keyword. _Note_ One could use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) in the above example so that we consume less memory: ```mamba -def factorial(x: Int) -> Int := match x { +def factorial(x: Int) -> Int := match x where 0 => 1 - n => [ + n => do def ans := 1 for i in 1 ..= n do ans := ans * i ans - ] -} + end +end ``` -Logically, the `[...]` and `{...}` notation leads us nicely to collections in Mamba. - ### 🍡 Collections In Mamba, sets, lists, and maps are first class citizens. @@ -174,7 +172,7 @@ Where we iterate over the list in the order of the keys. Unlike C-style languages (which is nearly the whole world at this point), we index collections using `collection()`. We namely don't distinguish between a mapping and a function, because a function is (generally speaking) also a type of mapping. -The above mapping, for instance, is a representation of some functino with a very small domain (only three items). +The above mapping, for instance, is a representation of some function with a very small domain (only three items). Therefore, we index indexable collections (mappings and list) using the `collection()` notation. ### ✏️🖊️ Mutability @@ -210,7 +208,7 @@ You will also see some "pure" functions, these will be explained later. ```mamba class MatrixErr(def message: Str): Exception(message) -class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { +class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := where # Accessor for matrix contents def contents(fin self) -> List[Int] := [self.a, self.b, self.c, self.d] @@ -220,27 +218,27 @@ class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { # Determinant recomputation (pure function) def pure determinant(fin self) -> Int := self.a * self.d - self.b * self.c - def scale(self, factor: Int) := [ + def scale(self, factor: Int) := do self.a := self.a * factor self.b := self.b * factor self.c := self.c * factor self.d := self.d * factor - ] + end - # Reset turns this matrix into an 2x2 identity matrix, regardless of the intial value. - def reset(self) := [ + # Reset turns this matrix into an 2x2 identity matrix, regardless of the initial value. + def reset(self) := do self.a := 1 self.b := 0 self.c := 0 self.d := 1 - ] -} + end +end ``` Notice how `self` is not mutable in `trace`, meaning we can only read variables, whereas in `scale`, `self` is mutable, so we can change properties of `self`. _In general_, the notation of a class is: -`class MyClass() := {}` +`class MyClass() := where end` The body of the class is optional, i.e. one can create "just" a data class. As for constructor arguments: @@ -261,24 +259,24 @@ As for the class body We can change the relevant parts of the above example to use a class constant: ```mamba -class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { +class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := where def x: Int := ORIGIN_X def y: Int := ORIGIN_Y - def move(self, dx: Int, dy: Int) := [ + def move(self, dx: Int, dy: Int) := do self.x := self.x + dx self.y := self.y + dy - ] + end # Unlike the matrix before, reset resets this point to the value it was when it was instantiated. - def reset(self) := [ + def reset(self) := do self.x := ORIGIN_X self.y := ORIGIN_Y - ] + end def info(fin self) -> Str := "Currently at ({self.x}, {self.y}), originally from ({ORIGIN_X}, {ORIGIN_Y})" -} +end ``` Last, we have `trait`s, which in Mamba are more fine-grained building blocks to describe the behaviour of instances. @@ -289,28 +287,28 @@ For those familiar with object oriented programming, we favour a trait based sys Consider example with iterators (which briefly showcases language generics): ```mamba -trait Iterator[T] := { +trait Iterator[T] := where def has_next(self) -> Bool def next(self) -> T? # syntax sugar for Option[T] -} +end -class RangeIter(def _start: Int, def _end: Int) := { +class RangeIter(def _start: Int, def _end: Int) := where def _current: Int := _start -} +end -def Iterator[Int] for RangeIter := { +def Iterator[Int] for RangeIter := where def has_next(self) -> Bool := self._current < self._stop - def next(self) -> Int? := if self.has_next() then [ + def next(self) -> Int? := if self.has_next() then do def value := self._current self._current := self._current + 1 value - ] else None -} + end else None +end ``` Prefer using an adjective (e.g. `Iterable`, `Hashable`, `Comparable`) when defining a trait, as this describes something a class and its instances can do. -The syntax here is `trait := { for `. +The syntax here is `trait := where for `. Lastly, like Rust, types (traits) can also be used as generics. This would allow, for instance, for defining a `Hash` trait and enforcing for a hashmap that keys implement said trait. @@ -327,7 +325,7 @@ trait Ordered[T]: Equality, Comparable Mamba also has type refinement features to assign additional properties to types. -Note: Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the comlexit of the language. +Note: Having this as a first-class language feature and incorporating it into the grammar may have benefits, but does increase the complexity of the language. Arguably, it might detract from the elegance of the type system as well; A different solution could be to just have a dedicated interface baked into the standard library for this purpose. @@ -335,17 +333,16 @@ The general syntax is `type MyType: MainType when `. The expression can be of any form (and size), but **must** evaluate to a boolean. ```mamba -type SpecialInt: Int when self >= 0 and self <= 100 or self mod 2 = 0 +type SpecialInt: Int where self >= 0 and self <= 100 or self mod 2 = 0 ``` -We also introduce some syntax sugar again, where we can use `{` `}` to write each element of the conjunction on its own line. _Note on performance: In terms of correctness, the order of the conjunctions obviously doesn't matter, but those who care about performance should know they are evaluated in order, so best to have simple ones first._ ```mamba -type SpecialInt: Int when { +type SpecialInt: Int when self >= 0 self <= 100 or self mod 2 = 0 -} +end ``` Type refinement also allows us to specify the domain and co-domain of a function, say, one that only takes and returns positive integers: @@ -356,10 +353,10 @@ Type refinement also allows us to specify the domain and co-domain of a function # we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. type PosInt: Int when self >= 0 -def factorial(x: PosInt) -> PosInt := match x { +def factorial(x: PosInt) -> PosInt := match x where 0 => 1 n => n * factorial(n - 1) -} +end ``` At the call site, one could do @@ -386,21 +383,22 @@ type InvertibleMatrix: Matrix when self.determinant() != 0.0 class MatrixErr(def message: Str): Exception(message) ## Matrix, which now takes floats as argument -class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := { +class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := where def _last_op: Str? := None def determinant(fin self) -> Float := self.a * self.d - self.b * self.c - def inverse(self: InvertibleMatrix) -> Matrix := + def inverse(self: InvertibleMatrix) -> Matrix := do def det := self.determinant() self._last_op := "inverse" Matrix(self.d / det, -self.b / det, -self.c / det, self.a / det) + end def last_op(fin self) -> Str ! MatrixErr := if self._last_op != None then self._last_op else ! MatrixErr("No operation performed") -} +end ``` Within the then branch of the if statement, we know that `self._last_message` is a `Str`. @@ -413,11 +411,11 @@ For each type, we use `when` to show that it is a type refinement, which certain ```mamba def m := Matrix(1.0, 2.0, 3.0, 4.0) -if m isa InvertibleMatrix then +if m isa InvertibleMatrix then do def m_inv := m.inverse() print("Original matrix: {m}") print("Inverse: {m_inv}") -else +end else print("Matrix is singular (not invertible).") def last_op = m.last_op()! @@ -472,12 +470,12 @@ Immutable variables and pure functions make it easier to write declarative progr def fin taylor := 7 # the sin function is pure, its output depends solely on the input -def pure sin(x: Int) -> Int := [ +def pure sin(x: Int) -> Int := do def ans := x for i in (1 ..= taylor).step(2) do ans := ans + (x ^ (i + 2)) / (factorial (i + 2)) ans -] +end ``` ### 🤚 Total functions (🇻 x+) @@ -507,27 +505,27 @@ Instead, we place heavy restrictions on total functions, enforcing that they are - `Range` : `a..b` - `RangeInclusive` : `a..=b` -Put another way, we sidestep the issue by ensuring that our system is still sound, but incomplete by acknoweldging that we cannot prove termination for arbitary functions! +Put another way, we sidestep the issue by ensuring that our system is still sound, but incomplete by acknowledging that we cannot prove termination for arbitrary functions! -Take for instance this naive implementation of the Fibbonaci sequence: +Take for instance this naive implementation of the Fibonacci sequence: ```mamba -## fibbonaci, implemented using recursion and not dynamic programming -def total pure fibbonaci(x: PosInt) -> Int := match x { +## Fibonacci, implemented using recursion and not dynamic programming +def total pure fibonacci(x: PosInt) -> Int := match x where 0 => 0 1 => 1 - n => fibbonaci(n - 1) + fibbonaci(n - 2) -} + n => fibonacci(n - 1) + fibonacci(n - 2) +end ``` This would, with some substitution magic, give the following _call tree_ (showing only the important parts): ``` - fibbonaci(x) + fibonacci(x) | + # addition operator / \ -fibbonaci(x - 1) fibbonaci(x - 2) +fibonacci(x - 1) fibonacci(x - 2) ``` Thus, this function has the property of a final function, and we may thus mark it as `total` if we so choose. @@ -541,30 +539,30 @@ However, this is ripe for abuse, so instead, we require that each argument imple ```mamba # if we implement strictly decreasing, we must implement measure # These are non-overridable method which uses this measure -trait def StrictlyDecreases: Measurable { +trait def StrictlyDecreases: Measurable where def fin meta decreases(self, other: Self) -> Bool := self.measure() < other.measure() def fin meta equal(self, other: Self) -> Bool := self.measure() = other.measure() def fin meta subtract(self, other: Self) -> Measurable := self.measure() - other.measure() # this we must implement def meta measure(self) -> Measurable -} +end ``` This avoids abuse of `decreases` (i.e. one could write `def fin meta decreases(self, other: Self) := True`). Instead, ordering is reduced to numeric ordering, which is verifiable and depends on the output of a pure function. -It is for instance defined for the built-in primtive `Int`. +It is for instance defined for the built-in primitive `Int`. ```mamba # Measure for int just returns self -def StrictlyDecreases for Int { +def StrictlyDecreases for Int where def meta measure(self) -> Measurable := self -} +end # For string, we as an example use the length of the string (Which is also an integer) -def StrictlyDecreases for Str { +def StrictlyDecreases for Str where def meta measure(self) -> Measurable := self.len() -} +end ``` Both of the above return an `Int`, which is part of the library and implements the `Measured` trait. @@ -590,7 +588,7 @@ def Measurable for Int ``` We require that the measured item implements basic arithmetic so that we can add and subtract as we traverse those trees where we interweave recursive calls. -_Paeno arithmetic, essentially, forms the logical bedrock of the system which proves functions are total._ +_Peano arithmetic, essentially, forms the logical bedrock of the system which proves functions are total._ Only meta functions can be evaluated at compile time, see the section on meta functions below. In general: @@ -635,7 +633,7 @@ This is useful when one wants to document how one derived a meta in the form of ### ⚠ Error handling Unlike Python, Mamba does not have `try` `except` and `finally` (or `try` `catch` as it is sometimes known). -Instead, we aim to directly handle errors on-site so the origin of errors is more tracable. +Instead, we aim to directly handle errors on-site so the origin of errors is more traceable. The following is an attempt mixing and matching `Result` monad (of languages like Rust and Scala), with a more first-class approach of exceptions in languages like Kotlin. Again, this represents a trade-off between elegancy of the type system and simplicity of the grammar versus having first-class language features. Arguably it may be easier to just use Monads, similar to how Rust's solution. @@ -653,12 +651,12 @@ if m isa InvertibleMatrix then else print("Matrix is singular (not invertible).") -def last_op = m.last_op() ! { - err: MatrixErr(message) => [ +def last_op = m.last_op() ! where + err: MatrixErr(message) => do print("Error when getting last op: \"{message}\"") "N/A" # optionally we can also return, but here we assign default value - ] -} + end +end print("Last operation was: {last_op}") ``` @@ -667,34 +665,21 @@ In the above script, we will always print an error (gracefully) and assign some Here we showcase how we try to handle errors on-site instead of in a (large) `try` block. This also prevents us from wrapping large code blocks in a `try`, where it might not be clear what statement or expression might throw what error. -`m.last_op() ! { ... }` is syntax sugar for - -```mamba -match m.last_op() { - err: MatrixErr(message) => print("Error when getting last op: \"{message}\"") -} -``` - -So esentially, we add `!` as a way to shorthand match on exceptions. -Currently, we allow both notations, but this comes at the cost of there not being "one way" to handle exceptions. -This can lead to similar problems like with Scala where we have multiple ways to do the same thing. -We can, of course, add warnings to strongly encourage the "right" way to handle exceptions. - This can also be combined with an assign. In that case, we must either always return (halting execution or exiting the function), or evaluate to a value. This is shown below: ```mamba -def a: Int := function_may_throw_err() ! { - err: MyErr => [ +def a: Int := function_may_throw_err() ! where + err: MyErr => do print("We have a problem: {err.message}.") return # we return, halting execution - ] - err: MyOtherErr => [ + end + err: MyOtherErr => do print("We have another problem: {err.message}.") 0 # ... or we assign default value 0 to a - ] -} + end +end print("a has value {a}.") ``` @@ -710,7 +695,7 @@ Only when the union is empty, which happens when every error case is covered, do If `a` is is type `Result[...,...]`, and we are required to do error handling later. So if we don't want to handle any of the exception cases at a given point, we just append an `!` to a function. -The exception(s) must be handeld further up the stack. +The exception(s) must be handled further up the stack. ```mamba def a := function_may_throw_err() ! @@ -721,12 +706,12 @@ print("a has value {a}.") This also gives an alternative way to write the above example, where we only case about a subset of the exceptions here. ```mamba -def a: Result[Int, MyErr] := function_may_throw_err() ! { - err: MyOtherErr => [ +def a: Result[Int, MyErr] := function_may_throw_err() ! where + err: MyOtherErr => do print("We have another problem: {err.message}.") 0 # ... or we assign default value 0 to a - ] -} + end +end a = a ! # Result[Int, MyErr] => Int, where if error case, an exception is raised. @@ -743,12 +728,12 @@ The general syntax is ` recover print("We have a problem: {err.message}.") -} recover [ +end recover do print("cleaning up resource") some_cleanup_function() -] +end ``` ## 💻 The Command Line Interface diff --git a/docs/spec/grammar.md b/docs/spec/grammar.md index 0c3c26b7..e35872bb 100644 --- a/docs/spec/grammar.md +++ b/docs/spec/grammar.md @@ -100,8 +100,8 @@ The grammar of the language in Extended Backus-Naur Form (EBNF). e-notation ::= ( integer | real ) "E" [ "-" ] integer string ::= """ { character } """ - code-block ::= "[" expr-or-stmt { newline expr-or-stmt } "]" - code-set ::= "{" expr-or-stmt { newline expr-or-stmt } "}" + code-block ::= "do" expr-or-stmt { newline expr-or-stmt } "end" + code-set ::= "where" expr-or-stmt { newline expr-or-stmt } "end" control-flow-expr::= if | match if ::= "if" expression "then" expression [ "else" expression ] diff --git a/docs/spec/reserved.md b/docs/spec/reserved.md index 13471eda..388d2b36 100644 --- a/docs/spec/reserved.md +++ b/docs/spec/reserved.md @@ -75,7 +75,7 @@ Keyword | Use `while` | Denote start of while statement `for` | Denote start of for statement `in` | Specify which collection to iterate over in for statement -`do` | Specify what needs to be done in control flow statement +`do` | Specify what needs to be done in control flow statement, and start of code block `continue`| Continue onto next iteration within loop `break` | Exit loop @@ -85,6 +85,12 @@ Keyword | Use ---|--- `return` | Return from a function or method +## Blocks + +Keyword | Use +`end` | Denote end of code block of set +`where` | Denote start of code set + ## 3.2.2 Special Characters The following is a list of characters in the language diff --git a/src/check/context/clss/generic.rs b/src/check/context/clss/generic.rs index 5f510782..f3fc0d7d 100644 --- a/src/check/context/clss/generic.rs +++ b/src/check/context/clss/generic.rs @@ -352,7 +352,8 @@ mod test { #[test] fn from_class_inline_args() -> Result<(), Vec> { - let source = "class MyClass(def fin a: Int, b: Int): Parent(b) := { def c: Int := a + b }"; + let source = + "class MyClass(def fin a: Int, b: Int): Parent(b) := where def c: Int := a + b end"; let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast)?; diff --git a/src/check/mod.rs b/src/check/mod.rs index bd2ea945..9b7a8cba 100644 --- a/src/check/mod.rs +++ b/src/check/mod.rs @@ -82,15 +82,11 @@ mod tests { let ast = src.parse::().unwrap(); let result = check_all(&[ast]).unwrap(); - let NodeTy::Block { statements } = &result[0].node else { - panic!() - }; - let NodeTy::VariableDef { expr: Some(expr), .. - } = &statements[0].node + } = &result[0].node else { - panic!("Expected variabledef: {:?}", statements[0].node) + panic!("Expected variabledef: {:?}", result[0].node) }; assert_eq!(expr.ty, Some(Name::from("Int"))); diff --git a/src/parse/block.rs b/src/parse/block.rs index 47bf7f6a..143d0086 100644 --- a/src/parse/block.rs +++ b/src/parse/block.rs @@ -1,11 +1,10 @@ use crate::parse::ast::Node; use crate::parse::ast::AST; use crate::parse::class::{parse_class, parse_type_def}; -use crate::parse::collection::{parse_list_partial, parse_set_or_dict_partial}; use crate::parse::expr_or_stmt::parse_expr_or_stmt; use crate::parse::expression::is_start_expression; use crate::parse::iterator::LexIterator; -use crate::parse::lex::token::{Lex, Token}; +use crate::parse::lex::token::Token; use crate::parse::result::{custom, ParseResult}; use crate::parse::statement::{is_start_statement, parse_import}; @@ -50,55 +49,31 @@ pub fn parse_statements(it: &mut LexIterator) -> ParseResult> { } /// Similar to parse code block, but used in situations where a list is also allowed (when parsing an expression). -pub fn parse_code_block_or_list(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("block block or list")?; +pub fn parse_code_block(it: &mut LexIterator) -> ParseResult { + let start = it.start_pos("code block")?; it.eat_while(&Token::NL); - it.eat(&Token::LSBrack, "block block or list")?; - let statements = it.parse_vec(&parse_statements, "block block", start)?; - let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); + it.eat(&Token::Do, "code block")?; + let statements = it.parse_vec(&parse_statements, "code block", start)?; - if statements.len() == 1 && it.peek_if(&|lex: &Lex| lex.token == Token::Comma) { - parse_list_partial(start, statements.first().unwrap(), it) - } else { - it.eat(&Token::RSBrack, "block block or list")?; - Ok(Box::from(AST::new( - start.union(end), - Node::Block { statements }, - ))) - } + let end = it.eat(&Token::End, "code block")?; + Ok(Box::from(AST::new( + start.union(end), + Node::Block { statements }, + ))) } pub fn parse_code_set(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("block set")?; + let start = it.start_pos("code set")?; it.eat_while(&Token::NL); - it.eat(&Token::LCBrack, "block set")?; - let statements = it.parse_vec(&parse_statements, "block set", start)?; + it.eat(&Token::Where, "code set")?; + let statements = it.parse_vec(&parse_statements, "code set", start)?; let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); - it.eat(&Token::RCBrack, "block set")?; + it.eat(&Token::End, "code set")?; Ok(Box::from(AST::new( start.union(end), Node::Block { statements }, ))) } - -pub fn parse_code_set_or_set(it: &mut LexIterator) -> ParseResult { - let start = it.start_pos("block set or set")?; - it.eat_while(&Token::NL); - - it.eat(&Token::LCBrack, "block set or set")?; - let statements = it.parse_vec(&parse_statements, "block set", start)?; - let end = statements.last().cloned().map_or(start, |stmt| stmt.pos); - - if statements.len() == 1 && it.peek_if(&|lex: &Lex| lex.token == Token::Comma) { - parse_set_or_dict_partial(start, statements.first().unwrap(), it) - } else { - it.eat(&Token::RCBrack, "block set or set")?; - Ok(Box::from(AST::new( - start.union(end), - Node::Block { statements }, - ))) - } -} diff --git a/src/parse/control_flow_stmt.rs b/src/parse/control_flow_stmt.rs index 7c9679fb..a55fad0c 100644 --- a/src/parse/control_flow_stmt.rs +++ b/src/parse/control_flow_stmt.rs @@ -238,7 +238,7 @@ mod test { #[test] fn if_with_block_verify() { - let source = String::from("if a then\n[c\nd]"); + let source = String::from("if a then\ndo\nc\nd\nend"); let ast: AST = source.parse().unwrap(); let (cond, then, el) = match &ast.node { diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index f113e7e6..efa5cf14 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -1,5 +1,5 @@ use crate::parse::ast::{Node, AST}; -use crate::parse::block::{parse_code_block_or_list, parse_code_set_or_set}; +use crate::parse::block::{parse_code_block, parse_code_set}; use crate::parse::control_flow_expr::parse_match_cases; use crate::parse::iterator::LexIterator; use crate::parse::lex::token::{Lex, Token}; @@ -11,8 +11,8 @@ use crate::parse::statement::{is_start_statement, parse_reassignment}; pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { let expr_or_stmt = it.peek_or_err( &|it, lex| match &lex.token { - Token::LSBrack => it.parse(&parse_code_block_or_list, "expression", lex.pos), - Token::LCBrack => it.parse(&parse_code_set_or_set, "statement", lex.pos), + Token::Where => it.parse(&parse_code_set, "statement", lex.pos), + Token::Do => it.parse(&parse_code_block, "statement", lex.pos), _ if is_start_statement(lex) => parse_statement(it), _ => parse_expression(it), }, diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index e59e1b6b..be84a0a7 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -113,6 +113,9 @@ pub enum Token { Question, Pass, + + Where, + End, } /// Name structure, used to give a more descriptive name of a token. @@ -298,6 +301,8 @@ impl fmt::Display for Token { Token::When => write!(f, "when"), Token::Pass => write!(f, "pass"), + Token::Where => write!(f, "where"), + Token::End => write!(f, "end"), } } } diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index 0f8040f4..100febc6 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -265,6 +265,9 @@ fn as_op_or_id(string: String) -> Token { "when" => Token::When, "pass" => Token::Pass, + "where" => Token::Where, + "end" => Token::End, + _ => Token::Id(string), } } diff --git a/src/parse/ty.rs b/src/parse/ty.rs index 68cc6b52..33283d45 100644 --- a/src/parse/ty.rs +++ b/src/parse/ty.rs @@ -15,9 +15,9 @@ pub fn parse_id(it: &mut LexIterator) -> ParseResult { let end = it.eat(&Token::Id(id.clone()), "identifier")?; Ok(Box::from(AST::new(end, Node::Id { lit: id.clone() }))) } - Token::LSBrack => { + Token::LRBrack => { let mut elements = vec![]; - let start = it.eat(&Token::LSBrack, "identifier tuple")?; + let start = it.eat(&Token::LRBrack, "identifier tuple")?; it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { elements.push(*it.parse(&parse_expr_no_type, "identifier", start)?); it.eat_if(&Token::Comma); @@ -28,7 +28,7 @@ pub fn parse_id(it: &mut LexIterator) -> ParseResult { Ok(Box::from(AST::new(end, Node::Tuple { elements }))) } _ => Err(Box::from(expected_one_of( - &[Token::Id(String::new()), Token::LSBrack], + &[Token::Id(String::new()), Token::LRBrack], lex, "identifier", ))), @@ -77,7 +77,7 @@ pub fn parse_type(it: &mut LexIterator) -> ParseResult { let node = Node::Type { id, generics }; Ok(Box::from(AST::new(start.union(end), node))) } - Token::LSBrack => it.parse(&parse_type_tuple, "type", start), + Token::LRBrack => it.parse(&parse_type_tuple, "type", start), Token::LCBrack => it.parse(&parse_type_set, "type", start), _ => Err(Box::from(expected_one_of( &[Token::Id(String::new()), Token::LSBrack, Token::LCBrack], @@ -172,7 +172,7 @@ pub fn parse_type_set(it: &mut LexIterator) -> ParseResult { pub fn parse_type_tuple(it: &mut LexIterator) -> ParseResult { let start = it.start_pos("type tuple")?; - it.eat(&Token::LSBrack, "type tuple")?; + it.eat(&Token::LRBrack, "type tuple")?; let mut types = vec![]; it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { diff --git a/tests/resource/invalid/syntax/assign_and_while.mamba b/tests/resource/invalid/syntax/assign_and_while.mamba index 50bc50f5..aecd8ff9 100644 --- a/tests/resource/invalid/syntax/assign_and_while.mamba +++ b/tests/resource/invalid/syntax/assign_and_while.mamba @@ -4,9 +4,9 @@ def c := 4.2E2 def illegal := e * def other def d := 0 -while e < f, b do [ +while e < f, b do do def g := h * i j := k mod 2 + "300" -] +end while l < m do print n diff --git a/tests/resource/invalid/syntax/top_lvl_class_access.mamba b/tests/resource/invalid/syntax/top_lvl_class_access.mamba index ad33dce1..af32f94d 100644 --- a/tests/resource/invalid/syntax/top_lvl_class_access.mamba +++ b/tests/resource/invalid/syntax/top_lvl_class_access.mamba @@ -1,3 +1,3 @@ -class X := { +class X := where def y.y: Int := 10 -} +end diff --git a/tests/resource/invalid/type/class/access_unassigned_class_var.mamba b/tests/resource/invalid/type/class/access_unassigned_class_var.mamba index 6e248d7b..f0ea3d84 100644 --- a/tests/resource/invalid/type/class/access_unassigned_class_var.mamba +++ b/tests/resource/invalid/type/class/access_unassigned_class_var.mamba @@ -1,6 +1,6 @@ -class X := { +class X := where def z: Int def __init__(self) := self.z -} +end diff --git a/tests/resource/invalid/type/class/access_unassigned_field.mamba b/tests/resource/invalid/type/class/access_unassigned_field.mamba index 2b15d204..19a8b1bf 100644 --- a/tests/resource/invalid/type/class/access_unassigned_field.mamba +++ b/tests/resource/invalid/type/class/access_unassigned_field.mamba @@ -1,6 +1,6 @@ -class X := { +class X := where def z: Int def __init__(self) := def z: Int := self.z + 5 -} +end diff --git a/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba b/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba index 51a8786a..49304fe9 100644 --- a/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba +++ b/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba @@ -1,10 +1,10 @@ class Y(x: Int) -class X := { +class X := where def y: Y def __init__(a: Int) := self.y = Y(a) -} +end def x := X(10) diff --git a/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba b/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba index 34982df1..2ec39ece 100644 --- a/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba +++ b/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba @@ -1,6 +1,6 @@ -class X := { +class X := where def z: Int def __init__(self, x: Int) := self.y := x -} +end diff --git a/tests/resource/invalid/type/class/compound_field.mamba b/tests/resource/invalid/type/class/compound_field.mamba index a6044713..871299d5 100644 --- a/tests/resource/invalid/type/class/compound_field.mamba +++ b/tests/resource/invalid/type/class/compound_field.mamba @@ -1,6 +1,6 @@ -class MyClass := { +class MyClass := where def a: Int := 10 def b: Int := self.a + 1 -} +end def my_class := MyClass() diff --git a/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba b/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba index d92915ee..885d92b9 100644 --- a/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba +++ b/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba @@ -1,6 +1,6 @@ -class X := { +class X := where def (a, b): (Int, Int) def __init__(self, x: Int) := self.a = x -} +end diff --git a/tests/resource/invalid/type/class/parent_is_class.mamba b/tests/resource/invalid/type/class/parent_is_class.mamba index 2cf91998..3b82db2c 100644 --- a/tests/resource/invalid/type/class/parent_is_class.mamba +++ b/tests/resource/invalid/type/class/parent_is_class.mamba @@ -1,5 +1,5 @@ class MyType[A: MyGeneric, C](def super_field: Str) -class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") := { +class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") := where def some_function(self) -> Int := 10 -} +end diff --git a/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba b/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba index fa395b22..ed7ef2ce 100644 --- a/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba +++ b/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba @@ -1,6 +1,6 @@ -class X := { +class X := where def z: Int def __init__(self, x: Int) := self.z += 10 -} +end diff --git a/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba b/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba index 9bc01129..e98a1618 100644 --- a/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba +++ b/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba @@ -1,7 +1,7 @@ -class X := { +class X := where def y: Int def z: Int def __init__(self, x: Int) := self.y := x -} +end diff --git a/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba b/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba index cfb32c7c..f9413943 100644 --- a/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba +++ b/tests/resource/invalid/type/control_flow/access_match_arms_variable.mamba @@ -1,5 +1,5 @@ -match 10 { +match 10 where n => print("10") -} +end print(n) diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba index 5f74ac09..04dfc3ef 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba @@ -1,7 +1,7 @@ -class MyClass := { +class MyClass := where def x: Int def __init__(self) := if False then print("something") else self.x := 10 -} +end diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba index 4990514a..6917a6e2 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba @@ -1,12 +1,12 @@ -class MyClass := { +class MyClass := where def x: Int def __init__(self) := - match 10 { + match 10 where 2 => self.x := 2 3 => self.x := 3 4 => print("o") 5 => print("o") _ => print("p") - } -} + end +end diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba index 7e050719..9f70ccc5 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba @@ -1,4 +1,4 @@ -class MyClass := { +class MyClass := where def x: Int def __init__(self) := @@ -6,4 +6,4 @@ class MyClass := { self.x := 10 else print("something") -} +end diff --git a/tests/resource/invalid/type/control_flow/different_type_shadow.mamba b/tests/resource/invalid/type/control_flow/different_type_shadow.mamba index 4b3e4e19..63b08e70 100644 --- a/tests/resource/invalid/type/control_flow/different_type_shadow.mamba +++ b/tests/resource/invalid/type/control_flow/different_type_shadow.mamba @@ -1,4 +1,4 @@ match 10 { n => print(x) -} +end diff --git a/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba b/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba index 4b3e4e19..63b08e70 100644 --- a/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba +++ b/tests/resource/invalid/type/control_flow/undefined_var_in_match_arm.mamba @@ -1,4 +1,4 @@ match 10 { n => print(x) -} +end diff --git a/tests/resource/invalid/type/definition/assign_to_function_call.mamba b/tests/resource/invalid/type/definition/assign_to_function_call.mamba index 9f55aee0..7ed90768 100644 --- a/tests/resource/invalid/type/definition/assign_to_function_call.mamba +++ b/tests/resource/invalid/type/definition/assign_to_function_call.mamba @@ -1,10 +1,10 @@ class A := def c: C -class C := { +class C := where def fin my_field: Int := 10 def my_field_accessor(self) -> Int := self.my_field -} +end def a := A() a.c.my_field_accessor() := 20 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba index f15c6cc9..1b38a495 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba @@ -1,10 +1,10 @@ class A := def c: C -class C := { +class C := where def my_class: D := D() def my_field_accessor(self) -> D := self.my_class -} +end class D def fin my_field: Int := 10 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba index 14f32807..d0256e8f 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba @@ -1,10 +1,10 @@ class A := def c: C -class C := { +class C := where def my_class: D := D() def my_field_accessor(self) -> D := self.my_class -} +end class D := def fin my_field: Int := 10 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba index 6a964934..dd301297 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba @@ -1,10 +1,10 @@ class A := def c: C -class C := { +class C := where def fin my_class: D := D() def my_field_accessor(self) -> D := self.my_class -} +end class D := def my_field: Int := 10 diff --git a/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba b/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba index 2ea7b630..c2410974 100644 --- a/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba +++ b/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba @@ -1,4 +1,4 @@ -class X := { +class X := where def some_higher_order(self, fun: Int -> Int) -> Int? := return fun(10) def fancy(self) -> Int := return self.some_higher_order(\x: Int := x * 2) -} +end diff --git a/tests/resource/invalid/type/error/handle_only_id.mamba b/tests/resource/invalid/type/error/handle_only_id.mamba index 49035cbb..7a95c268 100644 --- a/tests/resource/invalid/type/error/handle_only_id.mamba +++ b/tests/resource/invalid/type/error/handle_only_id.mamba @@ -4,12 +4,12 @@ class MyErr2(msg: String): Exception(msg) def f(x: Int) -> Int ! { MyErr1, MyErr2 } := x def a := f(10) ! { - MyErr1 => [ + MyErr1 => do print("Something went wrong") -1 ] - MyErr2 => [ + MyErr2 => do print("Something else went wrong") -2 ] -} +end diff --git a/tests/resource/invalid/type/error/unhandled_exception.mamba b/tests/resource/invalid/type/error/unhandled_exception.mamba index a69ce2e3..1e7f3672 100644 --- a/tests/resource/invalid/type/error/unhandled_exception.mamba +++ b/tests/resource/invalid/type/error/unhandled_exception.mamba @@ -5,10 +5,10 @@ def f(x: Int) -> Int ! { MyException1, MyException2 } := match x { 0 => 20 1 => ! MyException1() 2 => ! MyException2() -} +end def g() -> Int := f(2) ! { err: MyException1 => print("a") -} +end g() diff --git a/tests/resource/invalid/type/function/call_mut_function.mamba b/tests/resource/invalid/type/function/call_mut_function.mamba index 6f532a52..7c694af2 100644 --- a/tests/resource/invalid/type/function/call_mut_function.mamba +++ b/tests/resource/invalid/type/function/call_mut_function.mamba @@ -1,9 +1,9 @@ -class MyClass := { +class MyClass := where def a: Int := 20 # not allowed, self is fin! def f(fin self, x: Int) -> Int := self.a := x + 20 -} +end def fin my_class := MyClass() my_class.f(10) diff --git a/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba b/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba index ed3df670..3c94d4ae 100644 --- a/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba +++ b/tests/resource/invalid/type/function/call_mut_function_on_non_mut.mamba @@ -1,7 +1,7 @@ -class MyClass := { +class MyClass := where def a: Int := 20 def f(self, x: Int) -> Int := self.a := x + 20 -} +end def fin my_class := MyClass() # should not be allowed, self is fin! diff --git a/tests/resource/valid/class/assign_to_nullable_field.mamba b/tests/resource/valid/class/assign_to_nullable_field.mamba index d3a5e304..c6601f5f 100644 --- a/tests/resource/valid/class/assign_to_nullable_field.mamba +++ b/tests/resource/valid/class/assign_to_nullable_field.mamba @@ -1,6 +1,6 @@ -class MyServer() := { +class MyServer() := where def _message: Str? := None def send(self, x: Str) := self._message := x -} +end diff --git a/tests/resource/valid/class/assign_types_double_nested.mamba b/tests/resource/valid/class/assign_types_double_nested.mamba index fb5e9674..97143ac6 100644 --- a/tests/resource/valid/class/assign_types_double_nested.mamba +++ b/tests/resource/valid/class/assign_types_double_nested.mamba @@ -1,8 +1,8 @@ class Y(def a: Float) -class X(a: Float) := { +class X(a: Float) := where def y: Y := Y(a) -} +end def x := X(10) diff --git a/tests/resource/valid/class/class_super_one_line_init.mamba b/tests/resource/valid/class/class_super_one_line_init.mamba index 0f955b1a..91ea7557 100644 --- a/tests/resource/valid/class/class_super_one_line_init.mamba +++ b/tests/resource/valid/class/class_super_one_line_init.mamba @@ -1,6 +1,6 @@ trait MyType -class MyClass2(other_field: Int, z: Int) := { +class MyClass2(other_field: Int, z: Int) := where def fin z_modified: Str := "asdf" def other_field: Int := z + other_field -} +end diff --git a/tests/resource/valid/class/fun_with_body_in_interface.mamba b/tests/resource/valid/class/fun_with_body_in_interface.mamba index 0fd577e5..b453d902 100644 --- a/tests/resource/valid/class/fun_with_body_in_interface.mamba +++ b/tests/resource/valid/class/fun_with_body_in_interface.mamba @@ -1,4 +1,4 @@ -trait MyType := { +trait MyType := when def abstract_fun(my_arg: Int) -> Str def concrete_fun(x: Int) -> Int := return x + 10 -} +end diff --git a/tests/resource/valid/class/multiple_parent.mamba b/tests/resource/valid/class/multiple_parent.mamba index ef7613df..eede1f67 100644 --- a/tests/resource/valid/class/multiple_parent.mamba +++ b/tests/resource/valid/class/multiple_parent.mamba @@ -1,6 +1,6 @@ trait MyType trait MyType2 -class MyClass1: { MyType, MyType2 } := { +class MyClass1: { MyType, MyType2 } := where def other: Int -} +end diff --git a/tests/resource/valid/class/parent.mamba b/tests/resource/valid/class/parent.mamba index 9be95f5e..daaf008f 100644 --- a/tests/resource/valid/class/parent.mamba +++ b/tests/resource/valid/class/parent.mamba @@ -1,11 +1,11 @@ -trait MyType := { +trait MyType := when def fun_a(self) def factorial(self, x: Int) -> Int -} +end -class MyClass1: MyType := { +class MyClass1: MyType := where def other: Int def fun_a(self) := print("hello) def factorial(self, x: Int) -> Int := x * 1 -} +end diff --git a/tests/resource/valid/class/print_types_double_nested.mamba b/tests/resource/valid/class/print_types_double_nested.mamba index ad682e7c..42fa3b47 100644 --- a/tests/resource/valid/class/print_types_double_nested.mamba +++ b/tests/resource/valid/class/print_types_double_nested.mamba @@ -1,8 +1,8 @@ class Y(def a: Float) -class X(a: Float) := { +class X(a: Float) := where def y: Y := Y(a) -} +end def x := X(10) diff --git a/tests/resource/valid/class/same_var_different_type.mamba b/tests/resource/valid/class/same_var_different_type.mamba index 913a689b..2fac47ff 100644 --- a/tests/resource/valid/class/same_var_different_type.mamba +++ b/tests/resource/valid/class/same_var_different_type.mamba @@ -1,9 +1,9 @@ -class C1 := { +class C1 := where def a: Str := "str" def f(self) -> Str := self.a -} +end -class C2 := { +class C2 := where def a: Int := 10 def f(self) -> Str := self.a -} +end diff --git a/tests/resource/valid/class/shadow.mamba b/tests/resource/valid/class/shadow.mamba index f6cc68a0..32403eff 100644 --- a/tests/resource/valid/class/shadow.mamba +++ b/tests/resource/valid/class/shadow.mamba @@ -13,7 +13,7 @@ x.f1() def x := MyClass2() x.f2() -class MyClass := { +class MyClass := where def x: MyClass3 := MyClass3() def g() := @@ -21,4 +21,4 @@ class MyClass := { def f(x: MyClass4) := x.f4() -} +end diff --git a/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba b/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba index b3d8d840..b7c1e71c 100644 --- a/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba +++ b/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba @@ -1,5 +1,5 @@ -class X := { +class X := where def y: Int? # typically, statements are evaluated in order, but Mamba makes no guarantees about this behaviour! print("No assignments here!") -} +end diff --git a/tests/resource/valid/class/types.mamba b/tests/resource/valid/class/types.mamba index 4e965d21..7bae8fdd 100644 --- a/tests/resource/valid/class/types.mamba +++ b/tests/resource/valid/class/types.mamba @@ -1,21 +1,23 @@ class MyGeneric: Str -type SomeState: MyClass when self.private_field > 2 -type OtherState: MyClass when { +type SomeState: MyClass where self.private_field > 2 +type OtherState: MyClass when self.private_field > 10 self.private_field < 200 self.required_field < 50 -} +end -trait SuperInterface := +trait SuperInterface := where def bar: Int +end -trait MyInterface: SuperInterface := +trait MyInterface: SuperInterface := where def required_field: Int def higher_order(self) -> Int +end # some class -class MyClass(def my_field: Int, other_field: Str := "Hello"): MyInterface +class MyClass(def my_field: Int, other_field: Str := "Hello"): MyInterface := where def required_field: Int := 100 def private_field: Int := 20 @@ -24,3 +26,4 @@ class MyClass(def my_field: Int, other_field: Str := "Hello"): MyInterface def some_higher_order(self, fun: Int -> Int) -> Int := return fun(self.my_field) def higher_order(self) -> Int := return self.some_higher_order(\x: Int := x * 2) +end diff --git a/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba b/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba index c47441db..c3d163ac 100644 --- a/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba +++ b/tests/resource/valid/class/unassigned_tuple_second_nullable.mamba @@ -1,3 +1,3 @@ -class X := { +class X := where def (y, z): (Int, Int?) := (10, None) -} +end diff --git a/tests/resource/valid/control_flow/assign_if.mamba b/tests/resource/valid/control_flow/assign_if.mamba index 3c1d32d2..e4ff0e99 100644 --- a/tests/resource/valid/control_flow/assign_if.mamba +++ b/tests/resource/valid/control_flow/assign_if.mamba @@ -1,6 +1,7 @@ -def my_var := if True then +def my_var := if True then do print("then") 10 -else +end else do print("else") 20 +end diff --git a/tests/resource/valid/control_flow/assign_match.mamba b/tests/resource/valid/control_flow/assign_match.mamba index a33c142d..dd5bf694 100644 --- a/tests/resource/valid/control_flow/assign_match.mamba +++ b/tests/resource/valid/control_flow/assign_match.mamba @@ -1,4 +1,5 @@ def c := True -def my_var := match c +def my_var := match c where True => 10 False => 20 +end diff --git a/tests/resource/valid/control_flow/double_assign_if.mamba b/tests/resource/valid/control_flow/double_assign_if.mamba index 937f3610..3b4d1a69 100644 --- a/tests/resource/valid/control_flow/double_assign_if.mamba +++ b/tests/resource/valid/control_flow/double_assign_if.mamba @@ -1,6 +1,6 @@ def other := 20 -def my_variable := if True then +def my_variable := if True then do other := 30 20 -else +end else 10 diff --git a/tests/resource/valid/control_flow/for_statements.mamba b/tests/resource/valid/control_flow/for_statements.mamba index fe33550a..0ef92ea3 100644 --- a/tests/resource/valid/control_flow/for_statements.mamba +++ b/tests/resource/valid/control_flow/for_statements.mamba @@ -1,22 +1,26 @@ def b := {1, 2} -for b in b do +for b in b do do print(b + 5) def new := b + 1 new := 30 print(new) +end def e := {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} -for d in e do +for d in e do do print(d) print(d - 1) print(d + 1) +end -for i in 0 .. 34 do +for i in 0 .. 34 do do print(i) +end -for i in 0 ..= 345 do +for i in 0 ..= 345 do do print(i) +end def a := 1 def b := 112 diff --git a/tests/resource/valid/definition/assign_to_inner_mut.mamba b/tests/resource/valid/definition/assign_to_inner_mut.mamba index d0658e45..ae4fe177 100644 --- a/tests/resource/valid/definition/assign_to_inner_mut.mamba +++ b/tests/resource/valid/definition/assign_to_inner_mut.mamba @@ -1,10 +1,10 @@ class A := def c: C -class C := { +class C := where def my_class: D := D() def my_field_accessor(self) -> D := self.my_class -} +end class D := def my_field: Int := 10 diff --git a/tests/resource/valid/definition/assign_with_match.mamba b/tests/resource/valid/definition/assign_with_match.mamba index fcbef27c..97e8b0f7 100644 --- a/tests/resource/valid/definition/assign_with_match.mamba +++ b/tests/resource/valid/definition/assign_with_match.mamba @@ -2,4 +2,4 @@ def a := match 40 { 2 => 3 4 => 30 _ => 300 -} +end diff --git a/tests/resource/valid/definition/assign_with_match_different_types.mamba b/tests/resource/valid/definition/assign_with_match_different_types.mamba index 7ba92041..e793cd64 100644 --- a/tests/resource/valid/definition/assign_with_match_different_types.mamba +++ b/tests/resource/valid/definition/assign_with_match_different_types.mamba @@ -6,4 +6,4 @@ def a:= match 40 { 2 => MyClass() 4 => MyClass1() _ => MyClass2() -} +end diff --git a/tests/resource/valid/definition/assign_with_match_type_annotation.mamba b/tests/resource/valid/definition/assign_with_match_type_annotation.mamba index 6b93275f..7440b3ae 100644 --- a/tests/resource/valid/definition/assign_with_match_type_annotation.mamba +++ b/tests/resource/valid/definition/assign_with_match_type_annotation.mamba @@ -2,4 +2,4 @@ def a: Int := match 40 { 2 => 3 4 => 30 _ => 300 -} +end diff --git a/tests/resource/valid/definition/assign_with_try_except.mamba b/tests/resource/valid/definition/assign_with_try_except.mamba index 55a2859e..bdf94f5b 100644 --- a/tests/resource/valid/definition/assign_with_try_except.mamba +++ b/tests/resource/valid/definition/assign_with_try_except.mamba @@ -3,4 +3,4 @@ def g() -> Int ! Exception := def a := g() ! { err: Exception => 10 -} +end diff --git a/tests/resource/valid/definition/function_ret_super_in_class.mamba b/tests/resource/valid/definition/function_ret_super_in_class.mamba index 660a24d3..7eb28cf1 100644 --- a/tests/resource/valid/definition/function_ret_super_in_class.mamba +++ b/tests/resource/valid/definition/function_ret_super_in_class.mamba @@ -1,7 +1,7 @@ -class X := { +class X := where def some_higher_order(self, fun: Int -> Int) -> Int := fun(10) def fancy(self) -> Int? := self.some_higher_order(\x: Int := x * 2) -} +end def x := X() x.fancy() diff --git a/tests/resource/valid/definition/function_with_match.mamba b/tests/resource/valid/definition/function_with_match.mamba index 9b8fb183..12ba85d1 100644 --- a/tests/resource/valid/definition/function_with_match.mamba +++ b/tests/resource/valid/definition/function_with_match.mamba @@ -3,5 +3,5 @@ def f(x: Int) -> Int := 2 => 3 4 => 30 _ => 300 - } + end \ No newline at end of file diff --git a/tests/resource/valid/error/exception_in_fun.mamba b/tests/resource/valid/error/exception_in_fun.mamba index 4084511d..981b102c 100644 --- a/tests/resource/valid/error/exception_in_fun.mamba +++ b/tests/resource/valid/error/exception_in_fun.mamba @@ -1,6 +1,6 @@ def g() -> Int ! Exception := ! Exception("A") -def f(x: Int) -> Int := g() ! { +def f(x: Int) -> Int := g() ! where err: Exception => x + 10 -} +end diff --git a/tests/resource/valid/error/exception_in_fun_super.mamba b/tests/resource/valid/error/exception_in_fun_super.mamba index 7d4db65c..24633859 100644 --- a/tests/resource/valid/error/exception_in_fun_super.mamba +++ b/tests/resource/valid/error/exception_in_fun_super.mamba @@ -2,6 +2,6 @@ class MyException(msg: Str): Exception(msg) def g() -> Int ! MyException := ! MyException("A") -def f(x: Int) -> Int := g() ! { +def f(x: Int) -> Int := g() ! where err: Exception => x + 10 -} +end diff --git a/tests/resource/valid/error/handle.mamba b/tests/resource/valid/error/handle.mamba index 81a0b495..2f160a69 100644 --- a/tests/resource/valid/error/handle.mamba +++ b/tests/resource/valid/error/handle.mamba @@ -10,11 +10,13 @@ def f(x: Int) -> Int ! { MyErr1, MyErr2 } := else return x + 2 -def a := f(10) ! { - err: MyErr1 => +def a := f(10) ! where + err: MyErr1 => do print("Something went wrong") -1 - err: MyErr2 => + end + err: MyErr2 => do print("Something else went wrong") -2 -} + end +end diff --git a/tests/resource/valid/error/handle_only_id.mamba b/tests/resource/valid/error/handle_only_id.mamba index 433e76c7..82ed49f4 100644 --- a/tests/resource/valid/error/handle_only_id.mamba +++ b/tests/resource/valid/error/handle_only_id.mamba @@ -3,11 +3,13 @@ class MyErr2(msg: Str): Exception(msg) def f(x: Int) -> Int ! { MyErr1, MyErr2 } := x -def a := f(10) ! { - _ : MyErr1 => [ +def a := f(10) ! where + _ : MyErr1 => do print("Something went wrong") - -1] - _: MyErr2 => [ + -1 + end + _: MyErr2 => do print("Something else went wrong") - -2] -} + -2 + end +end diff --git a/tests/resource/valid/error/handle_var_usable_after.mamba b/tests/resource/valid/error/handle_var_usable_after.mamba index b18f4a6d..fcaf0264 100644 --- a/tests/resource/valid/error/handle_var_usable_after.mamba +++ b/tests/resource/valid/error/handle_var_usable_after.mamba @@ -2,10 +2,11 @@ class MyErr(def message: Str): Exception def function_may_throw_err() -> Int ! MyErr := 10 -def a := function_may_throw_err() ! { - err: MyErr => [ +def a := function_may_throw_err() ! where + err: MyErr => do print("We have a problem: {err.message}.") - 20] -} + 20 + end +end print("a has value {a}.") diff --git a/tests/resource/valid/error/nested_exception.mamba b/tests/resource/valid/error/nested_exception.mamba index 4385efe5..ab2b8cee 100644 --- a/tests/resource/valid/error/nested_exception.mamba +++ b/tests/resource/valid/error/nested_exception.mamba @@ -2,13 +2,13 @@ class MyException1(msg: Str): Exception(msg) class MyException2(msg: Str): Exception(msg) def f(x: Int) -> Int ! { MyException1, MyException2 } := - match x { + match x where 0 => 20 1 => ! MyException1() 2 => ! MyException2() - } + end def g() -> Int ! MyException2 := - f(2) ! { err: MyException1 => 10 } + f(2) ! where err: MyException1 => 10 end g() diff --git a/tests/resource/valid/function/definition.mamba b/tests/resource/valid/function/definition.mamba index e56c20c2..429a4bb1 100644 --- a/tests/resource/valid/function/definition.mamba +++ b/tests/resource/valid/function/definition.mamba @@ -1,11 +1,11 @@ -def fun_a() -> Int? := [ +def fun_a() -> Int? := do print(11) if True and True then print("hello") if False or True then print("world") def a := None ? 11 if True then return 10 else return None -] +end def fun_b(b: Int) := print(b) @@ -19,12 +19,12 @@ def fun_e(m: Int, o: (Str, Str), r: (Int, (Str, Str)) -> Int) -> Int := return r def fun_v(y: Str, ab: Str -> Str -> Bool) -> Str -> Bool := return ab(y) -class MyClass(def a: Int, def b: Int) - def some_function(self, c: Int) -> Int := [ +class MyClass(def a: Int, def b: Int) := where + def some_function(self, c: Int) -> Int := do def d := 20 d := 10 + 30 return c + 20 + d - ] + end def +(self, other: MyClass) -> MyClass := return MyClass(self.a + self.b + other.some_function(self.a), self.b) def -(self, other: MyClass) -> MyClass := return self + other @@ -39,6 +39,7 @@ class MyClass(def a: Int, def b: Int) def sqrt(self) -> MyClass := return MyClass(self.a // self.b, self.a // self.b) def mod(self, other: MyClass) -> MyClass := return MyClass(self.a mod self.b, self.b) +end def factorial(x: Int) -> Int := x * factorial(x - 1) diff --git a/tests/resource/valid/function/match_function.mamba b/tests/resource/valid/function/match_function.mamba index 83a4fd3e..4c261df2 100644 --- a/tests/resource/valid/function/match_function.mamba +++ b/tests/resource/valid/function/match_function.mamba @@ -1,5 +1,5 @@ -def f(x: Int) -> Str := match x { +def f(x: Int) -> Str := match x where 1 => "One" 2 => "Two" _ => "Three" -} +end diff --git a/tests/resource/valid/function/return_last_expression.mamba b/tests/resource/valid/function/return_last_expression.mamba index d5e52a45..2269a27d 100644 --- a/tests/resource/valid/function/return_last_expression.mamba +++ b/tests/resource/valid/function/return_last_expression.mamba @@ -1,6 +1,6 @@ -def my_fun() -> Str := [ +def my_fun() -> Str := do print("a statement") "a" -] +end print(my_fun()) diff --git a/tests/resource/valid/operation/equality_different_types.mamba b/tests/resource/valid/operation/equality_different_types.mamba index 12461cf7..f679e1ad 100644 --- a/tests/resource/valid/operation/equality_different_types.mamba +++ b/tests/resource/valid/operation/equality_different_types.mamba @@ -1,7 +1,7 @@ -class MyClass := { +class MyClass := where def __eq__(self, other: MyOtherClass) -> Bool := True def __ne__(self, other: MyOtherClass) -> Bool := False -} +end class MyOtherClass diff --git a/tests/resource/valid/readme_example/builtin_trait.mamba b/tests/resource/valid/readme_example/builtin_trait.mamba index c7f37b3b..f1e5900e 100644 --- a/tests/resource/valid/readme_example/builtin_trait.mamba +++ b/tests/resource/valid/readme_example/builtin_trait.mamba @@ -5,9 +5,9 @@ meta trait Measurable: Add, Sub, Eq, Comparable # Built in to the standard library # The idea is that this allows performing arithmetic not just at runtime but at compile-time. -def Measurable for Int { +def Measurable for Int where def const less_than(self, other: Int) -> Bool := self < other def const unary_sub(self) -> Int := -other def const add(self, other: Int) -> Int := self + other def const equal(self, other: Int) -> Bool := self = other -} +end diff --git a/tests/resource/valid/readme_example/class.mamba b/tests/resource/valid/readme_example/class.mamba index 310e376e..72173589 100644 --- a/tests/resource/valid/readme_example/class.mamba +++ b/tests/resource/valid/readme_example/class.mamba @@ -1,6 +1,6 @@ class MatrixErr(def message: Str): Exception(message) -class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { +class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := where # Accessor for matrix contents def contents(fin self) -> List[Int] := [a, b, c, d] @@ -10,18 +10,18 @@ class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := { # Determinant recomputation (pure function) def pure determinant(fin self) -> Int := a * d - b * c - def scale(self, factor: Int) := [ + def scale(self, factor: Int) := do self.a := self.a * factor self.b := self.b * factor self.c := self.c * factor self.d := self.d * factor - ] + end # Reset turns this matrix into an 2x2 identity matrix, regardless of the intial value. - def reset(self) := [ + def reset(self) := do self.a := 1 self.b := 0 self.c := 0 self.d := 1 - ] -} + end +end diff --git a/tests/resource/valid/readme_example/class_with_constants.mamba b/tests/resource/valid/readme_example/class_with_constants.mamba index c4f6be44..e69e7b37 100644 --- a/tests/resource/valid/readme_example/class_with_constants.mamba +++ b/tests/resource/valid/readme_example/class_with_constants.mamba @@ -1,20 +1,20 @@ -class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := { +class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := where # ORIGIN_X and ORIGIN_Y are constructor constants — they cannot be changed # current x and y are mutable fields initialized from the constants def x: Int := ORIGIN_X def y: Int := ORIGIN_Y - def move(self, dx: Int, dy: Int) := [ + def move(self, dx: Int, dy: Int) := do self.x := self.x + dx self.y := self.y + dy - ] + end - # Unlike the matrix before, reset resets this point to the value it was when it was instantiated. - def reset(self) := [ + # Unlike the matrix before, reset resets this point to the value it was where it was instantiated. + def reset(self) := do self.x := ORIGIN_X self.y := ORIGIN_Y - ] + end def info(fin self) -> Str := "Currently at ({self.x}, {self.y}), originally from ({ORIGIN_X}, {ORIGIN_Y})" -} +end \ No newline at end of file diff --git a/tests/resource/valid/readme_example/error_handling.mamba b/tests/resource/valid/readme_example/error_handling.mamba index 5b3c7216..f1b76ec0 100644 --- a/tests/resource/valid/readme_example/error_handling.mamba +++ b/tests/resource/valid/readme_example/error_handling.mamba @@ -5,11 +5,11 @@ if m isa InvertibleMatrix then else print("Matrix is singular (not invertible).") -def last_op = m.last_op() ! { - err: MatrixErr(message) => [ - print("Error when getting last op: \"{message}\"") +def last_op = m.last_op() ! where + err: MatrixErr(message) => do + print("Error where getting last op: \"{message}\"") "N/A" # optionally we can also return, but here we assign default value - ] -} + end +end print("Last operation was: {last_op}") diff --git a/tests/resource/valid/readme_example/error_handling_as_expression.mamba b/tests/resource/valid/readme_example/error_handling_as_expression.mamba index baef47ef..eb32a015 100644 --- a/tests/resource/valid/readme_example/error_handling_as_expression.mamba +++ b/tests/resource/valid/readme_example/error_handling_as_expression.mamba @@ -1,12 +1,12 @@ -def a: Int := function_may_throw_err() ! { - err: MyErr => [ +def a: Int := function_may_throw_err() ! where + err: MyErr => do print("We have a problem: {err.message}.") return # we return, halting execution - ] - err: MyOtherErr => [ + end + err: MyOtherErr => do print("We have another problem: {err.message}.") 0 # ... or we assign default value 0 to a - ] -} + end +end print("a has value {a}.") diff --git a/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba b/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba index f1379e94..feed530f 100644 --- a/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba +++ b/tests/resource/valid/readme_example/error_handling_desyntax_sugared.mamba @@ -1,3 +1,3 @@ match m.last_op() { - err: MatrixErr(message) => print("Error when getting last op: \"{message}\"") + err: MatrixErr(message) => print("Error where getting last op: \"{message}\"") } diff --git a/tests/resource/valid/readme_example/error_handling_handle_subset.mamba b/tests/resource/valid/readme_example/error_handling_handle_subset.mamba index 80f9b034..b494c631 100644 --- a/tests/resource/valid/readme_example/error_handling_handle_subset.mamba +++ b/tests/resource/valid/readme_example/error_handling_handle_subset.mamba @@ -1,9 +1,9 @@ -def a: Result[Int, MyErr] := function_may_throw_err() ! { - err: MyOtherErr => [ - print("We have another problem: {err.message}.") - 0 # ... or we assign default value 0 to a - ] -} +def a: Result[Int, MyErr] := function_may_throw_err() ! where + err: MyOtherErr => do + print("We have another problem: {err.message}.") + 0 # ... or we assign default value 0 to a + end +end a = a ! # Result[Int, MyErr] => Int, where if error case, an exception is raised. diff --git a/tests/resource/valid/readme_example/error_handling_recover.mamba b/tests/resource/valid/readme_example/error_handling_recover.mamba index 50e0671a..5a584521 100644 --- a/tests/resource/valid/readme_example/error_handling_recover.mamba +++ b/tests/resource/valid/readme_example/error_handling_recover.mamba @@ -1,6 +1,6 @@ -def a: Result[Int, MyErr] := function_may_throw_err() ! { +def a: Result[Int, MyErr] := function_may_throw_err() ! where err: MyOtherErr => print("We have a problem: {err.message}.") -} recover [ +end recover do print("cleaning up resource") some_cleanup_function() -] +end diff --git a/tests/resource/valid/readme_example/factorial.mamba b/tests/resource/valid/readme_example/factorial.mamba index 124a8bbf..f046511f 100644 --- a/tests/resource/valid/readme_example/factorial.mamba +++ b/tests/resource/valid/readme_example/factorial.mamba @@ -1,8 +1,8 @@ # Factorial of x -def factorial(x: Int) -> Int := match x { +def factorial(x: Int) -> Int := match x where 0 => 1 n => n * factorial(n - 1) -} +end def num := input("Compute factorial: ") if num.is_digit() then [ diff --git a/tests/resource/valid/readme_example/factorial_dynamic.mamba b/tests/resource/valid/readme_example/factorial_dynamic.mamba index bf9f6fee..b1ff387c 100644 --- a/tests/resource/valid/readme_example/factorial_dynamic.mamba +++ b/tests/resource/valid/readme_example/factorial_dynamic.mamba @@ -1,8 +1,8 @@ -def factorial(x: Int) -> Int := match x { +def factorial(x: Int) -> Int := match x where 0 => 1 - n => [ + n => do def ans := 1 for i in 1 ..= n do ans := ans * i ans - ] -} + end +end diff --git a/tests/resource/valid/readme_example/impl_trait.mamba b/tests/resource/valid/readme_example/impl_trait.mamba index 9c18769a..3ae5e576 100644 --- a/tests/resource/valid/readme_example/impl_trait.mamba +++ b/tests/resource/valid/readme_example/impl_trait.mamba @@ -1,9 +1,9 @@ # Measure for int just returns self -def StrictlyDecreases for Int { +def StrictlyDecreases for Int where def meta measure(self) -> Measurable := self -} +end # For string, we as an example use the length of the string (Which is also an integer) -def StrictlyDecreases for Str { +def StrictlyDecreases for Str where def meta measure(self) -> Measurable := self.len() -} +end diff --git a/tests/resource/valid/readme_example/pure_functions.mamba b/tests/resource/valid/readme_example/pure_functions.mamba index 296897f5..27219783 100644 --- a/tests/resource/valid/readme_example/pure_functions.mamba +++ b/tests/resource/valid/readme_example/pure_functions.mamba @@ -2,9 +2,9 @@ def fin taylor := 7 # the sin function is pure, its output depends solely on the input -def pure sin(x: Int) -> Int := [ +def pure sin(x: Int) -> Int := do def ans := x for i in (1 ..= taylor).step(2) do ans := ans + (x ^ (i + 2)) / (factorial (i + 2)) ans -] +end diff --git a/tests/resource/valid/readme_example/total_functions.mamba b/tests/resource/valid/readme_example/total_functions.mamba index 760f2a7c..d28fb955 100644 --- a/tests/resource/valid/readme_example/total_functions.mamba +++ b/tests/resource/valid/readme_example/total_functions.mamba @@ -1,6 +1,6 @@ ## fibbonaci, implemented using recursion and not dynamic programming -def total pure fibbonaci(x: PosInt) -> Int := match x { +def total pure fibbonaci(x: PosInt) -> Int := match x where 0 => 0 1 => 1 n => fibbonaci(n - 1) + fibbonaci(n - 2) -} +end diff --git a/tests/resource/valid/readme_example/trait_fin_meta.mamba b/tests/resource/valid/readme_example/trait_fin_meta.mamba index 19e9e60c..567116a1 100644 --- a/tests/resource/valid/readme_example/trait_fin_meta.mamba +++ b/tests/resource/valid/readme_example/trait_fin_meta.mamba @@ -1,10 +1,10 @@ # if we implement strictly decreasing, we must implement measure # These are non-overridable method which uses this measure -trait def StrictlyDecreases: Measurable { +trait def StrictlyDecreases: Measurable where def fin meta decreases(self, other: Self) -> Bool := self.measure() < other.measure() def fin meta equal(self, other: Self) -> Bool := self.measure() = other.measure() def fin meta subtract(self, other: Self) -> Measurable := self.measure() - other.measure() # this we must implement def meta measure(self) -> Measurable -} +end diff --git a/tests/resource/valid/readme_example/trait_inheritance.mamba b/tests/resource/valid/readme_example/trait_inheritance.mamba index 5e077af5..d268d6ea 100644 --- a/tests/resource/valid/readme_example/trait_inheritance.mamba +++ b/tests/resource/valid/readme_example/trait_inheritance.mamba @@ -1,3 +1,3 @@ -trait Ordered[T]: Equality, Comparable { +trait Ordered[T]: Equality, Comparable where def less_than(self, other: T) -> Bool -} +end diff --git a/tests/resource/valid/readme_example/traits.mamba b/tests/resource/valid/readme_example/traits.mamba index 01e4bbf1..165f2833 100644 --- a/tests/resource/valid/readme_example/traits.mamba +++ b/tests/resource/valid/readme_example/traits.mamba @@ -1,13 +1,13 @@ -trait Iterator[T] := { +trait Iterator[T] := where def has_next(self) -> Bool def next(self) -> T? # syntax sugar for Option[T] -} +end -class RangeIter(def _start: Int, def _end: Int) := { +class RangeIter(def _start: Int, def _end: Int) := where def _current: Int := _start -} +end -def Iterator[Int] for RangeIter := { +def Iterator[Int] for RangeIter := where def has_next(self) -> Bool := self._current < self._stop def next(self) -> Int? := if self.has_next() then [ @@ -15,4 +15,4 @@ def Iterator[Int] for RangeIter := { self._current := self._current + 1 value ] else None -} +end diff --git a/tests/resource/valid/readme_example/type_refinement_in_fun.mamba b/tests/resource/valid/readme_example/type_refinement_in_fun.mamba index 7452b337..51eed2ce 100644 --- a/tests/resource/valid/readme_example/type_refinement_in_fun.mamba +++ b/tests/resource/valid/readme_example/type_refinement_in_fun.mamba @@ -1,9 +1,9 @@ # we list the conditions below, which are a list of boolean expressions. # this first-class language feature desugars to an list of checks which are done at the call site. -# we avoid desugaring to a function (at least when transpiling to Python) as to not clash with existing functions. +# we avoid desugaring to a function (at least where transpiling to Python) as to not clash with existing functions. type PosInt: Int when self >= 0 -def factorial(x: PosInt) -> PosInt := match x { +def factorial(x: PosInt) -> PosInt := match x where 0 => 1 n => n * factorial(n - 1) -} +end diff --git a/tests/resource/valid/readme_example/type_refinement_matrix.mamba b/tests/resource/valid/readme_example/type_refinement_matrix.mamba index 28ed10c3..fd8c4e19 100644 --- a/tests/resource/valid/readme_example/type_refinement_matrix.mamba +++ b/tests/resource/valid/readme_example/type_refinement_matrix.mamba @@ -3,18 +3,19 @@ type InvertibleMatrix: Matrix when self.determinant() != 0.0 class MatrixErr(def message: Str): Exception(message) ## Matrix, which now takes floats as argument -class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := { +class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := where def _last_op: Str? := None def determinant(fin self) -> Float := self.a * self.d - self.b * self.c - def inverse(self: InvertibleMatrix) -> Matrix := + def inverse(self: InvertibleMatrix) -> Matrix := do def det := self.determinant() self._last_op := "inverse" Matrix(self.d / det, -self.b / det, -self.c / det, self.a / det) + end def last_op(fin self) -> Str ! MatrixErr := if self._last_op != None then self._last_op else ! MatrixErr("No operation performed") -} +end diff --git a/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba b/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba index 4e2bde0f..178ed0e3 100644 --- a/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba +++ b/tests/resource/valid/readme_example/type_refinement_on_matrix.mamba @@ -1,10 +1,10 @@ def m := Matrix(1.0, 2.0, 3.0, 4.0) -if m isa InvertibleMatrix then +if m isa InvertibleMatrix then do def m_inv := m.inverse() print("Original matrix: {m}") print("Inverse: {m_inv}") -else +end else print("Matrix is singular (not invertible).") def last_op = m.last_op()! diff --git a/tests/resource/valid/readme_example/type_refinement_set.mamba b/tests/resource/valid/readme_example/type_refinement_set.mamba index 739d8984..4cfebeaf 100644 --- a/tests/resource/valid/readme_example/type_refinement_set.mamba +++ b/tests/resource/valid/readme_example/type_refinement_set.mamba @@ -1,4 +1,4 @@ -type SpecialInt: Int when { +type SpecialInt: Int when self >= 0 self <= 100 or self mod 2 = 0 -} +end From 7aa6c40a5164c2f508e0b6f0e30d07de00c57ef2 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Sat, 28 Mar 2026 10:16:44 +0100 Subject: [PATCH 36/44] fix: allow newlines before expr or statement May want a more structural approach --- src/parse/expr_or_stmt.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index efa5cf14..051585b3 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -9,6 +9,8 @@ use crate::parse::statement::parse_statement; use crate::parse::statement::{is_start_statement, parse_reassignment}; pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { + it.eat_while(&Token::NL); // TODO more structured way to deal with NL + let expr_or_stmt = it.peek_or_err( &|it, lex| match &lex.token { Token::Where => it.parse(&parse_code_set, "statement", lex.pos), From 12c7675ab9d9da851339b886a299044151173561 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Sat, 28 Mar 2026 10:26:56 +0100 Subject: [PATCH 37/44] fix: parse class body --- src/check/context/clss/generic.rs | 14 ++++++++------ src/parse/block.rs | 2 +- src/parse/class.rs | 10 +++++----- src/parse/control_flow_expr.rs | 30 +++++++++--------------------- src/parse/expr_or_stmt.rs | 3 +++ src/parse/result.rs | 6 +++++- src/parse/ty.rs | 5 ++--- 7 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/check/context/clss/generic.rs b/src/check/context/clss/generic.rs index f3fc0d7d..a3c9efb4 100644 --- a/src/check/context/clss/generic.rs +++ b/src/check/context/clss/generic.rs @@ -356,7 +356,8 @@ mod test { "class MyClass(def fin a: Int, b: Int): Parent(b) := where def c: Int := a + b end"; let ast: AST = source.parse().expect("valid class syntax"); - let generic_class = GenericClass::try_from(&ast)?; + let generic_class = GenericClass::try_from(&ast) + .expect(format!("expected class, was {:#?}", ast.node).as_str()); assert_eq!(generic_class.name, StringName::from("MyClass")); assert!(!generic_class.is_py_type); @@ -414,7 +415,7 @@ mod test { #[test] fn from_class() -> Result<(), Vec> { - let source = "class MyClass\n def c: Int := a + b\n"; + let source = "class MyClass := where def c: Int := a + b end"; let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -450,7 +451,7 @@ mod test { #[test] fn from_class_with_generic() -> Result<(), Vec> { - let source = "class MyClass[T]\n def c: T\n"; + let source = "class MyClass[T] := where def c: T end"; let ast: AST = source.parse().expect("valid type syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -487,10 +488,11 @@ mod test { #[test] fn from_type_with_generic() -> Result<(), Vec> { - let source = "type MyType[T]\n def c: T\n"; + let source = "type MyType[T] where def c: T end"; let ast: AST = source.parse().expect("valid type syntax"); - let generic_class = GenericClass::try_from(&ast)?; + let generic_class = GenericClass::try_from(&ast) + .expect(format!("was not class, was {:#?}", ast.node).as_str()); let name = StringName::new("MyType", &[Name::from("T")]); assert_eq!(generic_class.name, name.clone()); @@ -524,7 +526,7 @@ mod test { #[test] fn from_type_def() -> Result<(), Vec> { - let source = "type MyType\n def c: String\n"; + let source = "type MyType where def c: String end"; let ast: AST = source.parse().expect("valid type syntax"); let generic_class = GenericClass::try_from(&ast)?; diff --git a/src/parse/block.rs b/src/parse/block.rs index 143d0086..7c4cf4c8 100644 --- a/src/parse/block.rs +++ b/src/parse/block.rs @@ -12,7 +12,7 @@ pub fn parse_statements(it: &mut LexIterator) -> ParseResult> { let start = it.start_pos("statements")?; let mut statements: Vec = Vec::new(); - it.peek_while_not_tokens(&[], &mut |it, lex| match &lex.token { + it.peek_while_not_tokens(&[Token::End], &mut |it, lex| match &lex.token { Token::NL => it.eat(&Token::NL, "statements").map(|_| ()), Token::Import | Token::From => { diff --git a/src/parse/class.rs b/src/parse/class.rs index ec31f21e..c5f4233b 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -16,7 +16,7 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { let ty = it.parse(&parse_type, "class", start)?; let mut args = vec![]; - if it.eat_if(&Token::LSBrack).is_some() { + if it.eat_if(&Token::LRBrack).is_some() { it.peek_while_not_token(&Token::RRBrack, &mut |it, lex| match lex.token { Token::Def => { args.push(*it.parse(&parse_definition, "constructor argument", start)?); @@ -50,10 +50,10 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { }, )?; } - it.eat_if(&Token::NL); - it.eat_if(&Token::Assign); + it.eat_while(&Token::NL); let (body, pos) = if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) { + it.eat(&Token::Assign, "class")?; let body = it.parse(&parse_code_set, "class", start)?; (Some(body.clone()), start.union(body.pos)) } else { @@ -131,7 +131,7 @@ pub fn parse_type_def(it: &mut LexIterator) -> ParseResult { }; Ok(Box::from(AST::new(start.union(end), node))) } - _ if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) => { + _ if it.peek_if(&|lex: &Lex| lex.token == Token::Where) => { it.eat_if(&Token::NL); let body = it.parse(&parse_code_set, "type definition", start)?; let isa = isa.clone(); @@ -403,7 +403,7 @@ mod test { #[test] fn class_with_parent_with_args_with_body() -> ParseResult<()> { - let source = "class Class: Parent(\"hello world\") := { def var := 10 }"; + let source = "class Class: Parent(\"hello world\") := where def var := 10 end"; let mut it = LexIterator::from_str(source)?; parse_class(&mut it).map(|_| ()) } diff --git a/src/parse/control_flow_expr.rs b/src/parse/control_flow_expr.rs index 7d29c19c..2281c9b3 100644 --- a/src/parse/control_flow_expr.rs +++ b/src/parse/control_flow_expr.rs @@ -1,3 +1,4 @@ +use crate::common::position::Position; use crate::parse::ast::Node; use crate::parse::ast::AST; use crate::parse::expr_or_stmt::parse_expr_or_stmt; @@ -54,7 +55,8 @@ fn parse_match(it: &mut LexIterator) -> ParseResult { let start = it.start_pos("match")?; it.eat(&Token::Match, "match")?; let cond = it.parse(&parse_expression, "match", start)?; - it.eat(&Token::NL, "match")?; + it.eat_while(&Token::NL); + it.eat(&Token::Where, "match")?; let cases = it.parse_vec(&parse_match_cases, "match", start)?; let end = cases.last().cloned().map_or(cond.pos, |case| case.pos); @@ -63,15 +65,14 @@ fn parse_match(it: &mut LexIterator) -> ParseResult { } pub fn parse_match_cases(it: &mut LexIterator) -> ParseResult> { - let start = it.eat(&Token::LCBrack, "match cases")?; let mut cases = vec![]; - it.peek_while_not_token(&Token::RCBrack, &mut |it, _| { - cases.push(*it.parse(&parse_match_case, "match case", start)?); + it.peek_while_not_token(&Token::End, &mut |it, _| { + cases.push(*it.parse(&parse_match_case, "match case", Position::invisible())?); it.eat_if(&Token::NL); Ok(()) })?; - it.eat(&Token::RCBrack, "match cases")?; + it.eat(&Token::End, "match cases")?; Ok(cases) } @@ -162,7 +163,7 @@ mod test { #[test] fn match_verify() { - let source = String::from("match a\n{ a => b\n c => d}"); + let source = String::from("match a\nwhere a => b\n c => d \nend"); let ast: AST = source.parse().unwrap(); let Node::Match { cond, cases } = &ast.node else { @@ -233,7 +234,7 @@ mod test { #[test] fn if_expression() -> ParseResult<()> { - let source = String::from("if a then\n b\n"); + let source = String::from("if a then\nb\n"); let ast: AST = source.parse()?; let Node::IfElse { cond, then, el } = ast.node else { @@ -246,10 +247,6 @@ mod test { lit: String::from("a") } ); - let then = match &then.node { - Node::Block { statements } => statements[0].clone(), - _ => panic!("Expected then block, got {then:?}"), - }; assert_eq!( then.node, Node::Id { @@ -276,23 +273,14 @@ mod test { lit: String::from("a") } ); - let then = match &then.node { - Node::Block { statements } => statements[0].clone(), - _ => panic!("Expected then block, got {then:?}"), - }; assert_eq!( then.node, Node::Id { lit: String::from("b") } ); - - let el = match el.clone().unwrap().node { - Node::Block { statements } => statements[0].clone(), - _ => panic!("Expected then block, got {then:?}"), - }; assert_eq!( - el.node, + el.unwrap().node, Node::Id { lit: String::from("c") } diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index 051585b3..e137e486 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -24,6 +24,9 @@ pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { // if expression/statement followed by newline and indent, we are dealing with a handle block if it.peek_if(&|lex: &Lex| lex.token == Token::Raise) { + it.eat(&Token::Raise, "handle cases")?; + it.eat(&Token::Where, "handle cases")?; + // parse handle cases if indentation block after let cases = it.parse_vec(&parse_match_cases, "handle cases", expr_or_stmt.pos)?; let end = cases.last().map_or(expr_or_stmt.pos, |stmt| stmt.pos); diff --git a/src/parse/result.rs b/src/parse/result.rs index 3cefffd4..2eeddcc8 100644 --- a/src/parse/result.rs +++ b/src/parse/result.rs @@ -89,7 +89,11 @@ pub fn expected(expected: &Token, actual: &Lex, parsing: &str) -> ParseErr { "Expected {}{expected:?} token while parsing {}{parsing}, but found {}", an_or_a(expected), an_or_a(parsing), - actual.token + if actual.token.equals_name() { + format!("'{}'", actual.token) + } else { + format!("{} ('{}')", actual.token.name(), actual.token) + } ); ParseErr { pos: actual.pos, diff --git a/src/parse/ty.rs b/src/parse/ty.rs index 33283d45..d5d9b9f1 100644 --- a/src/parse/ty.rs +++ b/src/parse/ty.rs @@ -129,13 +129,12 @@ pub fn parse_conditions(it: &mut LexIterator) -> ParseResult> { let mut conditions = vec![]; if it.eat_if(&Token::NL).is_some() { - it.eat(&Token::LRBrack, "conditions")?; - it.peek_while_not_token(&Token::RRBrack, &mut |it, _| { + it.peek_while_not_token(&Token::End, &mut |it, _| { conditions.push(*it.parse(&parse_condition, "conditions", start)?); it.eat_if(&Token::NL); Ok(()) })?; - it.eat(&Token::RRBrack, "conditions")?; + it.eat(&Token::End, "conditions")?; } else { let start = it.start_pos("conditions")?; conditions.push(*it.parse(&parse_condition, "conditions", start)?); From 6240098f56a2dad343f258d6870e7a6f57e9f6fa Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 30 Mar 2026 10:15:35 +0200 Subject: [PATCH 38/44] fix: parse import set --- src/parse/ast/mod.rs | 8 +++++ src/parse/statement.rs | 75 +++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/parse/ast/mod.rs b/src/parse/ast/mod.rs index 98396d86..a84106c3 100644 --- a/src/parse/ast/mod.rs +++ b/src/parse/ast/mod.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +use std::process::Termination; use crate::check::context::arg; use crate::check::context::function::python::INIT; @@ -34,6 +35,13 @@ impl AST { } } +impl Termination for AST { + /// AST is always success + fn report(self) -> std::process::ExitCode { + std::process::ExitCode::SUCCESS + } +} + pub type OptAST = Option>; #[derive(PartialEq, Eq, Hash, Debug, Clone)] diff --git a/src/parse/statement.rs b/src/parse/statement.rs index 24e01dac..f11e45cd 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -9,7 +9,7 @@ use crate::parse::iterator::LexIterator; use crate::parse::lex::token::{Lex, Token}; use crate::parse::operation::parse_expression; use crate::parse::result::{custom, expected_one_of}; -use crate::parse::result::{eof_expected_one_of, expected, ParseResult}; +use crate::parse::result::{eof_expected_one_of, ParseResult}; use crate::parse::ty::{parse_expression_type, parse_id}; pub fn parse_statement(it: &mut LexIterator) -> ParseResult { @@ -66,42 +66,49 @@ pub fn parse_import(it: &mut LexIterator) -> ParseResult { None }; - let end = it.eat(&Token::Import, "import")?; - let mut import = vec![]; - it.peek_while_not_tokens(&[Token::As, Token::NL], &mut |it, _| { - import.push(*it.parse(&parse_id, "import", start)?); - it.eat_if(&Token::Comma); - Ok(()) - })?; + it.eat(&Token::Import, "import")?; + let (node, end) = if it.eat_if(&Token::LCBrack).is_none() { + let import = *it.parse(&parse_id, "import", start)?; - let alias = if it.eat_if(&Token::As).is_some() { + let alias = if it.eat_if(&Token::As).is_some() { + Some(*it.parse(&parse_id, "as", start)?) + } else { + None + }; + + let end: crate::common::position::Position = match &alias { + Some(ast) => ast.pos, + _ => import.pos, + }; + let node = Node::Import { + from, + import: vec![import], + alias: alias.map_or(vec![], |a| vec![a]), + }; + (node, end) + } else { + let mut import = vec![]; let mut alias = vec![]; - it.peek_while_not_token(&Token::NL, &mut |it, lex| match lex.token { - Token::Id(_) => { + it.peek_while_not_token(&Token::RCBrack, &mut |it, _| { + import.push(*it.parse(&parse_id, "import set", start)?); + if it.eat_if(&Token::As).is_some() { alias.push(*it.parse(&parse_id, "as", start)?); - it.eat_if(&Token::Comma); - Ok(()) } - _ => Err(Box::from(expected(&Token::Id(String::new()), lex, "as"))), + + it.eat_if(&Token::Comma); + Ok(()) })?; - alias - } else { - vec![] - }; - let end = match (import.last(), alias.last()) { - (_, Some(ast)) => ast.pos, - (Some(ast), _) => ast.pos, - (..) => end, - }; - Ok(Box::from(AST::new( - start.union(end), - Node::Import { + let end = it.eat(&Token::RCBrack, "import set")?; + let node = Node::Import { from, import, alias, - }, - ))) + }; + (node, end) + }; + + Ok(Box::from(AST::new(start.union(end), node))) } pub fn parse_reassignment(pre: &AST, it: &mut LexIterator) -> ParseResult { @@ -219,9 +226,17 @@ pub fn is_start_statement(lex: &Lex) -> bool { #[cfg(test)] mod test { + use super::*; use crate::common::position::{CaretPos, Position}; - use crate::parse::ast::node_op::NodeOp; - use crate::parse::ast::{Node, AST}; + use test_case::test_case; + + #[test_case("from a import b")] + #[test_case("from a import b as c")] + #[test_case("from a import {b as c, d as e}")] + #[test_case("from a import b as c, d as e" => matches Err(_))] + fn parse(src: &str) -> ParseResult { + src.parse::() + } #[test] fn parse_return() { From b92d38de40d831af931dcaf6d1845cdbee7056e1 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 30 Mar 2026 10:35:54 +0200 Subject: [PATCH 39/44] chore: add llvm build tools preview to nix --- flake.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flake.nix b/flake.nix index 3b6bee38..8e007cc1 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,7 @@ clang # C++ tooling llvmPackages.bintools # + lvm2 # LLVM build tools rustc # Start off with rust from nix store, reproducible start of rust toolchain # We keep rustup around for making it easy to switch channels @@ -73,6 +74,9 @@ export WORKSPACE="$PWD" export STARSHIP_CONFIG="$WORKSPACE/.config/startship.toml" + export LLVM_COV="${pkgs.nushell}/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-cov" + export LLVM_PROFDATA="${pkgs.nushell}/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata" + # first check for user-specific config if [ -f "$(pwd)/.config/nushell/config.nu" ]; then exec "${pkgs.nushell}/bin/nu" --login --config "$(pwd)/.config/nushell/config.nu" From 6455016583a60037e12a1802eab497309f4deb53 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 30 Mar 2026 11:28:15 +0200 Subject: [PATCH 40/44] fix: multiple aliases for imports --- src/check/ast/mod.rs | 2 +- src/check/ast/node.rs | 2 +- src/check/context/generic.rs | 26 ++++------ src/check/context/mod.rs | 2 +- src/generate/ast/mod.rs | 6 +-- src/generate/ast/node.rs | 4 +- src/generate/convert/class.rs | 46 ------------------ src/generate/convert/mod.rs | 48 ++++++++++++------- src/generate/convert/state.rs | 43 +++++++---------- src/parse/ast/mod.rs | 2 +- src/parse/ast/node.rs | 22 +++++++-- src/parse/class.rs | 12 ++--- src/parse/expr_or_stmt.rs | 5 +- src/parse/lex/token.rs | 1 + src/parse/result.rs | 24 +++++++--- src/parse/statement.rs | 14 ++++-- tests/parse/valid.rs | 5 +- tests/resource/valid/control_flow/if.mamba | 5 +- .../valid/control_flow/match_stmt.mamba | 2 +- .../definition/assign_with_try_except.mamba | 2 +- 20 files changed, 130 insertions(+), 143 deletions(-) diff --git a/src/check/ast/mod.rs b/src/check/ast/mod.rs index 3d90a573..f7a06207 100644 --- a/src/check/ast/mod.rs +++ b/src/check/ast/mod.rs @@ -81,7 +81,7 @@ pub enum NodeTy { Import { from: Option>, import: Vec, - alias: Vec, + alias: Vec>, }, Class { ty: StringName, diff --git a/src/check/ast/node.rs b/src/check/ast/node.rs index 60b266ac..5004024b 100644 --- a/src/check/ast/node.rs +++ b/src/check/ast/node.rs @@ -27,7 +27,7 @@ impl From<(&Node, &Finished)> for NodeTy { .collect(), alias: alias .iter() - .map(|ast| ASTTy::from((ast, finished))) + .map(|ast| ast.as_ref().map(|ast| ASTTy::from((ast, finished)))) .collect(), }, Node::Class { diff --git a/src/check/context/generic.rs b/src/check/context/generic.rs index ed2b7e19..43b51545 100644 --- a/src/check/context/generic.rs +++ b/src/check/context/generic.rs @@ -1,13 +1,10 @@ use std::collections::HashSet; use std::convert::TryFrom; -use itertools::EitherOrBoth::{Both, Left, Right}; -use itertools::Itertools; - use crate::check::context::clss::generic::GenericClass; use crate::check::context::field::generic::{GenericField, GenericFields}; use crate::check::context::function::generic::GenericFunction; -use crate::check::result::{TypeErr, TypeResult}; +use crate::check::result::TypeResult; use crate::parse::ast::{Node, OptAST, AST}; pub fn generics( @@ -69,21 +66,18 @@ fn single_ast( /// From import. /// /// A more elaborate import system will extract the signature of the class. -fn from_import(_from: &OptAST, import: &[AST], alias: &[AST]) -> TypeResult> { - let (mut classes, mut errs) = (vec![], vec![]); - for pair in import.iter().zip_longest(alias) { +fn from_import( + _from: &OptAST, + import: &[AST], + alias: &[Option], +) -> TypeResult> { + let mut classes = vec![]; + for pair in import.iter().zip(alias) { match pair { - Left(import) => classes.push(GenericClass::try_from_id(import)?), - Both(_, alias) => classes.push(GenericClass::try_from_id(alias)?), - Right(alias) => { - let msg = format!("alias with no matching import: {}", alias.node); - errs.push(TypeErr::new(alias.pos, &msg)); - } + (import, None) => classes.push(GenericClass::try_from_id(import)?), + (_, Some(alias)) => classes.push(GenericClass::try_from_id(alias)?), } } - if !errs.is_empty() { - return Err(errs); - } Ok(classes) } diff --git a/src/check/context/mod.rs b/src/check/context/mod.rs index c86c5627..d1c36cbb 100644 --- a/src/check/context/mod.rs +++ b/src/check/context/mod.rs @@ -246,7 +246,7 @@ mod tests { #[test] pub fn test_import_as_too_many_parse() { - let file = "import IPv4Address as Other, Other2" + let file = "import IPv4Address as {Other, Other2}" .parse::() .unwrap(); Context::try_from(vec![file].as_slice()).unwrap_err(); diff --git a/src/generate/ast/mod.rs b/src/generate/ast/mod.rs index 928e6955..275cd03a 100644 --- a/src/generate/ast/mod.rs +++ b/src/generate/ast/mod.rs @@ -57,9 +57,9 @@ fn to_py(core: &Core, ind: usize) -> String { } else { String::from("") }, - comma_delimited(import, ind), - if !alias.is_empty() { - format!(" as {}", comma_delimited(alias, ind)) + to_py(import, ind), + if let Some(alias) = alias { + format!(" as {}", to_py(alias, ind)) } else { String::from("") } diff --git a/src/generate/ast/node.rs b/src/generate/ast/node.rs index 939b5910..31554785 100644 --- a/src/generate/ast/node.rs +++ b/src/generate/ast/node.rs @@ -10,8 +10,8 @@ use crate::ASTTy; pub enum Core { Import { from: Option>, - import: Vec, - alias: Vec, + import: Box, + alias: Option>, }, ClassDef { name: Box, diff --git a/src/generate/convert/class.rs b/src/generate/convert/class.rs index dd2d61eb..d2075d3b 100644 --- a/src/generate/convert/class.rs +++ b/src/generate/convert/class.rs @@ -350,7 +350,6 @@ fn init( #[cfg(test)] mod tests { use crate::common::position::Position; - use crate::generate::ast::node::Core; use crate::generate::gen; use crate::parse::ast::{Node, AST}; use crate::ASTTy; @@ -370,51 +369,6 @@ mod tests { }}; } - #[test] - fn import_verify() { - let from = Some(to_pos!(Node::Break)); - let import = vec![ - to_pos_unboxed!(Node::ENum { - num: String::from("a"), - exp: String::from("100") - }), - to_pos_unboxed!(Node::Real { - lit: String::from("3000.5") - }), - ]; - let alias = vec![]; - let import = to_pos!(Node::Import { - from, - import, - alias - }); - - let (from, import, alias) = match gen(&ASTTy::from(&*import)) { - Ok(Core::Import { - from, - import, - alias, - }) => (from.clone(), import.clone(), alias.clone()), - other => panic!("Expected import but got {other:?}"), - }; - - assert_eq!(*from.unwrap(), Core::Break); - assert_eq!( - import[0], - Core::ENum { - num: String::from("a"), - exp: String::from("100") - } - ); - assert_eq!( - import[1], - Core::Float { - float: String::from("3000.5") - } - ); - assert!(alias.is_empty()); - } - #[test] fn condition_verify() { let cond = to_pos!(Node::Id { diff --git a/src/generate/convert/mod.rs b/src/generate/convert/mod.rs index e0b71a3b..d73640e9 100644 --- a/src/generate/convert/mod.rs +++ b/src/generate/convert/mod.rs @@ -41,15 +41,27 @@ pub fn convert_node(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Context from, import, alias, - } => Core::Import { - from: if let Some(from) = from { - Some(Box::from(convert_node(from, imp, state, ctx)?)) - } else { - None - }, - import: convert_vec(import, imp, state, ctx)?, - alias: convert_vec(alias, imp, state, ctx)?, - }, + } => { + let mut statements = vec![]; + for (import, alias) in import.iter().zip(alias) { + let alias = if let Some(alias) = alias { + Some(Box::new(convert_node(alias, imp, state, ctx)?)) + } else { + None + }; + + statements.push(Core::Import { + from: if let Some(from) = from { + Some(Box::from(convert_node(from, imp, state, ctx)?)) + } else { + None + }, + import: Box::new(convert_node(import, imp, state, ctx)?), + alias, + }); + } + Core::Block { statements } + } NodeTy::VariableDef { .. } | NodeTy::FunDef { .. } | NodeTy::FunArg { .. } => { convert_def(ast, imp, state, ctx)? @@ -521,21 +533,21 @@ mod tests { import: vec![to_pos_unboxed!(Node::Id { lit: String::from("a") })], - alias: vec![to_pos_unboxed!(Node::Id { + alias: vec![Some(to_pos_unboxed!(Node::Id { lit: String::from("b") - })] + }))] }); assert_eq!( gen(&ASTTy::from(&_break)).unwrap(), Core::Import { from: None, - import: vec![Core::Id { + import: Box::new(Core::Id { lit: String::from("a") - }], - alias: vec![Core::Id { + }), + alias: Some(Box::new(Core::Id { lit: String::from("b") - }], + })), } ); } @@ -650,10 +662,10 @@ mod tests { import, Core::Import { from: None, - import: vec![Core::Id { + import: Box::new(Core::Id { lit: String::from("math") - }], - alias: vec![], + }), + alias: None, } ); assert_eq!( diff --git a/src/generate/convert/state.rs b/src/generate/convert/state.rs index a7867ad3..1796b3c3 100644 --- a/src/generate/convert/state.rs +++ b/src/generate/convert/state.rs @@ -1,7 +1,5 @@ use std::collections::BTreeMap; -use itertools::Itertools; - use crate::check::name::Name; use crate::generate::ast::node::Core; use crate::generate::GenArguments; @@ -133,45 +131,38 @@ impl Imports { pub fn add_import(&mut self, import: &str) { let import = Core::Import { from: None, - import: vec![Core::Id { + import: Box::new(Core::Id { lit: String::from(import), - }], - alias: vec![], + }), + alias: None, }; if !self.imports.contains(&import) { self.imports.push(import); } } + /// Note that no alias checks are done currently. + /// Therefore, this may currently overwrite aliases. pub fn add_from_import(&mut self, from: &str, import: &str) { if let Some(Core::Import { - import: imports, - alias, + import: current_import, + alias: _alias, .. }) = self.from_imports.get(&String::from(from)) { - let new = Core::Id { - lit: String::from(import), - }; - let imports: Vec = if !imports.contains(&new) { - imports.clone().into_iter().chain(vec![new]).collect() - } else { - imports.to_vec() + let import = Core::Id { + lit: String::from(from), }; + if import == **current_import { + return; // this is a NOP, import already exists + } let import = Core::Import { from: Some(Box::from(Core::Id { lit: String::from(from), })), - import: imports - .iter() - .sorted_by_key(|c| match c { - Core::Id { lit } => lit.clone(), - _ => String::from(""), - }) - .cloned() - .collect(), - alias: alias.clone(), + import: Box::new(import), + alias: None, }; self.from_imports.insert(String::from(from), import); return; @@ -181,10 +172,10 @@ impl Imports { from: Some(Box::from(Core::Id { lit: String::from(from), })), - import: vec![Core::Id { + import: Box::new(Core::Id { lit: String::from(import), - }], - alias: vec![], + }), + alias: None, }; self.from_imports.insert(String::from(from), import); } diff --git a/src/parse/ast/mod.rs b/src/parse/ast/mod.rs index a84106c3..c1a6bcb7 100644 --- a/src/parse/ast/mod.rs +++ b/src/parse/ast/mod.rs @@ -49,7 +49,7 @@ pub enum Node { Import { from: Option>, import: Vec, - alias: Vec, + alias: Vec>, }, Class { ty: Box, diff --git a/src/parse/ast/node.rs b/src/parse/ast/node.rs index b5cd6a4f..e1055a6d 100644 --- a/src/parse/ast/node.rs +++ b/src/parse/ast/node.rs @@ -25,6 +25,19 @@ fn equal_vec(this: &[AST], other: &[AST]) -> bool { } } +fn equal_nullable_vec(this: &[Option], other: &[Option]) -> bool { + if this.len() != other.len() { + false + } else { + for (left, right) in this.iter().zip(other) { + if !equal_optional(&left.clone().map(Box::new), &right.clone().map(Box::new)) { + return false; + } + } + true + } +} + impl Display for Node { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { let name = match &self { @@ -183,7 +196,10 @@ impl Node { } => Node::Import { from: from.map(|a| a.map(mapping)).map(Box::from), import: import.iter().map(|i| i.map(mapping)).collect(), - alias: alias.iter().map(|a| a.map(mapping)).collect(), + alias: alias + .iter() + .map(|a| a.as_ref().map(|a| a.map(mapping))) + .collect(), }, Node::Class { ty, @@ -520,7 +536,7 @@ impl Node { import: ri, alias: ra, }, - ) => lf == rf && equal_vec(li, ri) && equal_vec(la, ra), + ) => lf == rf && equal_vec(li, ri) && equal_nullable_vec(la, ra), ( Node::Class { ty: lt, @@ -1450,7 +1466,7 @@ mod test { two_ast!(Node::Import { from: Some(Box::from(AST::new(Position::invisible(), Node::Break))), import: vec![AST::new(Position::invisible(), Node::Continue)], - alias: vec![AST::new(Position::invisible(), Node::Pass)], + alias: vec![Some(AST::new(Position::invisible(), Node::Pass))], }); } diff --git a/src/parse/class.rs b/src/parse/class.rs index c5f4233b..7af23026 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -223,7 +223,7 @@ mod test { } ); assert_eq!( - alias[0].node, + alias[0].as_ref().unwrap().node, Node::Id { lit: String::from("e") } @@ -232,7 +232,7 @@ mod test { #[test] fn from_import_as_verify() { - let source = String::from("from c import d,f as e,g"); + let source = String::from("from c import {d,f as e,g}"); let ast = source.parse::().unwrap(); let (from, import, alias) = match ast.node { @@ -250,8 +250,8 @@ mod test { lit: String::from("c") } ); - assert_eq!(import.len(), 2); - assert_eq!(alias.len(), 2); + assert_eq!(import.len(), 3); + assert_eq!(alias.len(), 3); assert_eq!( import[0].node, Node::Id { @@ -265,13 +265,13 @@ mod test { } ); assert_eq!( - alias[0].node, + alias[0].as_ref().unwrap().node, Node::Id { lit: String::from("e") } ); assert_eq!( - alias[1].node, + alias[1].as_ref().unwrap().node, Node::Id { lit: String::from("g") } diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index e137e486..ea244144 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -26,6 +26,7 @@ pub fn parse_expr_or_stmt(it: &mut LexIterator) -> ParseResult { if it.peek_if(&|lex: &Lex| lex.token == Token::Raise) { it.eat(&Token::Raise, "handle cases")?; it.eat(&Token::Where, "handle cases")?; + it.eat_while(&Token::NL); // TODO more systemic way to deal with newlines? // parse handle cases if indentation block after let cases = it.parse_vec(&parse_match_cases, "handle cases", expr_or_stmt.pos)?; @@ -294,13 +295,13 @@ mod test { ); assert_eq!(alias.len(), 2); assert_eq!( - alias[0].node, + alias[0].as_ref().unwrap().node, Node::Id { lit: String::from("c") } ); assert_eq!( - alias[1].node, + alias[1].as_ref().unwrap().node, Node::Id { lit: String::from("d") } diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index be84a0a7..30e9d167 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -199,6 +199,7 @@ impl fmt::Display for TokenName<'_> { Token::Ver => write!(f, "vertial"), Token::To => write!(f, "to"), Token::BTo => write!(f, "broad to"), + Token::BSlash => write!(f, "backslash"), Token::NL => write!(f, "newline"), Token::Underscore => write!(f, "underscore"), diff --git a/src/parse/result.rs b/src/parse/result.rs index 2eeddcc8..b674cb0e 100644 --- a/src/parse/result.rs +++ b/src/parse/result.rs @@ -64,13 +64,23 @@ impl From for ParseErr { } } +fn comma_delm_tokens(tokens: &[Token]) -> String { + comma_delm(tokens.iter().map(|t| { + if t.equals_name() { + format!("'{t}'") + } else { + format!("{} ('{t}')", t.name()) + } + })) +} + pub fn expected_one_of(tokens: &[Token], actual: &Lex, parsing: &str) -> ParseErr { let msg = format!( - "Expected one of [{}] while parsing {}{parsing}, but found {}", - comma_delm(tokens), + "Expected one of {{{}}} while parsing {}{parsing}, but found {}", + comma_delm_tokens(tokens), an_or_a(parsing), if actual.token.equals_name() { - format!("'{}'", actual.token) + format!("{}", actual.token) } else { format!("{} ('{}')", actual.token.name(), actual.token) } @@ -119,13 +129,13 @@ pub fn eof_expected_one_of(tokens: &[Token], parsing: &str) -> ParseErr { pos: Position::invisible(), msg: match tokens { tokens if tokens.len() > 1 => format!( - "Expected one of [{}] tokens while parsing {}{parsing}", - comma_delm(tokens), + "Expected one of {{{}}} tokens while parsing {}{parsing}", + comma_delm_tokens(tokens), an_or_a(parsing), ), tokens if tokens.len() == 1 => format!( - "Expected a {} token while parsing {}{parsing}", - comma_delm(tokens), + "Expected a '{}' token while parsing {}{parsing}", + comma_delm_tokens(tokens), an_or_a(parsing), ), _ => format!( diff --git a/src/parse/statement.rs b/src/parse/statement.rs index f11e45cd..6f500c0d 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -83,7 +83,7 @@ pub fn parse_import(it: &mut LexIterator) -> ParseResult { let node = Node::Import { from, import: vec![import], - alias: alias.map_or(vec![], |a| vec![a]), + alias: alias.map_or(vec![None], |a| vec![Some(a)]), }; (node, end) } else { @@ -91,9 +91,11 @@ pub fn parse_import(it: &mut LexIterator) -> ParseResult { let mut alias = vec![]; it.peek_while_not_token(&Token::RCBrack, &mut |it, _| { import.push(*it.parse(&parse_id, "import set", start)?); - if it.eat_if(&Token::As).is_some() { - alias.push(*it.parse(&parse_id, "as", start)?); - } + alias.push(if it.eat_if(&Token::As).is_some() { + Some(*it.parse(&parse_id, "as", start)?) + } else { + None + }); it.eat_if(&Token::Comma); Ok(()) @@ -234,6 +236,10 @@ mod test { #[test_case("from a import b as c")] #[test_case("from a import {b as c, d as e}")] #[test_case("from a import b as c, d as e" => matches Err(_))] + #[test_case("import b")] + #[test_case("import b as c")] + #[test_case("import {b as c, d as e}")] + #[test_case("import b as c, d as e" => matches Err(_))] fn parse(src: &str) -> ParseResult { src.parse::() } diff --git a/tests/parse/valid.rs b/tests/parse/valid.rs index 7cb3dd3f..2a092414 100644 --- a/tests/parse/valid.rs +++ b/tests/parse/valid.rs @@ -47,14 +47,15 @@ use mamba::parse::result::ParseResult; #[test_case("reamde_example", "type_refinement_matrix" => ignore["rewrite parser"])] #[test_case("reamde_example", "type_refinement_on_matrix" => ignore["rewrite parser"])] #[test_case("reamde_example", "type_refinement_set" => ignore["rewrite parser"])] -fn syntax(input_dir: &str, file_name: &str) -> ParseResult<()> { +fn syntax(input_dir: &str, file_name: &str) -> ParseResult { let file_name = format!("{file_name}.mamba"); let source = resource_content(true, &[input_dir], &file_name).unwrap(); // include path and source in error for faster debugging - source.parse::().map(|_| ()).map_err(|mut e| { + source.parse::().map_err(|mut e| { e.source = Some(source); e.path = Some(PathBuf::new().join(input_dir).join(file_name)); + eprintln!("{e}"); e }) } diff --git a/tests/resource/valid/control_flow/if.mamba b/tests/resource/valid/control_flow/if.mamba index 65cf7151..94168f80 100644 --- a/tests/resource/valid/control_flow/if.mamba +++ b/tests/resource/valid/control_flow/if.mamba @@ -6,7 +6,7 @@ if False then "hello" else "world" def cond := True and False cond := True or False -if cond then +if cond then do "asdf" print("hello \"world\"") @@ -19,7 +19,7 @@ if cond then if cond or False then "hhh" else iii -else +end else do "other" print("hello \"world\"") @@ -27,5 +27,6 @@ else "bbb" else "ccc" +end if "as" = "as" then print("hi") else print("asdf") diff --git a/tests/resource/valid/control_flow/match_stmt.mamba b/tests/resource/valid/control_flow/match_stmt.mamba index 4788de2a..d6702d9f 100644 --- a/tests/resource/valid/control_flow/match_stmt.mamba +++ b/tests/resource/valid/control_flow/match_stmt.mamba @@ -2,7 +2,7 @@ def a := "d" def (b, bb, bbb) := (0, 1, 2) -match (b, bb, bbb) +match (b, bb, bbb) where (0, 1, 2) => print("hello world") def nested := "other" diff --git a/tests/resource/valid/definition/assign_with_try_except.mamba b/tests/resource/valid/definition/assign_with_try_except.mamba index bdf94f5b..50fcef1c 100644 --- a/tests/resource/valid/definition/assign_with_try_except.mamba +++ b/tests/resource/valid/definition/assign_with_try_except.mamba @@ -1,6 +1,6 @@ def g() -> Int ! Exception := ! Exception("A") -def a := g() ! { +def a := g() ! where err: Exception => 10 end From 6eedd04e6098fb0e9551ba5d46e957c9c999acea Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 30 Mar 2026 15:50:55 +0200 Subject: [PATCH 41/44] feat: simplify grammar, class only requires where No assign required. Additionally, update some tests for imports --- README.md | 12 +++--- src/check/context/clss/generic.rs | 6 +-- src/check/context/mod.rs | 8 ---- src/generate/convert/mod.rs | 7 +++- src/parse/class.rs | 42 +++++++------------ src/parse/expr_or_stmt.rs | 12 +++--- src/parse/lex/token.rs | 4 +- src/parse/statement.rs | 40 +++++++++--------- .../invalid/syntax/top_lvl_class_access.mamba | 2 +- .../type/class/access_field_wrong_type.mamba | 4 +- .../class/access_function_wrong_type.mamba | 4 +- .../class/access_unassigned_class_var.mamba | 2 +- .../type/class/access_unassigned_field.mamba | 2 +- .../invalid/type/class/args_and_init.mamba | 3 +- .../assign_to_inner_inner_not_allowed.mamba | 2 +- .../class/assign_to_non_existent_self.mamba | 2 +- .../invalid/type/class/compound_field.mamba | 2 +- .../type/class/generic_unknown_type.mamba | 3 +- .../type/class/incompat_parent_generics.mamba | 3 +- .../invalid/type/class/no_generic_arg.mamba | 3 +- .../class/one_tuple_not_assigned_to.mamba | 2 +- .../invalid/type/class/parent_is_class.mamba | 2 +- .../type/class/reassign_function.mamba | 4 +- .../type/class/reassign_non_existent.mamba | 4 +- .../reassign_to_unassigned_class_var.mamba | 2 +- .../type/class/reassign_wrong_type.mamba | 4 +- .../type/class/same_parent_twice.mamba | 3 +- .../top_level_class_not_assigned_to.mamba | 2 +- .../type/class/wrong_generic_type.mamba | 3 +- .../class_field_assigned_to_only_else.mamba | 2 +- ...field_assigned_to_only_one_arm_match.mamba | 17 ++++---- .../class_field_assigned_to_only_then.mamba | 2 +- .../definition/assign_to_function_call.mamba | 5 ++- .../definition/assign_to_inner_non_mut.mamba | 8 ++-- .../definition/assign_to_inner_non_mut2.mamba | 8 ++-- .../definition/assign_to_inner_non_mut3.mamba | 8 ++-- .../function_ret_in_class_not_super.mamba | 2 +- .../definition/nested_non_mut_field.mamba | 7 ++-- .../non_mutable_in_call_chain.mamba | 12 ++++-- .../definition/raises_non_exception.mamba | 13 +++--- .../definition/reassign_non_mut_field.mamba | 4 +- .../definition/tuple_modify_inner_mut.mamba | 4 +- .../type/definition/tuple_modify_mut.mamba | 4 +- .../definition/tuple_modify_mut_entire.mamba | 4 +- .../invalid/type/error/handle_only_id.mamba | 6 +-- .../type/function/call_mut_function.mamba | 2 +- .../class/assign_to_nullable_field.mamba | 2 +- .../class/class_super_one_line_init.mamba | 2 +- tests/resource/valid/class/doc_strings.mamba | 3 +- .../class/fun_with_body_in_interface.mamba | 2 +- .../class/generic_unknown_type_unused.mamba | 3 +- tests/resource/valid/class/generics.mamba | 3 +- tests/resource/valid/class/parent.mamba | 4 +- tests/resource/valid/class/shadow.mamba | 14 ++++--- tests/resource/valid/class/simple_class.mamba | 3 +- .../valid/class/top_level_tuple.mamba | 4 +- .../top_level_unassigned_but_nullable.mamba | 2 +- tests/resource/valid/class/types.mamba | 6 +-- .../resource/valid/class/with_generics.mamba | 7 ++-- ...s_field_assigned_to_both_branches_if.mamba | 3 +- ...s_field_assigned_to_exhaustive_match.mamba | 3 +- .../control_flow/shadow_in_if_arms.mamba | 11 +++-- .../control_flow/shadow_in_if_arms_else.mamba | 9 ++-- .../control_flow/shadow_in_if_arms_then.mamba | 10 +++-- .../all_mutable_in_call_chain.mamba | 15 +++---- .../definition/assign_to_inner_mut.mamba | 8 ++-- .../assign_with_if_different_types.mamba | 3 +- .../assign_with_match_different_types.mamba | 2 +- .../function_ret_super_in_class.mamba | 2 +- .../valid/definition/nested_function.mamba | 9 ++-- .../valid/definition/nested_mut_field.mamba | 8 ++-- .../valid/definition/tuple_modify_mut.mamba | 4 +- .../definition/tuple_modify_outer_mut.mamba | 4 +- .../definition/tuple_non_lit_modify_mut.mamba | 3 +- .../resource/valid/function/definition.mamba | 2 +- .../operation/equality_different_types.mamba | 2 +- .../valid/operation/greater_than_int.mamba | 3 +- .../operation/greater_than_other_int.mamba | 3 +- .../valid/operation/multiply_other_int.mamba | 3 +- .../resource/valid/readme_example/class.mamba | 2 +- .../readme_example/class_with_constants.mamba | 2 +- .../valid/readme_example/trait_fin_meta.mamba | 2 +- .../valid/readme_example/traits.mamba | 6 +-- .../type_refinement_matrix.mamba | 2 +- 84 files changed, 253 insertions(+), 220 deletions(-) diff --git a/README.md b/README.md index fd23f5b5..c18f7db8 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ You will also see some "pure" functions, these will be explained later. ```mamba class MatrixErr(def message: Str): Exception(message) -class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := where +class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) where # Accessor for matrix contents def contents(fin self) -> List[Int] := [self.a, self.b, self.c, self.d] @@ -259,7 +259,7 @@ As for the class body We can change the relevant parts of the above example to use a class constant: ```mamba -class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := where +class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) where def x: Int := ORIGIN_X def y: Int := ORIGIN_Y @@ -287,16 +287,16 @@ For those familiar with object oriented programming, we favour a trait based sys Consider example with iterators (which briefly showcases language generics): ```mamba -trait Iterator[T] := where +trait Iterator[T] where def has_next(self) -> Bool def next(self) -> T? # syntax sugar for Option[T] end -class RangeIter(def _start: Int, def _end: Int) := where +class RangeIter(def _start: Int, def _end: Int) where def _current: Int := _start end -def Iterator[Int] for RangeIter := where +def Iterator[Int] for RangeIter where def has_next(self) -> Bool := self._current < self._stop def next(self) -> Int? := if self.has_next() then do @@ -383,7 +383,7 @@ type InvertibleMatrix: Matrix when self.determinant() != 0.0 class MatrixErr(def message: Str): Exception(message) ## Matrix, which now takes floats as argument -class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := where +class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) where def _last_op: Str? := None def determinant(fin self) -> Float := self.a * self.d - self.b * self.c diff --git a/src/check/context/clss/generic.rs b/src/check/context/clss/generic.rs index a3c9efb4..b391b280 100644 --- a/src/check/context/clss/generic.rs +++ b/src/check/context/clss/generic.rs @@ -353,7 +353,7 @@ mod test { #[test] fn from_class_inline_args() -> Result<(), Vec> { let source = - "class MyClass(def fin a: Int, b: Int): Parent(b) := where def c: Int := a + b end"; + "class MyClass(def fin a: Int, b: Int): Parent(b) where def c: Int := a + b end"; let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast) @@ -415,7 +415,7 @@ mod test { #[test] fn from_class() -> Result<(), Vec> { - let source = "class MyClass := where def c: Int := a + b end"; + let source = "class MyClass where def c: Int := a + b end"; let ast: AST = source.parse().expect("valid class syntax"); let generic_class = GenericClass::try_from(&ast)?; @@ -451,7 +451,7 @@ mod test { #[test] fn from_class_with_generic() -> Result<(), Vec> { - let source = "class MyClass[T] := where def c: T end"; + let source = "class MyClass[T] where def c: T end"; let ast: AST = source.parse().expect("valid type syntax"); let generic_class = GenericClass::try_from(&ast)?; diff --git a/src/check/context/mod.rs b/src/check/context/mod.rs index d1c36cbb..17939b05 100644 --- a/src/check/context/mod.rs +++ b/src/check/context/mod.rs @@ -244,14 +244,6 @@ mod tests { .unwrap(); } - #[test] - pub fn test_import_as_too_many_parse() { - let file = "import IPv4Address as {Other, Other2}" - .parse::() - .unwrap(); - Context::try_from(vec![file].as_slice()).unwrap_err(); - } - #[test] pub fn test_from_import_parse() { let file = "from ipaddress import IPv4Address".parse::().unwrap(); diff --git a/src/generate/convert/mod.rs b/src/generate/convert/mod.rs index d73640e9..e761840a 100644 --- a/src/generate/convert/mod.rs +++ b/src/generate/convert/mod.rs @@ -538,8 +538,13 @@ mod tests { }))] }); + let statements = match gen(&ASTTy::from(&_break)).unwrap() { + Core::Block { statements } => statements, + _ => panic!("Expected block, because multiple imports generated"), + }; + assert_eq!( - gen(&ASTTy::from(&_break)).unwrap(), + statements[0], Core::Import { from: None, import: Box::new(Core::Id { diff --git a/src/parse/class.rs b/src/parse/class.rs index 7af23026..415db4d4 100644 --- a/src/parse/class.rs +++ b/src/parse/class.rs @@ -34,26 +34,22 @@ pub fn parse_class(it: &mut LexIterator) -> ParseResult { let mut parents = vec![]; if it.eat_if(&Token::DoublePoint).is_some() { - it.peek_while_not_tokens( - &[Token::NL, Token::Assign], - &mut |it, lex| match lex.token { - Token::Id(_) | Token::LSBrack => { - parents.push(*it.parse(&parse_parent, "parents", start)?); - it.eat_if(&Token::Comma); - Ok(()) - } - _ => Err(Box::from(expected( - &Token::Id(String::new()), - &lex.clone(), - "parents", - ))), - }, - )?; + it.peek_while_not_tokens(&[Token::NL, Token::Where], &mut |it, lex| match lex.token { + Token::Id(_) | Token::LSBrack => { + parents.push(*it.parse(&parse_parent, "parents", start)?); + it.eat_if(&Token::Comma); + Ok(()) + } + _ => Err(Box::from(expected( + &Token::Id(String::new()), + &lex.clone(), + "parents", + ))), + })?; } it.eat_while(&Token::NL); - let (body, pos) = if it.peek_if(&|lex: &Lex| lex.token == Token::Assign) { - it.eat(&Token::Assign, "class")?; + let (body, pos) = if it.peek_if(&|lex: &Lex| lex.token == Token::Where) { let body = it.parse(&parse_code_set, "class", start)?; (Some(body.clone()), start.union(body.pos)) } else { @@ -190,7 +186,7 @@ mod test { assert_eq!(from, None); assert_eq!(import.len(), 1); - assert!(alias.is_empty()); + assert!(alias[0].is_none()); assert_eq!( import[0].node, Node::Id { @@ -264,16 +260,10 @@ mod test { lit: String::from("f") } ); - assert_eq!( - alias[0].as_ref().unwrap().node, - Node::Id { - lit: String::from("e") - } - ); assert_eq!( alias[1].as_ref().unwrap().node, Node::Id { - lit: String::from("g") + lit: String::from("e") } ); } @@ -403,7 +393,7 @@ mod test { #[test] fn class_with_parent_with_args_with_body() -> ParseResult<()> { - let source = "class Class: Parent(\"hello world\") := where def var := 10 end"; + let source = "class Class: Parent(\"hello world\") where def var := 10 end"; let mut it = LexIterator::from_str(source)?; parse_class(&mut it).map(|_| ()) } diff --git a/src/parse/expr_or_stmt.rs b/src/parse/expr_or_stmt.rs index ea244144..2f36c20a 100644 --- a/src/parse/expr_or_stmt.rs +++ b/src/parse/expr_or_stmt.rs @@ -262,12 +262,12 @@ mod test { lit: String::from("c") } ); - assert_eq!(alias.len(), 0); + assert_eq!(alias.len(), 1); } #[test] fn import_as_verify() { - let source = String::from("import a, b as c, d"); + let source = String::from("import {a, b as c, d}"); let ast = source.parse::().unwrap(); let (from, import, alias) = match &ast.node { @@ -280,7 +280,7 @@ mod test { }; assert_eq!(*from, None); - assert_eq!(import.len(), 2); + assert_eq!(import.len(), 3); assert_eq!( import[0].node, Node::Id { @@ -293,9 +293,9 @@ mod test { lit: String::from("b") } ); - assert_eq!(alias.len(), 2); + assert_eq!(alias.len(), 3); assert_eq!( - alias[0].as_ref().unwrap().node, + alias[1].as_ref().unwrap().node, Node::Id { lit: String::from("c") } @@ -303,7 +303,7 @@ mod test { assert_eq!( alias[1].as_ref().unwrap().node, Node::Id { - lit: String::from("d") + lit: String::from("c") } ); } diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index 30e9d167..7188ba68 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -190,8 +190,8 @@ impl fmt::Display for TokenName<'_> { Token::Eq => write!(f, "equal"), Token::Neq => write!(f, "not equal"), - Token::LRBrack => write!(f, "left roung bracket"), - Token::RRBrack => write!(f, "right rount bracket"), + Token::LRBrack => write!(f, "left round bracket"), + Token::RRBrack => write!(f, "right round bracket"), Token::LSBrack => write!(f, "left square bracket"), Token::RSBrack => write!(f, "right square bracket"), Token::LCBrack => write!(f, "left curly bracket"), diff --git a/src/parse/statement.rs b/src/parse/statement.rs index 6f500c0d..1f86ae9b 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -67,26 +67,7 @@ pub fn parse_import(it: &mut LexIterator) -> ParseResult { }; it.eat(&Token::Import, "import")?; - let (node, end) = if it.eat_if(&Token::LCBrack).is_none() { - let import = *it.parse(&parse_id, "import", start)?; - - let alias = if it.eat_if(&Token::As).is_some() { - Some(*it.parse(&parse_id, "as", start)?) - } else { - None - }; - - let end: crate::common::position::Position = match &alias { - Some(ast) => ast.pos, - _ => import.pos, - }; - let node = Node::Import { - from, - import: vec![import], - alias: alias.map_or(vec![None], |a| vec![Some(a)]), - }; - (node, end) - } else { + let (node, end) = if it.eat_if(&Token::LCBrack).is_some() { let mut import = vec![]; let mut alias = vec![]; it.peek_while_not_token(&Token::RCBrack, &mut |it, _| { @@ -108,6 +89,25 @@ pub fn parse_import(it: &mut LexIterator) -> ParseResult { alias, }; (node, end) + } else { + let import = *it.parse(&parse_id, "import", start)?; + + let alias = if it.eat_if(&Token::As).is_some() { + Some(*it.parse(&parse_id, "as", start)?) + } else { + None + }; + + let end: crate::common::position::Position = match &alias { + Some(ast) => ast.pos, + _ => import.pos, + }; + let node = Node::Import { + from, + import: vec![import], + alias: alias.map_or(vec![None], |a| vec![Some(a)]), + }; + (node, end) }; Ok(Box::from(AST::new(start.union(end), node))) diff --git a/tests/resource/invalid/syntax/top_lvl_class_access.mamba b/tests/resource/invalid/syntax/top_lvl_class_access.mamba index af32f94d..77d8a27c 100644 --- a/tests/resource/invalid/syntax/top_lvl_class_access.mamba +++ b/tests/resource/invalid/syntax/top_lvl_class_access.mamba @@ -1,3 +1,3 @@ -class X := where +class X where def y.y: Int := 10 end diff --git a/tests/resource/invalid/type/class/access_field_wrong_type.mamba b/tests/resource/invalid/type/class/access_field_wrong_type.mamba index 73c86960..016b52d8 100644 --- a/tests/resource/invalid/type/class/access_field_wrong_type.mamba +++ b/tests/resource/invalid/type/class/access_field_wrong_type.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def something: String := "my string" - +end def my_class := MyClass() def b := my_class.something diff --git a/tests/resource/invalid/type/class/access_function_wrong_type.mamba b/tests/resource/invalid/type/class/access_function_wrong_type.mamba index 3e152931..5566fbb8 100644 --- a/tests/resource/invalid/type/class/access_function_wrong_type.mamba +++ b/tests/resource/invalid/type/class/access_function_wrong_type.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def something(self) -> String := return "my string" - +end def my_class := MyClass() def b := my_class.something() diff --git a/tests/resource/invalid/type/class/access_unassigned_class_var.mamba b/tests/resource/invalid/type/class/access_unassigned_class_var.mamba index f0ea3d84..6f4b10a2 100644 --- a/tests/resource/invalid/type/class/access_unassigned_class_var.mamba +++ b/tests/resource/invalid/type/class/access_unassigned_class_var.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def z: Int def __init__(self) := diff --git a/tests/resource/invalid/type/class/access_unassigned_field.mamba b/tests/resource/invalid/type/class/access_unassigned_field.mamba index 19a8b1bf..ee85e5d5 100644 --- a/tests/resource/invalid/type/class/access_unassigned_field.mamba +++ b/tests/resource/invalid/type/class/access_unassigned_field.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def z: Int def __init__(self) := diff --git a/tests/resource/invalid/type/class/args_and_init.mamba b/tests/resource/invalid/type/class/args_and_init.mamba index db195fa5..0d84dc1e 100644 --- a/tests/resource/invalid/type/class/args_and_init.mamba +++ b/tests/resource/invalid/type/class/args_and_init.mamba @@ -1,2 +1,3 @@ -class MyClass(arg1: SomeType) := +class MyClass(arg1: SomeType) where def __init__(self) := print("cannot have two constructors") +end diff --git a/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba b/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba index 49304fe9..c1af40b9 100644 --- a/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba +++ b/tests/resource/invalid/type/class/assign_to_inner_inner_not_allowed.mamba @@ -1,6 +1,6 @@ class Y(x: Int) -class X := where +class X where def y: Y def __init__(a: Int) := self.y = Y(a) diff --git a/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba b/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba index 2ec39ece..8d934700 100644 --- a/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba +++ b/tests/resource/invalid/type/class/assign_to_non_existent_self.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def z: Int def __init__(self, x: Int) := diff --git a/tests/resource/invalid/type/class/compound_field.mamba b/tests/resource/invalid/type/class/compound_field.mamba index 871299d5..2d58290f 100644 --- a/tests/resource/invalid/type/class/compound_field.mamba +++ b/tests/resource/invalid/type/class/compound_field.mamba @@ -1,4 +1,4 @@ -class MyClass := where +class MyClass where def a: Int := 10 def b: Int := self.a + 1 end diff --git a/tests/resource/invalid/type/class/generic_unknown_type.mamba b/tests/resource/invalid/type/class/generic_unknown_type.mamba index 1c958e14..a0b39469 100644 --- a/tests/resource/invalid/type/class/generic_unknown_type.mamba +++ b/tests/resource/invalid/type/class/generic_unknown_type.mamba @@ -1,5 +1,6 @@ -class MyClass[A: UnknownType] := +class MyClass[A: UnknownType] where def f() -> Int := 10 +end # should error because suddenly we are using an ill-typed class. def my_class := MyClass[10]() diff --git a/tests/resource/invalid/type/class/incompat_parent_generics.mamba b/tests/resource/invalid/type/class/incompat_parent_generics.mamba index 70a87ce5..d1c30f16 100644 --- a/tests/resource/invalid/type/class/incompat_parent_generics.mamba +++ b/tests/resource/invalid/type/class/incompat_parent_generics.mamba @@ -1,4 +1,5 @@ class SuperClass[B: Int] -class MyClass[A: String]: SuperClass[A] := +class MyClass[A: String]: SuperClass[A] where def my_fun(x: A) -> A := x +end diff --git a/tests/resource/invalid/type/class/no_generic_arg.mamba b/tests/resource/invalid/type/class/no_generic_arg.mamba index b42f6e8d..95e5e48d 100644 --- a/tests/resource/invalid/type/class/no_generic_arg.mamba +++ b/tests/resource/invalid/type/class/no_generic_arg.mamba @@ -1,4 +1,5 @@ -class MyClass[A] := +class MyClass[A] where def my_fun(x: A) -> A := x +end def my_class := MyClass() diff --git a/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba b/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba index 885d92b9..a3352610 100644 --- a/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba +++ b/tests/resource/invalid/type/class/one_tuple_not_assigned_to.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def (a, b): (Int, Int) def __init__(self, x: Int) := diff --git a/tests/resource/invalid/type/class/parent_is_class.mamba b/tests/resource/invalid/type/class/parent_is_class.mamba index 3b82db2c..f550e312 100644 --- a/tests/resource/invalid/type/class/parent_is_class.mamba +++ b/tests/resource/invalid/type/class/parent_is_class.mamba @@ -1,5 +1,5 @@ class MyType[A: MyGeneric, C](def super_field: Str) -class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") := where +class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") where def some_function(self) -> Int := 10 end diff --git a/tests/resource/invalid/type/class/reassign_function.mamba b/tests/resource/invalid/type/class/reassign_function.mamba index 81336845..ece15bfa 100644 --- a/tests/resource/invalid/type/class/reassign_function.mamba +++ b/tests/resource/invalid/type/class/reassign_function.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def something() -> Int := 10 - +end def my_class := MyClass() my_class.something() := 20 diff --git a/tests/resource/invalid/type/class/reassign_non_existent.mamba b/tests/resource/invalid/type/class/reassign_non_existent.mamba index 778cd984..e783b0d6 100644 --- a/tests/resource/invalid/type/class/reassign_non_existent.mamba +++ b/tests/resource/invalid/type/class/reassign_non_existent.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def something: Int := 10 - +end def my_class := MyClass() my_class.other_thing := 20 diff --git a/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba b/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba index ed7ef2ce..5f759bab 100644 --- a/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba +++ b/tests/resource/invalid/type/class/reassign_to_unassigned_class_var.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def z: Int def __init__(self, x: Int) := diff --git a/tests/resource/invalid/type/class/reassign_wrong_type.mamba b/tests/resource/invalid/type/class/reassign_wrong_type.mamba index 8281c09a..25891ec3 100644 --- a/tests/resource/invalid/type/class/reassign_wrong_type.mamba +++ b/tests/resource/invalid/type/class/reassign_wrong_type.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def something: Int := 10 - +end def my_class := MyClass() my_class.something := "A string" diff --git a/tests/resource/invalid/type/class/same_parent_twice.mamba b/tests/resource/invalid/type/class/same_parent_twice.mamba index f4c089ae..747c21dc 100644 --- a/tests/resource/invalid/type/class/same_parent_twice.mamba +++ b/tests/resource/invalid/type/class/same_parent_twice.mamba @@ -1,4 +1,5 @@ class MyType(a: String) -class MyClass1: MyType("asdf"), MyType("qwerty") := +class MyClass1: MyType("asdf"), MyType("qwerty") where def other: Int +end diff --git a/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba b/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba index e98a1618..4f5ea146 100644 --- a/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba +++ b/tests/resource/invalid/type/class/top_level_class_not_assigned_to.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def y: Int def z: Int diff --git a/tests/resource/invalid/type/class/wrong_generic_type.mamba b/tests/resource/invalid/type/class/wrong_generic_type.mamba index a129b1c9..b67e701a 100644 --- a/tests/resource/invalid/type/class/wrong_generic_type.mamba +++ b/tests/resource/invalid/type/class/wrong_generic_type.mamba @@ -1,4 +1,5 @@ -class MyClass[A: String] := +class MyClass[A: String] where def my_fun(x: A) -> A := x +end def my_class := MyClass[Int]() diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba index 04dfc3ef..3004701b 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_else.mamba @@ -1,4 +1,4 @@ -class MyClass := where +class MyClass where def x: Int def __init__(self) := diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba index 6917a6e2..5c3e4dfe 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_one_arm_match.mamba @@ -1,12 +1,11 @@ -class MyClass := where +class MyClass where def x: Int - def __init__(self) := - match 10 where - 2 => self.x := 2 - 3 => self.x := 3 - 4 => print("o") - 5 => print("o") - _ => print("p") - end + def __init__(self) := match 10 where + 2 => self.x := 2 + 3 => self.x := 3 + 4 => print("o") + 5 => print("o") + _ => print("p") + end end diff --git a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba index 9f70ccc5..7bd28ea0 100644 --- a/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba +++ b/tests/resource/invalid/type/control_flow/class_field_assigned_to_only_then.mamba @@ -1,4 +1,4 @@ -class MyClass := where +class MyClass where def x: Int def __init__(self) := diff --git a/tests/resource/invalid/type/definition/assign_to_function_call.mamba b/tests/resource/invalid/type/definition/assign_to_function_call.mamba index 7ed90768..295cd530 100644 --- a/tests/resource/invalid/type/definition/assign_to_function_call.mamba +++ b/tests/resource/invalid/type/definition/assign_to_function_call.mamba @@ -1,7 +1,8 @@ -class A := +class A where def c: C +end -class C := where +class C where def fin my_field: Int := 10 def my_field_accessor(self) -> Int := self.my_field end diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba index 1b38a495..e8e79663 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut.mamba @@ -1,13 +1,15 @@ -class A := +class A where def c: C +end -class C := where +class C where def my_class: D := D() def my_field_accessor(self) -> D := self.my_class end -class D +class D where def fin my_field: Int := 10 +end def a := A() a.c.my_field_accessor().mmy_field := 20 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba index d0256e8f..3c8d7115 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut2.mamba @@ -1,13 +1,15 @@ -class A := +class A where def c: C +end -class C := where +class C where def my_class: D := D() def my_field_accessor(self) -> D := self.my_class end -class D := +class D where def fin my_field: Int := 10 +end def a := A() a.c.my_field_accessor().my_field := 20 diff --git a/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba b/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba index dd301297..2dab001e 100644 --- a/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba +++ b/tests/resource/invalid/type/definition/assign_to_inner_non_mut3.mamba @@ -1,13 +1,15 @@ -class A := +class A where def c: C +end -class C := where +class C where def fin my_class: D := D() def my_field_accessor(self) -> D := self.my_class end -class D := +class D where def my_field: Int := 10 +end def a := A() a.c.my_field_accessor().my_field := 20 diff --git a/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba b/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba index c2410974..24d821f5 100644 --- a/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba +++ b/tests/resource/invalid/type/definition/function_ret_in_class_not_super.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def some_higher_order(self, fun: Int -> Int) -> Int? := return fun(10) def fancy(self) -> Int := return self.some_higher_order(\x: Int := x * 2) end diff --git a/tests/resource/invalid/type/definition/nested_non_mut_field.mamba b/tests/resource/invalid/type/definition/nested_non_mut_field.mamba index bad417f7..612d8775 100644 --- a/tests/resource/invalid/type/definition/nested_non_mut_field.mamba +++ b/tests/resource/invalid/type/definition/nested_non_mut_field.mamba @@ -1,9 +1,10 @@ -class B := +class B where def my_num: Int := 10 +end -class A := +class A where def fin b: B - +end def a := A() a.b.my_num := 20 diff --git a/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba b/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba index 9bf64755..6c9dfc69 100644 --- a/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba +++ b/tests/resource/invalid/type/definition/non_mutable_in_call_chain.mamba @@ -1,14 +1,18 @@ -class A := +class A where def b: B := B() +end -class B := +class B where def fin c: C := C() +end -class C := +class C where def d: D := D() +end -class D :+ +class D where def e: Int := 100 +end def a := A() a.b.c.d.e := 20 diff --git a/tests/resource/invalid/type/definition/raises_non_exception.mamba b/tests/resource/invalid/type/definition/raises_non_exception.mamba index dc4526a6..554ecf5f 100644 --- a/tests/resource/invalid/type/definition/raises_non_exception.mamba +++ b/tests/resource/invalid/type/definition/raises_non_exception.mamba @@ -1,10 +1,9 @@ class MyErr1 -def f(x: Int) -> Int ! MyErr1 := - if x < 0 then - ! MyErr1() +def f(x: Int) -> Int ! MyErr1 := if x < 0 then + ! MyErr1() +else + if x > 10 then + ! MyErr2("Greater than 10.") else - if x > 10 then - ! MyErr2("Greater than 10.") - else - return x + 2 + return x + 2 diff --git a/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba b/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba index bdd44a35..8990fd29 100644 --- a/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba +++ b/tests/resource/invalid/type/definition/reassign_non_mut_field.mamba @@ -1,6 +1,6 @@ -class A := +class A where def fin my_num: Int := 10 - +end def a := A() a.my_num := 20 diff --git a/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba b/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba index 4050c3dd..12c1b06a 100644 --- a/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba +++ b/tests/resource/invalid/type/definition/tuple_modify_inner_mut.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def my_field: Int := 10 - +end def (a, fin b) := (MyClass(), 10) b := 30 diff --git a/tests/resource/invalid/type/definition/tuple_modify_mut.mamba b/tests/resource/invalid/type/definition/tuple_modify_mut.mamba index 5216cbaa..0201701c 100644 --- a/tests/resource/invalid/type/definition/tuple_modify_mut.mamba +++ b/tests/resource/invalid/type/definition/tuple_modify_mut.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def my_field: Int := 10 - +end def fin (a, b) := (MyClass(), 10) b := 20 diff --git a/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba b/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba index e044ba49..d3d8ae15 100644 --- a/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba +++ b/tests/resource/invalid/type/definition/tuple_modify_mut_entire.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def my_field: Int := 10 - +end def fin c := (MyClass(), 10) c := (MyClass(), 30) diff --git a/tests/resource/invalid/type/error/handle_only_id.mamba b/tests/resource/invalid/type/error/handle_only_id.mamba index 7a95c268..441079dd 100644 --- a/tests/resource/invalid/type/error/handle_only_id.mamba +++ b/tests/resource/invalid/type/error/handle_only_id.mamba @@ -3,13 +3,13 @@ class MyErr2(msg: String): Exception(msg) def f(x: Int) -> Int ! { MyErr1, MyErr2 } := x -def a := f(10) ! { +def a := f(10) ! where MyErr1 => do print("Something went wrong") -1 - ] + end MyErr2 => do print("Something else went wrong") -2 - ] + end end diff --git a/tests/resource/invalid/type/function/call_mut_function.mamba b/tests/resource/invalid/type/function/call_mut_function.mamba index 7c694af2..845bd507 100644 --- a/tests/resource/invalid/type/function/call_mut_function.mamba +++ b/tests/resource/invalid/type/function/call_mut_function.mamba @@ -1,4 +1,4 @@ -class MyClass := where +class MyClass where def a: Int := 20 # not allowed, self is fin! diff --git a/tests/resource/valid/class/assign_to_nullable_field.mamba b/tests/resource/valid/class/assign_to_nullable_field.mamba index c6601f5f..b48ee17e 100644 --- a/tests/resource/valid/class/assign_to_nullable_field.mamba +++ b/tests/resource/valid/class/assign_to_nullable_field.mamba @@ -1,4 +1,4 @@ -class MyServer() := where +class MyServer() where def _message: Str? := None def send(self, x: Str) := diff --git a/tests/resource/valid/class/class_super_one_line_init.mamba b/tests/resource/valid/class/class_super_one_line_init.mamba index 91ea7557..c8d8ea89 100644 --- a/tests/resource/valid/class/class_super_one_line_init.mamba +++ b/tests/resource/valid/class/class_super_one_line_init.mamba @@ -1,6 +1,6 @@ trait MyType -class MyClass2(other_field: Int, z: Int) := where +class MyClass2(other_field: Int, z: Int) where def fin z_modified: Str := "asdf" def other_field: Int := z + other_field end diff --git a/tests/resource/valid/class/doc_strings.mamba b/tests/resource/valid/class/doc_strings.mamba index 0c8b69d4..1e253384 100644 --- a/tests/resource/valid/class/doc_strings.mamba +++ b/tests/resource/valid/class/doc_strings.mamba @@ -3,7 +3,7 @@ It can do many thing """ -class MyClass +class MyClass where """This is my class It can do many things @@ -14,3 +14,4 @@ class MyClass This is my function, it can't do so much """ 200 +end diff --git a/tests/resource/valid/class/fun_with_body_in_interface.mamba b/tests/resource/valid/class/fun_with_body_in_interface.mamba index b453d902..20c9c5b9 100644 --- a/tests/resource/valid/class/fun_with_body_in_interface.mamba +++ b/tests/resource/valid/class/fun_with_body_in_interface.mamba @@ -1,4 +1,4 @@ -trait MyType := when +trait MyType when def abstract_fun(my_arg: Int) -> Str def concrete_fun(x: Int) -> Int := return x + 10 end diff --git a/tests/resource/valid/class/generic_unknown_type_unused.mamba b/tests/resource/valid/class/generic_unknown_type_unused.mamba index 0b5c1fbe..21ab855d 100644 --- a/tests/resource/valid/class/generic_unknown_type_unused.mamba +++ b/tests/resource/valid/class/generic_unknown_type_unused.mamba @@ -1,2 +1,3 @@ -class MyClass[A: UnknownType] := +class MyClass[A: UnknownType] where def f() -> Int := 10 +end diff --git a/tests/resource/valid/class/generics.mamba b/tests/resource/valid/class/generics.mamba index dec4c313..91c6e2f7 100644 --- a/tests/resource/valid/class/generics.mamba +++ b/tests/resource/valid/class/generics.mamba @@ -3,7 +3,7 @@ class Err2(msg: Str): Exception(msg) class MyType[A: MyGeneric, C](def super_field: Str) -class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") +class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over the slow donkey") where def fin z_modified: Str := "asdf" def other_field: Int := 10 @@ -40,3 +40,4 @@ class MyClass2[C, A: MyGeneric]: MyType[A, C]("the quick brown fox jumped over t def some_higher_order(self, fun: Int -> Int) -> Int? := fun(10) def fancy(self) -> Int? := self.some_higher_order(\x: Int := x * 2) +end diff --git a/tests/resource/valid/class/parent.mamba b/tests/resource/valid/class/parent.mamba index daaf008f..95ece94a 100644 --- a/tests/resource/valid/class/parent.mamba +++ b/tests/resource/valid/class/parent.mamba @@ -1,9 +1,9 @@ -trait MyType := when +trait MyType when def fun_a(self) def factorial(self, x: Int) -> Int end -class MyClass1: MyType := where +class MyClass1: MyType where def other: Int def fun_a(self) := print("hello) diff --git a/tests/resource/valid/class/shadow.mamba b/tests/resource/valid/class/shadow.mamba index 32403eff..4a533372 100644 --- a/tests/resource/valid/class/shadow.mamba +++ b/tests/resource/valid/class/shadow.mamba @@ -1,11 +1,15 @@ -class MyClass1 := +class MyClass1 where def f1(self) := print("1") -class MyClass2 := +end +class MyClass2 where def f2(self) := print("2") -class MyClass3 := +end +class MyClass3 where def f3(self) := print("3") -class MyClass4 := +end +class MyClass4 where def f4(self) := print("4") +end def x := MyClass1() x.f1() @@ -13,7 +17,7 @@ x.f1() def x := MyClass2() x.f2() -class MyClass := where +class MyClass where def x: MyClass3 := MyClass3() def g() := diff --git a/tests/resource/valid/class/simple_class.mamba b/tests/resource/valid/class/simple_class.mamba index ef8d07b1..e0cc5367 100644 --- a/tests/resource/valid/class/simple_class.mamba +++ b/tests/resource/valid/class/simple_class.mamba @@ -1,5 +1,6 @@ -class MyClass := +class MyClass where def my_function() -> Int := 10 +end def my_class := MyClass() print(my_class.my_function()) diff --git a/tests/resource/valid/class/top_level_tuple.mamba b/tests/resource/valid/class/top_level_tuple.mamba index 0e975d9a..90f0fc34 100644 --- a/tests/resource/valid/class/top_level_tuple.mamba +++ b/tests/resource/valid/class/top_level_tuple.mamba @@ -1,6 +1,6 @@ -class A +class A where def (a, b): (Int, Int) := (10, 100) - +end def a := A() print(a.a) diff --git a/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba b/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba index b7c1e71c..8ec0c281 100644 --- a/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba +++ b/tests/resource/valid/class/top_level_unassigned_but_nullable.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def y: Int? # typically, statements are evaluated in order, but Mamba makes no guarantees about this behaviour! print("No assignments here!") diff --git a/tests/resource/valid/class/types.mamba b/tests/resource/valid/class/types.mamba index 7bae8fdd..b41debfa 100644 --- a/tests/resource/valid/class/types.mamba +++ b/tests/resource/valid/class/types.mamba @@ -7,17 +7,17 @@ type OtherState: MyClass when self.required_field < 50 end -trait SuperInterface := where +trait SuperInterface where def bar: Int end -trait MyInterface: SuperInterface := where +trait MyInterface: SuperInterface where def required_field: Int def higher_order(self) -> Int end # some class -class MyClass(def my_field: Int, other_field: Str := "Hello"): MyInterface := where +class MyClass(def my_field: Int, other_field: Str := "Hello"): MyInterface where def required_field: Int := 100 def private_field: Int := 20 diff --git a/tests/resource/valid/class/with_generics.mamba b/tests/resource/valid/class/with_generics.mamba index d40e30e5..753a93a9 100644 --- a/tests/resource/valid/class/with_generics.mamba +++ b/tests/resource/valid/class/with_generics.mamba @@ -1,8 +1,9 @@ -class InnerClass[A] := +class InnerClass[A] where def inner(a: A) -> A := a - -class OuterClass[B] := +end +class OuterClass[B] where def inner_class: InnerClass[B] +end def outer_class := OuterClass() print(outer_class.inner_class.inner("hello world")) diff --git a/tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if.mamba b/tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if.mamba index 83bd1249..cfe9d10b 100644 --- a/tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if.mamba +++ b/tests/resource/valid/control_flow/class_field_assigned_to_both_branches_if.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass where def x: Int def __init__(self) := @@ -6,3 +6,4 @@ class MyClass self.x := 10 else self.x := 20 +end diff --git a/tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match.mamba b/tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match.mamba index 19dcb1a6..b250f26a 100644 --- a/tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match.mamba +++ b/tests/resource/valid/control_flow/class_field_assigned_to_exhaustive_match.mamba @@ -1,4 +1,4 @@ -class MyClass +class MyClass where def x: Int def __init__(self) := @@ -6,3 +6,4 @@ class MyClass 2 => self.x := 2 3 => self.x := 3 _ => self.x := 3 +end diff --git a/tests/resource/valid/control_flow/shadow_in_if_arms.mamba b/tests/resource/valid/control_flow/shadow_in_if_arms.mamba index 21f543bd..7423d2a3 100644 --- a/tests/resource/valid/control_flow/shadow_in_if_arms.mamba +++ b/tests/resource/valid/control_flow/shadow_in_if_arms.mamba @@ -1,11 +1,14 @@ -class MyClass1 +class MyClass1 where def f1(self) := print("1") -class MyClass2 +end +class MyClass2 where def f2(self) := print("2") +end -if True then +if True then do def x := MyClass1() x.f1() -else +end else do def x := MyClass2() x.f2() +end diff --git a/tests/resource/valid/control_flow/shadow_in_if_arms_else.mamba b/tests/resource/valid/control_flow/shadow_in_if_arms_else.mamba index 36ffb07c..33bf762d 100644 --- a/tests/resource/valid/control_flow/shadow_in_if_arms_else.mamba +++ b/tests/resource/valid/control_flow/shadow_in_if_arms_else.mamba @@ -1,11 +1,14 @@ -class MyClass1 +class MyClass1 where def f1(self) := pass -class MyClass2 +end +class MyClass2 where def f2(self) := pass +end def x := MyClass2() if True then x.f2() -else +else do def x := MyClass1() x.f1() +end diff --git a/tests/resource/valid/control_flow/shadow_in_if_arms_then.mamba b/tests/resource/valid/control_flow/shadow_in_if_arms_then.mamba index 4ad154cb..acbcb9c5 100644 --- a/tests/resource/valid/control_flow/shadow_in_if_arms_then.mamba +++ b/tests/resource/valid/control_flow/shadow_in_if_arms_then.mamba @@ -1,11 +1,13 @@ -class MyClass1 +class MyClass1 where def f1(self) := pass -class MyClass2 +end +class MyClass2 where def f2(self) := pass +end def x := MyClass2() -if True then +if True then do def x := MyClass1() x.f1() -else +end else x.f2() diff --git a/tests/resource/valid/definition/all_mutable_in_call_chain.mamba b/tests/resource/valid/definition/all_mutable_in_call_chain.mamba index 1eb49144..da76e786 100644 --- a/tests/resource/valid/definition/all_mutable_in_call_chain.mamba +++ b/tests/resource/valid/definition/all_mutable_in_call_chain.mamba @@ -1,14 +1,15 @@ -class A := +class A where def b: B := B() - -class B := +end +class B where def c: C := C() - -class C := +end +class C where def d: D := D() - -class D := +end +class D where def e: Int := 100 +end def a := A() a.b.c.d.e := 20 diff --git a/tests/resource/valid/definition/assign_to_inner_mut.mamba b/tests/resource/valid/definition/assign_to_inner_mut.mamba index ae4fe177..389d4b7a 100644 --- a/tests/resource/valid/definition/assign_to_inner_mut.mamba +++ b/tests/resource/valid/definition/assign_to_inner_mut.mamba @@ -1,13 +1,15 @@ -class A := +class A where def c: C +end -class C := where +class C where def my_class: D := D() def my_field_accessor(self) -> D := self.my_class end -class D := +class D where def my_field: Int := 10 +end def a := A() a.c.my_field_accessor().my_field := 20 diff --git a/tests/resource/valid/definition/assign_with_if_different_types.mamba b/tests/resource/valid/definition/assign_with_if_different_types.mamba index 27029cb2..5d1622cc 100644 --- a/tests/resource/valid/definition/assign_with_if_different_types.mamba +++ b/tests/resource/valid/definition/assign_with_if_different_types.mamba @@ -1,5 +1,6 @@ -class MyClass +class MyClass where def __str__(self) -> Str := "M" +end def a := if True then 20 else MyClass() diff --git a/tests/resource/valid/definition/assign_with_match_different_types.mamba b/tests/resource/valid/definition/assign_with_match_different_types.mamba index e793cd64..e42aacba 100644 --- a/tests/resource/valid/definition/assign_with_match_different_types.mamba +++ b/tests/resource/valid/definition/assign_with_match_different_types.mamba @@ -2,7 +2,7 @@ class MyClass class MyClass1 class MyClass2 -def a:= match 40 { +def a:= match 40 where 2 => MyClass() 4 => MyClass1() _ => MyClass2() diff --git a/tests/resource/valid/definition/function_ret_super_in_class.mamba b/tests/resource/valid/definition/function_ret_super_in_class.mamba index 7eb28cf1..eedd1670 100644 --- a/tests/resource/valid/definition/function_ret_super_in_class.mamba +++ b/tests/resource/valid/definition/function_ret_super_in_class.mamba @@ -1,4 +1,4 @@ -class X := where +class X where def some_higher_order(self, fun: Int -> Int) -> Int := fun(10) def fancy(self) -> Int? := self.some_higher_order(\x: Int := x * 2) end diff --git a/tests/resource/valid/definition/nested_function.mamba b/tests/resource/valid/definition/nested_function.mamba index 1d7ad8ba..59268b84 100644 --- a/tests/resource/valid/definition/nested_function.mamba +++ b/tests/resource/valid/definition/nested_function.mamba @@ -1,11 +1,14 @@ -class A := +class A where def b: B +end -class B := +class B where def my_c(self) -> C := C() +end -class C := +class C where def fin my_field: Int := 10 +end def a := A() print(a.b.my_c().my_field) diff --git a/tests/resource/valid/definition/nested_mut_field.mamba b/tests/resource/valid/definition/nested_mut_field.mamba index b37332b4..8c9d7b9a 100644 --- a/tests/resource/valid/definition/nested_mut_field.mamba +++ b/tests/resource/valid/definition/nested_mut_field.mamba @@ -1,9 +1,9 @@ -class B := +class B where def my_num: Int := 10 - -class A := +end +class A where def b: B - +end def a := A() a.b.my_num := 20 diff --git a/tests/resource/valid/definition/tuple_modify_mut.mamba b/tests/resource/valid/definition/tuple_modify_mut.mamba index 7964e55b..6b7bd0b9 100644 --- a/tests/resource/valid/definition/tuple_modify_mut.mamba +++ b/tests/resource/valid/definition/tuple_modify_mut.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def my_field: Int := 10 - +end def (a, b) := (MyClass(), 10) a.my_field := 20 diff --git a/tests/resource/valid/definition/tuple_modify_outer_mut.mamba b/tests/resource/valid/definition/tuple_modify_outer_mut.mamba index 7964e55b..6b7bd0b9 100644 --- a/tests/resource/valid/definition/tuple_modify_outer_mut.mamba +++ b/tests/resource/valid/definition/tuple_modify_outer_mut.mamba @@ -1,6 +1,6 @@ -class MyClass := +class MyClass where def my_field: Int := 10 - +end def (a, b) := (MyClass(), 10) a.my_field := 20 diff --git a/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba b/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba index b163df63..cdb916c5 100644 --- a/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba +++ b/tests/resource/valid/definition/tuple_non_lit_modify_mut.mamba @@ -1,5 +1,6 @@ -class MyClass := +class MyClass where def my_field: Int := 10 +end def f() -> (MyClass, Int) := (MyClass(), 10) diff --git a/tests/resource/valid/function/definition.mamba b/tests/resource/valid/function/definition.mamba index 429a4bb1..031be196 100644 --- a/tests/resource/valid/function/definition.mamba +++ b/tests/resource/valid/function/definition.mamba @@ -19,7 +19,7 @@ def fun_e(m: Int, o: (Str, Str), r: (Int, (Str, Str)) -> Int) -> Int := return r def fun_v(y: Str, ab: Str -> Str -> Bool) -> Str -> Bool := return ab(y) -class MyClass(def a: Int, def b: Int) := where +class MyClass(def a: Int, def b: Int) where def some_function(self, c: Int) -> Int := do def d := 20 d := 10 + 30 diff --git a/tests/resource/valid/operation/equality_different_types.mamba b/tests/resource/valid/operation/equality_different_types.mamba index f679e1ad..952978b1 100644 --- a/tests/resource/valid/operation/equality_different_types.mamba +++ b/tests/resource/valid/operation/equality_different_types.mamba @@ -1,4 +1,4 @@ -class MyClass := where +class MyClass where def __eq__(self, other: MyOtherClass) -> Bool := True def __ne__(self, other: MyOtherClass) -> Bool := False end diff --git a/tests/resource/valid/operation/greater_than_int.mamba b/tests/resource/valid/operation/greater_than_int.mamba index 1f986f95..f247fca4 100644 --- a/tests/resource/valid/operation/greater_than_int.mamba +++ b/tests/resource/valid/operation/greater_than_int.mamba @@ -1,5 +1,6 @@ -class MyClass(def a: Int) := +class MyClass(def a: Int) where def f(self) -> Bool := self.a > 10 +end def a := MyClass(10) diff --git a/tests/resource/valid/operation/greater_than_other_int.mamba b/tests/resource/valid/operation/greater_than_other_int.mamba index 99e16bf8..8d233154 100644 --- a/tests/resource/valid/operation/greater_than_other_int.mamba +++ b/tests/resource/valid/operation/greater_than_other_int.mamba @@ -1,5 +1,6 @@ -class MyClass(def a: Int) := +class MyClass(def a: Int) where def f(self, other: MyClass) -> Bool := self.a > other.a +end def a := MyClass(10) def b := MyClass(20) diff --git a/tests/resource/valid/operation/multiply_other_int.mamba b/tests/resource/valid/operation/multiply_other_int.mamba index caed9d4a..71c01baa 100644 --- a/tests/resource/valid/operation/multiply_other_int.mamba +++ b/tests/resource/valid/operation/multiply_other_int.mamba @@ -1,5 +1,6 @@ -class MyClass(def a: Int) := +class MyClass(def a: Int) where def f(self, other: MyClass) -> Int := self.a * other.a +end def a := MyClass(10) def b := MyClass(20) diff --git a/tests/resource/valid/readme_example/class.mamba b/tests/resource/valid/readme_example/class.mamba index 72173589..e2da210b 100644 --- a/tests/resource/valid/readme_example/class.mamba +++ b/tests/resource/valid/readme_example/class.mamba @@ -1,6 +1,6 @@ class MatrixErr(def message: Str): Exception(message) -class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) := where +class Matrix2x2(def a: Int, def b: Int, def c: Int, def d: Int) where # Accessor for matrix contents def contents(fin self) -> List[Int] := [a, b, c, d] diff --git a/tests/resource/valid/readme_example/class_with_constants.mamba b/tests/resource/valid/readme_example/class_with_constants.mamba index e69e7b37..4b56e563 100644 --- a/tests/resource/valid/readme_example/class_with_constants.mamba +++ b/tests/resource/valid/readme_example/class_with_constants.mamba @@ -1,4 +1,4 @@ -class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) := where +class Point2D(ORIGIN_X: Int, ORIGIN_Y: Int) where # ORIGIN_X and ORIGIN_Y are constructor constants — they cannot be changed # current x and y are mutable fields initialized from the constants def x: Int := ORIGIN_X diff --git a/tests/resource/valid/readme_example/trait_fin_meta.mamba b/tests/resource/valid/readme_example/trait_fin_meta.mamba index 567116a1..85237dc1 100644 --- a/tests/resource/valid/readme_example/trait_fin_meta.mamba +++ b/tests/resource/valid/readme_example/trait_fin_meta.mamba @@ -1,6 +1,6 @@ # if we implement strictly decreasing, we must implement measure # These are non-overridable method which uses this measure -trait def StrictlyDecreases: Measurable where +trait StrictlyDecreases: Measurable where def fin meta decreases(self, other: Self) -> Bool := self.measure() < other.measure() def fin meta equal(self, other: Self) -> Bool := self.measure() = other.measure() def fin meta subtract(self, other: Self) -> Measurable := self.measure() - other.measure() diff --git a/tests/resource/valid/readme_example/traits.mamba b/tests/resource/valid/readme_example/traits.mamba index 165f2833..a8d7c776 100644 --- a/tests/resource/valid/readme_example/traits.mamba +++ b/tests/resource/valid/readme_example/traits.mamba @@ -1,13 +1,13 @@ -trait Iterator[T] := where +trait Iterator[T] where def has_next(self) -> Bool def next(self) -> T? # syntax sugar for Option[T] end -class RangeIter(def _start: Int, def _end: Int) := where +class RangeIter(def _start: Int, def _end: Int) where def _current: Int := _start end -def Iterator[Int] for RangeIter := where +def Iterator[Int] for RangeIter where def has_next(self) -> Bool := self._current < self._stop def next(self) -> Int? := if self.has_next() then [ diff --git a/tests/resource/valid/readme_example/type_refinement_matrix.mamba b/tests/resource/valid/readme_example/type_refinement_matrix.mamba index fd8c4e19..10434a42 100644 --- a/tests/resource/valid/readme_example/type_refinement_matrix.mamba +++ b/tests/resource/valid/readme_example/type_refinement_matrix.mamba @@ -3,7 +3,7 @@ type InvertibleMatrix: Matrix when self.determinant() != 0.0 class MatrixErr(def message: Str): Exception(message) ## Matrix, which now takes floats as argument -class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) := where +class Matrix2x2(def a: Float, def b: Float, def c: Float, def d: Float) where def _last_op: Str? := None def determinant(fin self) -> Float := self.a * self.d - self.b * self.c From 5ae21c27c59bd6c2bcc649e2fc4c922b6a2f05b9 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 13 Apr 2026 10:38:54 +0200 Subject: [PATCH 42/44] chore: bump toolchain 1.88 -> 1.94.1 --- rust-toolchain.toml | 2 +- src/check/constrain/generate/collection.rs | 2 +- src/check/constrain/generate/control_flow.rs | 1 + src/check/constrain/unify/function.rs | 1 - src/generate/convert/builder.rs | 6 +++--- src/io.rs | 2 +- src/lib.rs | 2 +- src/parse/iterator.rs | 5 ++++- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ee3a3837..bb2fc52d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.88.0" +channel = "1.94.1" profile = "minimal" components = ["rustfmt", "clippy"] diff --git a/src/check/constrain/generate/collection.rs b/src/check/constrain/generate/collection.rs index bcf1f610..5dd759c2 100644 --- a/src/check/constrain/generate/collection.rs +++ b/src/check/constrain/generate/collection.rs @@ -119,7 +119,7 @@ fn gen_builder( generate(pair, &conds_env, ctx, constr)?; } - if let Some(conditions) = conditions.strip_prefix(&[cond.clone()]) { + if let Some(conditions) = conditions.strip_prefix(std::slice::from_ref(cond)) { for cond in conditions { generate(cond, &conds_env, ctx, constr)?; let cond = Expected::from(cond); diff --git a/src/check/constrain/generate/control_flow.rs b/src/check/constrain/generate/control_flow.rs index 896716ee..8bd237f5 100644 --- a/src/check/constrain/generate/control_flow.rs +++ b/src/check/constrain/generate/control_flow.rs @@ -11,6 +11,7 @@ use crate::check::name::true_name::TrueName; use crate::check::result::TypeErr; use crate::parse::ast::{Node, AST}; +#[allow(clippy::result_large_err)] // will revisit in future pub fn gen_flow( ast: &AST, env: &Environment, diff --git a/src/check/constrain/unify/function.rs b/src/check/constrain/unify/function.rs index 88aa3a00..3a888ee6 100644 --- a/src/check/constrain/unify/function.rs +++ b/src/check/constrain/unify/function.rs @@ -33,7 +33,6 @@ pub fn unify_function( let arguments_union: Vec> = name .names .iter() - .cloned() .map(|n| n.args(right.pos)) .collect::>()?; diff --git a/src/generate/convert/builder.rs b/src/generate/convert/builder.rs index 1dbaf3d7..f8395ff5 100644 --- a/src/generate/convert/builder.rs +++ b/src/generate/convert/builder.rs @@ -18,7 +18,7 @@ pub fn convert_builder(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Cont if let Some(col) = conditions.first() { let conds = conditions - .strip_prefix(&[col.clone()]) + .strip_prefix(std::slice::from_ref(col)) .expect("Unreachable"); let conds = convert_vec(conds, imp, state, ctx)?; let col = Box::from(convert_node(col, imp, state, ctx)?); @@ -37,7 +37,7 @@ pub fn convert_builder(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Cont if let Some(col) = conditions.first() { let conds = conditions - .strip_prefix(&[col.clone()]) + .strip_prefix(std::slice::from_ref(col)) .expect("Unreachable"); let conds = convert_vec(conds, imp, state, ctx)?; let col = Box::from(convert_node(col, imp, state, ctx)?); @@ -53,7 +53,7 @@ pub fn convert_builder(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Cont if let Some(col) = conditions.first() { let conds = conditions - .strip_prefix(&[col.clone()]) + .strip_prefix(std::slice::from_ref(col)) .expect("Unreachable"); let conds = convert_vec(conds, imp, state, ctx)?; let col = Box::from(convert_node(col, imp, state, ctx)?); diff --git a/src/io.rs b/src/io.rs index c99763f3..ebaedee8 100644 --- a/src/io.rs +++ b/src/io.rs @@ -56,7 +56,7 @@ pub fn relative_files(in_path: &Path) -> Result, String> { let mut relative_paths = vec![]; for absolute_result in glob { - let absolute_path = absolute_result.map_err(|e| (e.to_string()))?; + let absolute_path = absolute_result.map_err(|e| e.to_string())?; let relative_path = diff_paths(absolute_path.as_path(), in_path) .ok_or_else(|| String::from("Unable to create relative path"))?; relative_paths.push(relative_path.into_os_string()); diff --git a/src/lib.rs b/src/lib.rs index ecc4916b..70e7e7ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,7 +153,7 @@ pub fn mamba_to_python( .iter() .map(|(src, path)| { src.parse::() - .map_err(|err| err.with_source(&Some(src.clone()), &path.clone())) + .map_err(|err| Box::new(err.with_source(&Some(src.clone()), &path.clone()))) }) .partition(Result::is_ok); diff --git a/src/parse/iterator.rs b/src/parse/iterator.rs index 2016d030..1432dea7 100644 --- a/src/parse/iterator.rs +++ b/src/parse/iterator.rs @@ -72,7 +72,10 @@ impl LexIterator { match self.it.next() { Some(Lex { token: actual, pos }) if Token::same_type(&actual, token) => Ok(pos), Some(lex) => Err(Box::from(expected(token, &lex, err_msg))), - None => Err(Box::from(eof_expected_one_of(&[token.clone()], err_msg))), + None => Err(Box::from(eof_expected_one_of( + std::slice::from_ref(token), + err_msg, + ))), } } From c9d807e9032da5ef848e2e8e155670ce8add9a29 Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Mon, 13 Apr 2026 16:40:23 +0200 Subject: [PATCH 43/44] feat: remove pass from language --- src/check/ast/mod.rs | 17 --- src/check/ast/node.rs | 1 - src/check/constrain/generate/expression.rs | 8 -- src/check/constrain/generate/mod.rs | 1 - src/check/context/function/generic.rs | 8 -- src/generate/ast/mod.rs | 2 +- src/generate/ast/node.rs | 2 +- src/generate/convert/class.rs | 2 +- src/generate/convert/definition.rs | 8 +- src/generate/convert/mod.rs | 7 -- src/parse/ast/mod.rs | 1 - src/parse/ast/node.rs | 118 +++++++++++++++++---- src/parse/lex/token.rs | 2 - src/parse/lex/tokenize.rs | 1 - src/parse/statement.rs | 7 -- 15 files changed, 106 insertions(+), 79 deletions(-) diff --git a/src/check/ast/mod.rs b/src/check/ast/mod.rs index f7a06207..d4158aea 100644 --- a/src/check/ast/mod.rs +++ b/src/check/ast/mod.rs @@ -389,20 +389,3 @@ pub enum NodeTy { }, Empty, } - -#[cfg(test)] -mod test { - use crate::check::name::Name; - use crate::common::position::Position; - use crate::parse::ast::Node; - use crate::{ASTTy, AST}; - - #[test] - fn to_ty() { - let node = Node::Pass; - let ast = AST::new(Position::invisible(), node.clone()); - let ast_ty = ASTTy::from(&ast).with_ty(&Name::from("Dummy")); - - assert_eq!(ast_ty.ty, Some(Name::from("Dummy"))); - } -} diff --git a/src/check/ast/node.rs b/src/check/ast/node.rs index 5004024b..7504a81c 100644 --- a/src/check/ast/node.rs +++ b/src/check/ast/node.rs @@ -468,7 +468,6 @@ impl From<(&Node, &Finished)> for NodeTy { Node::Continue => NodeTy::Continue, Node::ReturnEmpty => NodeTy::ReturnEmpty, Node::Underscore => NodeTy::Underscore, - Node::Pass => NodeTy::Pass, _ => NodeTy::Empty, } } diff --git a/src/check/constrain/generate/expression.rs b/src/check/constrain/generate/expression.rs index 36d680ab..fdd6fc2b 100644 --- a/src/check/constrain/generate/expression.rs +++ b/src/check/constrain/generate/expression.rs @@ -41,14 +41,6 @@ pub fn gen_expr( generate(right, env, ctx, constr)?; Ok(env.clone()) } - Node::Pass => { - if let Some(expected_ret_ty) = &env.return_type { - constr.add("pass", &Expected::none(ast.pos), expected_ret_ty, env); - Ok(env.clone()) - } else { - Ok(env.clone()) - } - } _ => Err(vec![TypeErr::new(ast.pos, "Expected an expression")]), } diff --git a/src/check/constrain/generate/mod.rs b/src/check/constrain/generate/mod.rs index 9fea074e..69eca76d 100644 --- a/src/check/constrain/generate/mod.rs +++ b/src/check/constrain/generate/mod.rs @@ -63,7 +63,6 @@ pub fn generate( ExpressionType { .. } | Id { .. } | Question { .. } => gen_expr(ast, env, ctx, constr), AnonFun { .. } => gen_expr(ast, env, ctx, constr), - Pass => gen_expr(ast, env, ctx, constr), With { .. } => gen_resources(ast, env, ctx, constr), diff --git a/src/check/context/function/generic.rs b/src/check/context/function/generic.rs index 438c046f..a0f002dd 100644 --- a/src/check/context/function/generic.rs +++ b/src/check/context/function/generic.rs @@ -135,16 +135,8 @@ mod test { use crate::check::context::function::generic::GenericFunction; use crate::check::name::string_name::StringName; use crate::check::name::Name; - use crate::common::position::Position; - use crate::parse::ast::Node; use crate::{TypeErr, AST}; - #[test] - fn from_non_fundef_node() { - let ast = AST::new(Position::invisible(), Node::Pass); - assert!(GenericFunction::try_from(&ast).is_err()) - } - #[test] fn from_fundef() -> Result<(), Vec> { let source = "def f(fin a: Int, b: String := \"a\") -> String ! E := pass"; diff --git a/src/generate/ast/mod.rs b/src/generate/ast/mod.rs index 275cd03a..385f103d 100644 --- a/src/generate/ast/mod.rs +++ b/src/generate/ast/mod.rs @@ -480,7 +480,7 @@ fn to_py(core: &Core, ind: usize) -> String { newline_if_body(body, ind) ), - Core::Pass => String::from("pass"), + Core::Nop => String::from("pass"), Core::None => String::from("None"), Core::Empty => String::new(), diff --git a/src/generate/ast/node.rs b/src/generate/ast/node.rs index 31554785..3855bf59 100644 --- a/src/generate/ast/node.rs +++ b/src/generate/ast/node.rs @@ -277,7 +277,7 @@ pub enum Core { expr: Box, }, UnderScore, - Pass, + Nop, None, Empty, TryExcept { diff --git a/src/generate/convert/class.rs b/src/generate/convert/class.rs index d2075d3b..8c0810e3 100644 --- a/src/generate/convert/class.rs +++ b/src/generate/convert/class.rs @@ -188,7 +188,7 @@ fn extract_class( .collect(); let statements = if body_stmts.is_empty() { - vec![Core::Pass] + vec![Core::Nop] } else { body_stmts }; diff --git a/src/generate/convert/definition.rs b/src/generate/convert/definition.rs index e37c2e60..36115788 100644 --- a/src/generate/convert/definition.rs +++ b/src/generate/convert/definition.rs @@ -79,7 +79,7 @@ pub fn convert_def(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Context) }; let (dec, body) = if state.interface && expression.is_none() { imp.add_from_import("abc", "abstractmethod"); - (vec![String::from("abstractmethod")], Box::from(Core::Pass)) + (vec![String::from("abstractmethod")], Box::from(Core::Nop)) } else { ( vec![], @@ -90,7 +90,7 @@ pub fn convert_def(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Context) &state.expand_ty(true).is_last_must_be_ret(ty.is_some()), ctx, )?, - None => Core::Pass, + None => Core::Nop, }), ) }; @@ -475,7 +475,7 @@ mod test { default: None, } ); - assert_eq!(*body, Core::Pass); + assert_eq!(*body, Core::Nop); } #[test] @@ -523,7 +523,7 @@ mod test { })), } ); - assert_eq!(*body, Core::Pass); + assert_eq!(*body, Core::Nop); } #[test] diff --git a/src/generate/convert/mod.rs b/src/generate/convert/mod.rs index e761840a..e53bd4a0 100644 --- a/src/generate/convert/mod.rs +++ b/src/generate/convert/mod.rs @@ -316,7 +316,6 @@ pub fn convert_node(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Context NodeTy::Raise { .. } | NodeTy::Handle { .. } => convert_handle(ast, imp, state, ctx)?, - NodeTy::Pass => Core::Pass, _ => Core::Empty, }; @@ -491,12 +490,6 @@ mod tests { assert_eq!(gen(&ASTTy::from(&_continue)).unwrap(), Core::Continue); } - #[test] - fn pass_verify() { - let pass = to_pos!(Node::Pass); - assert_eq!(gen(&ASTTy::from(&pass)).unwrap(), Core::Pass); - } - #[test] fn return_verify() { let expr = to_pos!(Node::Str { diff --git a/src/parse/ast/mod.rs b/src/parse/ast/mod.rs index c1a6bcb7..130688b5 100644 --- a/src/parse/ast/mod.rs +++ b/src/parse/ast/mod.rs @@ -357,7 +357,6 @@ pub enum Node { }, ReturnEmpty, Underscore, - Pass, Question { left: Box, right: Box, diff --git a/src/parse/ast/node.rs b/src/parse/ast/node.rs index e1055a6d..c8a5ce9a 100644 --- a/src/parse/ast/node.rs +++ b/src/parse/ast/node.rs @@ -175,7 +175,6 @@ impl Display for Node { Node::Continue => format!("{}", Token::Continue), Node::Return { .. } | Node::ReturnEmpty => String::from("return"), Node::Underscore => format!("{}", Token::Underscore), - Node::Pass => format!("{}", Token::Pass), Node::Question { .. } => String::from("ternary operator"), Node::QuestionOp { .. } => String::from("unsafe operator"), }; @@ -1268,7 +1267,6 @@ mod test { map_eq!(Node::Continue, Node::Continue, old, new); map_eq!(Node::ReturnEmpty, Node::ReturnEmpty, old, new); map_eq!(Node::Underscore, Node::Underscore, old, new); - map_eq!(Node::Pass, Node::Pass, old, new); } #[test] @@ -1466,7 +1464,12 @@ mod test { two_ast!(Node::Import { from: Some(Box::from(AST::new(Position::invisible(), Node::Break))), import: vec![AST::new(Position::invisible(), Node::Continue)], - alias: vec![Some(AST::new(Position::invisible(), Node::Pass))], + alias: vec![Some(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string() + } + ))], }); } @@ -1475,7 +1478,12 @@ mod test { let node = Node::Class { ty: Box::new(AST::new(Position::invisible(), Node::Continue)), args: vec![AST::new(Position::invisible(), Node::ReturnEmpty)], - parents: vec![AST::new(Position::invisible(), Node::Pass)], + parents: vec![AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )], body: Some(Box::from(AST::new(Position::invisible(), Node::new_self()))), }; @@ -1496,7 +1504,12 @@ mod test { fn parent_equal_value() { let node = Node::Parent { ty: Box::new(AST::new(Position::invisible(), Node::new_self())), - args: vec![AST::new(Position::invisible(), Node::Pass)], + args: vec![AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )], }; two_ast!(node); @@ -1505,7 +1518,12 @@ mod test { #[test] fn reassign_equal_value() { let node = Node::Reassign { - left: Box::new(AST::new(Position::invisible(), Node::Pass)), + left: Box::new(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )), right: Box::new(AST::new(Position::invisible(), Node::ReturnEmpty)), op: NodeOp::Sub, }; @@ -1517,7 +1535,12 @@ mod test { fn def_equal_value() { let first = Box::from(AST::new(Position::invisible(), Node::Continue)); let second = Box::from(AST::new(Position::invisible(), Node::Break)); - let third = Box::from(AST::new(Position::invisible(), Node::Pass)); + let third = Box::from(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )); two_ast!(Node::VariableDef { mutable: false, @@ -1564,7 +1587,12 @@ mod test { fn handle_same_value() { let first = Box::from(AST::new(Position::invisible(), Node::Continue)); let second = Box::from(AST::new(Position::invisible(), Node::Break)); - let third = Box::from(AST::new(Position::invisible(), Node::Pass)); + let third = Box::from(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )); two_ast!(Node::Handle { cases: vec![*first.clone()], @@ -1573,7 +1601,12 @@ mod test { two_ast!(Node::With { resource: first.clone(), alias: Some((second.clone(), false, Some(third.clone()))), - expr: Box::from(AST::new(Position::invisible(), Node::Pass)) + expr: Box::from(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string() + } + )) }); } @@ -1614,7 +1647,12 @@ mod test { #[test] fn expression_type_equal_value() { let expr = Box::from(AST::new(Position::invisible(), Node::Continue)); - let expr2 = Box::from(AST::new(Position::invisible(), Node::Pass)); + let expr2 = Box::from(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )); two_ast!(Node::ExpressionType { expr: expr.clone(), mutable: false, @@ -1626,7 +1664,12 @@ mod test { fn type_equal_value() { let first = Box::from(AST::new(Position::invisible(), Node::Continue)); let second = Box::from(AST::new(Position::invisible(), Node::Break)); - let third = Box::from(AST::new(Position::invisible(), Node::Pass)); + let third = Box::from(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )); two_ast!(Node::TypeDef { ty: first.clone(), @@ -1689,7 +1732,12 @@ mod test { }, Node::Str { lit: String::from("yuk"), - expressions: vec![AST::new(Position::invisible(), Node::Pass)] + expressions: vec![AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string() + } + )] } ); } @@ -1951,7 +1999,9 @@ mod test { two_ast!(Node::Continue); two_ast!(Node::ReturnEmpty); two_ast!(Node::Underscore); - two_ast!(Node::Pass); + two_ast!(Node::Id { + lit: "Random".to_string() + }); } #[test] @@ -1976,7 +2026,12 @@ mod test { fn block_end_with_expression_is_expression() { let node = Node::Block { statements: vec![ - AST::new(Position::invisible(), Node::Pass), + AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + ), AST::new( Position::invisible(), Node::Int { @@ -1998,7 +2053,12 @@ mod test { lit: String::from("3"), }, ), - AST::new(Position::invisible(), Node::Pass), + AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + ), ], }; assert!(!node.is_expression()) @@ -2018,7 +2078,12 @@ mod test { lit: "True".to_string(), }, )), - then: Box::new(AST::new(Position::invisible(), Node::Pass)), + then: Box::new(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )), el: None, }; assert!(!node.is_expression()) @@ -2033,8 +2098,18 @@ mod test { lit: "True".to_string(), }, )), - then: Box::new(AST::new(Position::invisible(), Node::Pass)), - el: Some(Box::new(AST::new(Position::invisible(), Node::Pass))), + then: Box::new(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )), + el: Some(Box::new(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + ))), }; assert!(node.is_expression()) } @@ -2043,7 +2118,12 @@ mod test { fn expression_is_expression() { let first = Box::from(AST::new(Position::invisible(), Node::Continue)); let second = Box::from(AST::new(Position::invisible(), Node::Break)); - let third = Box::from(AST::new(Position::invisible(), Node::Pass)); + let third = Box::from(AST::new( + Position::invisible(), + Node::Id { + lit: "Random".to_string(), + }, + )); assert!(Node::AnonFun { args: vec![*first.clone()], diff --git a/src/parse/lex/token.rs b/src/parse/lex/token.rs index 7188ba68..2a76bdf7 100644 --- a/src/parse/lex/token.rs +++ b/src/parse/lex/token.rs @@ -112,7 +112,6 @@ pub enum Token { With, Question, - Pass, Where, End, @@ -301,7 +300,6 @@ impl fmt::Display for Token { Token::Raise => write!(f, "!"), Token::When => write!(f, "when"), - Token::Pass => write!(f, "pass"), Token::Where => write!(f, "where"), Token::End => write!(f, "end"), } diff --git a/src/parse/lex/tokenize.rs b/src/parse/lex/tokenize.rs index 100febc6..f928f7c2 100644 --- a/src/parse/lex/tokenize.rs +++ b/src/parse/lex/tokenize.rs @@ -263,7 +263,6 @@ fn as_op_or_id(string: String) -> Token { "in" => Token::In, "when" => Token::When, - "pass" => Token::Pass, "where" => Token::Where, "end" => Token::End, diff --git a/src/parse/statement.rs b/src/parse/statement.rs index 1f86ae9b..3c3531ef 100644 --- a/src/parse/statement.rs +++ b/src/parse/statement.rs @@ -15,10 +15,6 @@ use crate::parse::ty::{parse_expression_type, parse_id}; pub fn parse_statement(it: &mut LexIterator) -> ParseResult { it.peek_or_err( &|it, lex| match lex.token { - Token::Pass => { - let end = it.eat(&Token::Pass, "statement")?; - Ok(Box::from(AST::new(end, Node::Pass))) - } Token::Raise => { it.eat(&Token::Raise, "statement")?; let error = it.parse(&parse_expression, "statement", lex.pos)?; @@ -33,7 +29,6 @@ pub fn parse_statement(it: &mut LexIterator) -> ParseResult { Token::Ret => parse_return(it), _ => Err(Box::from(expected_one_of( &[ - Token::Pass, Token::Raise, Token::Def, Token::With, @@ -46,7 +41,6 @@ pub fn parse_statement(it: &mut LexIterator) -> ParseResult { ))), }, &[ - Token::Pass, Token::Raise, Token::Def, Token::With, @@ -219,7 +213,6 @@ pub fn is_start_statement(lex: &Lex) -> bool { | Token::Fin | Token::For | Token::While - | Token::Pass | Token::Raise | Token::With | Token::Ret From b839cdc9f7f0ee6ed15c1485576b90ac6e5276df Mon Sep 17 00:00:00 2001 From: Joel Abrahams Date: Wed, 15 Apr 2026 16:12:33 +0200 Subject: [PATCH 44/44] fix: newlines in match --- src/parse/ast/node.rs | 7 +------ src/parse/control_flow_expr.rs | 4 +++- tests/resource/valid/control_flow/match_stmt.mamba | 12 ++++++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/parse/ast/node.rs b/src/parse/ast/node.rs index c8a5ce9a..5c102073 100644 --- a/src/parse/ast/node.rs +++ b/src/parse/ast/node.rs @@ -2053,12 +2053,7 @@ mod test { lit: String::from("3"), }, ), - AST::new( - Position::invisible(), - Node::Id { - lit: "Random".to_string(), - }, - ), + AST::new(Position::invisible(), Node::Break), ], }; assert!(!node.is_expression()) diff --git a/src/parse/control_flow_expr.rs b/src/parse/control_flow_expr.rs index 2281c9b3..b0ef0db6 100644 --- a/src/parse/control_flow_expr.rs +++ b/src/parse/control_flow_expr.rs @@ -57,6 +57,8 @@ fn parse_match(it: &mut LexIterator) -> ParseResult { let cond = it.parse(&parse_expression, "match", start)?; it.eat_while(&Token::NL); it.eat(&Token::Where, "match")?; + it.eat_while(&Token::NL); + let cases = it.parse_vec(&parse_match_cases, "match", start)?; let end = cases.last().cloned().map_or(cond.pos, |case| case.pos); @@ -68,7 +70,7 @@ pub fn parse_match_cases(it: &mut LexIterator) -> ParseResult> { let mut cases = vec![]; it.peek_while_not_token(&Token::End, &mut |it, _| { cases.push(*it.parse(&parse_match_case, "match case", Position::invisible())?); - it.eat_if(&Token::NL); + it.eat_while(&Token::NL); Ok(()) })?; diff --git a/tests/resource/valid/control_flow/match_stmt.mamba b/tests/resource/valid/control_flow/match_stmt.mamba index d6702d9f..2d209888 100644 --- a/tests/resource/valid/control_flow/match_stmt.mamba +++ b/tests/resource/valid/control_flow/match_stmt.mamba @@ -4,14 +4,18 @@ def (b, bb, bbb) := (0, 1, 2) match (b, bb, bbb) where (0, 1, 2) => print("hello world") +end def nested := "other" -match nested - "a" => +match nested where + "a" => do "b" "c" - "c" => match nested + end + "c" => match nested where "other" => "even_other" "other_one" => "better_one" - _ => "default" \ No newline at end of file + _ => "default" + end +end