diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index d0abed0c..212f7448 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -6,7 +6,11 @@ edition = "2021" repository = "https://github.com/aiken-lang/aiken" homepage = "https://github.com/aiken-lang/aiken" license = "Apache-2.0" -authors = ["Lucas Rosa ", "Kasey White ", "KtorZ "] +authors = [ + "Lucas Rosa ", + "Kasey White ", + "KtorZ ", +] rust-version = "1.66.1" [dependencies] diff --git a/crates/aiken-lang/src/tests/gen_uplc.rs b/crates/aiken-lang/src/tests/gen_uplc.rs deleted file mode 100644 index 6769278d..00000000 --- a/crates/aiken-lang/src/tests/gen_uplc.rs +++ /dev/null @@ -1,485 +0,0 @@ -fn assert_uplc(source_code: &str, expected: Term) { - let mut project = TestProject::new(); - - let modules = CheckedModules::singleton(project.check(project.parse(source_code))); - let mut generator = modules.new_generator( - &project.functions, - &project.data_types, - &project.module_types, - ); - - let Some(checked_module) = modules.values().next() - else { - unreachable!("There's got to be one right?") - }; - - let mut scripts = vec![]; - - for def in checked_module.ast.definitions() { - if let Definition::Test(func) = def { - scripts.push(( - checked_module.input_path.clone(), - checked_module.name.clone(), - func, - )); - } - } - - assert_eq!(scripts.len(), 1); - - let script = &scripts[0]; - - let Function { body, .. } = script.2; - - let program = generator.generate_test(body); - - let debruijn_program: Program = program.try_into().unwrap(); - - let expected = Program { - version: (1, 0, 0), - term: expected, - }; - - let expected = optimize::aiken_optimize_and_intern(expected); - - let expected: Program = expected.try_into().unwrap(); - - assert_eq!(debruijn_program.to_pretty(), expected.to_pretty()); - - let eval = debruijn_program.eval(ExBudget::default()); - - assert!(!eval.failed()) -} - -#[cfg(test)] -mod test { - - #[test] - fn acceptance_test_6_if_else() { - let src = r#" - test bar() { - let x = 1 - if x == 1 { - True - } else { - False - } - } - "#; - - assert_uplc( - src, - Term::equals_integer() - .apply(Term::integer(1.into())) - .apply(Term::integer(1.into())) - .delayed_if_else(Term::bool(true), Term::bool(false)), - ); - } - - #[test] - fn acceptance_test_1_length() { - let src = r#" - pub fn length(xs: List) -> Int { - when xs is { - [] -> - 0 - [_, ..rest] -> - 1 + length(rest) - } - } - - test length_1() { - length([1, 2, 3]) == 3 - } - "#; - - assert_uplc( - src, - Term::equals_integer() - .apply( - Term::var("length") - .lambda("length") - .apply(Term::var("length").apply(Term::var("length"))) - .lambda("length") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::integer(0.into()), - Term::add_integer() - .apply(Term::integer(1.into())) - .apply( - Term::var("length") - .apply(Term::var("length")) - .apply(Term::var("rest")), - ) - .lambda("rest") - .apply(Term::tail_list().apply(Term::var("xs"))), - ) - .lambda("xs") - .lambda("length"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])), - ) - .apply(Term::integer(3.into())), - ); - } - - #[test] - fn acceptance_test_2_repeat() { - let src = r#" - pub fn repeat(x: a, n: Int) -> List { - if n <= 0 { - [] - } else { - [x, ..repeat(x, n - 1)] - } - } - - test repeat_1() { - repeat("aiken", 2) == ["aiken", "aiken"] - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::list_data().apply( - Term::var("repeat") - .lambda("repeat") - .apply(Term::var("repeat").apply(Term::var("repeat"))) - .lambda("repeat") - .apply( - Term::less_than_equals_integer() - .apply(Term::var("n")) - .apply(Term::integer(0.into())) - .delayed_if_else( - Term::empty_list(), - Term::mk_cons() - .apply(Term::b_data().apply(Term::var("x"))) - .apply( - Term::var("repeat") - .apply(Term::var("repeat")) - .apply(Term::var("x")) - .apply( - Term::sub_integer() - .apply(Term::var("n")) - .apply(Term::integer(1.into())), - ), - ), - ) - .lambda("n") - .lambda("x") - .lambda("repeat"), - ) - .apply(Term::byte_string("aiken".as_bytes().to_vec())) - .apply(Term::integer(2.into())), - ), - ) - .apply(Term::list_data().apply(Term::list_values(vec![ - Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), - Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), - ]))), - ); - } - - #[test] - fn acceptance_test_3_concat() { - let src = r#" - pub fn foldr(xs: List, f: fn(a, b) -> b, zero: b) -> b { - when xs is { - [] -> - zero - [x, ..rest] -> - f(x, foldr(rest, f, zero)) - } - } - - pub fn concat(left: List, right: List) -> List { - foldr(left, fn(x, xs) { [x, ..xs] }, right) - } - - test concat_1() { - concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::list_data().apply( - Term::var("concat") - .lambda("concat") - .apply( - Term::var("foldr") - .apply(Term::var("left")) - .apply( - Term::mk_cons() - .apply(Term::i_data().apply(Term::var("x"))) - .apply(Term::var("xs")) - .lambda("xs") - .lambda("x"), - ) - .apply(Term::var("right")) - .lambda("right") - .lambda("left"), - ) - .lambda("foldr") - .apply(Term::var("foldr").apply(Term::var("foldr"))) - .lambda("foldr") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::var("zero"), - Term::var("f") - .apply(Term::var("x")) - .apply( - Term::var("foldr") - .apply(Term::var("foldr")) - .apply(Term::var("rest")) - .apply(Term::var("f")) - .apply(Term::var("zero")), - ) - .lambda("rest") - .apply(Term::tail_list().apply(Term::var("xs"))) - .lambda("x") - .apply( - Term::un_i_data().apply( - Term::head_list().apply(Term::var("xs")), - ), - ), - ) - .lambda("zero") - .lambda("f") - .lambda("xs") - .lambda("foldr"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ])), - ), - ) - .apply(Term::list_data().apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ]))), - ); - } - - #[test] - fn acceptance_test_4_concat_no_anon_func() { - let src = r#" - pub fn foldr(xs: List, f: fn(a, b) -> b, zero: b) -> b { - when xs is { - [] -> - zero - [x, ..rest] -> - f(x, foldr(rest, f, zero)) - } - } - - pub fn prepend(x: a, xs: List) -> List { - [x, ..xs] - } - - pub fn concat(left: List, right: List) -> List { - foldr(left, prepend, right) - } - - test concat_1() { - concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::list_data().apply( - Term::var("concat") - .lambda("concat") - .apply( - Term::var("foldr") - .apply(Term::var("left")) - .apply(Term::var("prepend")) - .apply(Term::var("right")) - .lambda("right") - .lambda("left"), - ) - .lambda("prepend") - .apply( - Term::mk_cons() - .apply(Term::i_data().apply(Term::var("x"))) - .apply(Term::var("xs")) - .lambda("xs") - .lambda("x"), - ) - .lambda("foldr") - .apply(Term::var("foldr").apply(Term::var("foldr"))) - .lambda("foldr") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::var("zero"), - Term::var("f") - .apply(Term::var("x")) - .apply( - Term::var("foldr") - .apply(Term::var("foldr")) - .apply(Term::var("rest")) - .apply(Term::var("f")) - .apply(Term::var("zero")), - ) - .lambda("rest") - .apply(Term::tail_list().apply(Term::var("xs"))) - .lambda("x") - .apply( - Term::un_i_data().apply( - Term::head_list().apply(Term::var("xs")), - ), - ), - ) - .lambda("zero") - .lambda("f") - .lambda("xs") - .lambda("foldr"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ])), - ), - ) - .apply(Term::list_data().apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ]))), - ); - } - - #[test] - fn acceptance_test_5_head_not_empty() { - let src = r#" - use aiken/builtin.{head_list} - - pub fn head(xs: List) -> Option { - when xs is { - [] -> None - _ -> Some(head_list(xs)) - } - } - - test head_1() { - head([1, 2, 3]) == Some(1) - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::var("head") - .lambda("head") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::Constant( - Constant::Data(PlutusData::Constr(Constr { - tag: 122, - any_constructor: None, - fields: vec![], - })) - .into(), - ), - Term::constr_data().apply(Term::integer(0.into())).apply( - Term::mk_cons() - .apply(Term::head_list().apply(Term::var("xs"))) - .apply(Term::empty_list()), - ), - ) - .lambda("xs"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])), - ) - .apply(Term::Constant( - Constant::Data(PlutusData::Constr(Constr { - tag: 121, - any_constructor: None, - fields: vec![PlutusData::BigInt(BigInt::Int(1.into()))], - })) - .into(), - )), - ); - } - - #[test] - fn mint_parameterized() { - assert_validator( - r#" - validator(utxo_ref: Int) { - fn mint(redeemer: Data, ctx: Data) { - True - } - } - "#, - json!({ - "title": "test_module.mint", - "redeemer": { - "title": "redeemer", - "schema": { - "$ref": "#/definitions/Data" - } - }, - "parameters": [ - { - "title": "utxo_ref", - "schema": { - "$ref": "#/definitions/Int" - } - } - ], - "compiledCode": "54010000322322253330054a22930b1bad00157341", - "hash": "0e31a2048fe4751926c4a1e5fd93c9c2ecc8035777884c15db157d11", - "definitions": { - "Data": { - "title": "Data", - "description": "Any Plutus data." - }, - "Int": { - "dataType": "integer" - } - } - }), - ); - } -} diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 5bf10a8c..628c9135 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -5,6 +5,7 @@ use super::{ schema::{Annotated, Schema}, }; use crate::module::{CheckedModule, CheckedModules}; + use aiken_lang::{ ast::{TypedArg, TypedFunction, TypedValidator}, gen_uplc::CodeGenerator, @@ -176,19 +177,14 @@ impl Validator { #[cfg(test)] mod test { use assert_json_diff::assert_json_eq; - use indexmap::IndexMap; - use pretty_assertions::assert_eq; + use serde_json::{self, json}; - use std::{collections::HashMap, path::PathBuf}; + use std::collections::HashMap; use aiken_lang::{ self, - ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction}, + ast::{Definition, Function}, builtins, - gen_uplc::builder::{DataTypeKey, FunctionAccessKey}, - parser, - tipo::TypeInfo, - IdGenerator, }; use uplc::{ ast::{self as uplc_ast, Constant, Name}, @@ -196,6 +192,8 @@ mod test { optimize, BigInt, Constr, PlutusData, }; + use crate::tests::TestProject; + use super::{ super::{ definitions::{Definitions, Reference}, @@ -204,95 +202,6 @@ mod test { }, *, }; - use crate::{module::ParsedModule, PackageName}; - - // TODO: Possible refactor this out of the module and have it used by `Project`. The idea would - // be to make this struct below the actual project, and wrap it in another metadata struct - // which contains all the config and I/O stuff regarding the project. - struct TestProject { - package: PackageName, - id_gen: IdGenerator, - module_types: HashMap, - functions: IndexMap, - data_types: IndexMap, - } - - impl TestProject { - fn new() -> Self { - let id_gen = IdGenerator::new(); - - let package = PackageName { - owner: "test".to_owned(), - repo: "project".to_owned(), - }; - - let mut module_types = HashMap::new(); - module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); - module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); - - let functions = builtins::prelude_functions(&id_gen); - let data_types = builtins::prelude_data_types(&id_gen); - - TestProject { - package, - id_gen, - module_types, - functions, - data_types, - } - } - - fn parse(&self, source_code: &str) -> ParsedModule { - let kind = ModuleKind::Validator; - let name = "test_module".to_owned(); - let (mut ast, extra) = - parser::module(source_code, kind).expect("Failed to parse module"); - ast.name = name.clone(); - - ParsedModule { - kind, - ast, - code: source_code.to_string(), - name, - path: PathBuf::new(), - extra, - package: self.package.to_string(), - } - } - - fn check(&mut self, module: ParsedModule) -> CheckedModule { - let mut warnings = vec![]; - - let ast = module - .ast - .infer( - &self.id_gen, - module.kind, - &self.package.to_string(), - &self.module_types, - Tracing::NoTraces, - &mut warnings, - ) - .expect("Failed to type-check module"); - - self.module_types - .insert(module.name.clone(), ast.type_info.clone()); - - let mut checked_module = CheckedModule { - kind: module.kind, - extra: module.extra, - name: module.name, - code: module.code, - package: module.package, - input_path: module.path, - ast, - }; - - checked_module.attach_doc_and_module_comments(); - - checked_module - } - } fn assert_validator(source_code: &str, expected: serde_json::Value) { let mut project = TestProject::new(); diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index d4c03c0b..b97749a0 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -11,6 +11,8 @@ pub mod paths; pub mod pretty; pub mod script; pub mod telemetry; +#[cfg(test)] +mod tests; use crate::blueprint::Blueprint; use aiken_lang::{ diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs new file mode 100644 index 00000000..6546d1de --- /dev/null +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -0,0 +1,449 @@ +use aiken_lang::ast::{Definition, Function}; +use uplc::{ + ast::{Constant, DeBruijn, Name, Program, Term}, + machine::cost_model::ExBudget, + optimize, BigInt, Constr, PlutusData, +}; + +use crate::module::CheckedModules; + +use super::TestProject; + +fn assert_uplc(source_code: &str, expected: Term) { + let mut project = TestProject::new(); + + let modules = CheckedModules::singleton(project.check(project.parse(source_code))); + let mut generator = modules.new_generator( + &project.functions, + &project.data_types, + &project.module_types, + ); + + let Some(checked_module) = modules.values().next() + else { + unreachable!("There's got to be one right?") + }; + + let mut scripts = vec![]; + + for def in checked_module.ast.definitions() { + if let Definition::Test(func) = def { + scripts.push(( + checked_module.input_path.clone(), + checked_module.name.clone(), + func, + )); + } + } + + assert_eq!(scripts.len(), 1); + + let script = &scripts[0]; + + let Function { body, .. } = script.2; + + let program = generator.generate_test(body); + + let debruijn_program: Program = program.try_into().unwrap(); + + let expected = Program { + version: (1, 0, 0), + term: expected, + }; + + let expected = optimize::aiken_optimize_and_intern(expected); + + let expected: Program = expected.try_into().unwrap(); + + assert_eq!(debruijn_program.to_pretty(), expected.to_pretty()); + + let eval = debruijn_program.eval(ExBudget::default()); + + assert!(!eval.failed()) +} + +#[test] +fn acceptance_test_6_if_else() { + let src = r#" + test bar() { + let x = 1 + if x == 1 { + True + } else { + False + } + } + "#; + + assert_uplc( + src, + Term::equals_integer() + .apply(Term::integer(1.into())) + .apply(Term::integer(1.into())) + .delayed_if_else(Term::bool(true), Term::bool(false)), + ); +} + +#[test] +fn acceptance_test_1_length() { + let src = r#" + pub fn length(xs: List) -> Int { + when xs is { + [] -> + 0 + [_, ..rest] -> + 1 + length(rest) + } + } + + test length_1() { + length([1, 2, 3]) == 3 + } + "#; + + assert_uplc( + src, + Term::equals_integer() + .apply( + Term::var("length") + .lambda("length") + .apply(Term::var("length").apply(Term::var("length"))) + .lambda("length") + .apply( + Term::var("xs") + .delayed_choose_list( + Term::integer(0.into()), + Term::add_integer() + .apply(Term::integer(1.into())) + .apply( + Term::var("length") + .apply(Term::var("length")) + .apply(Term::var("rest")), + ) + .lambda("rest") + .apply(Term::tail_list().apply(Term::var("xs"))), + ) + .lambda("xs") + .lambda("length"), + ) + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), + ])), + ) + .apply(Term::integer(3.into())), + ); +} + +#[test] +fn acceptance_test_2_repeat() { + let src = r#" + pub fn repeat(x: a, n: Int) -> List { + if n <= 0 { + [] + } else { + [x, ..repeat(x, n - 1)] + } + } + + test repeat_1() { + repeat("aiken", 2) == ["aiken", "aiken"] + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply( + Term::list_data().apply( + Term::var("repeat") + .lambda("repeat") + .apply(Term::var("repeat").apply(Term::var("repeat"))) + .lambda("repeat") + .apply( + Term::less_than_equals_integer() + .apply(Term::var("n")) + .apply(Term::integer(0.into())) + .delayed_if_else( + Term::empty_list(), + Term::mk_cons() + .apply(Term::b_data().apply(Term::var("x"))) + .apply( + Term::var("repeat") + .apply(Term::var("repeat")) + .apply(Term::var("x")) + .apply( + Term::sub_integer() + .apply(Term::var("n")) + .apply(Term::integer(1.into())), + ), + ), + ) + .lambda("n") + .lambda("x") + .lambda("repeat"), + ) + .apply(Term::byte_string("aiken".as_bytes().to_vec())) + .apply(Term::integer(2.into())), + ), + ) + .apply(Term::list_data().apply(Term::list_values(vec![ + Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), + Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), + ]))), + ); +} + +#[test] +fn acceptance_test_3_concat() { + let src = r#" + pub fn foldr(xs: List, f: fn(a, b) -> b, zero: b) -> b { + when xs is { + [] -> + zero + [x, ..rest] -> + f(x, foldr(rest, f, zero)) + } + } + + pub fn concat(left: List, right: List) -> List { + foldr(left, fn(x, xs) { [x, ..xs] }, right) + } + + test concat_1() { + concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply( + Term::list_data().apply( + Term::var("concat") + .lambda("concat") + .apply( + Term::var("foldr") + .apply(Term::var("left")) + .apply( + Term::mk_cons() + .apply(Term::i_data().apply(Term::var("x"))) + .apply(Term::var("xs")) + .lambda("xs") + .lambda("x"), + ) + .apply(Term::var("right")) + .lambda("right") + .lambda("left"), + ) + .lambda("foldr") + .apply(Term::var("foldr").apply(Term::var("foldr"))) + .lambda("foldr") + .apply( + Term::var("xs") + .delayed_choose_list( + Term::var("zero"), + Term::var("f") + .apply(Term::var("x")) + .apply( + Term::var("foldr") + .apply(Term::var("foldr")) + .apply(Term::var("rest")) + .apply(Term::var("f")) + .apply(Term::var("zero")), + ) + .lambda("rest") + .apply(Term::tail_list().apply(Term::var("xs"))) + .lambda("x") + .apply( + Term::un_i_data() + .apply(Term::head_list().apply(Term::var("xs"))), + ), + ) + .lambda("zero") + .lambda("f") + .lambda("xs") + .lambda("foldr"), + ) + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), + ])) + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), + ])), + ), + ) + .apply(Term::list_data().apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), + ]))), + ); +} + +#[test] +fn acceptance_test_4_concat_no_anon_func() { + let src = r#" + pub fn foldr(xs: List, f: fn(a, b) -> b, zero: b) -> b { + when xs is { + [] -> + zero + [x, ..rest] -> + f(x, foldr(rest, f, zero)) + } + } + + pub fn prepend(x: a, xs: List) -> List { + [x, ..xs] + } + + pub fn concat(left: List, right: List) -> List { + foldr(left, prepend, right) + } + + test concat_1() { + concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply( + Term::list_data().apply( + Term::var("concat") + .lambda("concat") + .apply( + Term::var("foldr") + .apply(Term::var("left")) + .apply(Term::var("prepend")) + .apply(Term::var("right")) + .lambda("right") + .lambda("left"), + ) + .lambda("prepend") + .apply( + Term::mk_cons() + .apply(Term::i_data().apply(Term::var("x"))) + .apply(Term::var("xs")) + .lambda("xs") + .lambda("x"), + ) + .lambda("foldr") + .apply(Term::var("foldr").apply(Term::var("foldr"))) + .lambda("foldr") + .apply( + Term::var("xs") + .delayed_choose_list( + Term::var("zero"), + Term::var("f") + .apply(Term::var("x")) + .apply( + Term::var("foldr") + .apply(Term::var("foldr")) + .apply(Term::var("rest")) + .apply(Term::var("f")) + .apply(Term::var("zero")), + ) + .lambda("rest") + .apply(Term::tail_list().apply(Term::var("xs"))) + .lambda("x") + .apply( + Term::un_i_data() + .apply(Term::head_list().apply(Term::var("xs"))), + ), + ) + .lambda("zero") + .lambda("f") + .lambda("xs") + .lambda("foldr"), + ) + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), + ])) + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), + ])), + ), + ) + .apply(Term::list_data().apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), + ]))), + ); +} + +#[test] +fn acceptance_test_5_head_not_empty() { + let src = r#" + use aiken/builtin.{head_list} + + pub fn head(xs: List) -> Option { + when xs is { + [] -> None + _ -> Some(head_list(xs)) + } + } + + test head_1() { + head([1, 2, 3]) == Some(1) + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply( + Term::var("head") + .lambda("head") + .apply( + Term::var("xs") + .delayed_choose_list( + Term::Constant( + Constant::Data(PlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: vec![], + })) + .into(), + ), + Term::constr_data().apply(Term::integer(0.into())).apply( + Term::mk_cons() + .apply(Term::head_list().apply(Term::var("xs"))) + .apply(Term::empty_list()), + ), + ) + .lambda("xs"), + ) + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), + ])), + ) + .apply(Term::Constant( + Constant::Data(PlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: vec![PlutusData::BigInt(BigInt::Int(1.into()))], + })) + .into(), + )), + ); +} diff --git a/crates/aiken-project/src/tests/mod.rs b/crates/aiken-project/src/tests/mod.rs new file mode 100644 index 00000000..fb730a0d --- /dev/null +++ b/crates/aiken-project/src/tests/mod.rs @@ -0,0 +1,106 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use aiken_lang::{ + ast::{ModuleKind, Tracing, TypedDataType, TypedFunction}, + gen_uplc::builder::{DataTypeKey, FunctionAccessKey}, + parser, + tipo::TypeInfo, + IdGenerator, +}; +use indexmap::IndexMap; + +use crate::{ + builtins, + module::{CheckedModule, ParsedModule}, + package_name::PackageName, +}; + +mod gen_uplc; + +// TODO: Possible refactor this out of the module and have it used by `Project`. The idea would +// be to make this struct below the actual project, and wrap it in another metadata struct +// which contains all the config and I/O stuff regarding the project. +pub struct TestProject { + pub package: PackageName, + pub id_gen: IdGenerator, + pub module_types: HashMap, + pub functions: IndexMap, + pub data_types: IndexMap, +} + +impl TestProject { + pub fn new() -> Self { + let id_gen = IdGenerator::new(); + + let package = PackageName { + owner: "test".to_owned(), + repo: "project".to_owned(), + }; + + let mut module_types = HashMap::new(); + module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); + module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); + + let functions = builtins::prelude_functions(&id_gen); + let data_types = builtins::prelude_data_types(&id_gen); + + TestProject { + package, + id_gen, + module_types, + functions, + data_types, + } + } + + pub fn parse(&self, source_code: &str) -> ParsedModule { + let kind = ModuleKind::Validator; + let name = "test_module".to_owned(); + let (mut ast, extra) = parser::module(source_code, kind).expect("Failed to parse module"); + ast.name = name.clone(); + + ParsedModule { + kind, + ast, + code: source_code.to_string(), + name, + path: PathBuf::new(), + extra, + package: self.package.to_string(), + } + } + + pub fn check(&mut self, module: ParsedModule) -> CheckedModule { + let mut warnings = vec![]; + + let ast = module + .ast + .infer( + &self.id_gen, + module.kind, + &self.package.to_string(), + &self.module_types, + Tracing::NoTraces, + &mut warnings, + ) + .expect("Failed to type-check module"); + + self.module_types + .insert(module.name.clone(), ast.type_info.clone()); + + let mut checked_module = CheckedModule { + kind: module.kind, + extra: module.extra, + name: module.name, + code: module.code, + package: module.package, + input_path: module.path, + ast, + }; + + checked_module.attach_doc_and_module_comments(); + + checked_module + } +}