diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 2f28b034..9dacea2f 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -258,6 +258,18 @@ pub struct ModuleConstant { pub tipo: T, } +pub type TypedValidator = Validator, TypedExpr>; +pub type UntypedValidator = Validator<(), UntypedExpr>; + +#[derive(Debug, Clone, PartialEq)] +pub struct Validator { + pub doc: Option, + pub end_position: usize, + pub function: Function, + pub location: Span, + pub params: Vec>, +} + pub type TypedDefinition = Definition, TypedExpr, String, String>; pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>; @@ -274,6 +286,8 @@ pub enum Definition { ModuleConstant(ModuleConstant), Test(Function), + + Validator(Validator), } impl Definition { @@ -284,6 +298,7 @@ impl Definition { | Definition::TypeAlias(TypeAlias { location, .. }) | Definition::DataType(DataType { location, .. }) | Definition::ModuleConstant(ModuleConstant { location, .. }) + | Definition::Validator(Validator { location, .. }) | Definition::Test(Function { location, .. }) => *location, } } @@ -295,6 +310,7 @@ impl Definition { | Definition::TypeAlias(TypeAlias { doc, .. }) | Definition::DataType(DataType { doc, .. }) | Definition::ModuleConstant(ModuleConstant { doc, .. }) + | Definition::Validator(Validator { doc, .. }) | Definition::Test(Function { doc, .. }) => { let _ = std::mem::replace(doc, Some(new_doc)); } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 8c27c166..612333d8 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -9,7 +9,7 @@ use crate::{ Definition, Function, IfBranch, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, TypedConstant, UnOp, UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, - UntypedDefinition, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, + UntypedDefinition, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE, }, docvec, @@ -236,6 +236,13 @@ impl<'comments> Formatter<'comments> { *end_position, ), + Definition::Validator(Validator { + end_position, + function, + params, + .. + }) => self.definition_validator(params, function, *end_position), + Definition::Test(Function { name, arguments: args, @@ -550,6 +557,59 @@ impl<'comments> Formatter<'comments> { .append("}") } + fn definition_validator<'a>( + &mut self, + params: &'a [UntypedArg], + function: &'a UntypedFunction, + end_position: usize, + ) -> Document<'a> { + // Fn and args + let head = "fn".to_doc().append(wrap_args( + function.arguments.iter().map(|e| (self.fn_arg(e), false)), + )); + + // Add return annotation + let head = match &function.return_annotation { + Some(anno) => head.append(" -> ").append(self.annotation(anno)), + None => head, + } + .group(); + + // Format body + let body = self.expr(&function.body); + + // Add any trailing comments + let body = match printed_comments(self.pop_comments(function.end_position), false) { + Some(comments) => body.append(line()).append(comments), + None => body, + }; + + // validator name(params) + let v_head = "validator" + .to_doc() + .append(" ") + .append(function.name.as_str()) + .append(wrap_args(params.iter().map(|e| (self.fn_arg(e), false)))); + + // Stick it all together + let inner_fn = head + .append(" {") + .append(line().append(body).nest(INDENT).group()) + .append(line()) + .append("}"); + + let inner_fn = match printed_comments(self.pop_comments(end_position), false) { + Some(comments) => inner_fn.append(line()).append(comments), + None => inner_fn, + }; + + v_head + .append(" {") + .append(line().append(inner_fn).nest(INDENT).group()) + .append(line()) + .append("}") + } + fn expr_fn<'a>( &mut self, args: &'a [UntypedArg], diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 26c29f5d..17d8ad0f 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -83,6 +83,7 @@ fn module_parser() -> impl Parser, Error = ParseEr import_parser(), data_parser(), type_alias_parser(), + validator_parser(), fn_parser(), test_parser(), constant_parser(), @@ -232,6 +233,73 @@ pub fn type_alias_parser() -> impl Parser impl Parser { + just(Token::Validator) + .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)) + .or_not() + .map_with_span(|arguments, span| (arguments.unwrap_or_default(), span)), + ) + .then( + just(Token::Fn) + .ignore_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( + |(((arguments, args_span), return_annotation), body), span| ast::Function { + arguments, + body: body.unwrap_or(expr::UntypedExpr::Todo { + kind: TodoKind::EmptyFunction, + location: span, + label: 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: "".to_string(), + public: false, + return_annotation, + return_type: (), + }, + ) + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .map_with_span(|((name, (params, params_span)), mut function), span| { + function.name = name; + + ast::UntypedDefinition::Validator(ast::Validator { + doc: None, + function, + location: Span { + start: span.start, + end: params_span.end, + }, + params, + end_position: span.end - 1, + }) + }) +} + pub fn fn_parser() -> impl Parser { pub_parser() .or_not() diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index e94de71c..ef48c07d 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -76,6 +76,7 @@ pub enum Token { Type, When, Trace, + Validator, } impl fmt::Display for Token { @@ -156,6 +157,7 @@ impl fmt::Display for Token { Token::Type => "type", Token::Test => "test", Token::ErrorTerm => "error", + Token::Validator => "validator", }; write!(f, "\"{s}\"") } diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 45dfe250..698f202b 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -38,6 +38,28 @@ fn windows_newline() { ) } +#[test] +fn validator() { + let code = indoc! {r#" + validator foo { + fn(datum, rdmr, ctx) { + True + } + } + "#}; + + assert_definitions( + code, + vec![ast::UntypedDefinition::Use(Use { + location: Span::new((), 0..12), + module: vec!["std".to_string(), "list".to_string()], + as_name: None, + unqualified: vec![], + package: (), + })], + ) +} + #[test] fn import() { let code = indoc! {r#" diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 6bcea04f..54a5b9d2 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -884,6 +884,7 @@ impl<'a> Environment<'a> { }) } Definition::Fn { .. } + | Definition::Validator { .. } | Definition::Use { .. } | Definition::ModuleConstant { .. } | Definition::Test { .. } => None, @@ -995,6 +996,7 @@ impl<'a> Environment<'a> { } Definition::Fn { .. } + | Definition::Validator { .. } | Definition::Test { .. } | Definition::Use { .. } | Definition::ModuleConstant { .. } => {} diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 2aead900..447aad18 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -531,6 +531,7 @@ fn str_to_keyword(word: &str) -> Option { "trace" => Some(Token::Trace), "test" => Some(Token::Test), "error" => Some(Token::ErrorTerm), + "validator" => Some(Token::Validator), _ => None, } }