Merge remote-tracking branch 'origin/main' into feature/sc-678/add-run-c-and-run-cuda-commands-to-bend

This commit is contained in:
Nicolas Abril 2024-05-14 23:38:28 +02:00
commit 46ec41141e
12 changed files with 659 additions and 183 deletions

View File

@ -2,13 +2,13 @@
This file provides a reference of each possible syntax of bend programming language.
Click [here](#bend-syntax) to see the bend syntax.
Click [here](#imp-syntax) to see the syntax for "imp", the variant of bend that looks like an imperative language like python.
Click [here](#core-syntax) to see the core syntax.
Click [here](#fun-syntax) to see the syntax for "fun", the variant of bend that looks like a functional language like Haskell or ML.
<div id="bend-syntax"></div>
<div id="imp-syntax"></div>
# Bend Syntax
# Imp Syntax
## Top-level definitions
@ -25,10 +25,12 @@ def main:
return add(40, 2)
```
A definition is composed by a name, a sequence of parameters and a body.
A function definition is composed by a name, a sequence of parameters and a body.
A top-level name can be anything matching the regex `[A-Za-z0-9_.-/]+`, except it can't have `__` (used for generated names).
The last statement of each branch of the function must be a `return`.
### Type
Defines an algebraic data type.
@ -59,22 +61,28 @@ Read [defining data types](./defining-data-types.md) to know more.
### Assignment
```python
value = 2;
return value;
value = 2
return value
(first, second) = (1, 2);
return second;
(first, second) = (1, 2)
return second
{x y} = {2 3};
{x y} = {2 3}
```
Assigns a value to a variable, it's possible to pattern match match tuples and superpositions.
Assigns a value to a variable.
It's possible to assign to a pattern, like a tuple or superposition, which will destructure the value returned by the expression.
```python
(first, second) = (1, 2)
```
### In-Place Operation
```python
x += 1;
return x;
x += 1
return x
```
The in-place operation does an infix operation and re-assigns a variable.
@ -89,23 +97,52 @@ The operations are:
### Return
```python
return "hello";
return "hello"
```
Returns the following expression. All paths or branches should end with a return.
Returns the expression that follows. The last statement of each branch of a function must be a `return`.
```py
// Allowed, all branches return
def max(a, b):
if a > b:
return a
else:
return b
```
```py
// Not allowed, early return
def Foo(x):
if test_condition(x):
return "err"
else:
y = map(x)
return y
```
```py
// Not allowed, one of the branches doesn't return
def Foo(a, b):
if a < b:
return a
else:
c = a + b
```
### If
```python
if condition:
return 0;
return 0
else:
return 1;
return 1
```
A branching statement where `else` is mandatory.
The condition must return an `u24` and it is equivalent to a switch statement:
The condition must return a `u24` number, where 0 will run the `else` branch and any other value will return the first one.
It is equivalent to a switch statement:
```
switch _ = condition:
@ -120,25 +157,25 @@ switch _ = condition:
```python
switch x = 4:
case 0:
return "Zero";
return 0
case 1:
return "One";
return 0
case _:
return "Not zero or one";
return x-2
```
A switch for native numbers, the pattern matching cases must start from `0` up to `_` sequentially.
It is possible to bind a variable name to the matching value, it allows the access to the predecessor `x-2` (in this case) or `bound_var-next_num` (in the general case).
In the last arm, the predecessor value is available with the name `x-2` (in this case) or `bound_var-next_num` (in the general case).
### Match
```python
match x = Option/none:
case Option/some:
return x.value;
y = x.value
case Option/none:
return 0;
y = 0
```
A pattern matching statement, the cases must be the constructor names of the matching value.
@ -150,9 +187,9 @@ It is possible to bind a variable name to the matching value. The fields of the
```python
fold x = Tree/leaf:
case Tree/node:
return x.value + x.left + x.right;
return x.value + x.left + x.right
case Tree/leaf:
return 0;
return 0
```
A fold statement. Reduces the given value with the given match cases.
@ -179,23 +216,28 @@ fold(Tree/Leaf)
Bend can be used to create recursive data structures:
```rust
bend x = 0 while x < 10:
left = go(x + 1);
right = go(x + 1);
return Tree/Node(left, right);
then:
return Tree/Leaf(x);
bend x = 0:
when x < 10:
left = go(x + 1)
right = go(x + 1)
y = Tree/Node(left, right)
else:
y = Tree/Leaf(x)
```
Which binds a variable to the return of an inline recursive function.
The function `go` is available inside the bend body and calls it recursively.
The function `go` is available inside the `when` arm of the `bend` and calls it recursively.
It is possible to initialize multiple variables:
It is possible to pass multiple state variables, which can be initialized:
```python
bend x = 1, y = 2 ... while condition(x, y, ...):
bend x = 1, y = 2 ...:
when condition(x, y, ...):
...
```
When calling `go`, the function must receive the same number of arguments as the number of state variables.
It is equivalent to this inline recursive function:
```python
@ -210,16 +252,34 @@ def bend(x, y, ...):
### Do
```python
do Result.bind:
x <- safe_div(2, 0);
return x;
do Result:
x <- safe_div(2, 0)
return x
```
A monadic do block.
Where `x <- ...` performs a monadic operation.
Other statements are allowed inside the `do` block.
Expects `Result` to be a type defined with `type` and a function `Result/bind` to be defined.
The monadic bind function should be of type `(Result a) -> (a -> Result b) -> Result b`, like this:
```
def Result/bind(res, nxt):
match res:
case Result/ok:
return nxt(res.value)
case Result/err:
return res
```
Other statements are allowed inside the `do` block and it can both return a value at the end and bind a variable, like branching statements do.
```
// Also ok:
do Result:
x <- safe_div(2, 0);
y = x
return y
```
## Expressions
@ -236,10 +296,12 @@ A variable can be anything matching the regex `[A-Za-z0-9_.-/]+`.
A variable is a name for some immutable expression. It is possible to rebind variables with the same name.
```python
x = 1;
x = x + 1;
x = 1
x = x + 1
```
Note that `-` is also used for negative numbers and as the numeric operator. Bend's grammar is greedily parsed from left to right, meaning that `x-3` always represents a name and not `x - 3` or a sequence of expressions like in `[x -3]`.
### Lambdas
```python
@ -266,7 +328,7 @@ Like lambdas, with the exception that the variable starts with a `$` sign. Every
Unscoped variables are not transformed and linearized like normal scoped variables.
Read [using scopeless lambdas](/docs/using-scopeless-lambdas.md) to know more about.
Read [using scopeless lambdas](/docs/using-scopeless-lambdas.md) to know more about their behavior.
### Function Call
@ -274,11 +336,19 @@ Read [using scopeless lambdas](/docs/using-scopeless-lambdas.md) to know more ab
callee(arg_1, arg_2, arg_n)
```
A call is written with a callee followed by a list of arguments.
A call is written with a callee followed by a list of arguments. Arguments can be optionally separated by `,`.
The effect of a function call is to substitute the callee with it's body and replace the arguments by the passed variables.
Accepts partial applications.
The called function can be any expression and it supports partial applications.
Optionally, if you call a function by its name, you can used named arguments:
```python
callee(expr1, expr2, arg4 = expr3, arg3 = expr4)
```
In case named arguments are used, they must come after the positional arguments and the function must be called with exactly the number of arguments of its definition.
### Tuple
@ -294,7 +364,7 @@ A Tuple is surrounded by `(` `)` and should contain 2 or more elements. Elements
{1 2 3}
```
A superposition of values is defined using `{` `}` with at least 2 expressions inside.
A superposition of values is defined using `{` `}` with at least 2 expressions inside. Elements can be optionally separated by `,`.
Read [sups and dups](./dups-and-sups.md) to know more.
@ -344,6 +414,17 @@ A Character is surrounded with `'`. Accepts unicode characters, unicode escapes
Only supports unicode codepoints up to `0xFFFFFF`.
### Symbol Literal
```python
// Becomes 2146 (33 << 6 + 34)
`hi`
```
A Symbol encodes a up to 4 base64 characters as a `u24` number. It is surrounded by `\``.
Empty characters are interpreted as `A` which has value 0, meaning that `B` is the same as `AAAB`.
### String Literal
```python
@ -397,7 +478,7 @@ fold list:
<div id="core-syntax"></div>
# Core Syntax
# Fun Syntax
## Top-level definitions
@ -410,15 +491,18 @@ Name (Ctr1 sub_arg1 sub_arg2) arg3 = rule0_body
Name Ctr2 arg3 = rule1_body
```
### Definitions
A top-level name can be anything matching the regex `[A-Za-z0-9_.-/]+`, except it can't have `__` (used for generated names).
Definitions can have multiple rules pattern matching each defined argument.
### Function Definitions
A function definition is composed of a sequence of pattern matching equations.
Each rule is the name of the function, a sequence of patterns and then the body.
```rust
identity x = x
Bool.neg True = False
Bool.neg False = True
(Bool.neg True) = False
(Bool.neg False) = True
MapMaybe (Some val) f = (Some (f val))
MapMaybe None f = None
@ -426,10 +510,6 @@ MapMaybe None f = None
Pair.get (fst, snd) f = (f fst snd)
```
A top-level name can be anything matching the regex `[A-Za-z0-9_.-/]+`, except it can't have `__` (used for generated names).
A function definition is composed of a sequence of rules, where a rule is the name of the function, a sequence of patterns and then the body.
A rule pattern can be:
- A variable.
@ -437,8 +517,19 @@ A rule pattern can be:
- A constructor.
- A tuple.
- A superposition.
- A wildcard `*`.
The rule body is a `term`, there is no statements in the language.
And the builtin types that desugar to one of the above:
- A list (becomes a constructor).
- A string (becomes a constructor).
- A natural number (becomes a constructor).
- A character (becomes a number).
- A symbol (becomes a number);
Unscoped variables can't be defined in a rule pattern.
The rule body is a term, there are no statements in the Fun variant of Bend.
Read [pattern matching](./pattern-matching.md) to learn about what exactly the rules for pattern matching equations are.
@ -457,6 +548,8 @@ data Tree
Each constructor is defined by a name followed by its fields. The `~` notation describes a recursive field.
The constructors inherit the name of their types and become functions (`Tree/Node` and `Tree/Leaf` in this case).
## Terms
### Variables
@ -484,21 +577,24 @@ let x = (+ x 1)
Lambdas represents anonymous inline functions, it can be written with `λ` or `@` followed by a pattern and a term.
A pattern is equivalent to a let:
A tuple or duplication pattern is equivalent to a lambda followed by a `let`.
```rust
λ(fst, snd) snd
λa let (fst, snd) = a; snd
λa let {x y} = a; x
λ{x y} (x y)
λa let {x y} = a; (x y)
```
### Unscoped Lambdas and Variables
### Unscoped Variables
```
λ$x $x
```
Same as lambdas, with the exception that the variable starts with a `$` sign. Every unscoped variable in a function must have a unique name and must be used exactly once.
Like a normal scoped variable, but starts with a `$` sign. Every unscoped variable in a function must have a unique name and must be used exactly once.
They can be defined anywhere a scoped variable would be defined in a term, like in a lambda or a `let`.
Unscoped variables are not transformed and linearized like normal scoped variables.
@ -598,11 +694,11 @@ It is desugared according to the chosen encoding. Read [pattern matching](./patt
Using `;` is optional.
### Monadic bind
### Monadic bind blocks
```rust
Result.bind (Result.ok val) f = (f val)
Result.bind err _ = err
Result/bind (Result.ok val) f = (f val)
Result/bind err _ = err
div a b = switch b {
0: (Result.err "Div by 0")
@ -614,18 +710,28 @@ rem a b = switch b {
_: (Result.ok (% a b))
}
Main = do Result.bind {
Main = do Result {
ask y = (div 3 2);
ask x = (rem y 0);
x
}
```
Receives a monadic bind function and then expects a block.
Receives a type defined with `data` or `type` and expects `Result/bind` to be defined as a monadic bind function.
It should be of type `(Result a) -> (a -> Result b) -> Result b`, like in the example above.
The block is followed by `ask` binds and ends with a return term.
Inside a `do` block, you can use `ask`, to access the continuation value of the monadic operation.
The monad bind function should be of type `(Monad a) -> (a -> (Monad b)) -> (Monad b)`.
```rust
ask y = (div 3 2)
ask x = (rem y 0)
x
// Becomes
(Result/bind (div 3 2) λy (Result/bind (rem y 0) λx x))
```
It can be used to force a sequence of operations. Since the continuation receives the result through a lambda, it is only fully evaluated after something is applied to it.
### Numbers and operations
@ -664,6 +770,17 @@ A Character is surrounded with `'`. Accepts unicode characters, unicode escapes
Only supports unicode codepoints up to `0xFFFFFF`.
### Symbol Literal
```python
// Becomes 2146 (33 << 6 + 34)
`hi`
```
A Symbol encodes a up to 4 base64 characters as a `u24` number. It is surrounded by `\``.
Empty characters are interpreted as `A` which has value 0, meaning that `B` is the same as `AAAB`.
### String Literal
```rust

View File

@ -468,19 +468,21 @@ impl<'a> TermParser<'a> {
Ok((bind, init))
},
"",
"when",
"{",
",",
false,
0,
)?;
let (bind, init): (Vec<_>, Vec<_>) = args.into_iter().unzip();
let bind = bind.into_iter().map(Some).collect::<Vec<_>>();
self.skip_trivia();
self.parse_keyword("when")?;
let cond = self.parse_term()?;
self.consume("{")?;
self.consume(":")?;
let step = self.parse_term()?;
self.consume("}")?;
self.consume("else")?;
self.consume("{")?;
self.skip_trivia();
self.parse_keyword("else")?;
self.consume(":")?;
let base = self.parse_term()?;
self.consume("}")?;
return Ok(Term::Bend {
@ -1049,7 +1051,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
/// Joins the characters into a u24 and returns it.
fn parse_quoted_symbol(&mut self) -> ParseResult<u32> {
self.consume_exactly("`")?;
let mut result = u32::MAX;
let mut result = 0;
let mut count = 0;
while count < 4 {
if self.starts_with("`") {

View File

@ -17,49 +17,70 @@ impl Definition {
impl Stmt {
fn gen_map_get(&mut self, id: &mut usize) {
match self {
Stmt::Assign { val, nxt, .. } | Stmt::Ask { val, nxt, .. } => {
Stmt::Assign { pat: _, val, nxt } => {
if let Some(nxt) = nxt {
nxt.gen_map_get(id);
}
let substitutions = val.substitute_map_gets(id);
if !substitutions.is_empty() {
*self = gen_get(self, substitutions);
}
}
Stmt::Ask { pat: _, val, nxt } => {
nxt.gen_map_get(id);
let substitutions = val.substitute_map_gets(id);
if !substitutions.is_empty() {
*self = gen_get(self, substitutions);
}
}
Stmt::InPlace { val, nxt, .. } => {
Stmt::InPlace { op: _, var: _, val, nxt } => {
nxt.gen_map_get(id);
let substitutions = val.substitute_map_gets(id);
if !substitutions.is_empty() {
*self = gen_get(self, substitutions);
}
}
Stmt::If { cond, then, otherwise } => {
Stmt::If { cond, then, otherwise, nxt } => {
then.gen_map_get(id);
otherwise.gen_map_get(id);
if let Some(nxt) = nxt {
nxt.gen_map_get(id);
}
let substitutions = cond.substitute_map_gets(id);
if !substitutions.is_empty() {
*self = gen_get(self, substitutions);
}
}
Stmt::Match { arg, arms, .. } | Stmt::Fold { arg, arms, .. } => {
Stmt::Match { bind: _, arg, arms, nxt } | Stmt::Fold { bind: _, arg, arms, nxt } => {
for arm in arms.iter_mut() {
arm.rgt.gen_map_get(id);
}
if let Some(nxt) = nxt {
nxt.gen_map_get(id);
}
let substitutions = arg.substitute_map_gets(id);
if !substitutions.is_empty() {
*self = gen_get(self, substitutions);
}
}
Stmt::Switch { arg, arms, .. } => {
Stmt::Switch { bind: _, arg, arms, nxt } => {
for arm in arms.iter_mut() {
arm.gen_map_get(id);
}
if let Some(nxt) = nxt {
nxt.gen_map_get(id);
}
let substitutions = arg.substitute_map_gets(id);
if !substitutions.is_empty() {
*self = gen_get(self, substitutions);
}
}
Stmt::Bend { bind: _, init, cond, step, base } => {
Stmt::Bend { bind: _, init, cond, step, base, nxt } => {
step.gen_map_get(id);
base.gen_map_get(id);
if let Some(nxt) = nxt {
nxt.gen_map_get(id);
}
let mut substitutions = cond.substitute_map_gets(id);
for init in init {
substitutions.extend(init.substitute_map_gets(id));
@ -68,7 +89,12 @@ impl Stmt {
*self = gen_get(self, substitutions);
}
}
Stmt::Do { bod, .. } => bod.gen_map_get(id),
Stmt::Do { typ: _, bod, nxt } => {
bod.gen_map_get(id);
if let Some(nxt) = nxt {
nxt.gen_map_get(id);
}
}
Stmt::Return { term } => {
let substitutions = term.substitute_map_gets(id);
if !substitutions.is_empty() {
@ -149,7 +175,7 @@ fn gen_get(current: &mut Stmt, substitutions: Substitutions) -> Stmt {
};
let pat = AssignPattern::Tup(vec![AssignPattern::Var(var), AssignPattern::Var(map_var)]);
Stmt::Assign { pat, val: Box::new(map_get_call), nxt: Box::new(acc) }
Stmt::Assign { pat, val: Box::new(map_get_call), nxt: Some(Box::new(acc)) }
})
}

View File

@ -73,13 +73,13 @@ pub enum InPlaceOp {
#[derive(Clone, Debug, Default)]
pub enum Stmt {
// {pat} = {val} ";" {nxt}
// {pat} = {val} ";"? {nxt}
Assign {
pat: AssignPattern,
val: Box<Expr>,
nxt: Box<Stmt>,
nxt: Option<Box<Stmt>>,
},
// {var} += {val} ";" {nxt}
// {var} += {val} ";"? {nxt}
InPlace {
op: InPlaceOp,
var: Name,
@ -90,55 +90,71 @@ pub enum Stmt {
// {then}
// "else" ":"
// {otherwise}
// <nxt>?
If {
cond: Box<Expr>,
then: Box<Stmt>,
otherwise: Box<Stmt>,
nxt: Option<Box<Stmt>>,
},
// "match" {arg} ":" ("as" {bind})?
// case {lft} ":" {rgt}
// ...
// <nxt>?
Match {
arg: Box<Expr>,
bind: Option<Name>,
arms: Vec<MatchArm>,
nxt: Option<Box<Stmt>>,
},
// "switch" {arg} ("as" {bind})?
// case 0..wildcard ":" {rgt}
// ...
// <nxt>?
Switch {
arg: Box<Expr>,
bind: Option<Name>,
arms: Vec<Stmt>,
nxt: Option<Box<Stmt>>,
},
// "bend" ({bind} ("="" {init})?)* "while" {cond} ":"
// {step}
// {step}
// "then" ":"
// {base}
// {base}
// <nxt>?
Bend {
bind: Vec<Option<Name>>,
init: Vec<Expr>,
cond: Box<Expr>,
step: Box<Stmt>,
base: Box<Stmt>,
nxt: Option<Box<Stmt>>,
},
// "fold" {arg} ("as" {bind})? ":" {arms}
// case {lft} ":" {rgt}
// ...
// <nxt>?
Fold {
arg: Box<Expr>,
bind: Option<Name>,
arms: Vec<MatchArm>,
nxt: Option<Box<Stmt>>,
},
// "do" {fun} ":" {block}
// "do" {fun} ":"
// {block}
// <nxt>?
Do {
typ: Name,
bod: Box<Stmt>,
nxt: Option<Box<Stmt>>,
},
// {pat} <- {val} ";" {nxt}
// {pat} <- {val} ";"? {nxt}
Ask {
pat: AssignPattern,
val: Box<Expr>,
nxt: Box<Stmt>,
},
// "return" {expr} ";"
// "return" {expr} ";"?
Return {
term: Box<Expr>,
},

View File

@ -15,7 +15,13 @@ impl Definition {
impl Stmt {
fn order_kwargs(&mut self, book: &Book) -> Result<(), String> {
match self {
Stmt::Assign { val, nxt, .. } | Stmt::Ask { val, nxt, .. } => {
Stmt::Assign { val, nxt, .. } => {
val.order_kwargs(book)?;
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Ask { val, nxt, .. } => {
val.order_kwargs(book)?;
nxt.order_kwargs(book)?;
}
@ -23,38 +29,58 @@ impl Stmt {
val.order_kwargs(book)?;
nxt.order_kwargs(book)?;
}
Stmt::If { cond, then, otherwise } => {
Stmt::If { cond, then, otherwise, nxt } => {
cond.order_kwargs(book)?;
then.order_kwargs(book)?;
otherwise.order_kwargs(book)?;
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Match { arg, arms, .. } => {
Stmt::Match { arg, arms, nxt, .. } => {
arg.order_kwargs(book)?;
for arm in arms {
arm.rgt.order_kwargs(book)?;
}
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Switch { arg, arms, .. } => {
Stmt::Switch { arg, arms, nxt, .. } => {
arg.order_kwargs(book)?;
for arm in arms {
arm.order_kwargs(book)?;
}
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Fold { arg, arms, .. } => {
Stmt::Fold { arg, arms, nxt, .. } => {
arg.order_kwargs(book)?;
for arm in arms {
arm.rgt.order_kwargs(book)?;
}
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Bend { bind: _, init, cond, step, base } => {
Stmt::Bend { bind: _, init, cond, step, base, nxt } => {
for init in init {
init.order_kwargs(book)?;
}
cond.order_kwargs(book)?;
step.order_kwargs(book)?;
base.order_kwargs(book)?;
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Do { typ: _, bod, nxt } => {
bod.order_kwargs(book)?;
if let Some(nxt) = nxt {
nxt.order_kwargs(book)?;
}
}
Stmt::Do { bod, .. } => bod.order_kwargs(book)?,
Stmt::Return { term } => term.order_kwargs(book)?,
Stmt::Err => {}
}

View File

@ -390,10 +390,15 @@ impl<'a> PyParser<'a> {
let val = self.parse_expr(true)?;
self.skip_trivia_inline();
self.try_consume_exactly(";");
self.consume_indent_exactly(*indent)?;
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Assign { pat, val: Box::new(val), nxt: Box::new(nxt) };
return Ok((stmt, nxt_indent));
let nxt_indent = self.advance_newlines();
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Assign { pat, val: Box::new(val), nxt: Some(Box::new(nxt)) };
return Ok((stmt, nxt_indent));
} else {
let stmt = Stmt::Assign { pat, val: Box::new(val), nxt: None };
return Ok((stmt, nxt_indent));
}
}
// Ask
if self.starts_with("<-") {
@ -480,9 +485,20 @@ impl<'a> PyParser<'a> {
self.consume_indent_exactly(*indent)?;
let (otherwise, nxt_indent) = self.parse_statement(indent)?;
indent.exit_level();
let stmt = Stmt::If { cond: Box::new(cond), then: Box::new(then), otherwise: Box::new(otherwise) };
Ok((stmt, nxt_indent))
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::If {
cond: Box::new(cond),
then: Box::new(then),
otherwise: Box::new(otherwise),
nxt: Some(Box::new(nxt)),
};
Ok((stmt, nxt_indent))
} else {
let stmt =
Stmt::If { cond: Box::new(cond), then: Box::new(then), otherwise: Box::new(otherwise), nxt: None };
Ok((stmt, nxt_indent))
}
}
fn parse_match(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> {
@ -501,8 +517,14 @@ impl<'a> PyParser<'a> {
arms.push(case);
}
indent.exit_level();
let stmt = Stmt::Match { arg: Box::new(arg), bind, arms };
Ok((stmt, nxt_indent))
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Match { arg: Box::new(arg), bind, arms, nxt: Some(Box::new(nxt)) };
Ok((stmt, nxt_indent))
} else {
let stmt = Stmt::Match { arg: Box::new(arg), bind, arms, nxt: None };
Ok((stmt, nxt_indent))
}
}
fn parse_match_arg(&mut self) -> ParseResult<(Option<Name>, Expr)> {
@ -586,8 +608,14 @@ impl<'a> PyParser<'a> {
}
}
indent.exit_level();
let stmt = Stmt::Switch { arg: Box::new(arg), bind, arms };
Ok((stmt, nxt_indent))
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Switch { arg: Box::new(arg), bind, arms, nxt: Some(Box::new(nxt)) };
Ok((stmt, nxt_indent))
} else {
let stmt = Stmt::Switch { arg: Box::new(arg), bind, arms, nxt: None };
Ok((stmt, nxt_indent))
}
}
fn parse_switch_case(&mut self, indent: &mut Indent) -> ParseResult<(Option<u32>, Stmt, Indent)> {
@ -637,8 +665,14 @@ impl<'a> PyParser<'a> {
arms.push(case);
}
indent.exit_level();
let stmt = Stmt::Fold { arg: Box::new(arg), bind, arms };
Ok((stmt, nxt_indent))
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Fold { arg: Box::new(arg), bind, arms, nxt: Some(Box::new(nxt)) };
Ok((stmt, nxt_indent))
} else {
let stmt = Stmt::Fold { arg: Box::new(arg), bind, arms, nxt: None };
Ok((stmt, nxt_indent))
}
}
/// "bend" (<bind> "=" <init> ","?)* ":"
@ -678,12 +712,33 @@ impl<'a> PyParser<'a> {
indent.exit_level();
indent.exit_level();
let stmt = Stmt::Bend { bind, init, cond: Box::new(cond), step: Box::new(step), base: Box::new(base) };
Ok((stmt, nxt_indent))
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Bend {
bind,
init,
cond: Box::new(cond),
step: Box::new(step),
base: Box::new(base),
nxt: Some(Box::new(nxt)),
};
Ok((stmt, nxt_indent))
} else {
let stmt = Stmt::Bend {
bind,
init,
cond: Box::new(cond),
step: Box::new(step),
base: Box::new(base),
nxt: None,
};
Ok((stmt, nxt_indent))
}
}
/// "do" <typ> ":"
/// <bod>
/// <nxt>?
fn parse_do(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> {
self.skip_trivia_inline();
let typ = self.parse_bend_name()?;
@ -696,8 +751,14 @@ impl<'a> PyParser<'a> {
let (bod, nxt_indent) = self.parse_statement(indent)?;
indent.exit_level();
let stmt = Stmt::Do { typ, bod: Box::new(bod) };
Ok((stmt, nxt_indent))
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Do { typ, bod: Box::new(bod), nxt: Some(Box::new(nxt)) };
Ok((stmt, nxt_indent))
} else {
let stmt = Stmt::Do { typ, bod: Box::new(bod), nxt: None };
Ok((stmt, nxt_indent))
}
}
/// "("<nam> ("," <nam>)* ")"
@ -828,7 +889,7 @@ impl<'a> PyParser<'a> {
}
def.order_kwargs(book)?;
def.gen_map_get();
let def = def.to_fun();
let def = def.to_fun()?;
book.defs.insert(def.name.clone(), def);
Ok(())
}

View File

@ -2,101 +2,271 @@ use super::{AssignPattern, Definition, Expr, Stmt};
use crate::fun::{self, Name};
impl Definition {
pub fn to_fun(self) -> fun::Definition {
let rule = fun::Rule {
pats: self.params.into_iter().map(|param| fun::Pattern::Var(Some(param))).collect(),
body: self.body.to_fun(),
pub fn to_fun(self) -> Result<fun::Definition, String> {
let body = self.body.into_fun().map_err(|e| format!("In function '{}': {}", self.name, e))?;
let body = match body {
StmtToFun::Return(term) => term,
StmtToFun::Assign(..) => {
return Err(format!("Function '{}' doesn't end with a return statement", self.name));
}
};
fun::Definition { name: self.name, rules: vec![rule], builtin: false }
let rule =
fun::Rule { pats: self.params.into_iter().map(|param| fun::Pattern::Var(Some(param))).collect(), body };
let def = fun::Definition { name: self.name, rules: vec![rule], builtin: false };
Ok(def)
}
}
impl AssignPattern {
pub fn to_fun(self) -> fun::Pattern {
pub fn into_fun(self) -> fun::Pattern {
match self {
AssignPattern::Var(name) => fun::Pattern::Var(Some(name)),
AssignPattern::Chn(name) => fun::Pattern::Chn(name),
AssignPattern::Tup(names) => {
fun::Pattern::Fan(fun::FanKind::Tup, fun::Tag::Static, names.into_iter().map(Self::to_fun).collect())
}
AssignPattern::Tup(names) => fun::Pattern::Fan(
fun::FanKind::Tup,
fun::Tag::Static,
names.into_iter().map(Self::into_fun).collect(),
),
AssignPattern::Sup(names) => {
fun::Pattern::Fan(fun::FanKind::Dup, fun::Tag::Auto, names.into_iter().map(Self::to_fun).collect())
fun::Pattern::Fan(fun::FanKind::Dup, fun::Tag::Auto, names.into_iter().map(Self::into_fun).collect())
}
AssignPattern::MapSet(..) => unreachable!(),
}
}
}
enum StmtToFun {
Return(fun::Term),
Assign(fun::Pattern, fun::Term),
}
impl Stmt {
pub fn to_fun(self) -> fun::Term {
match self {
Stmt::Assign { pat: AssignPattern::MapSet(map, key), val, nxt } => fun::Term::Let {
pat: Box::new(fun::Pattern::Var(Some(map.clone()))),
val: Box::new(fun::Term::call(fun::Term::Ref { nam: fun::Name::new("Map/set") }, [
fun::Term::Var { nam: map },
key.to_fun(),
val.to_fun(),
])),
nxt: Box::new(nxt.to_fun()),
},
Stmt::Assign { pat, val, nxt } => {
let pat = pat.to_fun();
let val = val.to_fun();
let nxt = nxt.to_fun();
fun::Term::Let { pat: Box::new(pat), val: Box::new(val), nxt: Box::new(nxt) }
fn into_fun(self) -> Result<StmtToFun, String> {
// TODO: Refactor this to not repeat everything.
// TODO: When we have an error with an assignment, we should show the offending assignment (eg. "{pat} = ...").
let stmt_to_fun = match self {
Stmt::Assign { pat: AssignPattern::MapSet(map, key), val, nxt: Some(nxt) } => {
let (nxt_pat, nxt) = match nxt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Let {
pat: Box::new(fun::Pattern::Var(Some(map.clone()))),
val: Box::new(fun::Term::call(fun::Term::Ref { nam: fun::Name::new("Map/set") }, [
fun::Term::Var { nam: map },
key.to_fun(),
val.to_fun(),
])),
nxt: Box::new(nxt),
};
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
}
Stmt::InPlace { op, var: nam, val, nxt } => fun::Term::Let {
pat: Box::new(fun::Pattern::Var(Some(nam.clone()))),
val: Box::new(fun::Term::Oper {
opr: op.to_lang_op(),
fst: Box::new(fun::Term::Var { nam }),
snd: Box::new(val.to_fun()),
}),
nxt: Box::new(nxt.to_fun()),
},
Stmt::If { cond, then, otherwise } => {
let arms = vec![otherwise.to_fun(), then.to_fun()];
fun::Term::Swt {
Stmt::Assign { pat: AssignPattern::MapSet(..), val: _, nxt: None } => {
return Err("Branch ends with map assignment.".to_string());
}
Stmt::Assign { pat, val, nxt: Some(nxt) } => {
let pat = pat.into_fun();
let val = val.to_fun();
let (nxt_pat, nxt) = match nxt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Let { pat: Box::new(pat), val: Box::new(val), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
}
Stmt::Assign { pat, val, nxt: None } => {
let pat = pat.into_fun();
let val = val.to_fun();
StmtToFun::Assign(pat, val)
}
Stmt::InPlace { op, var, val, nxt } => {
let (nxt_pat, nxt) = match nxt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Let {
pat: Box::new(fun::Pattern::Var(Some(var.clone()))),
val: Box::new(fun::Term::Oper {
opr: op.to_lang_op(),
fst: Box::new(fun::Term::Var { nam: var }),
snd: Box::new(val.to_fun()),
}),
nxt: Box::new(nxt),
};
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
}
Stmt::If { cond, then, otherwise, nxt } => {
let (pat, then, else_) = match (then.into_fun()?, otherwise.into_fun()?) {
(StmtToFun::Return(t), StmtToFun::Return(e)) => (None, t, e),
(StmtToFun::Assign(tp, t), StmtToFun::Assign(ep, e)) if tp == ep => (Some(tp), t, e),
(StmtToFun::Assign(..), StmtToFun::Assign(..)) => {
return Err("'if' branches end with different assignments.".to_string());
}
(StmtToFun::Return(..), StmtToFun::Assign(..)) => {
return Err(
"Expected 'else' branch from 'if' to return, but it ends with assignment.".to_string(),
);
}
(StmtToFun::Assign(..), StmtToFun::Return(..)) => {
return Err(
"Expected 'else' branch from 'if' to end with assignment, but it returns.".to_string(),
);
}
};
let arms = vec![else_, then];
let term = fun::Term::Swt {
arg: Box::new(cond.to_fun()),
bnd: Some(Name::new("%pred")),
with: Vec::new(),
pred: Some(Name::new("%pred-1")),
arms,
};
wrap_nxt_assign_stmt(term, nxt, pat)?
}
Stmt::Match { arg, bind, arms, nxt } => {
let arg = arg.to_fun();
let mut fun_arms = vec![];
let mut arms = arms.into_iter();
let fst = arms.next().unwrap();
let (fst_pat, fst_rgt) = match fst.rgt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
fun_arms.push((fst.lft, vec![], fst_rgt));
for arm in arms {
let (arm_pat, arm_rgt) = match arm.rgt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
match (&arm_pat, &fst_pat) {
(Some(arm_pat), Some(fst_pat)) if arm_pat != fst_pat => {
return Err("'match' arms end with different assignments.".to_string());
}
(Some(_), None) => {
return Err("Expected 'match' arms to end with assignment, but it returns.".to_string());
}
(None, Some(_)) => {
return Err("Expected 'match' arms to return, but it ends with assignment.".to_string());
}
(Some(_), Some(_)) => fun_arms.push((arm.lft, vec![], arm_rgt)),
(None, None) => fun_arms.push((arm.lft, vec![], arm_rgt)),
}
}
let term = fun::Term::Mat { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms: fun_arms };
wrap_nxt_assign_stmt(term, nxt, fst_pat)?
}
Stmt::Match { arg, bind, arms } => {
Stmt::Switch { arg, bind, arms, nxt } => {
let arg = arg.to_fun();
let arms = arms.into_iter().map(|arm| (arm.lft, Vec::new(), arm.rgt.to_fun())).collect();
fun::Term::Mat { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms }
let mut fun_arms = vec![];
let mut arms = arms.into_iter();
let fst = arms.next().unwrap();
let (fst_pat, fst) = match fst.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
fun_arms.push(fst);
for arm in arms {
let (arm_pat, arm) = match arm.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
match (&arm_pat, &fst_pat) {
(Some(arm_pat), Some(fst_pat)) if arm_pat != fst_pat => {
return Err("'switch' arms end with different assignments.".to_string());
}
(Some(_), None) => {
return Err("Expected 'switch' arms to end with assignment, but it returns.".to_string());
}
(None, Some(_)) => {
return Err("Expected 'switch' arms to return, but it ends with assignment.".to_string());
}
(Some(_), Some(_)) => fun_arms.push(arm),
(None, None) => fun_arms.push(arm),
}
}
let pred = Some(Name::new(format!("{}-{}", bind.clone().unwrap(), fun_arms.len() - 1)));
let term = fun::Term::Swt { arg: Box::new(arg), bnd: bind, with: Vec::new(), pred, arms: fun_arms };
wrap_nxt_assign_stmt(term, nxt, fst_pat)?
}
Stmt::Switch { arg, bind, arms } => {
Stmt::Fold { arg, bind, arms, nxt } => {
let arg = arg.to_fun();
let pred = Some(Name::new(format!("{}-{}", bind.clone().unwrap(), arms.len() - 1)));
let arms = arms.into_iter().map(Stmt::to_fun).collect();
fun::Term::Swt { arg: Box::new(arg), bnd: bind, with: Vec::new(), pred, arms }
let mut fun_arms = vec![];
let mut arms = arms.into_iter();
let fst = arms.next().unwrap();
let (fst_pat, fst_rgt) = match fst.rgt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
fun_arms.push((fst.lft, vec![], fst_rgt));
for arm in arms {
let (arm_pat, arm_rgt) = match arm.rgt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
match (&arm_pat, &fst_pat) {
(Some(arm_pat), Some(fst_pat)) if arm_pat != fst_pat => {
return Err("'fold' arms end with different assignments.".to_string());
}
(Some(_), None) => {
return Err("Expected 'fold' arms to end with assignment, but it returns.".to_string());
}
(None, Some(_)) => {
return Err("Expected 'fold' arms to return, but it ends with assignment.".to_string());
}
(Some(_), Some(_)) => fun_arms.push((arm.lft, vec![], arm_rgt)),
(None, None) => fun_arms.push((arm.lft, vec![], arm_rgt)),
}
}
let term = fun::Term::Fold { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms: fun_arms };
wrap_nxt_assign_stmt(term, nxt, fst_pat)?
}
Stmt::Fold { arg, bind, arms } => {
let arg = arg.to_fun();
let arms = arms.into_iter().map(|arm| (arm.lft, Vec::new(), arm.rgt.to_fun())).collect();
fun::Term::Fold { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms }
}
Stmt::Bend { bind, init, cond, step, base } => {
Stmt::Bend { bind, init, cond, step, base, nxt } => {
let init = init.into_iter().map(Expr::to_fun).collect();
let cond = cond.to_fun();
let step = step.to_fun();
let base = base.to_fun();
fun::Term::Bend { bind, init, cond: Box::new(cond), step: Box::new(step), base: Box::new(base) }
let (pat, step, base) = match (step.into_fun()?, base.into_fun()?) {
(StmtToFun::Return(s), StmtToFun::Return(b)) => (None, s, b),
(StmtToFun::Assign(sp, s), StmtToFun::Assign(bp, b)) if sp == bp => (Some(sp), s, b),
(StmtToFun::Assign(..), StmtToFun::Assign(..)) => {
return Err("'bend' branches end with different assignments.".to_string());
}
(StmtToFun::Return(..), StmtToFun::Assign(..)) => {
return Err(
"Expected 'else' branch from 'bend' to return, but it ends with assignment.".to_string(),
);
}
(StmtToFun::Assign(..), StmtToFun::Return(..)) => {
return Err(
"Expected 'else' branch from 'bend' to end with assignment, but it returns.".to_string(),
);
}
};
let term =
fun::Term::Bend { bind, init, cond: Box::new(cond), step: Box::new(step), base: Box::new(base) };
wrap_nxt_assign_stmt(term, nxt, pat)?
}
Stmt::Do { typ, bod } => fun::Term::Do { typ, bod: Box::new(bod.to_fun()) },
Self::Ask { pat, val, nxt } => fun::Term::Ask {
pat: Box::new(pat.to_fun()),
val: Box::new(val.to_fun()),
nxt: Box::new(nxt.to_fun()),
},
Stmt::Return { term } => term.to_fun(),
Stmt::Err => fun::Term::Err,
}
Stmt::Do { typ, bod, nxt } => {
let (pat, bod) = match bod.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Do { typ, bod: Box::new(bod) };
wrap_nxt_assign_stmt(term, nxt, pat)?
}
Self::Ask { pat, val, nxt } => {
let (nxt_pat, nxt) = match nxt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term =
fun::Term::Ask { pat: Box::new(pat.into_fun()), val: Box::new(val.to_fun()), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
}
Stmt::Return { term } => StmtToFun::Return(term.to_fun()),
Stmt::Err => unreachable!(),
};
Ok(stmt_to_fun)
}
}
@ -152,3 +322,28 @@ fn map_init(entries: Vec<(Expr, Expr)>) -> fun::Term {
}
map
}
/// If the statement was a return, returns it, erroring if there is another after it.
/// Otherwise, turns it into a 'let' and returns the next statement.
fn wrap_nxt_assign_stmt(
term: fun::Term,
nxt: Option<Box<Stmt>>,
pat: Option<fun::Pattern>,
) -> Result<StmtToFun, String> {
if let Some(nxt) = nxt {
if let Some(pat) = pat {
let (nxt_pat, nxt) = match nxt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Let { pat: Box::new(pat), val: Box::new(term), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { Ok(StmtToFun::Assign(pat, term)) } else { Ok(StmtToFun::Return(term)) }
} else {
Err("Statement ends with return but is not at end of function.".to_string())
}
} else if let Some(pat) = pat {
Ok(StmtToFun::Assign(pat, term))
} else {
Ok(StmtToFun::Return(term))
}
}

View File

@ -7,9 +7,10 @@ main =
let b = 2;
let c = 3;
let n = 0;
let tree = bend n when (< n depth) {
let tree = bend n{
when (< n depth):
(Tree/node (go (+ n 1)) (go (+ n 1)))
} else {
else:
(Tree/leaf c)
}
fold tree with a {

View File

@ -0,0 +1,26 @@
// Test that branching statements can end with an assignment
type Tree:
node { val, ~lft, ~rgt }
leaf
def main:
deep = 0
if deep:
n = 20
else:
n = 2
bend x = 1, h = 0:
when h < n:
y = Tree/node { val: x, lft: go(2 * x, h + 1), rgt: go(2 * x + 1, h + 1) }
else:
y = Tree/leaf
fold y:
case Tree/node:
z = y.val + y.lft + y.rgt
case Tree/leaf:
z = 0
return z

View File

@ -17,9 +17,10 @@ def tree_xor(tree):
main =
let depth = 10
let tree = bend n=0 when (< n depth) {
(Tree/node (go (+ n 1)) (go (+ n 1)))
} else {
if (% n 2) { (Tree/leaf Bool/True) } else { (Tree/leaf Bool/False) }
let tree = bend n = 0 {
when (< n depth):
(Tree/node (go (+ n 1)) (go (+ n 1)))
else:
if (% n 2) { (Tree/leaf Bool/True) } else { (Tree/leaf Bool/False) }
}
(tree_xor tree)

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/parse_file/imp_program.hvm
---
(symbols) = let x = (Map/set (Map/set Map/empty 4294967281 5) 2 3); let x = (Map/set x 4294967281 2); let x = (Map/set x 2 3); let (map/get%0, x) = (Map/get x 4294967281); (+ map/get%0 4286483570)
(symbols) = let x = (Map/set (Map/set Map/empty 49 5) 2 3); let x = (Map/set x 49 2); let x = (Map/set x 2 3); let (map/get%0, x) = (Map/get x 49); (+ map/get%0 8293490)
(mk_point) = (Point/Point 1 2)

View File

@ -0,0 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/branch_statements_assignment.hvm
---
6