diff --git a/crates/kind-derive/src/subst.rs b/crates/kind-derive/src/subst.rs index 10cdfb1f..05fbd462 100644 --- a/crates/kind-derive/src/subst.rs +++ b/crates/kind-derive/src/subst.rs @@ -291,9 +291,9 @@ impl<'a> Visitor for Subst<'a> { ExprKind::SeqRecord(sec) => { use kind_tree::concrete::SeqOperation::*; - self.visit_ident(&mut sec.ident[0]); + self.visit_ident(&mut sec.name); - self.visit_expr(&mut sec.typ_); + self.visit_expr(&mut sec.typ); match &mut sec.operation { Set(expr) => self.visit_expr(expr), diff --git a/crates/kind-parser/src/expr.rs b/crates/kind-parser/src/expr.rs index a742a301..63ec9233 100644 --- a/crates/kind-parser/src/expr.rs +++ b/crates/kind-parser/src/expr.rs @@ -879,6 +879,46 @@ impl<'a> Parser<'a> { } } + pub fn parse_seq(&mut self) -> Result, SyntaxDiagnostic> { + let start = self.range(); + + self.eat_variant(Token::Bang)?; + + let typ = self.parse_atom()?; + + let name = self.parse_id()?; + + let mut fields = vec![]; + + let mut end = self.range(); + + while let Token::Dot = &self.get() { + self.advance(); + end = self.range(); + fields.push(self.parse_id()?); + } + + let operation = if self.check_and_eat(Token::Eq) { + SeqOperation::Set(self.parse_expr(false)?) + } else if self.check_and_eat(Token::AtEq) { + SeqOperation::Mut(self.parse_expr(false)?) + } else { + SeqOperation::Get + }; + + Ok(Box::new(Expr { + data: ExprKind::SeqRecord( + SeqRecord { + typ, + name, + fields, + operation, + } + ), + range: start.mix(end), + })) + } + /// The infinite hell of else ifs. But it's the most readable way /// to check if the queue of tokens match a pattern as we need /// some looakhead tokens. @@ -904,6 +944,8 @@ impl<'a> Parser<'a> { self.parse_pi_or_lambda(false) } else if self.is_sigma_type() { self.parse_sigma_type() + } else if self.check_actual(Token::Bang) { + self.parse_seq() } else if self.check_actual(Token::Tilde) { self.parse_erased() } else { diff --git a/crates/kind-parser/src/lexer/mod.rs b/crates/kind-parser/src/lexer/mod.rs index 4c9bf2c4..cb410502 100644 --- a/crates/kind-parser/src/lexer/mod.rs +++ b/crates/kind-parser/src/lexer/mod.rs @@ -175,7 +175,20 @@ impl<'a> Lexer<'a> { ';' => self.single_token(Token::Semi, start), '$' => self.single_token(Token::Dollar, start), ',' => self.single_token(Token::Comma, start), - '+' => self.single_token(Token::Plus, start), + '+' => { + self.next_char(); + match self.peekable.peek() { + Some('=') => self.single_token(Token::PlusEq, start), + _ => (Token::Plus, self.mk_range(start)), + } + } + '@' => { + self.next_char(); + match self.peekable.peek() { + Some('=') => self.single_token(Token::AtEq, start), + _ => (Token::At, self.mk_range(start)), + } + } '-' => { self.next_char(); match self.peekable.peek() { diff --git a/crates/kind-parser/src/lexer/tokens.rs b/crates/kind-parser/src/lexer/tokens.rs index fe140da8..4a21bb06 100644 --- a/crates/kind-parser/src/lexer/tokens.rs +++ b/crates/kind-parser/src/lexer/tokens.rs @@ -75,6 +75,10 @@ pub enum Token { BangEq, Bang, + PlusEq, + AtEq, + At, + HashHash, Hash, @@ -181,6 +185,9 @@ impl fmt::Display for Token { Token::With => write!(f, "with"), Token::Return => write!(f, "return"), Token::Ask => write!(f, "ask"), + Token::PlusEq => write!(f, "+="), + Token::AtEq => write!(f, "@="), + Token::At => write!(f, "@"), } } } diff --git a/crates/kind-pass/src/desugar/expr.rs b/crates/kind-pass/src/desugar/expr.rs index b18c0bac..6e7859f2 100644 --- a/crates/kind-pass/src/desugar/expr.rs +++ b/crates/kind-pass/src/desugar/expr.rs @@ -1,6 +1,6 @@ use kind_span::{Locatable, Range}; use kind_tree::concrete::{self, expr, Literal, TopLevel}; -use kind_tree::desugared::{self}; +use kind_tree::desugared::{self, Expr}; use kind_tree::symbol::{Ident, QualifiedIdent}; use crate::diagnostic::{PassDiagnostic, Sugar}; @@ -79,7 +79,78 @@ impl<'a> DesugarState<'a> { range: Range, sub: &expr::SeqRecord, ) -> Box { - todo!() + use concrete::SeqOperation::*; + + let typ = self.desugar_expr(&sub.typ); + + let mut value = vec![]; + self.desugar_record_field_sequence(&mut value, typ, &sub.fields); + + match &sub.operation { + Set(expr) => { + let value_ident = Ident::generate("_value"); + let expr = self.desugar_expr(&expr); + + let mut result = value.iter().rfold(expr, |acc, (name, field)| { + let name = name.add_segment(field.to_str()).add_segment("mut"); + self.mk_desugared_ctr( + range, + name, + vec![Expr::var(value_ident.clone()), Expr::lambda(range.clone(), value_ident.clone(), acc, false)], + false, + ) + }); + + match &mut result.data { + desugared::ExprKind::Ctr { args, .. } => { + if let desugared::ExprKind::Var { name } = &mut args[0].data { + *name = sub.name.clone() + } + }, + _ => () + } + + result + }, + Mut(expr) => { + let value_ident = Ident::generate("_value"); + let expr = self.desugar_expr(&expr); + + let mut result = value.iter().rfold(expr, |acc, (name, field)| { + let name = name.add_segment(field.to_str()).add_segment("mut"); + Expr::lambda(name.range.clone(), value_ident.clone(), self.mk_desugared_ctr( + range, + name, + vec![Expr::var(value_ident.clone()), acc], + false, + ), false) + }); + + let mut result = match result.data { + desugared::ExprKind::Lambda { body, .. } => { + body + } + _ => panic!() + }; + + match &mut result.data { + desugared::ExprKind::Ctr { args, .. } => { + if let desugared::ExprKind::Var { name } = &mut args[0].data { + *name = sub.name.clone() + } + }, + _ => () + } + + result + } + Get => value + .iter() + .fold(Expr::var(sub.name.clone()), |acc, (name, field)| { + let name = name.add_segment(field.to_str()).add_segment("get"); + self.mk_desugared_ctr(range, name, vec![acc], false) + }), + } } pub(crate) fn desugar_sttm( diff --git a/crates/kind-pass/src/desugar/mod.rs b/crates/kind-pass/src/desugar/mod.rs index 5dfd1361..5316c412 100644 --- a/crates/kind-pass/src/desugar/mod.rs +++ b/crates/kind-pass/src/desugar/mod.rs @@ -23,6 +23,7 @@ pub mod attributes; pub mod destruct; pub mod expr; pub mod top_level; +pub mod record_field; pub struct DesugarState<'a> { pub errors: Sender>, diff --git a/crates/kind-pass/src/desugar/record_field.rs b/crates/kind-pass/src/desugar/record_field.rs new file mode 100644 index 00000000..ded1f45a --- /dev/null +++ b/crates/kind-pass/src/desugar/record_field.rs @@ -0,0 +1,91 @@ +use fxhash::FxHashMap; +use kind_tree::{ + concrete::{self, TopLevel}, + desugared::{self, Expr}, + symbol::{Ident, QualifiedIdent}, + telescope::Telescope, +}; + +use crate::subst::subst_on_expr; + +use super::DesugarState; + +impl<'a> DesugarState<'a> { + pub fn specialize_on_field( + &mut self, + typ: Box, + ) -> Option<( + QualifiedIdent, + &Telescope, + Telescope, + Vec>, + )> { + match typ.data { + desugared::ExprKind::Ctr { name, args } => { + let Some(TopLevel::RecordType(record)) = self.old_book.entries.get(name.to_str()) else { return None }; + let entry_constructor = self.old_book.meta.get( + record + .name + .add_segment(record.constructor.to_str()) + .to_str(), + )?; + Some(( + name, + &record.parameters, + entry_constructor + .arguments + .clone() + .drop(record.parameters.len()), + args, + )) + } + _ => None, + } + } + + pub fn desugar_record_field_sequence( + &mut self, + res: &mut Vec<(QualifiedIdent, Ident)>, + typ: Box, + fields: &[Ident], + ) { + if fields.is_empty() { + return; + } + + if let Some((name, params, record_fields, args)) = self.specialize_on_field(typ.clone()) { + if let Some(field) = record_fields + .iter() + .find(|x| x.name.to_str() == fields[0].to_str()) + { + let key = field.name.clone(); + + let pair = params + .iter() + .zip(args) + .map(|(k, v)| (k.name.to_string(), v)) + .collect::>(); + + let mut val = self.desugar_expr( + &field + .typ + .clone() + .unwrap_or_else(|| concrete::expr::Expr::typ(field.range.clone())), + ); + + + subst_on_expr(&mut val, pair); + + println!("{}", val); + + res.push((name, key)); + + self.desugar_record_field_sequence(res, val, &fields[1..]) + } else { + panic!("no field") + } + } else { + panic!("no record {}", typ) + } + } +} diff --git a/crates/kind-pass/src/lib.rs b/crates/kind-pass/src/lib.rs index a7251658..c6a185cc 100644 --- a/crates/kind-pass/src/lib.rs +++ b/crates/kind-pass/src/lib.rs @@ -11,3 +11,4 @@ mod diagnostic; pub mod expand; pub mod inline; pub mod unbound; +pub mod subst; \ No newline at end of file diff --git a/crates/kind-pass/src/subst.rs b/crates/kind-pass/src/subst.rs new file mode 100644 index 00000000..8bca013b --- /dev/null +++ b/crates/kind-pass/src/subst.rs @@ -0,0 +1,61 @@ +use fxhash::FxHashMap; +use kind_tree::desugared::*; + +pub fn subst_on_expr(expr: &mut Expr, substs: FxHashMap>) { + subst(Default::default(), expr, &substs) +} + +fn subst(bindings: im_rc::HashSet, expr: &mut Expr, substs: &FxHashMap>) { + use ExprKind::*; + + match &mut expr.data { + Var { name } => { + if !bindings.contains(name.to_str()) { + if let Some(res) = substs.get(name.to_str()) { + *expr = *res.clone(); + } + } + }, + All { param, typ, body, .. } => { + subst(bindings.clone(), typ, substs); + let mut on_body = bindings.clone(); + on_body.insert(param.to_string()); + subst(on_body.clone(), body, substs); + }, + Lambda { param, body, .. } => { + let mut on_body = bindings.clone(); + on_body.insert(param.to_string()); + subst(on_body.clone(), body, substs); + }, + App { fun, args } => { + subst(bindings.clone(), fun, substs); + for arg in args.iter_mut() { + subst(bindings.clone(), &mut arg.data, substs); + } + }, + Fun { name: _, args } | Ctr { name: _, args } => { + for arg in args.iter_mut() { + subst(bindings.clone(), arg, substs); + } + }, + Let { name, val, next } => { + subst(bindings.clone(), val, substs); + let mut on_body = bindings.clone(); + on_body.insert(name.to_string()); + subst(on_body.clone(), next, substs); + }, + Ann { expr, typ } => { + subst(bindings.clone(), expr, substs); + subst(bindings.clone(), typ, substs); + }, + Sub { expr, .. } => { + subst(bindings.clone(), expr, substs); + }, + Binary { left, right, .. } => { + subst(bindings.clone(), left, substs); + subst(bindings.clone(), right, substs); + }, + _ => () + } +} + diff --git a/crates/kind-pass/src/unbound/mod.rs b/crates/kind-pass/src/unbound/mod.rs index 8b2a7051..2398c9f6 100644 --- a/crates/kind-pass/src/unbound/mod.rs +++ b/crates/kind-pass/src/unbound/mod.rs @@ -638,9 +638,9 @@ impl Visitor for UnboundCollector { ExprKind::SeqRecord(sec) => { use kind_tree::concrete::SeqOperation::*; - self.visit_expr(&mut sec.typ_); + self.visit_expr(&mut sec.typ); - self.visit_ident(&mut sec.ident[0]); + self.visit_ident(&mut sec.name); match &mut sec.operation { Set(expr) => self.visit_expr(expr), diff --git a/crates/kind-tree/src/concrete/expr.rs b/crates/kind-tree/src/concrete/expr.rs index 978a40e1..a1b76867 100644 --- a/crates/kind-tree/src/concrete/expr.rs +++ b/crates/kind-tree/src/concrete/expr.rs @@ -145,8 +145,9 @@ pub enum SeqOperation { #[derive(Clone, Debug)] pub struct SeqRecord { - pub typ_: Box, - pub ident: Vec, + pub typ: Box, + pub name: Ident, + pub fields: Vec, pub operation: SeqOperation } @@ -592,7 +593,7 @@ impl Display for Expr { Hole => write!(f, "_"), SeqRecord(rec) => { use SeqOperation::*; - write!(f, "(!({}) {}", rec.typ_, rec.ident.iter().map(|x| x.to_str()).collect::>().join(","))?; + write!(f, "(!({}) {} {}", rec.typ, rec.name, rec.fields.iter().map(|x| format!(".{}", x.to_str())).collect::>().join(","))?; match &rec.operation { Set(expr) => write!(f, "+= {})", expr), Mut(expr) => write!(f, "@= {})", expr), diff --git a/crates/kind-tree/src/concrete/visitor.rs b/crates/kind-tree/src/concrete/visitor.rs index d0e9e2be..e840550c 100644 --- a/crates/kind-tree/src/concrete/visitor.rs +++ b/crates/kind-tree/src/concrete/visitor.rs @@ -492,6 +492,15 @@ pub fn walk_expr(ctx: &mut T, expr: &mut Expr) { ExprKind::Hole => {} ExprKind::Subst(subst) => ctx.visit_substitution(subst), ExprKind::Match(matcher) => ctx.visit_match(matcher), - ExprKind::SeqRecord(_) => todo!(), + ExprKind::SeqRecord(seq) => { + ctx.visit_ident(&mut seq.name); + ctx.visit_expr(&mut seq.typ); + + match &mut seq.operation { + SeqOperation::Set(expr) => ctx.visit_expr(expr), + SeqOperation::Mut(expr) => ctx.visit_expr(expr), + SeqOperation::Get => (), + } + }, } }