From c113582404f09b186d03052218c11d28b7d2a608 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 16 Mar 2023 22:42:07 +0100 Subject: [PATCH] Support multi-clause patterns as syntactic sugar And disable multi-patterns clauses. I was originally just controlling whether we did disable that from the parser but then I figured we could actually support multi-patterns clauses quite easily by simply desugaring a multi-pattern into multiple clauses. This is only a syntactic sugar, which means that the cost of writing that on-chain is as expensive as writing the fully expanded form; yet it seems like a useful shorthand; especially for short clause expressions. This commit however disables multi-pattern when clauses, which we do not support in the code-generation. Instead, one pattern on tuples for that. --- CHANGELOG.md | 1 + crates/aiken-lang/src/ast.rs | 23 ++++++ crates/aiken-lang/src/parser.rs | 12 +-- crates/aiken-lang/src/tests/parser.rs | 89 +++++++++------------- crates/aiken-lang/src/tipo/expr.rs | 4 +- examples/acceptance_tests/078/aiken.lock | 5 ++ examples/acceptance_tests/078/aiken.toml | 2 + examples/acceptance_tests/078/lib/tests.ak | 45 +++++++++++ examples/acceptance_tests/078/plutus.json | 8 ++ 9 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 examples/acceptance_tests/078/aiken.lock create mode 100644 examples/acceptance_tests/078/aiken.toml create mode 100644 examples/acceptance_tests/078/lib/tests.ak create mode 100644 examples/acceptance_tests/078/plutus.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e6efd067..6abd92b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - **aiken-lang**: the compiler now provides better feedback for type holes (i.e. `_`) in type annotations - **aiken-lang**: assignment and clause guard are now always formatted on a new line - **aiken-lang**: unused let-bindings are now fully removed from generated code and discarded unused let-binding now raise a warning +- **aiken-lang**: support multi-clause patterns (only as a syntactic sugar) ## [v0.0.29] - 2023-MM-DD diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 4ed4abef..d46cd40d 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -936,6 +936,29 @@ impl TypedClause { pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> { self.then.find_node(byte_index) } + + pub fn desugarize(self) -> Vec { + let mut alternative_patterns = self + .alternative_patterns + .into_iter() + .map(|pattern| Self { + location: self.location, + pattern, + alternative_patterns: vec![], + guard: self.guard.clone(), + then: self.then.clone(), + }) + .collect::>(); + + let mut clauses = vec![Self { + alternative_patterns: vec![], + ..self + }]; + + clauses.append(&mut alternative_patterns); + + clauses + } } pub type UntypedClauseGuard = ClauseGuard<()>; diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index b94867e2..4c0158a0 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -921,15 +921,9 @@ pub fn expr_parser( ); let when_clause_parser = pattern_parser() - .separated_by(just(Token::Comma)) - .at_least(1) .then( just(Token::Vbar) - .ignore_then( - pattern_parser() - .separated_by(just(Token::Comma)) - .at_least(1), - ) + .ignore_then(pattern_parser().map(|pattern| vec![pattern])) .repeated() .or_not(), ) @@ -968,9 +962,9 @@ pub fn expr_parser( }), ))) .map_with_span( - |(((patterns, alternative_patterns_opt), guard), then), span| ast::UntypedClause { + |(((pattern, alternative_patterns_opt), guard), then), span| ast::UntypedClause { location: span, - pattern: patterns, + pattern: vec![pattern], alternative_patterns: alternative_patterns_opt.unwrap_or_default(), guard, then, diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index c9a2de61..f8660170 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -11,14 +11,14 @@ fn assert_definitions(code: &str, definitions: Vec) { let (module, _extra) = parser::module(code, ast::ModuleKind::Validator).unwrap(); assert_eq!( - module, ast::UntypedModule { docs: vec![], kind: ast::ModuleKind::Validator, name: "".to_string(), type_info: (), definitions, - } + }, + module ) } @@ -901,11 +901,10 @@ fn block() { fn when() { let code = indoc! {r#" pub fn wow2(a: Int){ - when a, b is { - 1, 2 -> 3 - 1 | 4, 5 -> { + when a is { + 2 -> 3 + 1 | 4 | 5 -> { let amazing = 5 - amazing } 3 -> 9 @@ -933,100 +932,88 @@ fn when() { tipo: (), }], body: expr::UntypedExpr::When { - location: Span::new((), 23..138), - subjects: vec![ - expr::UntypedExpr::Var { - location: Span::new((), 28..29), - name: "a".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 31..32), - name: "b".to_string(), - }, - ], + location: Span::new((), 23..132), + subjects: vec![expr::UntypedExpr::Var { + location: Span::new((), 28..29), + name: "a".to_string(), + }], clauses: vec![ ast::Clause { - location: Span::new((), 42..51), - pattern: vec![ - ast::Pattern::Int { - location: Span::new((), 42..43), - value: "1".to_string(), - }, - ast::Pattern::Int { - location: Span::new((), 45..46), - value: "2".to_string(), - }, - ], + location: Span::new((), 39..45), + pattern: vec![ast::Pattern::Int { + location: Span::new((), 39..40), + value: "2".to_string(), + }], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Int { - location: Span::new((), 50..51), + location: Span::new((), 44..45), value: "3".to_string(), }, }, ast::Clause { - location: Span::new((), 56..112), + location: Span::new((), 50..106), pattern: vec![ast::Pattern::Int { - location: Span::new((), 56..57), + location: Span::new((), 50..51), value: "1".to_string(), }], - alternative_patterns: vec![vec![ - ast::Pattern::Int { - location: Span::new((), 60..61), + alternative_patterns: vec![ + vec![ast::Pattern::Int { + location: Span::new((), 54..55), value: "4".to_string(), - }, - ast::Pattern::Int { - location: Span::new((), 63..64), + }], + vec![ast::Pattern::Int { + location: Span::new((), 58..59), value: "5".to_string(), - }, - ]], + }], + ], guard: None, then: expr::UntypedExpr::Sequence { - location: Span::new((), 76..106), + location: Span::new((), 71..100), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new((), 76..91), + location: Span::new((), 71..86), value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 90..91), + location: Span::new((), 85..86), value: "5".to_string(), }), pattern: ast::Pattern::Var { - location: Span::new((), 80..87), + location: Span::new((), 75..82), name: "amazing".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Var { - location: Span::new((), 99..106), + location: Span::new((), 93..100), name: "amazing".to_string(), }, ], }, }, ast::Clause { - location: Span::new((), 117..123), + location: Span::new((), 111..117), pattern: vec![ast::Pattern::Int { - location: Span::new((), 117..118), + location: Span::new((), 111..112), value: "3".to_string(), }], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Int { - location: Span::new((), 122..123), + location: Span::new((), 116..117), value: "9".to_string(), }, }, ast::Clause { - location: Span::new((), 128..134), + location: Span::new((), 122..128), pattern: vec![ast::Pattern::Discard { name: "_".to_string(), - location: Span::new((), 128..129), + location: Span::new((), 122..123), }], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Int { - location: Span::new((), 133..134), + location: Span::new((), 127..128), value: "4".to_string(), }, }, @@ -1038,7 +1025,7 @@ fn when() { public: true, return_annotation: None, return_type: (), - end_position: 139, + end_position: 133, })], ) } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 2042ae7a..18710ee7 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1888,7 +1888,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let mut typed_subjects = Vec::with_capacity(subjects_count); let mut subject_types = Vec::with_capacity(subjects_count); - let mut typed_clauses = Vec::with_capacity(clauses.len()); + let mut typed_clauses = Vec::new(); let return_type = self.new_unbound_var(); @@ -1911,7 +1911,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { ) .map_err(|e| e.case_clause_mismatch())?; - typed_clauses.push(typed_clause); + typed_clauses.append(&mut typed_clause.desugarize()); } if let Err(unmatched) = diff --git a/examples/acceptance_tests/078/aiken.lock b/examples/acceptance_tests/078/aiken.lock new file mode 100644 index 00000000..3a78b1e7 --- /dev/null +++ b/examples/acceptance_tests/078/aiken.lock @@ -0,0 +1,5 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +requirements = [] +packages = [] diff --git a/examples/acceptance_tests/078/aiken.toml b/examples/acceptance_tests/078/aiken.toml new file mode 100644 index 00000000..fa0c3532 --- /dev/null +++ b/examples/acceptance_tests/078/aiken.toml @@ -0,0 +1,2 @@ +name = "aiken-lang/acceptance_test_078" +version = "0.0.0" diff --git a/examples/acceptance_tests/078/lib/tests.ak b/examples/acceptance_tests/078/lib/tests.ak new file mode 100644 index 00000000..4302864b --- /dev/null +++ b/examples/acceptance_tests/078/lib/tests.ak @@ -0,0 +1,45 @@ +type DayOfTheWeek { + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday +} + +fn is_work(day: DayOfTheWeek) { + when day is { + Tuesday | Wednesday | Thursday | Friday | Saturday -> + True + _ -> + False + } +} + +test is_work_1() { + is_work(Thursday) +} + +test is_work_2() { + !is_work(Monday) +} + +fn is_happy_hour(day: DayOfTheWeek, current_time: Int) { + when day is { + Monday | Sunday -> + True + Tuesday | Wednesday | Thursday | Friday | Saturday if current_time > 18 -> + True + _ -> + False + } +} + +test is_happy_hour_1() { + is_happy_hour(Sunday, 14) +} + +test is_happy_hour_2() { + is_happy_hour(Friday, 22) +} diff --git a/examples/acceptance_tests/078/plutus.json b/examples/acceptance_tests/078/plutus.json new file mode 100644 index 00000000..c659be26 --- /dev/null +++ b/examples/acceptance_tests/078/plutus.json @@ -0,0 +1,8 @@ +{ + "preamble": { + "title": "aiken-lang/acceptance_test_078", + "version": "0.0.0", + "plutusVersion": "v2" + }, + "validators": [] +} \ No newline at end of file