From 1f530f3b2466edb77295246630c064395039a62e Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 10 Mar 2024 11:26:41 +0100 Subject: [PATCH] Experiment with monadic bind. --- crates/aiken-lang/src/ast.rs | 2 + crates/aiken-lang/src/format.rs | 16 ++-- .../aiken-lang/src/parser/expr/assignment.rs | 13 ++- crates/aiken-lang/src/parser/lexer.rs | 3 +- crates/aiken-lang/src/parser/token.rs | 2 + crates/aiken-lang/src/tipo/expr.rs | 93 ++++++++++++++++++- 6 files changed, 109 insertions(+), 20 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 203a242d..409b7304 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1425,6 +1425,7 @@ impl Default for Bls12_381Point { #[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)] pub enum AssignmentKind { Let, + Bind, Expect, } @@ -1440,6 +1441,7 @@ impl AssignmentKind { pub fn location_offset(&self) -> usize { match self { AssignmentKind::Let => 3, + AssignmentKind::Bind => 3, AssignmentKind::Expect => 6, } } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index ee06815c..33861153 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -684,9 +684,10 @@ impl<'comments> Formatter<'comments> { kind: AssignmentKind, annotation: &'a Option, ) -> Document<'a> { - let keyword = match kind { - AssignmentKind::Let => "let", - AssignmentKind::Expect => "expect", + let (keyword, equal) = match kind { + AssignmentKind::Let => ("let", "="), + AssignmentKind::Bind => ("let", "<-"), + AssignmentKind::Expect => ("expect", "="), }; match pattern { @@ -708,7 +709,8 @@ impl<'comments> Formatter<'comments> { .to_doc() .append(" ") .append(pattern.append(annotation).group()) - .append(" =") + .append(" ") + .append(equal) .append(self.case_clause_value(value)) } } @@ -1842,11 +1844,7 @@ impl<'a> Documentable<'a> for &'a ArgName { } fn pub_(public: bool) -> Document<'static> { - if public { - "pub ".to_doc() - } else { - nil() - } + if public { "pub ".to_doc() } else { nil() } } impl<'a> Documentable<'a> for &'a UnqualifiedImport { diff --git a/crates/aiken-lang/src/parser/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs index 30f8fbf5..74d77e77 100644 --- a/crates/aiken-lang/src/parser/expr/assignment.rs +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -1,10 +1,9 @@ -use chumsky::prelude::*; - use crate::{ ast, expr::UntypedExpr, parser::{annotation, error::ParseError, pattern, token::Token}, }; +use chumsky::prelude::*; pub fn let_( r: Recursive<'_, Token, UntypedExpr, ParseError>, @@ -12,9 +11,9 @@ pub fn let_( just(Token::Let) .ignore_then(pattern()) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .then_ignore(just(Token::Equal)) + .then(choice((just(Token::Equal), just(Token::LArrow)))) .then(r.clone()) - .validate(move |((pattern, annotation), value), span, emit| { + .validate(move |(((pattern, annotation), kind), value), span, emit| { if matches!(value, UntypedExpr::Assignment { .. }) { emit(ParseError::invalid_assignment_right_hand_side(span)) } @@ -23,7 +22,11 @@ pub fn let_( location: span, value: Box::new(value), pattern, - kind: ast::AssignmentKind::Let, + kind: if kind == Token::LArrow { + ast::AssignmentKind::Bind + } else { + ast::AssignmentKind::Let + }, annotation, } }) diff --git a/crates/aiken-lang/src/parser/lexer.rs b/crates/aiken-lang/src/parser/lexer.rs index 77fb21f2..c1fc1282 100644 --- a/crates/aiken-lang/src/parser/lexer.rs +++ b/crates/aiken-lang/src/parser/lexer.rs @@ -163,6 +163,8 @@ pub fn lexer() -> impl Parser, Error = ParseError> { just("!=").to(Token::NotEqual), just('!').to(Token::Bang), just('?').to(Token::Question), + just("<-").to(Token::LArrow), + just("->").to(Token::RArrow), choice(( just("<=").to(Token::LessEqual), just('<').to(Token::Less), @@ -170,7 +172,6 @@ pub fn lexer() -> impl Parser, Error = ParseError> { just('>').to(Token::Greater), )), just('+').to(Token::Plus), - just("->").to(Token::RArrow), just('-').to(Token::Minus), just('*').to(Token::Star), just('/').to(Token::Slash), diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index 607eeaf6..6169749f 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -62,6 +62,7 @@ pub enum Token { Pipe, // '|>' Dot, // '.' RArrow, // '->' + LArrow, // '<-' DotDot, // '..' EndOfFile, // Docs/Extra @@ -152,6 +153,7 @@ impl fmt::Display for Token { Token::Pipe => "|>", Token::Dot => ".", Token::RArrow => "->", + Token::LArrow => "<-", Token::DotDot => "..", Token::EndOfFile => "EOF", Token::Comment => "//", diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index d78a296d..fe706e08 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -24,7 +24,7 @@ use crate::{ tipo::{fields::FieldMap, PatternConstructor, TypeVar}, }; use std::{cmp::Ordering, collections::HashMap, ops::Deref, rc::Rc}; -use vec1::Vec1; +use vec1::{vec1, Vec1}; #[derive(Debug)] pub(crate) struct ExprTyper<'a, 'b> { @@ -978,6 +978,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // If `expect` is explicitly used, we still check exhaustiveness but instead of returning an // error we emit a warning which explains that using `expect` is unnecessary. match kind { + AssignmentKind::Bind => { + unreachable!("monadic-bind should have been desugared earlier.") + } + AssignmentKind::Let => { self.environment .check_exhaustiveness(&[&pattern], location, true)? @@ -1708,15 +1712,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } fn infer_seq(&mut self, location: Span, untyped: Vec) -> Result { - let sequence = self.in_new_scope(|scope| { + let mut breakpoint = None; + + let mut sequence = self.in_new_scope(|scope| { let count = untyped.len(); let mut expressions = Vec::with_capacity(count); - for (i, expression) in untyped.into_iter().enumerate() { - let no_assignment = assert_no_assignment(&expression); + for (i, expression) in untyped.iter().enumerate() { + let no_assignment = assert_no_assignment(expression); - let typed_expression = scope.infer(expression)?; + let typed_expression = match expression { + UntypedExpr::Assignment { + kind: AssignmentKind::Bind, + .. + } => { + breakpoint = Some((i, expression.clone())); + return Ok(expressions); + } + _ => scope.infer(expression.to_owned())?, + }; expressions.push(match i.cmp(&(count - 1)) { // When the expression is the last in a sequence, we enforce it is NOT @@ -1738,6 +1753,74 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(expressions) })?; + if let Some(( + i, + UntypedExpr::Assignment { + location, + value, + pattern, + .. + }, + )) = breakpoint + { + let then = UntypedExpr::Sequence { + location, + expressions: untyped.into_iter().skip(i + 1).collect::>(), + }; + + // TODO: This must be constructed based on the inferred type of *value*. + // + // let tipo = self.infer(untyped_value.clone())?.tipo(); + // + // The following is the `and_then` for Option. The one for Fuzzer is a bit + // different. + let desugar = UntypedExpr::When { + location, + subject: value.clone(), + clauses: vec![ + UntypedClause { + location, + guard: None, + patterns: vec1![Pattern::Constructor { + location, + is_record: false, + with_spread: false, + name: "None".to_string(), + module: None, + constructor: (), + tipo: (), + arguments: vec![], + }], + then: UntypedExpr::Var { + location, + name: "None".to_string(), + }, + }, + UntypedClause { + location, + guard: None, + patterns: vec1![Pattern::Constructor { + location, + is_record: false, + with_spread: false, + name: "Some".to_string(), + module: None, + constructor: (), + tipo: (), + arguments: vec![CallArg { + location, + label: None, + value: pattern.clone(), + }], + }], + then, + }, + ], + }; + + sequence.push(self.infer(desugar)?); + }; + let unused = self .environment .warnings