From a65b4aa47193044bdabb44ab8e577f1870b056b6 Mon Sep 17 00:00:00 2001 From: rvcas Date: Wed, 7 Dec 2022 09:57:01 -0500 Subject: [PATCH 01/18] feat: add test def and test token --- crates/lang/src/ast.rs | 8 ++++++-- crates/lang/src/parser/lexer.rs | 1 + crates/lang/src/parser/token.rs | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 93a70889..3afbdd82 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -138,6 +138,8 @@ pub enum Definition { Use(Use), ModuleConstant(ModuleConstant), + + Test(Function), } impl Definition { @@ -147,7 +149,8 @@ impl Definition { | Definition::Use(Use { location, .. }) | Definition::TypeAlias(TypeAlias { location, .. }) | Definition::DataType(DataType { location, .. }) - | Definition::ModuleConstant(ModuleConstant { location, .. }) => *location, + | Definition::ModuleConstant(ModuleConstant { location, .. }) + | Definition::Test(Function { location, .. }) => *location, } } @@ -157,7 +160,8 @@ impl Definition { Definition::Fn(Function { doc, .. }) | Definition::TypeAlias(TypeAlias { doc, .. }) | Definition::DataType(DataType { doc, .. }) - | Definition::ModuleConstant(ModuleConstant { doc, .. }) => { + | Definition::ModuleConstant(ModuleConstant { doc, .. }) + | Definition::Test(Function { doc, .. }) => { let _ = std::mem::replace(doc, Some(new_doc)); } } diff --git a/crates/lang/src/parser/lexer.rs b/crates/lang/src/parser/lexer.rs index 1d140384..d845b741 100644 --- a/crates/lang/src/parser/lexer.rs +++ b/crates/lang/src/parser/lexer.rs @@ -67,6 +67,7 @@ pub fn lexer() -> impl Parser, Error = ParseError> { "check" => Token::Assert, "const" => Token::Const, "fn" => Token::Fn, + "test" => Token::Test, "if" => Token::If, "else" => Token::Else, "is" => Token::Is, diff --git a/crates/lang/src/parser/token.rs b/crates/lang/src/parser/token.rs index 8faaa339..0811268a 100644 --- a/crates/lang/src/parser/token.rs +++ b/crates/lang/src/parser/token.rs @@ -68,6 +68,7 @@ pub enum Token { Opaque, Pub, Use, + Test, Todo, Trace, Type, @@ -145,6 +146,7 @@ impl fmt::Display for Token { Token::Todo => "todo", Token::Trace => "try", Token::Type => "type", + Token::Test => "test", }; write!(f, "\"{}\"", s) } From ea487478250ac2f938cdc41a3b9542ccf0dbde98 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 7 Dec 2022 16:21:16 +0100 Subject: [PATCH 02/18] Extend parser for 'test' keyword. --- crates/lang/src/format.rs | 23 +++++++++++++++++++++-- crates/lang/src/parser.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/crates/lang/src/format.rs b/crates/lang/src/format.rs index 71e76d10..9a60bfca 100644 --- a/crates/lang/src/format.rs +++ b/crates/lang/src/format.rs @@ -215,7 +215,24 @@ impl<'comments> Formatter<'comments> { return_annotation, end_position, .. - }) => self.definition_fn(public, name, args, return_annotation, body, *end_position), + }) => self.definition_fn( + public, + "fn", + name, + args, + return_annotation, + body, + *end_position, + ), + + Definition::Test(Function { + name, + arguments: args, + body, + public, + end_position, + .. + }) => self.definition_fn(public, "test", name, args, &None, body, *end_position), Definition::TypeAlias(TypeAlias { alias, @@ -496,6 +513,7 @@ impl<'comments> Formatter<'comments> { fn definition_fn<'a>( &mut self, public: &'a bool, + keyword: &'a str, name: &'a str, args: &'a [UntypedArg], return_annotation: &'a Option, @@ -504,7 +522,8 @@ impl<'comments> Formatter<'comments> { ) -> Document<'a> { // Fn name and args let head = pub_(*public) - .append("fn ") + .append(keyword) + .append(" ") .append(name) .append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false)))); diff --git a/crates/lang/src/parser.rs b/crates/lang/src/parser.rs index d92e7e31..e7275c6d 100644 --- a/crates/lang/src/parser.rs +++ b/crates/lang/src/parser.rs @@ -74,6 +74,7 @@ fn module_parser() -> impl Parser, Error = ParseEr data_parser(), type_alias_parser(), fn_parser(), + test_parser(), constant_parser(), )) .repeated() @@ -266,6 +267,36 @@ pub fn fn_parser() -> impl Parser impl Parser { + just(Token::Test) + .ignore_then(select! {Token::Name {name} => name}) + .then_ignore(just(Token::LeftParen)) + .then_ignore(just(Token::RightParen)) + .map_with_span(|name, span| (name, span)) + .then( + expr_seq_parser() + .or_not() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .map_with_span(|((name, span_end), body), span| { + ast::UntypedDefinition::Fn(ast::Function { + arguments: vec![], + body: body.unwrap_or(expr::UntypedExpr::Todo { + kind: TodoKind::EmptyFunction, + location: span, + label: None, + }), + doc: None, + location: span_end, + end_position: span.end - 1, + name, + public: true, + return_annotation: None, + return_type: (), + }) + }) +} + fn constant_parser() -> impl Parser { pub_parser() .or_not() From bc785673b2c2537f1dc514819523956cb1b40ceb Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 8 Dec 2022 14:45:26 +0100 Subject: [PATCH 03/18] Fix compilation errors for the newly introduce test & add type inference. Tests are basically functions for which the return type should unify with bool. In principle, the type checker could also check that a test function has no arguments but, a test function with arguments wouldn't parse in the first place; feels a bit hacky but it works when considering the pipeline as a whole. Note that the code generation is still to be done. --- crates/lang/src/parser.rs | 2 +- crates/lang/src/tipo/environment.rs | 24 +++++++++++++++++++++++- crates/lang/src/tipo/infer.rs | 14 +++++++++++++- crates/project/src/lib.rs | 3 +++ examples/sample/validators/swap.ak | 4 ++++ 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/crates/lang/src/parser.rs b/crates/lang/src/parser.rs index e7275c6d..d2c98f1c 100644 --- a/crates/lang/src/parser.rs +++ b/crates/lang/src/parser.rs @@ -279,7 +279,7 @@ pub fn test_parser() -> impl Parser Environment<'a> { definition @ (Definition::TypeAlias { .. } | Definition::DataType { .. } | Definition::Use { .. } + | Definition::Test { .. } | Definition::ModuleConstant { .. }) => definition, } } @@ -911,7 +912,10 @@ impl<'a> Environment<'a> { } } - Definition::Fn { .. } | Definition::Use { .. } | Definition::ModuleConstant { .. } => {} + Definition::Fn { .. } + | Definition::Test { .. } + | Definition::Use { .. } + | Definition::ModuleConstant { .. } => {} } Ok(()) @@ -990,6 +994,24 @@ impl<'a> Environment<'a> { } } + Definition::Test(Function { name, location, .. }) => { + hydrators.insert(name.clone(), Hydrator::new()); + let arg_types = vec![]; + let return_type = builtins::bool(); + self.insert_variable( + name.clone(), + ValueConstructorVariant::ModuleFn { + name: name.clone(), + field_map: None, + module: module_name.to_owned(), + arity: 0, + location: *location, + builtin: None, + }, + function(arg_types, return_type), + ); + } + Definition::DataType(DataType { location, public, diff --git a/crates/lang/src/tipo/infer.rs b/crates/lang/src/tipo/infer.rs index 907b731d..9fc64e0a 100644 --- a/crates/lang/src/tipo/infer.rs +++ b/crates/lang/src/tipo/infer.rs @@ -6,6 +6,7 @@ use crate::{ RecordConstructorArg, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition, UntypedModule, Use, }, + builtins, builtins::function, parser::token::Token, IdGenerator, @@ -66,8 +67,8 @@ impl UntypedModule { for def in self.definitions().cloned() { match def { Definition::ModuleConstant { .. } => consts.push(def), - Definition::Fn { .. } + | Definition::Test { .. } | Definition::TypeAlias { .. } | Definition::DataType { .. } | Definition::Use { .. } => not_consts.push(def), @@ -233,6 +234,17 @@ fn infer_definition( })) } + Definition::Test(f) => { + if let Definition::Fn(f) = + infer_definition(Definition::Fn(f), module_name, hydrators, environment)? + { + environment.unify(f.return_type.clone(), builtins::bool(), f.location)?; + Ok(Definition::Test(f)) + } else { + unreachable!("test defintion inferred as something else than a function?") + } + } + Definition::TypeAlias(TypeAlias { doc, location, diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index ab44c08d..e580c803 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -336,6 +336,9 @@ impl Project { func, ); } + Definition::Test(_) => { + todo!() + } Definition::TypeAlias(ta) => { type_aliases.insert((module.name.clone(), ta.alias.clone()), ta); } diff --git a/examples/sample/validators/swap.ak b/examples/sample/validators/swap.ak index e1bc32b2..8764f3b6 100644 --- a/examples/sample/validators/swap.ak +++ b/examples/sample/validators/swap.ak @@ -11,3 +11,7 @@ pub fn spend(datum: sample.Datum, rdmr: sample.Redeemer, _ctx: Nil) -> Bool { z == #(#[222], #[222]) } + +test foo() { + 1 + 1 == 2 +} From 384c4daa4a8eed7c074a2bb6154e953c258f0e30 Mon Sep 17 00:00:00 2001 From: rvcas Date: Thu, 8 Dec 2022 10:01:24 -0500 Subject: [PATCH 04/18] feat: add test_gen function --- crates/lang/src/format.rs | 1 + crates/project/src/lib.rs | 92 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/crates/lang/src/format.rs b/crates/lang/src/format.rs index 9a60bfca..3dcc4775 100644 --- a/crates/lang/src/format.rs +++ b/crates/lang/src/format.rs @@ -510,6 +510,7 @@ impl<'comments> Formatter<'comments> { commented(doc, comments) } + #[allow(clippy::too_many_arguments)] fn definition_fn<'a>( &mut self, public: &'a bool, diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index e580c803..96610cbc 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -85,7 +85,12 @@ impl Project { self.compile(false, false) } - pub fn compile(&mut self, uplc_gen: bool, uplc_dump: bool) -> Result<(), Error> { + pub fn compile( + &mut self, + uplc_gen: bool, + uplc_dump: bool, + run_tests: bool, + ) -> Result<(), Error> { self.read_source_files()?; let parsed_modules = self.parse_sources()?; @@ -336,9 +341,7 @@ impl Project { func, ); } - Definition::Test(_) => { - todo!() - } + Definition::Test(_) => {} Definition::TypeAlias(ta) => { type_aliases.insert((module.name.clone(), ta.alias.clone()), ta); } @@ -388,6 +391,87 @@ impl Project { Ok(programs) } + // TODO: revisit ownership and lifetimes of data in this function + fn test_gen(&mut self, checked_modules: &CheckedModules) -> Result, Error> { + let mut programs = Vec::new(); + let mut functions = HashMap::new(); + let mut type_aliases = HashMap::new(); + let mut data_types = HashMap::new(); + let mut imports = HashMap::new(); + let mut constants = HashMap::new(); + + // let mut indices_to_remove = Vec::new(); + let mut tests = Vec::new(); + + for module in checked_modules.values() { + for (_index, def) in module.ast.definitions().enumerate() { + match def { + Definition::Fn(func) => { + functions.insert( + FunctionAccessKey { + module_name: module.name.clone(), + function_name: func.name.clone(), + }, + func, + ); + } + Definition::Test(func) => { + tests.push((module.name.clone(), func)); + // indices_to_remove.push(index); + } + Definition::TypeAlias(ta) => { + type_aliases.insert((module.name.clone(), ta.alias.clone()), ta); + } + Definition::DataType(dt) => { + data_types.insert( + DataTypeKey { + module_name: module.name.clone(), + defined_type: dt.name.clone(), + }, + dt, + ); + } + Definition::Use(import) => { + imports.insert((module.name.clone(), import.module.join("/")), import); + } + Definition::ModuleConstant(mc) => { + constants.insert((module.name.clone(), mc.name.clone()), mc); + } + } + } + + // for index in indices_to_remove.drain(0..) { + // module.ast.definitions.remove(index); + // } + } + + for (module_name, func_def) in tests { + let Function { + arguments, + name, + body, + .. + } = func_def; + + let mut generator = CodeGenerator::new( + &functions, + // &type_aliases, + &data_types, + // &imports, + // &constants, + &self.module_types, + ); + + let program = generator.generate(body.clone(), arguments.clone()); + + let script = Script::new(module_name, name.to_string(), program.try_into().unwrap()); + + programs.push(script); + } + + Ok(programs) + } + fn write_build_outputs(&self, programs: Vec