diff --git a/README.md b/README.md index 035923466..c18f7db85 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 @@ -33,13 +33,19 @@ Mamba is like Python, but with a few key features: - Explicit error handling - A distinction between mutability and immutability - 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 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). +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. + +This README: + +- Gives a quickstart for developers +- Gives a short overview of the syntax and language features in quick succession, as well as the occasional reasoning behind them. ## 🧑‍💻 Quickstart for developers 👨‍💻 @@ -67,151 +73,276 @@ 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 -def factorial(x: Int) -> Int := match x +# Factorial of 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.") ``` -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 `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 ``` -### 📋 Types, Classes, and Mutability +### 🍡 Collections -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. -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. +In Mamba, sets, lists, and maps are first class citizens. +They are baked into the language, including its grammar. -```mamba -from ipaddress import IPv4Address +Lists make use of square brackets: -class ServerError(def message: Str): Exception(message) +```mamba +# 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" ] + +# Indexing is done using round brackets! +print(a(0)) # prints '0' +``` -def fin always_the_same_message := "Connected!" +Sets and mappings, which are unordered, make use of curly brackets: -class MyServer(def ip_address: IPv4Address) - def is_connected: Bool := False - def _last_message: Str := "temp" +```mamba +# sets +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")' +print(ef(1)) # prints '1' +``` - def last_sent(fin self) -> Str ! ServerError := - self._last_message +In a way, a list is a type of mapping where the keys are the indexes of each item. +So: - def connect(self) := - self.is_connected := True - print(always_the_same_message) +```mamba +def numbers := [32, 504, 59] +``` - def send(self, message: Str) ! ServerError := - if self.is_connected then - self._last_message := message - else - ! ServerError("Not connected!") +Is essentially just shorthand for - def disconnect(self) := self.is_connected := False +```mamba +def numbers := { 0 => 32, 1 => 504, 2 => 59 } ``` -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`. -We can then use `MyServer` as follows: +Where we iterate over the list in the order of the keys. -```mamba -import ipaddress -from server import MyServer +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 function with a very small domain (only three items). +Therefore, we index indexable collections (mappings and list) using the `collection()` notation. -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) +### ✏️🖊️ Mutability -http_server.connect() -if my_server.is_connected then http_server.send("Hello World!") +Mutability gives us the power to modify an instance in the language after it is created: -# 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()! ``` +def a := 10 # we may modify a +def fin b := 20 # we may not modify b -### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) +a := a + 2 # allowed +# b := b + 2 # compilation error +``` -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. -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. +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. -Lets expand our server example from above, and rewrite it slightly: +### 📋 Types, Properties, and Classes + +Next, we introduce the concept of a class. +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. +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. ```mamba -from ipaddress import IPv4Address +class MatrixErr(def message: Str): Exception(message) + +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] + + # Trace of the matrix (a + d) + def pure trace(fin self) -> Int := self.a + self.d + + # Determinant recomputation (pure function) + def pure determinant(fin self) -> Int := self.a * self.d - self.b * self.c + + 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 initial value. + def reset(self) := do + self.a := 1 + self.b := 0 + self.c := 0 + self.d := 1 + end +end +``` -type ConnMyServer: MyServer when self.is_connected -type DisConnMyServer: MyServer when not self.is_connected +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 ServerErr(def message: Str): Exception(message) +`class MyClass() := where end` -class MyServer(self: DisConnMyServer, def ip_address: IPv4Address) - def is_connected: Bool := False - def _last_message: Str? := None +The body of the class is optional, i.e. one can create "just" a data class. +As for constructor arguments: - def last_sent(self) -> Str ! ServerErr := - if self.last_message != None then - self._last_message - else - ! ServerError("No last message!") +- 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. + 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. - def connect(self: DisConnMyServer) := self.is_connected := True +As for the class body - def send(self: ConnMyServer, message: Str) := self._last_message := message +- 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. - def disconnect(self: ConnMyServer) := self.is_connected := False +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 + def x: Int := ORIGIN_X + def y: Int := ORIGIN_Y + + 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) := 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 ``` -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. +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. +For those familiar with object oriented programming, we favour a trait based system over inheritance (like Rust, Mamba doesn't have inheritance). -Also Notice how above, we 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. +Consider example with iterators (which briefly showcases language generics): ```mamba -import ipaddress -from server import MyServer +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 + def _current: Int := _start +end + +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 + def value := self._current + self._current := self._current + 1 + value + end else None +end +``` -def fin some_ip := ipaddress.ip_address("151.101.193.140") -def my_server := MyServer(some_ip) +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 := where for `. -# The default state of http_server is DisconnectedHTTPServer, so we don't need to check that here -http_server.connect() +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. +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: -# 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!") +E.g. -print("last message sent before disconnect: \"{my_server.last_sent}\".") -if my_server isa ConnectedMyServer then my_server.disconnect() +```mamba +trait Ordered[T]: Equality, Comparable +``` + +### 🗃 Type refinement (🇻 0.4.1+) (Experimental!) + +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 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. + +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 where self >= 0 and self <= 100 or self mod 2 = 0 +``` + +_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 +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: @@ -220,47 +351,115 @@ 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 - self >= 0 ! NegativeError("Must be greater than 0") +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 + +```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`. -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: +Lets expand our matrix example from above, and rewrite it slightly: + +```mamba +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 + def _last_op: Str? := None + + def determinant(fin self) -> Float := self.a * self.d - self.b * self.c + + 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`. +This is because we performed a check in the if condition. + +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. + +```mamba +def m := Matrix(1.0, 2.0, 3.0, 4.0) + +if m isa InvertibleMatrix then do + def m_inv := m.inverse() + print("Original matrix: {m}") + print("Inverse: {m_inv}") +end else + print("Matrix is singular (not invertible).") -- 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. +def last_op = m.last_op()! +print("Last operation was: {last_op}") +``` -The goal of the compiler becomes: +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. + +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. +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. +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+) -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. +For use to be able to compare two instances, the instance must implement the `Equality` trait (which we showed above). -- Read non-final properties of `self`. +By default, functions are not pure. +When we mark a function `pure`, restrictions are enforced by the language: + +- `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 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. @@ -271,79 +470,272 @@ 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 := do 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 +end ``` +### 🤚 Total functions (🇻 x+) + +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. + + 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` + - `RangeTo` : `..b` + - `Range` : `a..b` + - `RangeInclusive` : `a..=b` + +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 Fibonacci sequence: + +```mamba +## Fibonacci, implemented using recursion and not dynamic programming +def total pure fibonacci(x: PosInt) -> Int := match x where + 0 => 0 + 1 => 1 + n => fibonacci(n - 1) + fibonacci(n - 2) +end +``` + +This would, with some substitution magic, give the following _call tree_ (showing only the important parts): + +``` + fibonacci(x) + | + + # addition operator + / \ +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. +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. + +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 `Measurable`. + +```mamba +# if we implement strictly decreasing, we must implement measure +# These are non-overridable method which uses this measure +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 primitive `Int`. + +```mamba +# Measure for int just returns self +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 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. +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. +# 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 +# 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. +_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: + +- 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. + +### Meta functions (🇻 x+) + +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-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 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 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 meta in the form of code, without re-calculating it each time at runtime. + +- 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 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. +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. +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. -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() ! 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 -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. -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() - 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}.") ``` -I we don't want to handle the exception cases here, we just append a `!` to a function. -This means that this exception must be handeld further up the stack. +We can also opt to not do any error handling, making the type of `a`: -```mamba -def a := function_may_throw_err()! +``` +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, 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 handled further up the stack. +```mamba +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() ! 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. -# if `function_may_throw_err` returned an exception, we will never reach this point 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() ! where + err: MyOtherErr => print("We have a problem: {err.message}.") +end recover do + print("cleaning up resource") + some_cleanup_function() +end +``` + ## 💻 The Command Line Interface ``` diff --git a/docs/spec/characters.md b/docs/spec/characters.md index 5d7a9bb74..4953e74a1 100644 --- a/docs/spec/characters.md +++ b/docs/spec/characters.md @@ -44,17 +44,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 @@ -76,8 +65,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/src/check/ast/mod.rs b/src/check/ast/mod.rs index 3d90a5735..34efc1bc9 100644 --- a/src/check/ast/mod.rs +++ b/src/check/ast/mod.rs @@ -271,10 +271,6 @@ pub enum NodeTy { Sqrt { expr: Box, }, - BAnd { - left: Box, - right: Box, - }, BOr { left: Box, right: Box, diff --git a/src/check/ast/node.rs b/src/check/ast/node.rs index 60b266ac0..6a85c53b5 100644 --- a/src/check/ast/node.rs +++ b/src/check/ast/node.rs @@ -312,29 +312,6 @@ impl From<(&Node, &Finished)> for NodeTy { Node::Sqrt { expr } => NodeTy::Sqrt { expr: Box::from(ASTTy::from((expr, finished))), }, - Node::BAnd { left, right } => NodeTy::BAnd { - left: Box::from(ASTTy::from((left, finished))), - right: Box::from(ASTTy::from((right, finished))), - }, - Node::BOr { left, right } => NodeTy::BOr { - left: Box::from(ASTTy::from((left, finished))), - right: Box::from(ASTTy::from((right, finished))), - }, - Node::BXOr { left, right } => NodeTy::BXOr { - left: Box::from(ASTTy::from((left, finished))), - right: Box::from(ASTTy::from((right, finished))), - }, - Node::BOneCmpl { expr } => NodeTy::BOneCmpl { - expr: Box::from(ASTTy::from((expr, finished))), - }, - Node::BLShift { left, right } => NodeTy::BLShift { - left: Box::from(ASTTy::from((left, finished))), - right: Box::from(ASTTy::from((right, finished))), - }, - Node::BRShift { left, right } => NodeTy::BRShift { - left: Box::from(ASTTy::from((left, finished))), - right: Box::from(ASTTy::from((right, finished))), - }, Node::Le { left, right } => NodeTy::Le { left: Box::from(ASTTy::from((left, finished))), right: Box::from(ASTTy::from((right, finished))), diff --git a/src/check/constrain/generate/call.rs b/src/check/constrain/generate/call.rs index 0d9e3cdd2..bc6e2deec 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/check/constrain/generate/mod.rs b/src/check/constrain/generate/mod.rs index fa76a5a80..4e933553b 100644 --- a/src/check/constrain/generate/mod.rs +++ b/src/check/constrain/generate/mod.rs @@ -86,10 +86,6 @@ pub fn generate( AddU { .. } | SubU { .. } => gen_op(ast, env, ctx, constr), Sqrt { .. } => gen_op(ast, env, ctx, constr), - BOneCmpl { .. } => gen_op(ast, env, ctx, constr), - BAnd { .. } | BOr { .. } | BXOr { .. } => gen_op(ast, env, ctx, constr), - BLShift { .. } | BRShift { .. } => gen_op(ast, env, ctx, constr), - Is { .. } | IsN { .. } | IsA { .. } | IsNA { .. } => gen_op(ast, env, ctx, constr), And { .. } | Or { .. } | Not { .. } => gen_op(ast, env, ctx, constr), diff --git a/src/check/constrain/generate/operation.rs b/src/check/constrain/generate/operation.rs index b0cf50ef2..ec0ed5ef5 100644 --- a/src/check/constrain/generate/operation.rs +++ b/src/check/constrain/generate/operation.rs @@ -92,52 +92,6 @@ pub fn gen_op( generate(expr, env, ctx, constr) } - Node::BOneCmpl { expr } => { - constr.add( - "binary compliment", - &Expected::from(expr), - &Expected::any(expr.pos), - env, - ); - generate(expr, env, ctx, constr)?; - Ok(env.clone()) - } - Node::BAnd { left, right } | Node::BOr { left, right } | Node::BXOr { left, right } => { - constr.add( - "binary logical op", - &Expected::from(left), - &Expected::any(left.pos), - env, - ); - constr.add( - "binary logical op", - &Expected::from(right), - &Expected::any(right.pos), - env, - ); - - bin_op(left, right, env, ctx, constr) - } - Node::BLShift { left, right } | Node::BRShift { left, right } => { - constr.add( - "binary shift", - &Expected::from(left), - &Expected::any(right.pos), - env, - ); - - let name = Name::from(INT); - let l_exp = Expected::from(right); - constr.add( - "binary shift", - &l_exp, - &Expected::new(right.pos, &Type { name }), - env, - ); - - bin_op(left, right, env, ctx, constr) - } - Node::Is { left, right } | Node::IsN { left, right } => { let bool = Expected::new( ast.pos, diff --git a/src/generate/ast/mod.rs b/src/generate/ast/mod.rs index 928e69558..00caff0f0 100644 --- a/src/generate/ast/mod.rs +++ b/src/generate/ast/mod.rs @@ -388,43 +388,6 @@ fn to_py(core: &Core, ind: usize) -> String { } Core::Sqrt { expr } => format!("math.sqrt({})", to_py(expr.as_ref(), ind)), - Core::BAnd { left, right } => { - format!( - "{} & {}", - to_py(left.as_ref(), ind), - to_py(right.as_ref(), ind) - ) - } - Core::BOr { left, right } => { - format!( - "{} | {}", - to_py(left.as_ref(), ind), - to_py(right.as_ref(), ind) - ) - } - Core::BXOr { left, right } => { - format!( - "{} ^ {}", - to_py(left.as_ref(), ind), - to_py(right.as_ref(), ind) - ) - } - Core::BOneCmpl { expr } => format!("~{}", to_py(expr, ind)), - Core::BLShift { left, right } => { - format!( - "{} << {}", - to_py(left.as_ref(), ind), - to_py(right.as_ref(), ind) - ) - } - Core::BRShift { left, right } => { - format!( - "{} >> {}", - to_py(left.as_ref(), ind), - to_py(right.as_ref(), ind) - ) - } - Core::Return { expr } => format!("return {}", to_py(expr.as_ref(), ind)), Core::For { expr, col, body } => format!( diff --git a/src/generate/ast/node.rs b/src/generate/ast/node.rs index 8258b1ca8..96de22aa3 100644 --- a/src/generate/ast/node.rs +++ b/src/generate/ast/node.rs @@ -209,29 +209,6 @@ pub enum Core { Sqrt { expr: Box, }, - BAnd { - left: Box, - right: Box, - }, - BOr { - left: Box, - right: Box, - }, - BXOr { - left: Box, - right: Box, - }, - BOneCmpl { - expr: Box, - }, - BLShift { - left: Box, - right: Box, - }, - BRShift { - left: Box, - right: Box, - }, For { expr: Box, col: Box, @@ -316,8 +293,6 @@ pub enum CoreOp { MulAssign, DivAssign, PowAssign, - BLShiftAssign, - BRShiftAssign, } impl TryFrom<(&ASTTy, &NodeOp)> for CoreOp { @@ -330,8 +305,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}"))), } @@ -388,8 +361,6 @@ impl Display for CoreOp { CoreOp::MulAssign => "*=", CoreOp::DivAssign => "/=", CoreOp::PowAssign => "**=", - CoreOp::BLShiftAssign => "<<=", - CoreOp::BRShiftAssign => ">>=", } ) } diff --git a/src/generate/convert/mod.rs b/src/generate/convert/mod.rs index e0b71a3b6..655027a86 100644 --- a/src/generate/convert/mod.rs +++ b/src/generate/convert/mod.rs @@ -208,30 +208,6 @@ pub fn convert_node(ast: &ASTTy, imp: &mut Imports, state: &State, ctx: &Context right: Box::from(convert_node(right, imp, state, ctx)?), }, - NodeTy::BAnd { left, right } => Core::BAnd { - left: Box::from(convert_node(left, imp, state, ctx)?), - right: Box::from(convert_node(right, imp, state, ctx)?), - }, - NodeTy::BOr { left, right } => Core::BOr { - left: Box::from(convert_node(left, imp, state, ctx)?), - right: Box::from(convert_node(right, imp, state, ctx)?), - }, - NodeTy::BXOr { left, right } => Core::BXOr { - left: Box::from(convert_node(left, imp, state, ctx)?), - right: Box::from(convert_node(right, imp, state, ctx)?), - }, - NodeTy::BOneCmpl { expr } => Core::BOneCmpl { - expr: Box::from(convert_node(expr, imp, state, ctx)?), - }, - NodeTy::BLShift { left, right } => Core::BLShift { - left: Box::from(convert_node(left, imp, state, ctx)?), - right: Box::from(convert_node(right, imp, state, ctx)?), - }, - NodeTy::BRShift { left, right } => Core::BRShift { - left: Box::from(convert_node(left, imp, state, ctx)?), - right: Box::from(convert_node(right, imp, state, ctx)?), - }, - NodeTy::AddU { expr } => Core::AddU { expr: Box::from(convert_node(expr, imp, state, ctx)?), }, diff --git a/src/lib.rs b/src/lib.rs index fea3f85b3..70e7e7ed7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,6 @@ impl From<&Arguments> for PipelineArguments { /// /// For each mamba source, a path can optionally be given for display in error /// messages. This path is not necessary however. -#[allow(clippy::result_large_err)] pub fn mamba_to_python( source: &[(String, Option)], source_dir: &PathBuf, @@ -154,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/ast/mod.rs b/src/parse/ast/mod.rs index a84106c34..5b1a0dfb4 100644 --- a/src/parse/ast/mod.rs +++ b/src/parse/ast/mod.rs @@ -250,29 +250,6 @@ pub enum Node { Sqrt { expr: Box, }, - BAnd { - left: Box, - right: Box, - }, - BOr { - left: Box, - right: Box, - }, - BXOr { - left: Box, - right: Box, - }, - BOneCmpl { - expr: Box, - }, - BLShift { - left: Box, - right: Box, - }, - BRShift { - left: Box, - right: Box, - }, Le { left: Box, right: Box, diff --git a/src/parse/ast/node.rs b/src/parse/ast/node.rs index b5cd6a4f4..1c89d7aa7 100644 --- a/src/parse/ast/node.rs +++ b/src/parse/ast/node.rs @@ -133,12 +133,6 @@ impl Display for Node { Node::Mod { left, right } => format!("{} mod {}", left.node, right.node), Node::Pow { left, right } => format!("{} ^ {}", left.node, right.node), Node::Sqrt { expr } => format!("sqrt {}", expr.node), - Node::BAnd { left, right } => format!("{} _and_ {}", left.node, right.node), - Node::BOr { left, right } => format!("{} _or_ {}", left.node, right.node), - Node::BXOr { left, right } => format!("{} _xor_ {}", left.node, right.node), - Node::BOneCmpl { expr } => format!("_not {}", expr.node), - Node::BLShift { left, right } => format!("{} << {}", left.node, right.node), - Node::BRShift { left, right } => format!("{} >> {}", left.node, right.node), Node::Le { left, right } => format!("{} < {}", left.node, right.node), Node::Ge { left, right } => format!("{} > {}", left.node, right.node), Node::Leq { left, right } => format!("{} <= {}", left.node, right.node), @@ -392,29 +386,6 @@ impl Node { Node::Sqrt { expr } => Node::Sqrt { expr: Box::from(expr.map(mapping)), }, - Node::BAnd { left, right } => Node::BAnd { - left: Box::from(left.map(mapping)), - right: Box::from(right.map(mapping)), - }, - Node::BOr { left, right } => Node::BOr { - left: Box::from(left.map(mapping)), - right: Box::from(right.map(mapping)), - }, - Node::BXOr { left, right } => Node::BXOr { - left: Box::from(left.map(mapping)), - right: Box::from(right.map(mapping)), - }, - Node::BOneCmpl { expr } => Node::BOneCmpl { - expr: Box::from(expr.map(mapping)), - }, - Node::BLShift { left, right } => Node::BLShift { - left: Box::from(left.map(mapping)), - right: Box::from(right.map(mapping)), - }, - Node::BRShift { left, right } => Node::BRShift { - left: Box::from(left.map(mapping)), - right: Box::from(right.map(mapping)), - }, Node::Le { left, right } => Node::Le { left: Box::from(left.map(mapping)), right: Box::from(right.map(mapping)), @@ -843,16 +814,6 @@ impl Node { right: rr, }, ) => ll.same_value(rl) && lr.same_value(rr), - ( - Node::BOr { - left: ll, - right: lr, - }, - Node::BOr { - left: rl, - right: rr, - }, - ) => ll.same_value(rl) && lr.same_value(rr), ( Node::Mod { left: ll, @@ -884,47 +845,6 @@ impl Node { right: rr, }, ) => ll.same_value(rl) && lr.same_value(rr), - ( - Node::BAnd { - left: ll, - right: lr, - }, - Node::BAnd { - left: rl, - right: rr, - }, - ) => ll.same_value(rl) && lr.same_value(rr), - ( - Node::BXOr { - left: ll, - right: lr, - }, - Node::BXOr { - left: rl, - right: rr, - }, - ) => ll.same_value(rl) && lr.same_value(rr), - (Node::BOneCmpl { expr: l }, Node::BOneCmpl { expr: r }) => l.same_value(r), - ( - Node::BLShift { - left: ll, - right: lr, - }, - Node::BLShift { - left: rl, - right: rr, - }, - ) => ll.same_value(rl) && lr.same_value(rr), - ( - Node::BRShift { - left: ll, - right: lr, - }, - Node::BRShift { - left: rl, - right: rr, - }, - ) => ll.same_value(rl) && lr.same_value(rr), ( Node::Leq { left: ll, @@ -1169,12 +1089,6 @@ impl Node { | Node::Mod { .. } | Node::Pow { .. } | Node::Sqrt { .. } - | Node::BAnd { .. } - | Node::BOr { .. } - | Node::BXOr { .. } - | Node::BOneCmpl { .. } - | Node::BLShift { .. } - | Node::BRShift { .. } | Node::Le { .. } | Node::Ge { .. } | Node::Leq { .. } @@ -1782,39 +1696,6 @@ mod test { left: left.clone(), right: right.clone() }); - two_ast!(Node::BAnd { - left: left.clone(), - right: right.clone() - }); - two_ast!(Node::BOr { - left: left.clone(), - right: right.clone() - }); - two_ast!(Node::BXOr { - left: left.clone(), - right: right.clone() - }); - - two_ast!(Node::BAnd { - left: left.clone(), - right: right.clone() - }); - two_ast!(Node::BOr { - left: left.clone(), - right: right.clone() - }); - two_ast!(Node::BXOr { - left: left.clone(), - right: right.clone() - }); - two_ast!(Node::BLShift { - left: left.clone(), - right: right.clone() - }); - two_ast!(Node::BRShift { - left: left.clone(), - right: right.clone() - }); two_ast!(Node::Le { left: left.clone(), @@ -1879,7 +1760,6 @@ mod test { two_ast!(Node::AddU { expr: expr.clone() }); two_ast!(Node::SubU { expr: expr.clone() }); two_ast!(Node::Sqrt { expr: expr.clone() }); - two_ast!(Node::BOneCmpl { expr: expr.clone() }); two_ast!(Node::Not { expr: expr.clone() }); } @@ -2170,35 +2050,6 @@ mod test { expr: right.clone() } .is_expression()); - assert!(Node::BAnd { - left: left.clone(), - right: right.clone() - } - .is_expression()); - assert!(Node::BOr { - left: left.clone(), - right: right.clone() - } - .is_expression()); - assert!(Node::BXOr { - left: left.clone(), - right: right.clone() - } - .is_expression()); - assert!(Node::BOneCmpl { - expr: right.clone() - } - .is_expression()); - assert!(Node::BLShift { - left: left.clone(), - right: right.clone() - } - .is_expression()); - assert!(Node::BRShift { - left: left.clone(), - right: right.clone() - } - .is_expression()); assert!(Node::Le { left: left.clone(), right: right.clone() @@ -2329,35 +2180,6 @@ mod test { expr: right.clone() } .is_operator()); - assert!(Node::BAnd { - left: left.clone(), - right: right.clone() - } - .is_operator()); - assert!(Node::BOr { - left: left.clone(), - right: right.clone() - } - .is_operator()); - assert!(Node::BXOr { - left: left.clone(), - right: right.clone() - } - .is_operator()); - assert!(Node::BOneCmpl { - expr: right.clone() - } - .is_operator()); - assert!(Node::BLShift { - left: left.clone(), - right: right.clone() - } - .is_operator()); - assert!(Node::BRShift { - left: left.clone(), - right: right.clone() - } - .is_operator()); assert!(Node::Le { left: left.clone(), right: right.clone() diff --git a/src/parse/block.rs b/src/parse/block.rs index 2a0f8ca33..38f45b0f9 100644 --- a/src/parse/block.rs +++ b/src/parse/block.rs @@ -11,47 +11,42 @@ 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(&[Token::Dedent], &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 && lex.token != Token::Dedent) { + Err(Box::from(expected_one_of( + &[Token::NL, Token::Dedent], + 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) } diff --git a/src/parse/call.rs b/src/parse/call.rs index 013607104..332ba7ea6 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 7c75c2e41..44bff0607 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 a69637e59..09497e4e7 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/iterator.rs b/src/parse/iterator.rs index 66a8907c6..2bac42f51 100644 --- a/src/parse/iterator.rs +++ b/src/parse/iterator.rs @@ -197,7 +197,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 3f511a35a..0a3261156 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; @@ -32,14 +30,6 @@ pub fn tokenize(input: &str) -> LexResult { 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) @@ -90,17 +80,13 @@ 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, - }, ] ); } #[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,9 +97,6 @@ mod tests { Token::MulAssign, Token::DivAssign, Token::PowAssign, - Token::BRShiftAssign, - Token::BLShiftAssign, - Token::Eof, ] ); } @@ -157,17 +140,13 @@ 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, - }, ] ); } #[test] fn comparison() { - let source = String::from("< > <= >= = != is i"); + let source = String::from("< > <= >= = != i"); let tokens = tokenize(&source).unwrap(); assert_eq!( tokens, @@ -197,17 +176,9 @@ mod tests { 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)), + pos: Position::new(CaretPos::new(1, 16), CaretPos::new(1, 17)), token: Token::Id(String::from("i")), }, - Lex { - pos: Position::new(CaretPos::new(1, 21), CaretPos::new(1, 21)), - token: Token::Eof, - }, ] ); } @@ -218,22 +189,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")), + }]], + ), + },] ); } @@ -243,40 +208,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, + }, + ]], + ), + },] ); } @@ -286,32 +245,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/token.rs b/src/parse/lex/token.rs index 514c0f328..bdc61b92f 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, @@ -50,8 +49,6 @@ pub enum Token { MulAssign, DivAssign, PowAssign, - BLShiftAssign, - BRShiftAssign, Def, Real(String), @@ -74,20 +71,12 @@ pub enum Token { Mod, Sqrt, - BAnd, - BOr, - BXOr, - BOneCmpl, - BLShift, - BRShift, - Ge, Geq, Le, Leq, Eq, - Is, Neq, And, Or, @@ -127,9 +116,6 @@ pub enum Token { Question, Pass, - Comment(String), - - Eof, } impl Token { @@ -157,7 +143,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"), @@ -175,8 +160,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}"), @@ -200,20 +183,12 @@ 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, "<"), 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"), @@ -253,9 +228,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 d3c4970be..ba56ccf89 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), }, @@ -81,27 +70,16 @@ 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()); + it.next().unwrap(); } - create(state, Token::Comment(comment)) + Ok(vec![]) } '!' => 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(); @@ -269,8 +247,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, diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b4fd78ab9..d57f09716 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; @@ -31,22 +31,10 @@ 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)?; - 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 279406b6e..6c7d8295a 100644 --- a/src/parse/operation.rs +++ b/src/parse/operation.rs @@ -30,27 +30,7 @@ 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 { @@ -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()), @@ -90,23 +64,13 @@ 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::In => bin_op!(it, parse_level_5, In, arithmetic.clone(), "in"), _ => Ok(arithmetic.clone()), }, Ok(arithmetic.clone()), @@ -199,14 +163,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) } @@ -437,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"); @@ -666,125 +582,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 ba1df9ff3..797b7bb16 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"))); } @@ -202,9 +192,7 @@ pub fn parse_return(it: &mut LexIterator) -> ParseResult { 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() - { + } else if it.peek_if(&|lex| lex.token == Token::Dedent) || it.peek_next().is_none() { let node = Node::ReturnEmpty; return Ok(Box::from(AST::new(start, node))); } diff --git a/tests/check/invalid.rs b/tests/check/invalid.rs index 52eca0d07..981cede70 100644 --- a/tests/check/invalid.rs +++ b/tests/check/invalid.rs @@ -131,7 +131,6 @@ use mamba::parse::ast::AST; #[test_case("operation", "in_dict_wrong_ty" => matches Err(_))] #[test_case("operation", "in_list_wrong_ty" => matches Err(_))] #[test_case("operation", "in_set_wrong_ty" => matches Err(_))] -#[test_case("operation", "isa_not_id" => matches Err(_))] #[test_case("operation", "reassign_to_nullable" => matches Err(_))] #[test_case("operation", "reassign_to_undefined" => matches Err(_))] #[test_case("operation", "string_minus" => matches Err(_))] diff --git a/tests/resource/invalid/type/operation/isa_not_id.mamba b/tests/resource/invalid/type/operation/isa_not_id.mamba deleted file mode 100644 index 32caa1a82..000000000 --- a/tests/resource/invalid/type/operation/isa_not_id.mamba +++ /dev/null @@ -1 +0,0 @@ -10 isa 10 diff --git a/tests/resource/valid/class/assign_types_double_nested.mamba b/tests/resource/valid/class/assign_types_double_nested.mamba index 395398552..18f922a45 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 335d53f8c..02137c527 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 61909e4d8..507c4ae6a 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 07ce66ab6..a14310ec7 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 bba089e63..903082ce1 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 f83bde3fb..2a46d623a 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 83d1b13b1..874f71a87 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 002a42adb..df94dd215 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/assign_types_no_annotation.mamba b/tests/resource/valid/operation/assign_types_no_annotation.mamba index d2feff385..ab228efcb 100644 --- a/tests/resource/valid/operation/assign_types_no_annotation.mamba +++ b/tests/resource/valid/operation/assign_types_no_annotation.mamba @@ -6,6 +6,3 @@ a *= 6 a /= 7 a ^= 2 - -a <<= 10 -a >>= 5 diff --git a/tests/resource/valid/operation/assign_types_no_annotation_check.py b/tests/resource/valid/operation/assign_types_no_annotation_check.py index 481233b18..ef01b92f9 100644 --- a/tests/resource/valid/operation/assign_types_no_annotation_check.py +++ b/tests/resource/valid/operation/assign_types_no_annotation_check.py @@ -6,6 +6,3 @@ a /= 7 a **= 2 - -a <<= 10 -a >>= 5 diff --git a/tests/resource/valid/operation/boolean.mamba b/tests/resource/valid/operation/boolean.mamba index 0ea7b31c1..d30c5bebe 100644 --- a/tests/resource/valid/operation/boolean.mamba +++ b/tests/resource/valid/operation/boolean.mamba @@ -14,6 +14,3 @@ def g := False def h := True def i := True def j := False - -c is d -g isa Exception diff --git a/tests/resource/valid/operation/boolean_check.py b/tests/resource/valid/operation/boolean_check.py index d3f805bde..dba84033a 100644 --- a/tests/resource/valid/operation/boolean_check.py +++ b/tests/resource/valid/operation/boolean_check.py @@ -14,6 +14,3 @@ h: bool = True i: bool = True j: bool = False - -c is d -isinstance(g, Exception)