[sc-686] Add record types and record type destructuring

This commit is contained in:
Nicolas Abril 2024-05-15 17:53:09 +02:00
parent 3e9785b707
commit a763052c11
25 changed files with 343 additions and 46 deletions

View File

@ -56,6 +56,22 @@ The exact function they become depends on the encoding.
Read [defining data types](./defining-data-types.md) to know more.
### Object
Defines a type with a single constructor (like a struct, a record or a class).
```python
object Pair { fst, snd }
object Function { name, args, body }
object Vec { len, data }
```
The constructor created from this definition has the same name as the type.
Since it only has one constructor, `fold`ing a recursive `object` requires some additional stop condition apart from pattern matching on the value itself (like an `if` statement).
## Statements
### Assignment
@ -251,6 +267,28 @@ def bend(x, y, ...):
return ...
```
### Open
```python
p = Point { x: 1, y: 2 }
open Point: p
return Point { x: p.x * p.x, y: p.y * p.y }
```
Brings the inner fields of an object into scope. The original variable can still be accessed, but doing so will cause any used fields to be duplicated.
It's equivalent to pattern matching on the object, with the restriction that its type must have only one constructor.
```python
open Point: p
...
// Equivalent to:
match p:
Point:
...
```
### Do
```python
@ -721,6 +759,25 @@ It is desugared according to the chosen encoding. Read [pattern matching](./patt
Using `;` is optional.
### Open
```rust
let x = (Pair 1 2);
open Pair x;
(+ x.fst x.snd)
```
Brings the inner fields of an object into scope. The original variable can still be accessed, but doing so will cause any used fields to be duplicated.
It's equivalent to pattern matching on the value, with the restriction that its type must have only one constructor.
```rust
let x = (Pair 1 2)
match x {
Pair: (+ x.fst x.snd)
}
```
### Monadic bind blocks
```rust

View File

@ -56,4 +56,6 @@ def sort(d, s, t):
return flow(d, s, sort(d-1, 0, t.a), sort(d-1, 1, t.b))
def main:
return sum(18, sort(18, 0, gen(18, 0)))
// Sorts the numbers from 0 to 2^depth.
depth = 14
return sum(depth, sort(depth, 0, gen(depth, 0)))

View File

@ -146,6 +146,7 @@ impl fmt::Display for Term {
write!(f, "({} {} {})", opr, fst, snd)
}
Term::List { els } => write!(f, "[{}]", DisplayJoin(|| els.iter(), ", "),),
Term::Open { typ, var, bod } => write!(f, "open {typ} {var}; {bod}"),
Term::Err => write!(f, "<Invalid>"),
})
}
@ -415,6 +416,9 @@ impl Term {
writeln!(f, "{:tab$}{}", "", base.display_pretty(tab + 2), tab = tab + 2)?;
write!(f, "{:tab$}}}", "")
}
Term::Open { typ, var, bod } => {
write!(f, "open {typ} {var};\n{:tab$}{}", "", bod.display_pretty(tab))
}
Term::Nat { val } => write!(f, "#{val}"),
Term::Num { val: Num::U24(val) } => write!(f, "{val}"),
Term::Num { val: Num::I24(val) } => write!(f, "{}{}", if *val < 0 { "-" } else { "+" }, val.abs()),

View File

@ -156,6 +156,11 @@ pub enum Term {
step: Box<Term>,
base: Box<Term>,
},
Open {
typ: Name,
var: Name,
bod: Box<Term>,
},
Ref {
nam: Name,
},
@ -370,6 +375,9 @@ impl Clone for Term {
step: step.clone(),
base: base.clone(),
},
Self::Open { typ, var, bod: nxt } => {
Self::Open { typ: typ.clone(), var: var.clone(), bod: nxt.clone() }
}
Self::Ref { nam } => Self::Ref { nam: nam.clone() },
Self::Era => Self::Era,
Self::Err => Self::Err,
@ -515,7 +523,9 @@ impl Term {
| Term::Use { val: fst, nxt: snd, .. }
| Term::App { fun: fst, arg: snd, .. }
| Term::Oper { fst, snd, .. } => ChildrenIter::Two([fst.as_ref(), snd.as_ref()]),
Term::Lam { bod, .. } | Term::Do { bod, .. } => ChildrenIter::One([bod.as_ref()]),
Term::Lam { bod, .. } | Term::Do { bod, .. } | Term::Open { bod, .. } => {
ChildrenIter::One([bod.as_ref()])
}
Term::Var { .. }
| Term::Link { .. }
| Term::Num { .. }
@ -548,7 +558,9 @@ impl Term {
| Term::Use { val: fst, nxt: snd, .. }
| Term::App { fun: fst, arg: snd, .. }
| Term::Oper { fst, snd, .. } => ChildrenIter::Two([fst.as_mut(), snd.as_mut()]),
Term::Lam { bod, .. } | Term::Do { bod, .. } => ChildrenIter::One([bod.as_mut()]),
Term::Lam { bod, .. } | Term::Do { bod, .. } | Term::Open { bod, .. } => {
ChildrenIter::One([bod.as_mut()])
}
Term::Var { .. }
| Term::Link { .. }
| Term::Num { .. }
@ -623,6 +635,7 @@ impl Term {
| Term::Ref { .. }
| Term::Era
| Term::Err => ChildrenIter::Zero([]),
Term::Open { .. } => unreachable!("Open should be removed in earlier pass"),
}
}
@ -682,6 +695,7 @@ impl Term {
| Term::Ref { .. }
| Term::Era
| Term::Err => ChildrenIter::Zero([]),
Term::Open { .. } => unreachable!("Open should be removed in earlier pass"),
}
}
@ -737,6 +751,7 @@ impl Term {
| Term::Ref { .. }
| Term::Era
| Term::Err => ChildrenIter::Zero([]),
Term::Open { .. } => unreachable!("Open should be removed in earlier pass"),
}
}

View File

@ -64,34 +64,48 @@ impl<'a> TermParser<'a> {
let mut indent = self.advance_newlines();
while !self.is_eof() {
let ini_idx = *self.index();
// Imp type definition
if self.try_parse_keyword("type") {
// Imp type definition
let mut prs = PyParser { input: self.input, index: *self.index() };
let (enum_, nxt_indent) = prs.parse_data_type(indent)?;
let (enum_, nxt_indent) = prs.parse_type(indent)?;
self.index = prs.index;
let end_idx = *self.index();
prs.add_type(enum_, &mut book, ini_idx, end_idx, builtin)?;
indent = nxt_indent;
} else if self.try_parse_keyword("def") {
// Imp function definition
continue;
}
// Imp record type definition
if self.try_parse_keyword("object") {
let mut prs = PyParser { input: self.input, index: *self.index() };
let (obj, nxt_indent) = prs.parse_object(indent)?;
self.index = prs.index;
let end_idx = *self.index();
prs.add_object(obj, &mut book, ini_idx, end_idx, builtin)?;
indent = nxt_indent;
continue;
}
// Imp function definition
if self.try_parse_keyword("def") {
let mut prs = PyParser { input: self.input, index: *self.index() };
let (def, nxt_indent) = prs.parse_def(indent)?;
self.index = prs.index;
let end_idx = *self.index();
prs.add_def(def, &mut book, ini_idx, end_idx)?;
indent = nxt_indent;
} else if self.try_parse_keyword("data") {
// Fun type definition
continue;
}
// Fun type definition
if self.try_parse_keyword("data") {
let (nam, adt) = self.parse_datatype(builtin)?;
let end_idx = *self.index();
self.with_ctx(book.add_adt(nam, adt), ini_idx, end_idx)?;
indent = self.advance_newlines();
} else {
// Fun function definition
let (name, rule) = self.parse_rule()?;
book.add_rule(name, rule, builtin);
indent = self.advance_newlines();
continue;
}
// Fun function definition
let (name, rule) = self.parse_rule()?;
book.add_rule(name, rule, builtin);
indent = self.advance_newlines();
}
Ok(book)
@ -494,6 +508,18 @@ impl<'a> TermParser<'a> {
});
}
// Open
if self.try_parse_keyword("open") {
unexpected_tag(self)?;
self.skip_trivia();
let typ = self.parse_top_level_name()?;
self.skip_trivia();
let var = self.parse_bend_name()?;
self.try_consume(";");
let bod = self.parse_term()?;
return Ok(Term::Open { typ, var, bod: Box::new(bod) });
}
// Var
unexpected_tag(self)?;
let nam = self.labelled(|p| p.parse_bend_name(), "term")?;

View File

@ -210,6 +210,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
| Term::Mat { .. } // Removed in earlier pass
| Term::Bend { .. } // Removed in desugar_bend
| Term::Fold { .. } // Removed in desugar_fold
| Term::Open { .. } // Removed in desugar_open
| Term::Nat { .. } // Removed in encode_nat
| Term::Str { .. } // Removed in encode_str
| Term::List { .. } // Removed in encode_list

View File

@ -0,0 +1,49 @@
use crate::{
diagnostics::Diagnostics,
fun::{Adts, Ctx, Term},
maybe_grow,
};
impl Ctx<'_> {
pub fn desugar_open(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
for def in self.book.defs.values_mut() {
for rule in def.rules.iter_mut() {
if let Err(err) = rule.body.desugar_open(&self.book.adts) {
self.info.add_rule_error(err, def.name.clone());
}
}
}
self.info.fatal(())
}
}
impl Term {
fn desugar_open(&mut self, adts: &Adts) -> Result<(), String> {
maybe_grow(|| {
if let Term::Open { typ, var, bod } = self {
if let Some(adt) = adts.get(&*typ) {
if adt.ctrs.len() == 1 {
let ctr = adt.ctrs.keys().next().unwrap();
*self = Term::Mat {
arg: Box::new(Term::Var { nam: var.clone() }),
bnd: Some(std::mem::take(var)),
with: vec![],
arms: vec![(Some(ctr.clone()), vec![], std::mem::take(bod))],
}
} else {
return Err(format!("Type '{typ}' of an 'open' has more than one constructor"));
}
} else {
return Err(format!("Type '{typ}' of an 'open' is not defined"));
}
}
for child in self.children_mut() {
child.desugar_open(adts)?;
}
Ok(())
})
}
}

View File

@ -209,6 +209,7 @@ impl Term {
| Term::List { .. }
| Term::Do { .. }
| Term::Ask { .. }
| Term::Open { .. }
| Term::Err => unreachable!(),
}
}
@ -254,7 +255,9 @@ impl Term {
| Term::Ref { .. }
| Term::Era
| Term::Err => FloatIter::Zero([]),
Term::Do { .. } | Term::Ask { .. } | Term::Bend { .. } | Term::Fold { .. } => unreachable!(),
Term::Do { .. } | Term::Ask { .. } | Term::Bend { .. } | Term::Fold { .. } | Term::Open { .. } => {
unreachable!()
}
}
}
}

View File

@ -6,6 +6,7 @@ pub mod desugar_bend;
pub mod desugar_do_blocks;
pub mod desugar_fold;
pub mod desugar_match_defs;
pub mod desugar_open;
pub mod encode_adts;
pub mod encode_match_terms;
pub mod expand_generated;

View File

@ -101,6 +101,9 @@ impl Stmt {
*self = gen_get(self, substitutions);
}
}
Stmt::Open { typ: _, var: _, nxt } => {
nxt.gen_map_get(id);
}
Stmt::Err => {}
}
}

View File

@ -160,6 +160,12 @@ pub enum Stmt {
Return {
term: Box<Expr>,
},
// "open" {typ} ":" {var} ";"? {nxt}
Open {
typ: Name,
var: Name,
nxt: Box<Stmt>,
},
#[default]
Err,
}

View File

@ -81,6 +81,9 @@ impl Stmt {
nxt.order_kwargs(book)?;
}
}
Stmt::Open { typ: _, var: _, nxt } => {
nxt.order_kwargs(book)?;
}
Stmt::Return { term } => term.order_kwargs(book)?,
Stmt::Err => {}
}

View File

@ -245,6 +245,7 @@ impl<'a> PyParser<'a> {
}
/// "λ" (<name> ","?)+ ":" <expr>
/// | "open" <type> ":" <var>
/// | <infix>
fn parse_expr(&mut self, inline: bool) -> ParseResult<Expr> {
fn parse_lam_var(p: &mut PyParser) -> ParseResult<(Name, bool)> {
@ -257,13 +258,15 @@ impl<'a> PyParser<'a> {
}
self.skip_trivia_maybe_inline(inline);
// lambda
if self.try_parse_keyword("lam") | self.try_consume_exactly("λ") {
let names = self.list_like(|p| parse_lam_var(p), "", ":", ",", false, 1)?;
let bod = self.parse_expr(inline)?;
Ok(Expr::Lam { names, bod: Box::new(bod) })
} else {
self.parse_infix_expr(0, inline)
return Ok(Expr::Lam { names, bod: Box::new(bod) });
}
self.parse_infix_expr(0, inline)
}
/// Named argument of a function call.
@ -329,16 +332,6 @@ impl<'a> PyParser<'a> {
/// Parses a statement and returns the indentation of the next statement.
fn parse_statement(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> {
maybe_grow(|| {
/* let got_indent = self.advance_indent(*indent)?;
match got_indent {
Indent::Eof => {
let Indent::Val(expected) = *indent else { unreachable!() };
let msg = format!("Indentation error. Expected {} spaces, got end-of-input.", expected);
let idx = *self.index();
self.with_ctx(Err(msg), idx, idx + 1)
}
Indent::Val(_) => {}
} */
if self.try_parse_keyword("return") {
self.parse_return()
} else if self.try_parse_keyword("if") {
@ -353,6 +346,8 @@ impl<'a> PyParser<'a> {
self.parse_bend(indent)
} else if self.try_parse_keyword("do") {
self.parse_do(indent)
} else if self.try_parse_keyword("open") {
self.parse_open(indent)
} else {
self.parse_assign(indent)
}
@ -799,6 +794,23 @@ impl<'a> PyParser<'a> {
Ok(AssignPattern::Var(var))
}
/// "open" {typ} ":" {var} ";"? {nxt}
fn parse_open(&mut self, indent: &mut Indent) -> ParseResult<(Stmt, Indent)> {
self.skip_trivia_inline();
let typ = self.labelled(|p| p.parse_bend_name(), "type name")?;
self.skip_trivia_inline();
self.consume_exactly(":")?;
self.skip_trivia_inline();
let var = self.labelled(|p| p.parse_bend_name(), "variable name")?;
self.skip_trivia_inline();
self.try_consume_exactly(";");
self.consume_new_line()?;
self.consume_indent_exactly(*indent)?;
let (nxt, nxt_indent) = self.parse_statement(indent)?;
let stmt = Stmt::Open { typ, var, nxt: Box::new(nxt) };
Ok((stmt, nxt_indent))
}
pub fn parse_def(&mut self, mut indent: Indent) -> ParseResult<(Definition, Indent)> {
if indent != Indent::Val(0) {
let msg = "Indentation error. Functions defined with 'def' must be at the start of the line.";
@ -827,14 +839,7 @@ impl<'a> PyParser<'a> {
Ok((def, nxt_indent))
}
pub fn parse_data_type(&mut self, mut indent: Indent) -> ParseResult<(Enum, Indent)> {
fn parse_variant_field(p: &mut PyParser) -> ParseResult<CtrField> {
let rec = p.try_consume_exactly("~");
p.skip_trivia();
let nam = p.parse_bend_name()?;
Ok(CtrField { nam, rec })
}
pub fn parse_type(&mut self, mut indent: Indent) -> ParseResult<(Enum, Indent)> {
if indent != Indent::Val(0) {
let msg = "Indentation error. Types defined with 'type' must be at the start of the line.";
let idx = *self.index();
@ -842,7 +847,7 @@ impl<'a> PyParser<'a> {
}
self.skip_trivia_inline();
let typ_name = self.parse_bend_name()?;
let typ_name = self.parse_top_level_name()?;
self.skip_trivia_inline();
self.consume_exactly(":")?;
self.consume_new_line()?;
@ -852,14 +857,17 @@ impl<'a> PyParser<'a> {
let mut variants = Vec::new();
let mut nxt_indent = indent;
while nxt_indent == indent {
let ctr_name = self.parse_bend_name()?;
let ctr_name = self.parse_top_level_name()?;
let ctr_name = Name::new(format!("{typ_name}/{ctr_name}"));
let mut fields = Vec::new();
self.skip_trivia_inline();
if self.starts_with("{") {
fields = self.list_like(|p| parse_variant_field(p), "{", "}", ",", false, 0)?;
fields = self.list_like(|p| p.parse_variant_field(), "{", "}", ",", false, 0)?;
}
variants.push(Variant { name: ctr_name, fields });
if !self.is_eof() {
self.consume_new_line()?;
}
nxt_indent = self.consume_indent_at_most(indent)?;
}
indent.exit_level();
@ -868,6 +876,35 @@ impl<'a> PyParser<'a> {
Ok((enum_, nxt_indent))
}
pub fn parse_object(&mut self, indent: Indent) -> ParseResult<(Variant, Indent)> {
if indent != Indent::Val(0) {
let msg = "Indentation error. Types defined with 'object' must be at the start of the line.";
let idx = *self.index();
return self.with_ctx(Err(msg), idx, idx + 1);
}
self.skip_trivia_inline();
let name = self.parse_top_level_name()?;
self.skip_trivia_inline();
let fields = if self.starts_with("{") {
self.list_like(|p| p.parse_variant_field(), "{", "}", ",", false, 0)?
} else {
vec![]
};
if !self.is_eof() {
self.consume_new_line()?;
}
let nxt_indent = self.advance_newlines();
Ok((Variant { name, fields }, nxt_indent))
}
fn parse_variant_field(&mut self) -> ParseResult<CtrField> {
let rec = self.try_consume_exactly("~");
self.skip_trivia();
let nam = self.parse_bend_name()?;
Ok(CtrField { nam, rec })
}
pub fn add_def(
&mut self,
mut def: Definition,
@ -919,6 +956,33 @@ impl<'a> PyParser<'a> {
Ok(())
}
pub fn add_object(
&mut self,
obj: Variant,
book: &mut Book,
ini_idx: usize,
end_idx: usize,
builtin: bool,
) -> ParseResult<()> {
if book.adts.contains_key(&obj.name) {
let msg = format!("Redefinition of type '{}'.", obj.name);
return self.with_ctx(Err(msg), ini_idx, end_idx);
}
let mut adt = Adt { ctrs: Default::default(), builtin };
if book.defs.contains_key(&obj.name) {
let msg = format!("Redefinition of function '{}'.", obj.name);
return self.with_ctx(Err(msg), ini_idx, end_idx);
}
if book.ctrs.contains_key(&obj.name) {
let msg = format!("Redefinition of constructor '{}'.", obj.name);
return self.with_ctx(Err(msg), ini_idx, end_idx);
}
book.ctrs.insert(obj.name.clone(), obj.name.clone());
adt.ctrs.insert(obj.name.clone(), obj.fields);
book.adts.insert(obj.name, adt);
Ok(())
}
fn expected_indent<T>(&mut self, expected: Indent, got: Indent) -> ParseResult<T> {
match (expected, got) {
(Indent::Eof, Indent::Eof) => unreachable!(),

View File

@ -255,7 +255,7 @@ impl Stmt {
let term = fun::Term::Do { typ, bod: Box::new(bod) };
wrap_nxt_assign_stmt(term, nxt, pat)?
}
Self::Ask { pat, val, nxt } => {
Stmt::Ask { pat, val, nxt } => {
let (nxt_pat, nxt) = match nxt.into_fun()? {
StmtToFun::Return(term) => (None, term),
StmtToFun::Assign(pat, term) => (Some(pat), term),
@ -264,6 +264,14 @@ impl Stmt {
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::Open { typ, var, 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::Open { typ, var, bod: 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!(),
};

View File

@ -90,6 +90,8 @@ pub fn desugar_book(
ctx.apply_args(args)?;
ctx.desugar_open()?;
ctx.book.encode_builtins();
ctx.resolve_refs()?;

View File

@ -0,0 +1,13 @@
data Pair = (new a b)
type State:
new { a, b }
def with_state(x, s):
open State: s
return Pair/new(s, x(s.a))
main =
let x = (with_state @x x (State/new 1 2))
open Pair x;
{x.a x.b}

View File

@ -0,0 +1,6 @@
object Pair { fst, snd }
def main:
x = Pair(1, 2)
open Pair: x
return x.fst

View File

@ -0,0 +1,8 @@
type MyTree:
node { a, b }
leaf { a }
def main:
x = MyTree/node(1, 2)
open MyTree: x
return x.a

View File

@ -0,0 +1,4 @@
def main:
x = 1
open MyType: x
return x.a

View File

@ -2,4 +2,4 @@
source: tests/golden_tests.rs
input_file: examples/bitonic_sort.bend
---
16515072
16760832

View File

@ -2,8 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/exp.bend
---
Warnings:
During readback:
Reached Root.
λa λb (<Invalid> λ$c $c $c)
Errors:
Error reading result from hvm. Output :
ERROR: attempt to clone a non-affine global reference.

View File

@ -0,0 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/open.bend
---
{λa (a 1 2) 1}

View File

@ -0,0 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/open_object.bend
---
1

View File

@ -0,0 +1,7 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/open_too_many_ctrs.bend
---
Errors:
In definition 'main':
Type 'MyTree' of an 'open' has more than one constructor

View File

@ -0,0 +1,7 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/open_undefined_type.bend
---
Errors:
In definition 'main':
Type 'MyType' of an 'open' is not defined