diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 2b6f0c9a6..f72bc609f 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -807,6 +807,11 @@ pub type TypedClauseGuard = ClauseGuard, String>; #[derive(Debug, Clone, PartialEq)] pub enum ClauseGuard { + Not { + location: Span, + value: Box, + }, + Equals { location: Span, left: Box, @@ -861,12 +866,6 @@ pub enum ClauseGuard { name: String, }, - // TupleIndex { - // location: Span, - // index: u64, - // tipo: Type, - // tuple: Box, - // }, Constant(Constant), } @@ -874,10 +873,10 @@ impl ClauseGuard { pub fn location(&self) -> Span { match self { ClauseGuard::Constant(constant) => constant.location(), - ClauseGuard::Or { location, .. } + ClauseGuard::Not { location, .. } + | ClauseGuard::Or { location, .. } | ClauseGuard::And { location, .. } | ClauseGuard::Var { location, .. } - // | ClauseGuard::TupleIndex { location, .. } | ClauseGuard::Equals { location, .. } | ClauseGuard::NotEquals { location, .. } | ClauseGuard::GtInt { location, .. } @@ -890,17 +889,15 @@ impl ClauseGuard { pub fn precedence(&self) -> u8 { // Ensure that this matches the other precedence function for guards match self { - ClauseGuard::Or { .. } => 1, - ClauseGuard::And { .. } => 2, - - ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } => 3, - + ClauseGuard::Not { .. } => 1, + ClauseGuard::Or { .. } => 2, + ClauseGuard::And { .. } => 3, + ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } => 4, ClauseGuard::GtInt { .. } | ClauseGuard::GtEqInt { .. } | ClauseGuard::LtInt { .. } - | ClauseGuard::LtEqInt { .. } => 4, - - ClauseGuard::Constant(_) | ClauseGuard::Var { .. } => 5, + | ClauseGuard::LtEqInt { .. } => 5, + ClauseGuard::Constant(_) | ClauseGuard::Var { .. } => 6, } } } @@ -909,10 +906,9 @@ impl TypedClauseGuard { pub fn tipo(&self) -> Arc { match self { ClauseGuard::Var { tipo, .. } => tipo.clone(), - // ClauseGuard::TupleIndex { type_, .. } => type_.clone(), ClauseGuard::Constant(constant) => constant.tipo(), - - ClauseGuard::Or { .. } + ClauseGuard::Not { .. } + | ClauseGuard::Or { .. } | ClauseGuard::And { .. } | ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } diff --git a/crates/aiken-lang/src/builder.rs b/crates/aiken-lang/src/builder.rs index d50e7f084..4f185a142 100644 --- a/crates/aiken-lang/src/builder.rs +++ b/crates/aiken-lang/src/builder.rs @@ -14,9 +14,12 @@ use uplc::{ use crate::{ air::Air, - ast::{AssignmentKind, Clause, Constant, DataType, Pattern, Span, TypedArg, TypedDataType}, + ast::{ + AssignmentKind, BinOp, Clause, ClauseGuard, Constant, DataType, Pattern, Span, TypedArg, + TypedDataType, UnOp, + }, expr::TypedExpr, - tipo::{PatternConstructor, Type, TypeVar, ValueConstructorVariant}, + tipo::{PatternConstructor, Type, TypeVar, ValueConstructor, ValueConstructorVariant}, }; #[derive(Clone, Debug)] @@ -1707,3 +1710,116 @@ pub fn replace_opaque_type(t: &mut Arc, data_types: IndexMap, String>, + clause_guard_vec: &mut Vec, + scope: Vec, +) { + match clause_guard { + ClauseGuard::Not { value, .. } => { + clause_guard_vec.push(Air::UnOp { + scope: scope.clone(), + op: UnOp::Not, + }); + + handle_clause_guard(value, clause_guard_vec, scope); + } + ClauseGuard::Equals { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::Eq, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::NotEquals { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::NotEq, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::GtInt { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::GtInt, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::GtEqInt { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::GtEqInt, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::LtInt { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::LtInt, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::LtEqInt { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::LtEqInt, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::Or { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::Or, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::And { left, right, .. } => { + clause_guard_vec.push(Air::BinOp { + scope: scope.clone(), + name: BinOp::And, + count: 2, + tipo: left.tipo(), + }); + handle_clause_guard(left, clause_guard_vec, scope.clone()); + handle_clause_guard(right, clause_guard_vec, scope); + } + ClauseGuard::Var { tipo, name, .. } => { + clause_guard_vec.push(Air::Var { + scope, + constructor: ValueConstructor::public( + tipo.clone(), + ValueConstructorVariant::LocalVariable { + location: Span::empty(), + }, + ), + name: name.clone(), + variant_name: String::new(), + }); + } + ClauseGuard::Constant(constant) => { + constants_ir(constant, clause_guard_vec, scope); + } + } +} diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index f8f7dd78a..2fc87ab44 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1539,6 +1539,9 @@ impl<'comments> Formatter<'comments> { fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> { match clause_guard { + ClauseGuard::Not { value, .. } => { + docvec!["!", self.clause_guard(value)] + } ClauseGuard::And { left, right, .. } => { self.clause_guard_bin_op(" && ", clause_guard.precedence(), left, right) } diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 312e2295e..201e61a75 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -989,9 +989,6 @@ pub fn expr_parser( }, ); - // TODO: do guards later - // let when_clause_guard_parser = just(Token::If); - let when_clause_parser = pattern_parser() .separated_by(just(Token::Comma)) .at_least(1) @@ -1005,20 +1002,29 @@ pub fn expr_parser( .repeated() .or_not(), ) - // TODO: do guards later - // .then(when_clause_guard_parser) + .then(choice(( + just(Token::If) + .ignore_then(when_clause_guard_parser()) + .or_not() + .then_ignore(just(Token::RArrow)), + just(Token::If) + .ignore_then(take_until(just(Token::RArrow))) + .validate(|_value, span, emit| { + emit(ParseError::invalid_when_clause_guard(span)); + None + }), + ))) // TODO: add hint "Did you mean to wrap a multi line clause in curly braces?" - .then_ignore(just(Token::RArrow)) .then(r.clone()) - .map_with_span(|((patterns, alternative_patterns_opt), then), span| { - ast::UntypedClause { + .map_with_span( + |(((patterns, alternative_patterns_opt), guard), then), span| ast::UntypedClause { location: span, pattern: patterns, alternative_patterns: alternative_patterns_opt.unwrap_or_default(), - guard: None, + guard, then, - } - }); + }, + ); let when_parser = just(Token::When) // TODO: If subject is empty we should return ParseErrorType::ExpectedExpr, @@ -1373,6 +1379,120 @@ pub fn expr_parser( }) } +pub fn when_clause_guard_parser() -> impl Parser, Error = ParseError> +{ + recursive(|r| { + let var_parser = select! { + Token::Name { name } => name, + Token::UpName { name } => name, + } + .map_with_span(|name, span| ast::ClauseGuard::Var { + name, + tipo: (), + location: span, + }); + + let constant_parser = constant_value_parser().map(ast::ClauseGuard::Constant); + + let block_parser = r + .clone() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)); + + let leaf_parser = choice((var_parser, constant_parser, block_parser)).boxed(); + + let unary_op = just(Token::Bang); + + let unary = unary_op + .map_with_span(|op, span| (op, span)) + .repeated() + .then(leaf_parser) + .foldr(|(_, span), value| ast::ClauseGuard::Not { + location: span.union(value.location()), + value: Box::new(value), + }) + .boxed(); + + let comparison_op = choice(( + just(Token::EqualEqual).to(BinOp::Eq), + just(Token::NotEqual).to(BinOp::NotEq), + just(Token::Less).to(BinOp::LtInt), + just(Token::Greater).to(BinOp::GtInt), + just(Token::LessEqual).to(BinOp::LtEqInt), + just(Token::GreaterEqual).to(BinOp::GtEqInt), + )); + + let comparison = unary + .clone() + .then(comparison_op.then(unary).repeated()) + .foldl(|left, (op, right)| { + let location = left.location().union(right.location()); + let left = Box::new(left); + let right = Box::new(right); + match op { + BinOp::Eq => ast::ClauseGuard::Equals { + location, + left, + right, + }, + BinOp::NotEq => ast::ClauseGuard::NotEquals { + location, + left, + right, + }, + BinOp::LtInt => ast::ClauseGuard::LtInt { + location, + left, + right, + }, + BinOp::GtInt => ast::ClauseGuard::GtInt { + location, + left, + right, + }, + BinOp::LtEqInt => ast::ClauseGuard::LtEqInt { + location, + left, + right, + }, + BinOp::GtEqInt => ast::ClauseGuard::GtEqInt { + location, + left, + right, + }, + _ => unreachable!(), + } + }) + .boxed(); + + let logical_op = choice(( + just(Token::AmperAmper).to(BinOp::And), + just(Token::VbarVbar).to(BinOp::Or), + )); + + comparison + .clone() + .then(logical_op.then(comparison).repeated()) + .foldl(|left, (op, right)| { + let location = left.location().union(right.location()); + let left = Box::new(left); + let right = Box::new(right); + match op { + BinOp::And => ast::ClauseGuard::And { + location, + left, + right, + }, + BinOp::Or => ast::ClauseGuard::Or { + location, + left, + right, + }, + _ => unreachable!(), + } + }) + }) +} + pub fn type_parser() -> impl Parser { recursive(|r| { choice(( diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 08acd7843..4b2d1055c 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -36,6 +36,16 @@ impl ParseError { } } + pub fn invalid_when_clause_guard(span: Span) -> Self { + Self { + kind: ErrorKind::InvalidWhenClause, + span, + while_parsing: None, + expected: HashSet::new(), + label: Some("invalid clause guard"), + } + } + pub fn malformed_base16_string_literal(span: Span) -> Self { Self { kind: ErrorKind::MalformedBase16StringLiteral, @@ -101,7 +111,7 @@ pub enum ErrorKind { #[help] hint: Option, }, - #[error("I tripped over a malformed base16-encoded string literal")] + #[error("I tripped over a malformed base16-encoded string literal.")] #[diagnostic(help("{}", formatdoc! { r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) string literals. @@ -113,6 +123,28 @@ pub enum ErrorKind { "# , "pub const".bright_blue(), "=".yellow(), "\"f4c9f9c4252d86702c2f4c2e49e6648c7cffe3c8f2b6b7d779788f50\"".bright_purple()}))] MalformedBase16StringLiteral, + #[error("I failed to understand a when clause guard.")] + #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))] + #[diagnostic(help("{}", formatdoc! { + r#"Clause guards are not as capable as standard expressions. While you can combine multiple clauses using '{operator_or}' and '{operator_and}', you can't do any arithmetic in there. They are mainly meant to compare pattern variables to some known constants using simple binary operators. + + For example, the following clauses are well-formed: + + {good} (x, _) if x == 10 -> ... + {good} (_, y) if y > 0 && y < 10 -> ... + {good} (x, y) if x && (y > 0 || y < 10) -> ... + + However, those aren't: + + {bad} (x, _) if x % 3 == 0 -> ... + {bad} (x, y) if x + y > 42 -> ... + "# + , operator_or = "||".yellow() + , operator_and = "&&".yellow() + , good = "✔️".green() + , bad = "✖️".red() + }))] + InvalidWhenClause, } #[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 1d4bb9429..6caa7e3fb 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -2128,3 +2128,267 @@ fn subtraction_vs_negate() { })], ); } + +#[test] +fn clause_guards() { + let code = indoc! {r#" + fn foo() { + when a is { + _ if 42 -> Void + _ if bar -> Void + _ if True -> Void + _ if a || b && c -> Void + _ if a || (b && c) -> Void + _ if a <= 42 || b > 14 || "str" -> Void + _ if a == 14 && !b -> Void + _ if !!True -> Void + } + } + "#}; + assert_definitions( + code, + vec![ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::When { + location: Span::new((), 13..250), + subjects: vec![expr::UntypedExpr::Var { + location: Span::new((), 18..19), + name: "a".to_string(), + }], + clauses: vec![ + ast::Clause { + location: Span::new((), 29..44), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 29..30), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::Constant(ast::Constant::Int { + location: Span::new((), 34..36), + value: "42".to_string(), + })), + then: expr::UntypedExpr::Var { + location: Span::new((), 40..44), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 49..65), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 49..50), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::Var { + location: Span::new((), 54..57), + name: "bar".to_string(), + tipo: (), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 61..65), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 70..87), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 70..71), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::Var { + location: Span::new((), 75..79), + tipo: (), + name: "True".to_string(), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 83..87), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 92..116), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 92..93), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::And { + location: Span::new((), 97..108), + left: Box::new(ast::ClauseGuard::Or { + location: Span::new((), 97..103), + left: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 97..98), + name: "a".to_string(), + tipo: (), + }), + right: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 102..103), + name: "b".to_string(), + tipo: (), + }), + }), + right: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 107..108), + name: "c".to_string(), + tipo: (), + }), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 112..116), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 121..147), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 121..122), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::Or { + location: Span::new((), 126..138), + left: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 126..127), + name: "a".to_string(), + tipo: (), + }), + right: Box::new(ast::ClauseGuard::And { + location: Span::new((), 132..138), + left: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 132..133), + name: "b".to_string(), + tipo: (), + }), + right: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 137..138), + name: "c".to_string(), + tipo: (), + }), + }), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 143..147), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 152..191), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 152..153), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::Or { + location: Span::new((), 157..183), + left: Box::new(ast::ClauseGuard::Or { + location: Span::new((), 157..174), + left: Box::new(ast::ClauseGuard::LtEqInt { + location: Span::new((), 157..164), + left: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 157..158), + name: "a".to_string(), + tipo: (), + }), + right: Box::new(ast::ClauseGuard::Constant( + ast::Constant::Int { + location: Span::new((), 162..164), + value: "42".to_string(), + }, + )), + }), + right: Box::new(ast::ClauseGuard::GtInt { + location: Span::new((), 168..174), + left: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 168..169), + name: "b".to_string(), + tipo: (), + }), + right: Box::new(ast::ClauseGuard::Constant( + ast::Constant::Int { + location: Span::new((), 172..174), + value: "14".to_string(), + }, + )), + }), + }), + right: Box::new(ast::ClauseGuard::Constant(ast::Constant::String { + location: Span::new((), 178..183), + value: "str".to_string(), + })), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 187..191), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 196..222), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 196..197), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::And { + location: Span::new((), 201..214), + left: Box::new(ast::ClauseGuard::Equals { + location: Span::new((), 201..208), + left: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 201..202), + name: "a".to_string(), + tipo: (), + }), + right: Box::new(ast::ClauseGuard::Constant(ast::Constant::Int { + location: Span::new((), 206..208), + value: "14".to_string(), + })), + }), + right: Box::new(ast::ClauseGuard::Not { + location: Span::new((), 212..214), + value: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 213..214), + name: "b".to_string(), + tipo: (), + }), + }), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 218..222), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 227..246), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 227..228), + }], + alternative_patterns: vec![], + guard: Some(ast::ClauseGuard::Not { + location: Span::new((), 232..238), + value: Box::new(ast::ClauseGuard::Not { + location: Span::new((), 233..238), + value: Box::new(ast::ClauseGuard::Var { + location: Span::new((), 234..238), + tipo: (), + name: "True".to_string(), + }), + }), + }), + then: expr::UntypedExpr::Var { + location: Span::new((), 242..246), + name: "Void".to_string(), + }, + }, + ], + }, + doc: None, + location: Span::new((), 0..8), + name: "foo".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 251, + })], + ); +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 492415227..3e48878fc 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1011,41 +1011,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } - // ClauseGuard::TupleIndex { - // location, - // tuple, - // index, - // .. - // } => { - // let tuple = self.infer_clause_guard(*tuple)?; - // match tuple.type_().as_ref() { - // Type::Tuple { elems } => { - // let type_ = elems - // .get(index as usize) - // .ok_or(Error::OutOfBoundsTupleIndex { - // location, - // index, - // size: elems.len(), - // })? - // .clone(); - // Ok(ClauseGuard::TupleIndex { - // location, - // index, - // type_, - // tuple: Box::new(tuple), - // }) - // } - - // typ if typ.is_unbound() => Err(Error::NotATupleUnbound { - // location: tuple.location(), - // }), - - // _ => Err(Error::NotATuple { - // location: tuple.location(), - // given: tuple.type_(), - // }), - // } - // } + ClauseGuard::Not { + location, value, .. + } => { + let value = self.infer_clause_guard(*value)?; + self.unify(bool(), value.tipo(), value.location())?; + Ok(ClauseGuard::Not { + location, + value: Box::new(value), + }) + } + ClauseGuard::And { location, left, diff --git a/crates/aiken-lang/src/uplc.rs b/crates/aiken-lang/src/uplc.rs index 3aeea6def..8ec8895eb 100644 --- a/crates/aiken-lang/src/uplc.rs +++ b/crates/aiken-lang/src/uplc.rs @@ -25,11 +25,12 @@ use crate::{ builder::{ check_replaceable_opaque_type, check_when_pattern_needs, constants_ir, convert_constants_to_data, convert_data_to_type, convert_type_to_data, get_common_ancestor, - get_generics_and_type, handle_func_dependencies_ir, handle_recursion_ir, - list_access_to_uplc, lookup_data_type_by_tipo, monomorphize, rearrange_clauses, - replace_opaque_type, wrap_validator_args, AssignmentProperties, ClauseProperties, - DataTypeKey, FuncComponents, FunctionAccessKey, + get_generics_and_type, handle_clause_guard, handle_func_dependencies_ir, + handle_recursion_ir, list_access_to_uplc, lookup_data_type_by_tipo, monomorphize, + rearrange_clauses, replace_opaque_type, wrap_validator_args, AssignmentProperties, + ClauseProperties, DataTypeKey, FuncComponents, FunctionAccessKey, }, + builtins::bool, expr::TypedExpr, tipo::{ ModuleValueConstructor, PatternConstructor, Type, TypeInfo, ValueConstructor, @@ -665,6 +666,33 @@ impl<'a> CodeGenerator<'a> { self.build_ir(&clause.then, &mut clause_then_vec, scope.clone()); + if let Some(clause_guard) = &clause.guard { + let mut clause_guard_vec = vec![]; + *clause_properties.is_complex_clause() = true; + let clause_guard_name = format!("__clause_guard_{}", self.id_gen.next()); + + clause_guard_vec.push(Air::Let { + scope: scope.clone(), + name: clause_guard_name.clone(), + }); + + handle_clause_guard(clause_guard, &mut clause_guard_vec, scope.clone()); + + clause_guard_vec.push(Air::ClauseGuard { + scope: scope.clone(), + subject_name: clause_guard_name, + tipo: bool(), + }); + + clause_guard_vec.push(Air::Bool { + scope: scope.clone(), + value: true, + }); + + clause_guard_vec.append(&mut clause_then_vec); + clause_then_vec = clause_guard_vec; + } + match clause_properties { ClauseProperties::ConstrClause { original_subject_name, @@ -4355,7 +4383,47 @@ impl<'a> CodeGenerator<'a> { let mut term = arg_stack.pop().unwrap(); if tipo.is_bool() { - if matches!(clause, Term::Constant(UplcConstant::Bool(true))) { + if complex_clause { + let other_clauses = term; + if matches!(clause, Term::Constant(UplcConstant::Bool(true))) { + term = if_else( + Term::Var(Name { + text: subject_name, + unique: 0.into(), + }), + Term::Delay(body.into()), + Term::Var(Name { + text: "__other_clauses_delayed".to_string(), + unique: 0.into(), + }), + ) + .force_wrap(); + } else { + term = if_else( + Term::Var(Name { + text: subject_name, + unique: 0.into(), + }), + Term::Var(Name { + text: "__other_clauses_delayed".to_string(), + unique: 0.into(), + }), + Term::Delay(body.into()), + ) + .force_wrap(); + } + + term = apply_wrap( + Term::Lambda { + parameter_name: Name { + text: "__other_clauses_delayed".to_string(), + unique: 0.into(), + }, + body: term.into(), + }, + Term::Delay(other_clauses.into()), + ); + } else if matches!(clause, Term::Constant(UplcConstant::Bool(true))) { term = delayed_if_else( Term::Var(Name { text: subject_name, @@ -5298,6 +5366,21 @@ impl<'a> CodeGenerator<'a> { let tuple_types = tipo.get_inner_types(); + if complex_clause { + let next_clause = arg_stack.pop().unwrap(); + + term = apply_wrap( + Term::Lambda { + parameter_name: Name { + text: "__other_clauses_delayed".to_string(), + unique: 0.into(), + }, + body: term.into(), + }, + Term::Delay(next_clause.into()), + ) + } + if tuple_types.len() == 2 { for (index, name) in indices.iter() { if *index == 0 { @@ -5372,21 +5455,6 @@ impl<'a> CodeGenerator<'a> { ); } } - - if complex_clause { - let next_clause = arg_stack.pop().unwrap(); - - term = apply_wrap( - Term::Lambda { - parameter_name: Name { - text: "__other_clauses_delayed".to_string(), - unique: 0.into(), - }, - body: term.into(), - }, - Term::Delay(next_clause.into()), - ) - } arg_stack.push(term); } } diff --git a/examples/acceptance_tests/036/assets/spend/spend/mainnet.addr b/examples/acceptance_tests/036/assets/spend/spend/mainnet.addr index 1f30832ec..f3d03b3d3 100644 --- a/examples/acceptance_tests/036/assets/spend/spend/mainnet.addr +++ b/examples/acceptance_tests/036/assets/spend/spend/mainnet.addr @@ -1 +1 @@ -addr1w8ehrka3eyh8hqxt647qm56z0ju3x8fsyjv2f3cwp5kr5ssvegjnw \ No newline at end of file +addr1w8r2ln3c7meykuf6ejw0qu5qtdfxh4e4p68v5e3c0lwmrmsdujvef \ No newline at end of file diff --git a/examples/acceptance_tests/036/assets/spend/spend/payment_script.json b/examples/acceptance_tests/036/assets/spend/spend/payment_script.json index 26f7ac73d..d232b059c 100644 --- a/examples/acceptance_tests/036/assets/spend/spend/payment_script.json +++ b/examples/acceptance_tests/036/assets/spend/spend/payment_script.json @@ -1,5 +1,5 @@ { "type": "PlutusScriptV2", "description": "Generated by Aiken", - "cborHex": "59015159014e01000022253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b1" + "cborHex": "590173590170010000323222253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b18008009112999aab9f00214984cd5cd98009aba1002333003003357440040021" } \ No newline at end of file diff --git a/examples/acceptance_tests/036/assets/spend/spend/script.cbor b/examples/acceptance_tests/036/assets/spend/spend/script.cbor index 8663ae49a..acc3a55d9 100644 --- a/examples/acceptance_tests/036/assets/spend/spend/script.cbor +++ b/examples/acceptance_tests/036/assets/spend/spend/script.cbor @@ -1 +1 @@ -59014e01000022253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b1 \ No newline at end of file +590170010000323222253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b18008009112999aab9f00214984cd5cd98009aba1002333003003357440040021 \ No newline at end of file diff --git a/examples/acceptance_tests/036/assets/spend/spend/testnet.addr b/examples/acceptance_tests/036/assets/spend/spend/testnet.addr index 54c31f14f..5a07d03ee 100644 --- a/examples/acceptance_tests/036/assets/spend/spend/testnet.addr +++ b/examples/acceptance_tests/036/assets/spend/spend/testnet.addr @@ -1 +1 @@ -addr_test1wrehrka3eyh8hqxt647qm56z0ju3x8fsyjv2f3cwp5kr5ssh3uwut \ No newline at end of file +addr_test1wrr2ln3c7meykuf6ejw0qu5qtdfxh4e4p68v5e3c0lwmrmsk5xskv \ No newline at end of file diff --git a/examples/acceptance_tests/047/assets/foo/spend/mainnet.addr b/examples/acceptance_tests/047/assets/foo/spend/mainnet.addr index 36be4414b..f7439b068 100644 --- a/examples/acceptance_tests/047/assets/foo/spend/mainnet.addr +++ b/examples/acceptance_tests/047/assets/foo/spend/mainnet.addr @@ -1 +1 @@ -addr1w9z47fyj9ffqck2fnld04k27zfe04wq6n9zj76u4ghu4xdcd0futm \ No newline at end of file +addr1wyahaesnnh44nkyjj4dv8jk3t4f7frwtzepjyujkk2wjkmczwqttr \ No newline at end of file diff --git a/examples/acceptance_tests/047/assets/foo/spend/payment_script.json b/examples/acceptance_tests/047/assets/foo/spend/payment_script.json index b33020997..ffae9c042 100644 --- a/examples/acceptance_tests/047/assets/foo/spend/payment_script.json +++ b/examples/acceptance_tests/047/assets/foo/spend/payment_script.json @@ -1,5 +1,5 @@ { "type": "PlutusScriptV2", "description": "Generated by Aiken", - "cborHex": "4e4d01000022253335734944526161" + "cborHex": "5831582f01000032322225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae880080041" } \ No newline at end of file diff --git a/examples/acceptance_tests/047/assets/foo/spend/script.cbor b/examples/acceptance_tests/047/assets/foo/spend/script.cbor index 862591fac..3f6d9dbcc 100644 --- a/examples/acceptance_tests/047/assets/foo/spend/script.cbor +++ b/examples/acceptance_tests/047/assets/foo/spend/script.cbor @@ -1 +1 @@ -4d01000022253335734944526161 \ No newline at end of file +582f01000032322225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae880080041 \ No newline at end of file diff --git a/examples/acceptance_tests/047/assets/foo/spend/testnet.addr b/examples/acceptance_tests/047/assets/foo/spend/testnet.addr index d6baa9fb4..fcfb7c3b0 100644 --- a/examples/acceptance_tests/047/assets/foo/spend/testnet.addr +++ b/examples/acceptance_tests/047/assets/foo/spend/testnet.addr @@ -1 +1 @@ -addr_test1wpz47fyj9ffqck2fnld04k27zfe04wq6n9zj76u4ghu4xdck8aqy7 \ No newline at end of file +addr_test1wqahaesnnh44nkyjj4dv8jk3t4f7frwtzepjyujkk2wjkmcex5hyx \ No newline at end of file diff --git a/examples/acceptance_tests/048/assets/foo/spend/mainnet.addr b/examples/acceptance_tests/048/assets/foo/spend/mainnet.addr new file mode 100644 index 000000000..d8a5b84ae --- /dev/null +++ b/examples/acceptance_tests/048/assets/foo/spend/mainnet.addr @@ -0,0 +1 @@ +addr1w9d2hmr5s8j9cz5xgzjzn3srsfajcwjjzeruaj5mh3kpuqcmvmz3r \ No newline at end of file diff --git a/examples/acceptance_tests/048/assets/foo/spend/payment_script.json b/examples/acceptance_tests/048/assets/foo/spend/payment_script.json new file mode 100644 index 000000000..b687b5b69 --- /dev/null +++ b/examples/acceptance_tests/048/assets/foo/spend/payment_script.json @@ -0,0 +1,5 @@ +{ + "type": "PlutusScriptV2", + "description": "Generated by Aiken", + "cborHex": "5855585301000032322225333573466e1cc8c0052f7b6301010400010101002323232002375a6aae78008dd69aab9d0010014802052616300100122253335573e004293099ab9b3001357420046660060066ae88008005" +} \ No newline at end of file diff --git a/examples/acceptance_tests/048/assets/foo/spend/script.cbor b/examples/acceptance_tests/048/assets/foo/spend/script.cbor new file mode 100644 index 000000000..e042c5308 --- /dev/null +++ b/examples/acceptance_tests/048/assets/foo/spend/script.cbor @@ -0,0 +1 @@ +585301000032322225333573466e1cc8c0052f7b6301010400010101002323232002375a6aae78008dd69aab9d0010014802052616300100122253335573e004293099ab9b3001357420046660060066ae88008005 \ No newline at end of file diff --git a/examples/acceptance_tests/048/assets/foo/spend/testnet.addr b/examples/acceptance_tests/048/assets/foo/spend/testnet.addr new file mode 100644 index 000000000..13c9cf36b --- /dev/null +++ b/examples/acceptance_tests/048/assets/foo/spend/testnet.addr @@ -0,0 +1 @@ +addr_test1wpd2hmr5s8j9cz5xgzjzn3srsfajcwjjzeruaj5mh3kpuqcqy077x \ No newline at end of file diff --git a/examples/acceptance_tests/048/lib/tests.ak b/examples/acceptance_tests/048/lib/tests.ak index 7cef193df..0e4c6eb07 100644 --- a/examples/acceptance_tests/048/lib/tests.ak +++ b/examples/acceptance_tests/048/lib/tests.ak @@ -1,9 +1,33 @@ -pub fn when_tuple(a: (Int, Int)) -> Int { +test foo_1() { + let a = False when a is { - (a, b) -> a + a if a -> False + _ -> True } } -test when_tuple_1() { - when_tuple((4, 1)) == 4 +test foo_2() { + let point = (14, 42) + when point is { + (x, _) if x > 100 -> False + (x, _) if x > 10 -> True + _ -> False + } +} + +test foo_3() { + let point = (14, 42) + when point is { + (x, y) if x == 14 && y <= 100 -> True + _ -> False + } +} + +test foo_4() { + let a = False + let point = (14, 42) + when point is { + (x, y) if !a -> x + y == 56 + _ -> False + } } diff --git a/examples/acceptance_tests/049/aiken.lock b/examples/acceptance_tests/049/aiken.lock new file mode 100644 index 000000000..3a78b1e7a --- /dev/null +++ b/examples/acceptance_tests/049/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/049/aiken.toml b/examples/acceptance_tests/049/aiken.toml new file mode 100644 index 000000000..e0b731588 --- /dev/null +++ b/examples/acceptance_tests/049/aiken.toml @@ -0,0 +1,3 @@ +name = 'aiken-lang/acceptance_test_049' +version = '0.0.0' +description = '' diff --git a/examples/acceptance_tests/049/assets/foo/spend/mainnet.addr b/examples/acceptance_tests/049/assets/foo/spend/mainnet.addr new file mode 100644 index 000000000..36be4414b --- /dev/null +++ b/examples/acceptance_tests/049/assets/foo/spend/mainnet.addr @@ -0,0 +1 @@ +addr1w9z47fyj9ffqck2fnld04k27zfe04wq6n9zj76u4ghu4xdcd0futm \ No newline at end of file diff --git a/examples/acceptance_tests/049/assets/foo/spend/payment_script.json b/examples/acceptance_tests/049/assets/foo/spend/payment_script.json new file mode 100644 index 000000000..b33020997 --- /dev/null +++ b/examples/acceptance_tests/049/assets/foo/spend/payment_script.json @@ -0,0 +1,5 @@ +{ + "type": "PlutusScriptV2", + "description": "Generated by Aiken", + "cborHex": "4e4d01000022253335734944526161" +} \ No newline at end of file diff --git a/examples/acceptance_tests/049/assets/foo/spend/script.cbor b/examples/acceptance_tests/049/assets/foo/spend/script.cbor new file mode 100644 index 000000000..862591fac --- /dev/null +++ b/examples/acceptance_tests/049/assets/foo/spend/script.cbor @@ -0,0 +1 @@ +4d01000022253335734944526161 \ No newline at end of file diff --git a/examples/acceptance_tests/049/assets/foo/spend/testnet.addr b/examples/acceptance_tests/049/assets/foo/spend/testnet.addr new file mode 100644 index 000000000..d6baa9fb4 --- /dev/null +++ b/examples/acceptance_tests/049/assets/foo/spend/testnet.addr @@ -0,0 +1 @@ +addr_test1wpz47fyj9ffqck2fnld04k27zfe04wq6n9zj76u4ghu4xdck8aqy7 \ No newline at end of file diff --git a/examples/acceptance_tests/049/lib/tests.ak b/examples/acceptance_tests/049/lib/tests.ak new file mode 100644 index 000000000..7cef193df --- /dev/null +++ b/examples/acceptance_tests/049/lib/tests.ak @@ -0,0 +1,9 @@ +pub fn when_tuple(a: (Int, Int)) -> Int { + when a is { + (a, b) -> a + } +} + +test when_tuple_1() { + when_tuple((4, 1)) == 4 +}