From ed92869fb9efba98a114e8cc5f5ac899b23952ee Mon Sep 17 00:00:00 2001 From: rvcas Date: Thu, 16 Mar 2023 18:33:57 -0400 Subject: [PATCH] feat(validator): parsing and typechecking for double validators --- crates/aiken-lang/src/ast.rs | 1 + crates/aiken-lang/src/format.rs | 25 ++-- crates/aiken-lang/src/parser.rs | 97 ++++++++------- crates/aiken-lang/src/tests/check.rs | 16 +-- crates/aiken-lang/src/tests/format.rs | 8 +- crates/aiken-lang/src/tests/parser.rs | 114 +++++++++++++++++- crates/aiken-lang/src/tipo/infer.rs | 52 +++++++- .../aiken-project/src/blueprint/validator.rs | 40 +++--- 8 files changed, 259 insertions(+), 94 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 2dba649c..2d5102fe 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -322,6 +322,7 @@ pub struct Validator { pub doc: Option, pub end_position: usize, pub fun: Function, + pub other_fun: Option>, pub location: Span, pub params: Vec>, } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 53f132dd..521263f2 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -504,12 +504,17 @@ impl<'comments> Formatter<'comments> { &mut self, params: &'a [UntypedArg], fun: &'a UntypedFunction, + other_fun: &'a Option, end_position: usize, ) -> Document<'a> { // Fn and args - let head = "fn".to_doc().append(wrap_args( - fun.arguments.iter().map(|e| (self.fn_arg(e), false)), - )); + let head = "fn" + .to_doc() + .append(" ") + .append(fun.name.to_doc()) + .append(wrap_args( + fun.arguments.iter().map(|e| (self.fn_arg(e), false)), + )); // Add return annotation let head = match &fun.return_annotation { @@ -528,15 +533,11 @@ impl<'comments> Formatter<'comments> { }; // validator name(params) - let v_head = "validator" - .to_doc() - .append(" ") - .append(fun.name.as_str()) - .append(if !params.is_empty() { - wrap_args(params.iter().map(|e| (self.fn_arg(e), false))) - } else { - "".to_doc() - }); + let v_head = "validator".to_doc().append(if !params.is_empty() { + wrap_args(params.iter().map(|e| (self.fn_arg(e), false))) + } else { + "".to_doc() + }); // Stick it all together let inner_fn = head diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 90e75a65..672765ae 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -242,9 +242,43 @@ pub fn type_alias_parser() -> impl Parser impl Parser { - just(Token::Validator) - .ignore_then(select! {Token::Name {name} => name}.map_with_span(|name, span| (name, span))) + let func_parser = just(Token::Fn) + .ignore_then(select! {Token::Name {name} => name}) .then( + fn_param_parser() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(|arguments, span| (arguments, span)), + ) + .then(just(Token::RArrow).ignore_then(type_parser()).or_not()) + .then( + expr_seq_parser() + .or_not() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .map_with_span( + |(((name, (arguments, args_span)), return_annotation), body), span| ast::Function { + arguments, + body: body.unwrap_or_else(|| expr::UntypedExpr::todo(span, None)), + doc: None, + location: Span { + start: span.start, + end: return_annotation + .as_ref() + .map(|l| l.location().end) + .unwrap_or_else(|| args_span.end), + }, + end_position: span.end - 1, + name, + public: false, + return_annotation, + return_type: (), + }, + ); + + just(Token::Validator) + .ignore_then( fn_param_parser() .separated_by(just(Token::Comma)) .allow_trailing() @@ -253,51 +287,32 @@ pub fn validator_parser() -> impl Parser Bool { + validator ( ) { + fn foo (d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool { True } } "#}; let expected = indoc! {r#" - validator foo { - fn(d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool { + validator { + fn foo(d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool { True } } diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index cc83922f..e723cca9 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -41,8 +41,8 @@ fn windows_newline() { #[test] fn validator() { let code = indoc! {r#" - validator foo { - fn(datum, rdmr, ctx) { + validator { + fn foo(datum, rdmr, ctx) { True } } @@ -91,14 +91,120 @@ fn validator() { name: "True".to_string(), }, doc: None, - location: Span::new((), 18..38), + location: Span::new((), 14..38), name: "foo".to_string(), public: false, return_annotation: None, return_type: (), end_position: 52, }, - location: Span::new((), 0..13), + other_fun: None, + location: Span::new((), 0..9), + params: vec![], + })], + ) +} + +#[test] +fn double_validator() { + let code = indoc! {r#" + validator { + fn foo(datum, rdmr, ctx) { + True + } + + fn bar(rdmr, ctx) { + True + } + } + "#}; + + assert_definitions( + code, + vec![ast::UntypedDefinition::Validator(ast::Validator { + doc: None, + end_position: 90, + fun: Function { + arguments: vec![ + ast::Arg { + arg_name: ast::ArgName::Named { + name: "datum".to_string(), + label: "datum".to_string(), + location: Span::new((), 21..26), + }, + location: Span::new((), 21..26), + annotation: None, + tipo: (), + }, + ast::Arg { + arg_name: ast::ArgName::Named { + name: "rdmr".to_string(), + label: "rdmr".to_string(), + location: Span::new((), 28..32), + }, + location: Span::new((), 28..32), + annotation: None, + tipo: (), + }, + ast::Arg { + arg_name: ast::ArgName::Named { + name: "ctx".to_string(), + label: "ctx".to_string(), + location: Span::new((), 34..37), + }, + location: Span::new((), 34..37), + annotation: None, + tipo: (), + }, + ], + body: expr::UntypedExpr::Var { + location: Span::new((), 45..49), + name: "True".to_string(), + }, + doc: None, + location: Span::new((), 14..38), + name: "foo".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 52, + }, + other_fun: Some(Function { + arguments: vec![ + ast::Arg { + arg_name: ast::ArgName::Named { + name: "rdmr".to_string(), + label: "rdmr".to_string(), + location: Span::new((), 64..68), + }, + location: Span::new((), 64..68), + annotation: None, + tipo: (), + }, + ast::Arg { + arg_name: ast::ArgName::Named { + name: "ctx".to_string(), + label: "ctx".to_string(), + location: Span::new((), 70..73), + }, + location: Span::new((), 70..73), + annotation: None, + tipo: (), + }, + ], + body: expr::UntypedExpr::Var { + location: Span::new((), 81..85), + name: "True".to_string(), + }, + doc: None, + location: Span::new((), 57..74), + name: "bar".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 88, + }), + location: Span::new((), 0..9), params: vec![], })], ) diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 53a395c6..25bf7458 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use crate::{ ast::{ DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor, - RecordConstructorArg, Span, Tracing, TypeAlias, TypedDefinition, TypedModule, - UntypedDefinition, UntypedModule, Use, Validator, + RecordConstructorArg, Span, Tracing, TypeAlias, TypedDefinition, TypedFunction, + TypedModule, UntypedDefinition, UntypedModule, Use, Validator, }, builtins, builtins::function, @@ -255,11 +255,12 @@ fn infer_definition( location, end_position, mut fun, - mut params, + other_fun, + params, }) => { let params_length = params.len(); - params.append(&mut fun.arguments); - fun.arguments = params; + let temp_params = params.iter().cloned().chain(fun.arguments); + fun.arguments = temp_params.collect(); if let Definition::Fn(mut typed_fun) = infer_definition( Definition::Fn(fun), @@ -285,10 +286,51 @@ fn infer_definition( }); } + let typed_other_fun = other_fun + .map(|mut other| -> Result { + let params = params.into_iter().chain(other.arguments); + other.arguments = params.collect(); + + if let Definition::Fn(mut other_typed_fun) = infer_definition( + Definition::Fn(other), + module_name, + hydrators, + environment, + tracing, + kind, + )? { + if !other_typed_fun.return_type.is_bool() { + return Err(Error::ValidatorMustReturnBool { + return_type: other_typed_fun.return_type.clone(), + location: other_typed_fun.location, + }); + } + + other_typed_fun.arguments.drain(0..params_length); + + if other_typed_fun.arguments.len() < 2 + || other_typed_fun.arguments.len() > 3 + { + return Err(Error::IncorrectValidatorArity { + count: typed_fun.arguments.len() as u32, + location: typed_fun.location, + }); + } + + Ok(other_typed_fun) + } else { + unreachable!( + "validator definition inferred as something other than a function?" + ) + } + }) + .transpose(); + Ok(Definition::Validator(Validator { doc, end_position, fun: typed_fun, + other_fun: typed_other_fun?, location, params: typed_params, })) diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 7a945413..1cb03deb 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -272,8 +272,8 @@ mod test { fn mint_basic() { assert_validator( r#" - validator mint { - fn(redeemer: Data, ctx: Data) { + validator { + fn mint(redeemer: Data, ctx: Data) { True } } @@ -302,8 +302,8 @@ mod test { fn mint_parameterized() { assert_validator( r#" - validator mint(utxo_ref: Int) { - fn(redeemer: Data, ctx: Data) { + validator(utxo_ref: Int) { + fn mint(redeemer: Data, ctx: Data) { True } } @@ -373,8 +373,8 @@ mod test { Abort } - validator simplified_hydra { - fn(datum: State, redeemer: Input, ctx: Data) { + validator { + fn simplified_hydra(datum: State, redeemer: Input, ctx: Data) { True } } @@ -485,8 +485,8 @@ mod test { fn tuples() { assert_validator( r#" - validator tuples { - fn(datum: (Int, ByteArray), redeemer: (Int, Int, Int), ctx: Void) { + validator { + fn tuples(datum: (Int, ByteArray), redeemer: (Int, Int, Int), ctx: Void) { True } } @@ -560,8 +560,8 @@ mod test { Infinite } - validator generics { - fn(redeemer: Either>, ctx: Void) { + validator { + fn generics(redeemer: Either>, ctx: Void) { True } } @@ -644,8 +644,8 @@ mod test { type UUID { UUID } - validator list_2_tuples_as_map { - fn(redeemer: Dict, ctx: Void) { + validator { + fn list_2_tuples_as_map(redeemer: Dict, ctx: Void) { True } } @@ -707,8 +707,8 @@ mod test { type UUID { UUID } - validator opaque_singleton_variants { - fn(redeemer: Dict, ctx: Void) { + validator { + fn opaque_singleton_variants(redeemer: Dict, ctx: Void) { True } } @@ -753,8 +753,8 @@ mod test { foo: Data } - validator nested_data { - fn(datum: Foo, redeemer: Int, ctx: Void) { + validator { + fn nested_data(datum: Foo, redeemer: Int, ctx: Void) { True } } @@ -814,8 +814,8 @@ mod test { Mul(Expr, Expr) } - validator recursive_types { - fn(redeemer: Expr, ctx: Void) { + validator { + fn recursive_types(redeemer: Expr, ctx: Void) { True } } @@ -899,8 +899,8 @@ mod test { } } - validator recursive_generic_types { - fn(datum: Foo, redeemer: LinkedList, ctx: Void) { + validator { + fn recursive_generic_types(datum: Foo, redeemer: LinkedList, ctx: Void) { True } }