Merge pull request #166 from aiken-lang/some-interesting-test-cases
Include generics to get test cases working
This commit is contained in:
		
						commit
						d9d1310c6d
					
				|  | @ -1,9 +1,13 @@ | |||
| root = true | ||||
| 
 | ||||
| [*.ak] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
| end_of_line = lf | ||||
| charset = utf-8 | ||||
| trim_trailing_whitespace = true | ||||
| insert_final_newline = true | ||||
| 
 | ||||
| [*.ak] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
| 
 | ||||
| [Makefile] | ||||
| indent_style = tabs | ||||
| indent_size = 4 | ||||
|  |  | |||
|  | @ -11,6 +11,10 @@ pub struct Args { | |||
|     #[clap(short, long)] | ||||
|     skip_tests: bool, | ||||
| 
 | ||||
|     /// When enabled, also pretty-print test UPLC on failure
 | ||||
|     #[clap(long)] | ||||
|     debug: bool, | ||||
| 
 | ||||
|     /// Only run tests if their path + name match the given string
 | ||||
|     #[clap(short, long)] | ||||
|     match_tests: Option<String>, | ||||
|  | @ -20,8 +24,11 @@ pub fn exec( | |||
|     Args { | ||||
|         directory, | ||||
|         skip_tests, | ||||
|         debug, | ||||
|         match_tests, | ||||
|     }: Args, | ||||
| ) -> miette::Result<()> { | ||||
|     crate::with_project(directory, |p| p.check(skip_tests, match_tests.clone())) | ||||
|     crate::with_project(directory, |p| { | ||||
|         p.check(skip_tests, match_tests.clone(), debug) | ||||
|     }) | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,7 @@ | |||
| use std::{env, path::PathBuf}; | ||||
| use std::collections::BTreeMap; | ||||
| use std::{env, path::PathBuf, process}; | ||||
| 
 | ||||
| use aiken_project::{ | ||||
|     config::Config, | ||||
|     telemetry::{self, TestInfo}, | ||||
|     Project, | ||||
| }; | ||||
| use aiken_project::{config::Config, pretty, script::EvalInfo, telemetry, Project}; | ||||
| use miette::IntoDiagnostic; | ||||
| use owo_colors::OwoColorize; | ||||
| use uplc::machine::cost_model::ExBudget; | ||||
|  | @ -35,12 +32,20 @@ where | |||
| 
 | ||||
|     if let Err(err) = build_result { | ||||
|         err.report(); | ||||
| 
 | ||||
|         miette::bail!("Failed: {} error(s), {warning_count} warning(s)", err.len(),); | ||||
|     }; | ||||
| 
 | ||||
|     println!("\nFinished with {warning_count} warning(s)\n"); | ||||
| 
 | ||||
|         println!("{}", "Summary".purple().bold()); | ||||
|         println!( | ||||
|             "    {} error(s), {}", | ||||
|             err.len(), | ||||
|             format!("{warning_count} warning(s)").yellow(), | ||||
|         ); | ||||
|         process::exit(1); | ||||
|     } else { | ||||
|         println!("{}", "Summary".purple().bold()); | ||||
|         println!( | ||||
|             "    0 error, {}", | ||||
|             format!("{warning_count} warning(s)").yellow(), | ||||
|         ); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
|  | @ -76,89 +81,149 @@ impl telemetry::EventListener for Terminal { | |||
|                     output_path.to_str().unwrap_or("").bright_blue() | ||||
|                 ); | ||||
|             } | ||||
|             telemetry::Event::EvaluatingFunction { results } => { | ||||
|                 println!("{}\n", "...Evaluating function".bold().purple()); | ||||
| 
 | ||||
|                 let (max_mem, max_cpu) = find_max_execution_units(&results); | ||||
| 
 | ||||
|                 for eval_info in &results { | ||||
|                     println!("    {}", fmt_eval(eval_info, max_mem, max_cpu)) | ||||
|                 } | ||||
|             } | ||||
|             telemetry::Event::RunningTests => { | ||||
|                 println!("{}\n", "...Running tests".bold().purple()); | ||||
|             } | ||||
|             telemetry::Event::FinishedTests { tests } => { | ||||
|                 let (max_mem, max_cpu) = tests.iter().fold( | ||||
|                     (0, 0), | ||||
|                     |(max_mem, max_cpu), TestInfo { spent_budget, .. }| { | ||||
|                         if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu { | ||||
|                             (spent_budget.mem, spent_budget.cpu) | ||||
|                         } else if spent_budget.mem > max_mem { | ||||
|                             (spent_budget.mem, max_cpu) | ||||
|                         } else if spent_budget.cpu > max_cpu { | ||||
|                             (max_mem, spent_budget.cpu) | ||||
|                         } else { | ||||
|                             (max_mem, max_cpu) | ||||
|                         } | ||||
|                     }, | ||||
|                 ); | ||||
|                 let (max_mem, max_cpu) = find_max_execution_units(&tests); | ||||
| 
 | ||||
|                 let max_mem = max_mem.to_string().len() as i32; | ||||
|                 let max_cpu = max_cpu.to_string().len() as i32; | ||||
| 
 | ||||
|                 for test_info in &tests { | ||||
|                     println!("{}", fmt_test(test_info, max_mem, max_cpu)) | ||||
|                 for (module, infos) in &group_by_module(&tests) { | ||||
|                     let first = fmt_test(infos.first().unwrap(), max_mem, max_cpu, false).len(); | ||||
|                     println!( | ||||
|                         "{} {} {}", | ||||
|                         "  ┌──".bright_black(), | ||||
|                         module.bold().blue(), | ||||
|                         pretty::pad_left("".to_string(), first - module.len() - 3, "─") | ||||
|                             .bright_black() | ||||
|                     ); | ||||
|                     for eval_info in infos { | ||||
|                         println!( | ||||
|                             "  {} {}", | ||||
|                             "│".bright_black(), | ||||
|                             fmt_test(eval_info, max_mem, max_cpu, true) | ||||
|                         ) | ||||
|                     } | ||||
|                     let last = fmt_test(infos.last().unwrap(), max_mem, max_cpu, false).len(); | ||||
|                     let summary = fmt_test_summary(infos, false).len(); | ||||
|                     println!( | ||||
|                         "{} {}\n", | ||||
|                         pretty::pad_right("  └".to_string(), last - summary + 5, "─") | ||||
|                             .bright_black(), | ||||
|                         fmt_test_summary(infos, true), | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
|                 let (n_passed, n_failed) = | ||||
|                     tests | ||||
|                         .iter() | ||||
|                         .fold((0, 0), |(n_passed, n_failed), test_info| { | ||||
|                             if test_info.is_passing { | ||||
|                                 (n_passed + 1, n_failed) | ||||
|                             } else { | ||||
|                                 (n_passed, n_failed + 1) | ||||
|                             } | ||||
|                         }); | ||||
| 
 | ||||
|                 println!( | ||||
|                     "{}", | ||||
|                     format!( | ||||
|                         "\n    Summary: {} test(s), {}; {}.", | ||||
|                         tests.len(), | ||||
|                         format!("{} passed", n_passed).bright_green(), | ||||
|                         format!("{} failed", n_failed).bright_red() | ||||
|                     ) | ||||
|                     .bold() | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn fmt_test(test_info: &TestInfo, max_mem: i32, max_cpu: i32) -> String { | ||||
|     let TestInfo { | ||||
|         is_passing, | ||||
|         test, | ||||
| fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String { | ||||
|     let EvalInfo { | ||||
|         success, | ||||
|         script, | ||||
|         spent_budget, | ||||
|     } = test_info; | ||||
|         .. | ||||
|     } = eval_info; | ||||
| 
 | ||||
|     let ExBudget { mem, cpu } = spent_budget; | ||||
|     let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " "); | ||||
|     let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " "); | ||||
| 
 | ||||
|     format!( | ||||
|         "{} [mem: {}, cpu: {}] {}", | ||||
|         if *success { | ||||
|             pretty::style_if(styled, "PASS".to_string(), |s| s.bold().green().to_string()) | ||||
|         } else { | ||||
|             pretty::style_if(styled, "FAIL".to_string(), |s| s.bold().red().to_string()) | ||||
|         }, | ||||
|         pretty::style_if(styled, mem_pad, |s| s.bright_white().to_string()), | ||||
|         pretty::style_if(styled, cpu_pad, |s| s.bright_white().to_string()), | ||||
|         pretty::style_if(styled, script.name.clone(), |s| s.bright_blue().to_string()), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String { | ||||
|     let (n_passed, n_failed) = tests | ||||
|         .iter() | ||||
|         .fold((0, 0), |(n_passed, n_failed), test_info| { | ||||
|             if test_info.success { | ||||
|                 (n_passed + 1, n_failed) | ||||
|             } else { | ||||
|                 (n_passed, n_failed + 1) | ||||
|             } | ||||
|         }); | ||||
|     format!( | ||||
|         "{} | {} | {}", | ||||
|         pretty::style_if(styled, format!("{} tests", tests.len()), |s| s | ||||
|             .bold() | ||||
|             .to_string()), | ||||
|         pretty::style_if(styled, format!("{} passed", n_passed), |s| s | ||||
|             .bright_green() | ||||
|             .bold() | ||||
|             .to_string()), | ||||
|         pretty::style_if(styled, format!("{} failed", n_failed), |s| s | ||||
|             .bright_red() | ||||
|             .bold() | ||||
|             .to_string()), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String { | ||||
|     let EvalInfo { | ||||
|         output, | ||||
|         script, | ||||
|         spent_budget, | ||||
|         .. | ||||
|     } = eval_info; | ||||
| 
 | ||||
|     let ExBudget { mem, cpu } = spent_budget; | ||||
| 
 | ||||
|     format!( | ||||
|         "    [{}] [mem: {}, cpu: {}] {}::{}", | ||||
|         if *is_passing { | ||||
|             "PASS".bold().green().to_string() | ||||
|         } else { | ||||
|             "FAIL".bold().red().to_string() | ||||
|         }, | ||||
|         pad_left(mem.to_string(), max_mem, " "), | ||||
|         pad_left(cpu.to_string(), max_cpu, " "), | ||||
|         test.module.blue(), | ||||
|         test.name.bright_blue() | ||||
|         "    {}::{} [mem: {}, cpu: {}]\n    │\n    ╰─▶ {}", | ||||
|         script.module.blue(), | ||||
|         script.name.bright_blue(), | ||||
|         pretty::pad_left(mem.to_string(), max_mem, " "), | ||||
|         pretty::pad_left(cpu.to_string(), max_cpu, " "), | ||||
|         output | ||||
|             .as_ref() | ||||
|             .map(|x| format!("{}", x)) | ||||
|             .unwrap_or_else(|| "Error.".to_string()), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| fn pad_left(mut text: String, n: i32, delimiter: &str) -> String { | ||||
|     let diff = n - text.len() as i32; | ||||
| 
 | ||||
|     if diff.is_positive() { | ||||
|         for _ in 0..diff { | ||||
|             text.insert_str(0, delimiter); | ||||
|         } | ||||
| fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> { | ||||
|     let mut modules = BTreeMap::new(); | ||||
|     for eval_info in infos { | ||||
|         let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default(); | ||||
|         xs.push(eval_info); | ||||
|     } | ||||
| 
 | ||||
|     text | ||||
|     modules | ||||
| } | ||||
| 
 | ||||
| fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) { | ||||
|     let (max_mem, max_cpu) = xs.iter().fold( | ||||
|         (0, 0), | ||||
|         |(max_mem, max_cpu), EvalInfo { spent_budget, .. }| { | ||||
|             if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu { | ||||
|                 (spent_budget.mem, spent_budget.cpu) | ||||
|             } else if spent_budget.mem > max_mem { | ||||
|                 (spent_budget.mem, max_cpu) | ||||
|             } else if spent_budget.cpu > max_cpu { | ||||
|                 (max_mem, spent_budget.cpu) | ||||
|             } else { | ||||
|                 (max_mem, max_cpu) | ||||
|             } | ||||
|         }, | ||||
|     ); | ||||
| 
 | ||||
|     (max_mem.to_string().len(), max_cpu.to_string().len()) | ||||
| } | ||||
|  |  | |||
|  | @ -28,16 +28,13 @@ pub enum Air { | |||
|         scope: Vec<u64>, | ||||
|         constructor: ValueConstructor, | ||||
|         name: String, | ||||
|         variant_name: String, | ||||
|     }, | ||||
| 
 | ||||
|     // Fn {
 | ||||
|     //  scope: Vec<u64>,
 | ||||
|     //     tipo: Arc<Type>,
 | ||||
|     //     is_capture: bool,
 | ||||
|     //     args: Vec<Arg<Arc<Type>>>,
 | ||||
|     //     body: Box<Self>,
 | ||||
|     //     return_annotation: Option<Annotation>,
 | ||||
|     // },
 | ||||
|     Fn { | ||||
|         scope: Vec<u64>, | ||||
|         params: Vec<String>, | ||||
|     }, | ||||
|     List { | ||||
|         scope: Vec<u64>, | ||||
|         count: usize, | ||||
|  | @ -67,6 +64,7 @@ pub enum Air { | |||
|     Builtin { | ||||
|         scope: Vec<u64>, | ||||
|         func: DefaultFunction, | ||||
|         tipo: Arc<Type>, | ||||
|     }, | ||||
| 
 | ||||
|     BinOp { | ||||
|  | @ -88,6 +86,7 @@ pub enum Air { | |||
|         module_name: String, | ||||
|         params: Vec<String>, | ||||
|         recursive: bool, | ||||
|         variant_name: String, | ||||
|     }, | ||||
| 
 | ||||
|     DefineConst { | ||||
|  | @ -237,6 +236,7 @@ impl Air { | |||
|             | Air::List { scope, .. } | ||||
|             | Air::ListAccessor { scope, .. } | ||||
|             | Air::ListExpose { scope, .. } | ||||
|             | Air::Fn { scope, .. } | ||||
|             | Air::Call { scope, .. } | ||||
|             | Air::Builtin { scope, .. } | ||||
|             | Air::BinOp { scope, .. } | ||||
|  |  | |||
|  | @ -66,10 +66,8 @@ impl UntypedModule { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub type TypedDefinition = Definition<Arc<Type>, TypedExpr, String, String>; | ||||
| pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>; | ||||
| 
 | ||||
| pub type TypedFunction = Function<Arc<Type>, TypedExpr>; | ||||
| pub type UntypedFunction = Function<(), UntypedExpr>; | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct Function<T, Expr> { | ||||
|  | @ -84,6 +82,24 @@ pub struct Function<T, Expr> { | |||
|     pub end_position: usize, | ||||
| } | ||||
| 
 | ||||
| pub type TypedTypeAlias = TypeAlias<Arc<Type>>; | ||||
| pub type UntypedTypeAlias = TypeAlias<()>; | ||||
| 
 | ||||
| impl TypedFunction { | ||||
|     pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> { | ||||
|         match &self.body { | ||||
|             TypedExpr::BinOp { | ||||
|                 name, | ||||
|                 tipo, | ||||
|                 left, | ||||
|                 right, | ||||
|                 .. | ||||
|             } if tipo == &bool() => Some((*name, left.clone(), right.clone())), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct TypeAlias<T> { | ||||
|     pub alias: String, | ||||
|  | @ -95,6 +111,9 @@ pub struct TypeAlias<T> { | |||
|     pub tipo: T, | ||||
| } | ||||
| 
 | ||||
| pub type TypedDataType = DataType<Arc<Type>>; | ||||
| pub type UntypedDataType = DataType<()>; | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct DataType<T> { | ||||
|     pub constructors: Vec<RecordConstructor<T>>, | ||||
|  | @ -107,6 +126,9 @@ pub struct DataType<T> { | |||
|     pub typed_parameters: Vec<T>, | ||||
| } | ||||
| 
 | ||||
| pub type TypedUse = Use<String>; | ||||
| pub type UntypedUse = Use<()>; | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct Use<PackageName> { | ||||
|     pub as_name: Option<String>, | ||||
|  | @ -116,6 +138,9 @@ pub struct Use<PackageName> { | |||
|     pub unqualified: Vec<UnqualifiedImport>, | ||||
| } | ||||
| 
 | ||||
| pub type TypedModuleConstant = ModuleConstant<Arc<Type>, String>; | ||||
| pub type UntypedModuleConstant = ModuleConstant<(), ()>; | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct ModuleConstant<T, ConstantRecordTag> { | ||||
|     pub doc: Option<String>, | ||||
|  | @ -127,6 +152,9 @@ pub struct ModuleConstant<T, ConstantRecordTag> { | |||
|     pub tipo: T, | ||||
| } | ||||
| 
 | ||||
| pub type TypedDefinition = Definition<Arc<Type>, TypedExpr, String, String>; | ||||
| pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>; | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub enum Definition<T, Expr, ConstantRecordTag, PackageName> { | ||||
|     Fn(Function<T, Expr>), | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -5,6 +5,7 @@ use std::sync::{ | |||
| 
 | ||||
| pub mod air; | ||||
| pub mod ast; | ||||
| pub mod builder; | ||||
| pub mod builtins; | ||||
| pub mod expr; | ||||
| pub mod format; | ||||
|  |  | |||
|  | @ -135,6 +135,14 @@ impl Type { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_option(&self) -> bool { | ||||
|         match self { | ||||
|             Self::App { module, name, .. } if "Option" == name && module.is_empty() => true, | ||||
|             Self::Var { tipo } => tipo.borrow().is_option(), | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_map(&self) -> bool { | ||||
|         match self { | ||||
|             Self::App { | ||||
|  | @ -143,7 +151,7 @@ impl Type { | |||
|                 if let Type::Tuple { elems } = &*args[0] { | ||||
|                     elems.len() == 2 | ||||
|                 } else if let Type::Var { tipo } = &*args[0] { | ||||
|                     matches!(tipo.borrow().get_uplc_type(), UplcType::Pair(_, _)) | ||||
|                     matches!(tipo.borrow().get_uplc_type(), Some(UplcType::Pair(_, _))) | ||||
|                 } else { | ||||
|                     false | ||||
|                 } | ||||
|  | @ -157,7 +165,42 @@ impl Type { | |||
|         matches!(self, Self::Tuple { .. }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_inner_type(&self) -> Vec<Arc<Type>> { | ||||
|     pub fn is_generic(&self) -> bool { | ||||
|         match self { | ||||
|             Type::App { args, .. } => { | ||||
|                 let mut is_a_generic = false; | ||||
|                 for arg in args { | ||||
|                     is_a_generic = is_a_generic || arg.is_generic(); | ||||
|                 } | ||||
|                 is_a_generic | ||||
|             } | ||||
| 
 | ||||
|             Type::Var { tipo } => tipo.borrow().is_generic(), | ||||
|             Type::Tuple { elems } => { | ||||
|                 let mut is_a_generic = false; | ||||
|                 for elem in elems { | ||||
|                     is_a_generic = is_a_generic || elem.is_generic(); | ||||
|                 } | ||||
|                 is_a_generic | ||||
|             } | ||||
|             Type::Fn { args, .. } => { | ||||
|                 let mut is_a_generic = false; | ||||
|                 for arg in args { | ||||
|                     is_a_generic = is_a_generic || arg.is_generic(); | ||||
|                 } | ||||
|                 is_a_generic | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_generic(&self) -> Option<u64> { | ||||
|         match self { | ||||
|             Type::Var { tipo } => tipo.borrow().get_generic(), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_inner_types(&self) -> Vec<Arc<Type>> { | ||||
|         if self.is_list() { | ||||
|             match self { | ||||
|                 Self::App { args, .. } => args.clone(), | ||||
|  | @ -169,6 +212,13 @@ impl Type { | |||
|                 Self::Tuple { elems } => elems.to_vec(), | ||||
|                 _ => vec![], | ||||
|             } | ||||
|         } else if matches!(self.get_uplc_type(), UplcType::Data) { | ||||
|             match self { | ||||
|                 Type::App { args, .. } => args.clone(), | ||||
|                 Type::Fn { args, .. } => args.clone(), | ||||
|                 Type::Var { tipo } => tipo.borrow().get_inner_type(), | ||||
|                 _ => unreachable!(), | ||||
|             } | ||||
|         } else { | ||||
|             vec![] | ||||
|         } | ||||
|  | @ -374,6 +424,13 @@ impl TypeVar { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_option(&self) -> bool { | ||||
|         match self { | ||||
|             Self::Link { tipo } => tipo.is_option(), | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_map(&self) -> bool { | ||||
|         match self { | ||||
|             Self::Link { tipo } => tipo.is_map(), | ||||
|  | @ -381,17 +438,33 @@ impl TypeVar { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_generic(&self) -> bool { | ||||
|         match self { | ||||
|             TypeVar::Generic { .. } => true, | ||||
|             TypeVar::Link { tipo } => tipo.is_generic(), | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_generic(&self) -> Option<u64> { | ||||
|         match self { | ||||
|             TypeVar::Generic { id } => Some(*id), | ||||
|             TypeVar::Link { tipo } => tipo.get_generic(), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_inner_type(&self) -> Vec<Arc<Type>> { | ||||
|         match self { | ||||
|             Self::Link { tipo } => tipo.get_inner_type(), | ||||
|             Self::Link { tipo } => tipo.get_inner_types(), | ||||
|             _ => vec![], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_uplc_type(&self) -> UplcType { | ||||
|     pub fn get_uplc_type(&self) -> Option<UplcType> { | ||||
|         match self { | ||||
|             Self::Link { tipo } => tipo.get_uplc_type(), | ||||
|             _ => unreachable!(), | ||||
|             Self::Link { tipo } => Some(tipo.get_uplc_type()), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,13 +1,18 @@ | |||
| use crate::{pretty, script::EvalHint}; | ||||
| use aiken_lang::{ | ||||
|     ast::{BinOp, Span}, | ||||
|     parser::error::ParseError, | ||||
|     tipo, | ||||
| }; | ||||
| use miette::{ | ||||
|     Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode, | ||||
| }; | ||||
| use std::{ | ||||
|     fmt::{Debug, Display}, | ||||
|     io, | ||||
|     path::{Path, PathBuf}, | ||||
| }; | ||||
| 
 | ||||
| use aiken_lang::{ast::Span, parser::error::ParseError, tipo}; | ||||
| use miette::{ | ||||
|     Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode, | ||||
| }; | ||||
| use uplc::machine::cost_model::ExBudget; | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| #[derive(thiserror::Error)] | ||||
|  | @ -28,7 +33,7 @@ pub enum Error { | |||
|     #[error(transparent)] | ||||
|     StandardIo(#[from] io::Error), | ||||
| 
 | ||||
|     #[error("Syclical module imports")] | ||||
|     #[error("Cyclical module imports")] | ||||
|     ImportCycle { modules: Vec<String> }, | ||||
| 
 | ||||
|     /// Useful for returning many [`Error::Parse`] at once
 | ||||
|  | @ -73,6 +78,15 @@ pub enum Error { | |||
|         src: String, | ||||
|         named: NamedSource, | ||||
|     }, | ||||
| 
 | ||||
|     #[error("{name} failed{}", if *verbose { format!("\n{src}") } else { String::new() } )] | ||||
|     TestFailure { | ||||
|         name: String, | ||||
|         path: PathBuf, | ||||
|         verbose: bool, | ||||
|         src: String, | ||||
|         evaluation_hint: Option<EvalHint>, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| impl Error { | ||||
|  | @ -148,6 +162,7 @@ impl Error { | |||
|             Error::Type { path, .. } => Some(path.to_path_buf()), | ||||
|             Error::ValidatorMustReturnBool { path, .. } => Some(path.to_path_buf()), | ||||
|             Error::WrongValidatorArity { path, .. } => Some(path.to_path_buf()), | ||||
|             Error::TestFailure { path, .. } => Some(path.to_path_buf()), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -163,6 +178,7 @@ impl Error { | |||
|             Error::Type { src, .. } => Some(src.to_string()), | ||||
|             Error::ValidatorMustReturnBool { src, .. } => Some(src.to_string()), | ||||
|             Error::WrongValidatorArity { src, .. } => Some(src.to_string()), | ||||
|             Error::TestFailure { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -203,6 +219,7 @@ impl Diagnostic for Error { | |||
|             Error::Format { .. } => None, | ||||
|             Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")), | ||||
|             Error::WrongValidatorArity { .. } => Some(Box::new("aiken::validators")), | ||||
|             Error::TestFailure { path, .. } => Some(Box::new(path.to_str().unwrap_or(""))), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -225,6 +242,34 @@ impl Diagnostic for Error { | |||
|             Error::Format { .. } => None, | ||||
|             Error::ValidatorMustReturnBool { .. } => Some(Box::new("Try annotating the validator's return type with Bool")), | ||||
|             Error::WrongValidatorArity { .. } => Some(Box::new("Validators require a minimum number of arguments please add the missing arguments.\nIf you don't need one of the required arguments use an underscore `_datum`.")), | ||||
|             Error::TestFailure { evaluation_hint, .. }  =>{ | ||||
|                 match evaluation_hint { | ||||
|                     None => None, | ||||
|                     Some(hint) => { | ||||
|                         let budget = ExBudget { mem: i64::MAX, cpu: i64::MAX, }; | ||||
|                         let left = pretty::boxed("left", match hint.left.eval(budget) { | ||||
|                             (Ok(term), _, _) => format!("{term}"), | ||||
|                             (Err(err), _, _) => format!("{err}"), | ||||
|                         }); | ||||
|                         let right = pretty::boxed("right", match hint.right.eval(budget) { | ||||
|                             (Ok(term), _, _) => format!("{term}"), | ||||
|                             (Err(err), _, _) => format!("{err}"), | ||||
|                         }); | ||||
|                         let msg = match hint.bin_op { | ||||
|                             BinOp::And => Some(format!("{left}\n\nand\n\n{right}\n\nshould both be true.")), | ||||
|                             BinOp::Or => Some(format!("{left}\n\nor\n\n{right}\n\nshould be true.")), | ||||
|                             BinOp::Eq => Some(format!("{left}\n\nshould be equal to\n\n{right}")), | ||||
|                             BinOp::NotEq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")), | ||||
|                             BinOp::LtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")), | ||||
|                             BinOp::LtEqInt => Some(format!("{left}\n\nshould be lower than or equal to\n\n{right}")), | ||||
|                             BinOp::GtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")), | ||||
|                             BinOp::GtInt => Some(format!("{left}\n\nshould be greater than or equal to\n\n{right}")), | ||||
|                             _ => None | ||||
|                         }?; | ||||
|                         Some(Box::new(msg)) | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -244,6 +289,7 @@ impl Diagnostic for Error { | |||
|             Error::WrongValidatorArity { location, .. } => Some(Box::new( | ||||
|                 vec![LabeledSpan::new_with_span(None, *location)].into_iter(), | ||||
|             )), | ||||
|             Error::TestFailure { .. } => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -259,6 +305,7 @@ impl Diagnostic for Error { | |||
|             Error::Format { .. } => None, | ||||
|             Error::ValidatorMustReturnBool { named, .. } => Some(named), | ||||
|             Error::WrongValidatorArity { named, .. } => Some(named), | ||||
|             Error::TestFailure { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,22 +1,21 @@ | |||
| use std::{ | ||||
|     collections::HashMap, | ||||
|     fs, | ||||
|     path::{Path, PathBuf}, | ||||
| }; | ||||
| 
 | ||||
| pub mod config; | ||||
| pub mod error; | ||||
| pub mod format; | ||||
| pub mod module; | ||||
| pub mod options; | ||||
| pub mod pretty; | ||||
| pub mod script; | ||||
| pub mod telemetry; | ||||
| 
 | ||||
| use aiken_lang::{ | ||||
|     ast::{Definition, Function, ModuleKind, TypedFunction}, | ||||
|     builtins, | ||||
|     ast::{ | ||||
|         Annotation, DataType, Definition, Function, ModuleKind, RecordConstructor, | ||||
|         RecordConstructorArg, Span, TypedDataType, TypedDefinition, TypedFunction, | ||||
|     }, | ||||
|     builder::{DataTypeKey, FunctionAccessKey}, | ||||
|     builtins::{self, generic_var}, | ||||
|     tipo::TypeInfo, | ||||
|     uplc::{CodeGenerator, DataTypeKey, FunctionAccessKey}, | ||||
|     uplc::CodeGenerator, | ||||
|     IdGenerator, | ||||
| }; | ||||
| use miette::NamedSource; | ||||
|  | @ -26,11 +25,16 @@ use pallas::{ | |||
|     ledger::{addresses::Address, primitives::babbage}, | ||||
| }; | ||||
| use pallas_traverse::ComputeHash; | ||||
| use script::Script; | ||||
| use script::{EvalHint, EvalInfo, Script}; | ||||
| use serde_json::json; | ||||
| use telemetry::{EventListener, TestInfo}; | ||||
| use std::{ | ||||
|     collections::HashMap, | ||||
|     fs, | ||||
|     path::{Path, PathBuf}, | ||||
| }; | ||||
| use telemetry::EventListener; | ||||
| use uplc::{ | ||||
|     ast::{DeBruijn, Program}, | ||||
|     ast::{Constant, DeBruijn, Program, Term}, | ||||
|     machine::cost_model::ExBudget, | ||||
| }; | ||||
| 
 | ||||
|  | @ -101,12 +105,20 @@ where | |||
|         self.compile(options) | ||||
|     } | ||||
| 
 | ||||
|     pub fn check(&mut self, skip_tests: bool, match_tests: Option<String>) -> Result<(), Error> { | ||||
|     pub fn check( | ||||
|         &mut self, | ||||
|         skip_tests: bool, | ||||
|         match_tests: Option<String>, | ||||
|         verbose: bool, | ||||
|     ) -> Result<(), Error> { | ||||
|         let options = Options { | ||||
|             code_gen_mode: if skip_tests { | ||||
|                 CodeGenMode::NoOp | ||||
|             } else { | ||||
|                 CodeGenMode::Test(match_tests) | ||||
|                 CodeGenMode::Test { | ||||
|                     match_tests, | ||||
|                     verbose, | ||||
|                 } | ||||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|  | @ -140,19 +152,47 @@ where | |||
|                 self.event_listener.handle_event(Event::GeneratingUPLC { | ||||
|                     output_path: self.output_path(), | ||||
|                 }); | ||||
| 
 | ||||
|                 let programs = self.code_gen(validators, &checked_modules)?; | ||||
| 
 | ||||
|                 self.write_build_outputs(programs, uplc_dump)?; | ||||
|                 Ok(()) | ||||
|             } | ||||
|             CodeGenMode::Test(match_tests) => { | ||||
|                 let tests = self.test_gen(&checked_modules)?; | ||||
|                 self.run_tests(tests, match_tests); | ||||
|             } | ||||
|             CodeGenMode::NoOp => (), | ||||
|         } | ||||
|             CodeGenMode::Test { | ||||
|                 match_tests, | ||||
|                 verbose, | ||||
|             } => { | ||||
|                 let tests = self | ||||
|                     .collect_scripts(&checked_modules, |def| matches!(def, Definition::Test(..)))?; | ||||
|                 if !tests.is_empty() { | ||||
|                     self.event_listener.handle_event(Event::RunningTests); | ||||
|                 } | ||||
|                 let results = self.eval_scripts(tests, match_tests); | ||||
|                 let errors: Vec<Error> = results | ||||
|                     .iter() | ||||
|                     .filter_map(|e| { | ||||
|                         if e.success { | ||||
|                             None | ||||
|                         } else { | ||||
|                             Some(Error::TestFailure { | ||||
|                                 name: e.script.name.clone(), | ||||
|                                 path: e.script.input_path.clone(), | ||||
|                                 evaluation_hint: e.script.evaluation_hint.clone(), | ||||
|                                 src: e.script.program.to_pretty(), | ||||
|                                 verbose, | ||||
|                             }) | ||||
|                         } | ||||
|                     }) | ||||
|                     .collect(); | ||||
| 
 | ||||
|         Ok(()) | ||||
|                 self.event_listener | ||||
|                     .handle_event(Event::FinishedTests { tests: results }); | ||||
|                 if !errors.is_empty() { | ||||
|                     Err(Error::List(errors)) | ||||
|                 } else { | ||||
|                     Ok(()) | ||||
|                 } | ||||
|             } | ||||
|             CodeGenMode::NoOp => Ok(()), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn read_source_files(&mut self) -> Result<(), Error> { | ||||
|  | @ -290,7 +330,7 @@ where | |||
|     fn validate_validators( | ||||
|         &self, | ||||
|         checked_modules: &mut CheckedModules, | ||||
|     ) -> Result<Vec<(String, TypedFunction)>, Error> { | ||||
|     ) -> Result<Vec<(PathBuf, String, TypedFunction)>, Error> { | ||||
|         let mut errors = Vec::new(); | ||||
|         let mut validators = Vec::new(); | ||||
|         let mut indices_to_remove = Vec::new(); | ||||
|  | @ -344,7 +384,11 @@ where | |||
|                             }) | ||||
|                         } | ||||
| 
 | ||||
|                         validators.push((module.name.clone(), func_def.clone())); | ||||
|                         validators.push(( | ||||
|                             module.input_path.clone(), | ||||
|                             module.name.clone(), | ||||
|                             func_def.clone(), | ||||
|                         )); | ||||
|                         indices_to_remove.push(index); | ||||
|                     } | ||||
|                 } | ||||
|  | @ -364,7 +408,7 @@ where | |||
| 
 | ||||
|     fn code_gen( | ||||
|         &mut self, | ||||
|         validators: Vec<(String, TypedFunction)>, | ||||
|         validators: Vec<(PathBuf, String, TypedFunction)>, | ||||
|         checked_modules: &CheckedModules, | ||||
|     ) -> Result<Vec<Script>, Error> { | ||||
|         let mut programs = Vec::new(); | ||||
|  | @ -374,6 +418,16 @@ where | |||
|         let mut imports = HashMap::new(); | ||||
|         let mut constants = HashMap::new(); | ||||
| 
 | ||||
|         let option_data_type = make_option(); | ||||
| 
 | ||||
|         data_types.insert( | ||||
|             DataTypeKey { | ||||
|                 module_name: "".to_string(), | ||||
|                 defined_type: "Option".to_string(), | ||||
|             }, | ||||
|             &option_data_type, | ||||
|         ); | ||||
| 
 | ||||
|         for module in checked_modules.values() { | ||||
|             for def in module.ast.definitions() { | ||||
|                 match def { | ||||
|  | @ -382,6 +436,7 @@ where | |||
|                             FunctionAccessKey { | ||||
|                                 module_name: module.name.clone(), | ||||
|                                 function_name: func.name.clone(), | ||||
|                                 variant_name: String::new(), | ||||
|                             }, | ||||
|                             func, | ||||
|                         ); | ||||
|  | @ -409,7 +464,7 @@ where | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (module_name, func_def) in validators { | ||||
|         for (input_path, module_name, func_def) in validators { | ||||
|             let Function { | ||||
|                 arguments, | ||||
|                 name, | ||||
|  | @ -426,9 +481,15 @@ where | |||
|                 &self.module_types, | ||||
|             ); | ||||
| 
 | ||||
|             let program = generator.generate(body, arguments); | ||||
|             let program = generator.generate(body, arguments, true); | ||||
| 
 | ||||
|             let script = Script::new(module_name, name, program.try_into().unwrap()); | ||||
|             let script = Script::new( | ||||
|                 input_path, | ||||
|                 module_name, | ||||
|                 name, | ||||
|                 program.try_into().unwrap(), | ||||
|                 None, | ||||
|             ); | ||||
| 
 | ||||
|             programs.push(script); | ||||
|         } | ||||
|  | @ -437,7 +498,11 @@ where | |||
|     } | ||||
| 
 | ||||
|     // TODO: revisit ownership and lifetimes of data in this function
 | ||||
|     fn test_gen(&mut self, checked_modules: &CheckedModules) -> Result<Vec<Script>, Error> { | ||||
|     fn collect_scripts( | ||||
|         &mut self, | ||||
|         checked_modules: &CheckedModules, | ||||
|         should_collect: fn(&TypedDefinition) -> bool, | ||||
|     ) -> Result<Vec<Script>, Error> { | ||||
|         let mut programs = Vec::new(); | ||||
|         let mut functions = HashMap::new(); | ||||
|         let mut type_aliases = HashMap::new(); | ||||
|  | @ -445,8 +510,18 @@ where | |||
|         let mut imports = HashMap::new(); | ||||
|         let mut constants = HashMap::new(); | ||||
| 
 | ||||
|         let option_data_type = make_option(); | ||||
| 
 | ||||
|         data_types.insert( | ||||
|             DataTypeKey { | ||||
|                 module_name: "".to_string(), | ||||
|                 defined_type: "Option".to_string(), | ||||
|             }, | ||||
|             &option_data_type, | ||||
|         ); | ||||
| 
 | ||||
|         // let mut indices_to_remove = Vec::new();
 | ||||
|         let mut tests = Vec::new(); | ||||
|         let mut scripts = Vec::new(); | ||||
| 
 | ||||
|         for module in checked_modules.values() { | ||||
|             for (_index, def) in module.ast.definitions().enumerate() { | ||||
|  | @ -456,12 +531,18 @@ where | |||
|                             FunctionAccessKey { | ||||
|                                 module_name: module.name.clone(), | ||||
|                                 function_name: func.name.clone(), | ||||
|                                 variant_name: String::new(), | ||||
|                             }, | ||||
|                             func, | ||||
|                         ); | ||||
|                         if should_collect(def) { | ||||
|                             scripts.push((module.input_path.clone(), module.name.clone(), func)); | ||||
|                         } | ||||
|                     } | ||||
|                     Definition::Test(func) => { | ||||
|                         tests.push((module.name.clone(), func)); | ||||
|                         if should_collect(def) { | ||||
|                             scripts.push((module.input_path.clone(), module.name.clone(), func)); | ||||
|                         } | ||||
|                         // indices_to_remove.push(index);
 | ||||
|                     } | ||||
|                     Definition::TypeAlias(ta) => { | ||||
|  | @ -490,7 +571,7 @@ where | |||
|             // }
 | ||||
|         } | ||||
| 
 | ||||
|         for (module_name, func_def) in tests { | ||||
|         for (input_path, module_name, func_def) in scripts { | ||||
|             let Function { | ||||
|                 arguments, | ||||
|                 name, | ||||
|  | @ -507,9 +588,34 @@ where | |||
|                 &self.module_types, | ||||
|             ); | ||||
| 
 | ||||
|             let program = generator.generate(body.clone(), arguments.clone()); | ||||
|             let evaluation_hint = if let Some((bin_op, left_src, right_src)) = func_def.test_hint() | ||||
|             { | ||||
|                 let left = CodeGenerator::new(&functions, &data_types, &self.module_types) | ||||
|                     .generate(*left_src, vec![], false) | ||||
|                     .try_into() | ||||
|                     .unwrap(); | ||||
|                 let right = CodeGenerator::new(&functions, &data_types, &self.module_types) | ||||
|                     .generate(*right_src, vec![], false) | ||||
|                     .try_into() | ||||
|                     .unwrap(); | ||||
|                 Some(EvalHint { | ||||
|                     bin_op, | ||||
|                     left, | ||||
|                     right, | ||||
|                 }) | ||||
|             } else { | ||||
|                 None | ||||
|             }; | ||||
| 
 | ||||
|             let script = Script::new(module_name, name.to_string(), program.try_into().unwrap()); | ||||
|             let program = generator.generate(body.clone(), arguments.clone(), false); | ||||
| 
 | ||||
|             let script = Script::new( | ||||
|                 input_path, | ||||
|                 module_name, | ||||
|                 name.to_string(), | ||||
|                 program.try_into().unwrap(), | ||||
|                 evaluation_hint, | ||||
|             ); | ||||
| 
 | ||||
|             programs.push(script); | ||||
|         } | ||||
|  | @ -517,7 +623,7 @@ where | |||
|         Ok(programs) | ||||
|     } | ||||
| 
 | ||||
|     fn run_tests(&self, tests: Vec<Script>, match_tests: Option<String>) { | ||||
|     fn eval_scripts(&self, scripts: Vec<Script>, match_name: Option<String>) -> Vec<EvalInfo> { | ||||
|         // TODO: in the future we probably just want to be able to
 | ||||
|         // tell the machine to not explode on budget consumption.
 | ||||
|         let initial_budget = ExBudget { | ||||
|  | @ -525,43 +631,41 @@ where | |||
|             cpu: i64::MAX, | ||||
|         }; | ||||
| 
 | ||||
|         if !tests.is_empty() { | ||||
|             self.event_listener.handle_event(Event::RunningTests); | ||||
|         } | ||||
| 
 | ||||
|         let mut results = Vec::new(); | ||||
| 
 | ||||
|         for test in tests { | ||||
|             let path = format!("{}{}", test.module, test.name); | ||||
|         for script in scripts { | ||||
|             let path = format!("{}{}", script.module, script.name); | ||||
| 
 | ||||
|             if matches!(&match_tests, Some(search_str) if !path.to_string().contains(search_str)) { | ||||
|             if matches!(&match_name, Some(search_str) if !path.to_string().contains(search_str)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             match test.program.eval(initial_budget) { | ||||
|                 (Ok(..), remaining_budget, _) => { | ||||
|                     let test_info = TestInfo { | ||||
|                         is_passing: true, | ||||
|                         test, | ||||
|             match script.program.eval(initial_budget) { | ||||
|                 (Ok(result), remaining_budget, _) => { | ||||
|                     let eval_info = EvalInfo { | ||||
|                         success: result != Term::Error | ||||
|                             && result != Term::Constant(Constant::Bool(false)), | ||||
|                         script, | ||||
|                         spent_budget: initial_budget - remaining_budget, | ||||
|                         output: Some(result), | ||||
|                     }; | ||||
| 
 | ||||
|                     results.push(test_info); | ||||
|                     results.push(eval_info); | ||||
|                 } | ||||
|                 (Err(_), remaining_budget, _) => { | ||||
|                     let test_info = TestInfo { | ||||
|                         is_passing: false, | ||||
|                         test, | ||||
|                 (Err(..), remaining_budget, _) => { | ||||
|                     let eval_info = EvalInfo { | ||||
|                         success: false, | ||||
|                         script, | ||||
|                         spent_budget: initial_budget - remaining_budget, | ||||
|                         output: None, | ||||
|                     }; | ||||
| 
 | ||||
|                     results.push(test_info); | ||||
|                     results.push(eval_info); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         self.event_listener | ||||
|             .handle_event(Event::FinishedTests { tests: results }); | ||||
|         results | ||||
|     } | ||||
| 
 | ||||
|     fn output_path(&self) -> PathBuf { | ||||
|  | @ -722,3 +826,40 @@ fn is_aiken_path(path: &Path, dir: impl AsRef<Path>) -> bool { | |||
|             .expect("is_aiken_path(): to_str"), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| fn make_option() -> TypedDataType { | ||||
|     DataType { | ||||
|         constructors: vec![ | ||||
|             RecordConstructor { | ||||
|                 location: Span::empty(), | ||||
|                 name: "Some".to_string(), | ||||
|                 arguments: vec![RecordConstructorArg { | ||||
|                     label: None, | ||||
|                     annotation: Annotation::Var { | ||||
|                         location: Span::empty(), | ||||
|                         name: "a".to_string(), | ||||
|                     }, | ||||
|                     location: Span::empty(), | ||||
|                     tipo: generic_var(0), | ||||
|                     doc: None, | ||||
|                 }], | ||||
|                 documentation: None, | ||||
|                 sugar: false, | ||||
|             }, | ||||
|             RecordConstructor { | ||||
|                 location: Span::empty(), | ||||
|                 name: "None".to_string(), | ||||
|                 arguments: vec![], | ||||
|                 documentation: None, | ||||
|                 sugar: false, | ||||
|             }, | ||||
|         ], | ||||
|         doc: None, | ||||
|         location: Span::empty(), | ||||
|         name: "Option".to_string(), | ||||
|         opaque: false, | ||||
|         parameters: vec!["a".to_string()], | ||||
|         public: true, | ||||
|         typed_parameters: vec![generic_var(0)], | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -3,7 +3,10 @@ pub struct Options { | |||
| } | ||||
| 
 | ||||
| pub enum CodeGenMode { | ||||
|     Test(Option<String>), | ||||
|     Test { | ||||
|         match_tests: Option<String>, | ||||
|         verbose: bool, | ||||
|     }, | ||||
|     Build(bool), | ||||
|     NoOp, | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,48 @@ | |||
| pub fn boxed(title: &str, content: String) -> String { | ||||
|     let n = content.lines().fold(0, |max, l| { | ||||
|         let n = l.len(); | ||||
|         if n > max { | ||||
|             n | ||||
|         } else { | ||||
|             max | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     let content = content | ||||
|         .lines() | ||||
|         .map(|line| format!("│ {} │", pad_right(line.to_string(), n, " "))) | ||||
|         .collect::<Vec<String>>() | ||||
|         .join("\n"); | ||||
| 
 | ||||
|     let top = format!("┍━ {}┑", pad_right(format!("{title} "), n, "━")); | ||||
|     let bottom = format!("┕{}┙", pad_right(String::new(), n + 2, "━")); | ||||
|     format!("{top}\n{content}\n{bottom}") | ||||
| } | ||||
| 
 | ||||
| pub fn pad_left(mut text: String, n: usize, delimiter: &str) -> String { | ||||
|     let diff = n as i32 - text.len() as i32; | ||||
|     if diff.is_positive() { | ||||
|         for _ in 0..diff { | ||||
|             text.insert_str(0, delimiter); | ||||
|         } | ||||
|     } | ||||
|     text | ||||
| } | ||||
| 
 | ||||
| pub fn pad_right(mut text: String, n: usize, delimiter: &str) -> String { | ||||
|     let diff = n as i32 - text.len() as i32; | ||||
|     if diff.is_positive() { | ||||
|         for _ in 0..diff { | ||||
|             text.push_str(delimiter); | ||||
|         } | ||||
|     } | ||||
|     text | ||||
| } | ||||
| 
 | ||||
| pub fn style_if(styled: bool, s: String, apply_style: fn(String) -> String) -> String { | ||||
|     if styled { | ||||
|         apply_style(s) | ||||
|     } else { | ||||
|         s | ||||
|     } | ||||
| } | ||||
|  | @ -1,18 +1,46 @@ | |||
| use crate::{ExBudget, Term}; | ||||
| use aiken_lang::ast::BinOp; | ||||
| use std::path::PathBuf; | ||||
| use uplc::ast::{NamedDeBruijn, Program}; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct Script { | ||||
|     pub input_path: PathBuf, | ||||
|     pub module: String, | ||||
|     pub name: String, | ||||
|     pub program: Program<NamedDeBruijn>, | ||||
|     pub evaluation_hint: Option<EvalHint>, | ||||
| } | ||||
| 
 | ||||
| impl Script { | ||||
|     pub fn new(module: String, name: String, program: Program<NamedDeBruijn>) -> Script { | ||||
|     pub fn new( | ||||
|         input_path: PathBuf, | ||||
|         module: String, | ||||
|         name: String, | ||||
|         program: Program<NamedDeBruijn>, | ||||
|         evaluation_hint: Option<EvalHint>, | ||||
|     ) -> Script { | ||||
|         Script { | ||||
|             input_path, | ||||
|             module, | ||||
|             name, | ||||
|             program, | ||||
|             evaluation_hint, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct EvalHint { | ||||
|     pub bin_op: BinOp, | ||||
|     pub left: Program<NamedDeBruijn>, | ||||
|     pub right: Program<NamedDeBruijn>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct EvalInfo { | ||||
|     pub success: bool, | ||||
|     pub script: Script, | ||||
|     pub spent_budget: ExBudget, | ||||
|     pub output: Option<Term<NamedDeBruijn>>, | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| use crate::script::Script; | ||||
| use crate::script::EvalInfo; | ||||
| use std::path::PathBuf; | ||||
| use uplc::machine::cost_model::ExBudget; | ||||
| 
 | ||||
| pub trait EventListener: std::fmt::Debug { | ||||
|     fn handle_event(&self, event: Event); | ||||
|  | @ -17,14 +16,11 @@ pub enum Event { | |||
|     GeneratingUPLC { | ||||
|         output_path: PathBuf, | ||||
|     }, | ||||
|     EvaluatingFunction { | ||||
|         results: Vec<EvalInfo>, | ||||
|     }, | ||||
|     RunningTests, | ||||
|     FinishedTests { | ||||
|         tests: Vec<TestInfo>, | ||||
|         tests: Vec<EvalInfo>, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| pub struct TestInfo { | ||||
|     pub is_passing: bool, | ||||
|     pub test: Script, | ||||
|     pub spent_budget: ExBudget, | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_001" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,14 @@ | |||
| pub fn length(xs: List<a>) -> Int { | ||||
|   when xs is { | ||||
|     [] -> 0 | ||||
|     [_, ..rest] -> 1 + length(rest) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| test length_1() { | ||||
|   length([1, 2, 3]) == 3 | ||||
| } | ||||
| 
 | ||||
| test length_2() { | ||||
|   length([]) == 0 | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_002" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,11 @@ | |||
| pub fn repeat(x: a, n: Int) -> List<a> { | ||||
|   if n <= 0 { | ||||
|     [] | ||||
|   } else { | ||||
|     [x, ..repeat(x, n - 1)] | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| test repeat_1() { | ||||
|   repeat("aiken", 2) == ["aiken", "aiken"] | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_003" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,14 @@ | |||
| pub fn foldr(xs: List<a>, 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<a>, right: List<a>) -> List<a> { | ||||
|   foldr(left, fn(x, xs) { [x, ..xs] }, right) | ||||
| } | ||||
| 
 | ||||
| test concat_1() { | ||||
|   concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_004" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,18 @@ | |||
| pub fn foldr(xs: List<a>, 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<a>) -> List<a> { | ||||
|   [x, ..xs] | ||||
| } | ||||
| 
 | ||||
| pub fn concat(left: List<a>, right: List<a>) -> List<a> { | ||||
|   foldr(left, prepend, right) | ||||
| } | ||||
| 
 | ||||
| test concat_1() { | ||||
|   concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_005" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,16 @@ | |||
| use aiken/builtin.{head_list} | ||||
| 
 | ||||
| pub fn head(xs: List<a>) -> Option<a> { | ||||
|   when xs is { | ||||
|     [] -> None | ||||
|     _ -> Some(head_list(xs)) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| test head_1() { | ||||
|   head([1, 2, 3]) == Some(1) | ||||
| } | ||||
| 
 | ||||
| test head_2() { | ||||
|   head([]) == None | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_006" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,3 @@ | |||
| test foo() { | ||||
|   #(1, []) == #(1, []) | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_007" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,9 @@ | |||
| pub fn unzip(xs: List<#(a, b)>) -> #(List<a>, List<b>) { | ||||
|   when xs is { | ||||
|     [] -> #([], []) | ||||
|     [#(a, b), ..rest] -> { | ||||
|       let #(a_tail, b_tail) = unzip(rest) | ||||
|       #([a, ..a_tail], [b, ..b_tail]) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_008" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,21 @@ | |||
| use aiken/builtin | ||||
| 
 | ||||
| pub fn is_empty(bytes: ByteArray) -> Bool { | ||||
|   builtin.length_of_bytearray(bytes) == 0 | ||||
| } | ||||
| 
 | ||||
| test is_empty_1() { | ||||
|   is_empty(#[]) == True | ||||
| } | ||||
| 
 | ||||
| test is_empty_1_alt() { | ||||
|   is_empty(#[]) | ||||
| } | ||||
| 
 | ||||
| test is_empty_2() { | ||||
|   is_empty(#[1]) == False | ||||
| } | ||||
| 
 | ||||
| test is_empty_2_alt() { | ||||
|   !is_empty(#[1]) | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_009" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,9 @@ | |||
| use aiken/builtin.{length_of_bytearray} | ||||
| 
 | ||||
| pub fn is_empty(bytes: ByteArray) -> Bool { | ||||
|   length_of_bytearray(bytes) == 0 | ||||
| } | ||||
| 
 | ||||
| test is_empty_1() { | ||||
|   is_empty(#[]) == True | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_010" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,14 @@ | |||
| pub fn map(opt: Option<a>, f: fn(a) -> b) -> Option<b> { | ||||
|   when opt is { | ||||
|     None -> None | ||||
|     Some(a) -> Some(f(a)) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| fn add_one(n: Int) -> Int { | ||||
|   n + 1 | ||||
| } | ||||
| 
 | ||||
| test map_1() { | ||||
|   map(None, add_one) == None | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_011" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,10 @@ | |||
| pub fn map(xs: List<a>, f: fn(a) -> result) -> List<result> { | ||||
|   when xs is { | ||||
|     [] -> [] | ||||
|     [x, ..rest] -> [f(x), ..map(rest, f)] | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| test map_1() { | ||||
|   map([], fn(n) { n + 1 }) == [] | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_012" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,18 @@ | |||
| use aiken/builtin | ||||
| 
 | ||||
| pub fn filter(xs: List<a>, f: fn(a) -> Bool) -> List<a> { | ||||
|   when xs is { | ||||
|     [] -> [] | ||||
|     [x, ..rest] -> | ||||
|       if f(x) { | ||||
|         [x, ..filter(rest, f)] | ||||
|       } else { | ||||
|         filter(rest, f) | ||||
|       } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| test filter_1() { | ||||
|   filter([1, | ||||
|       2, 3, 4, 5, 6], fn(x) { builtin.mod_integer(x, 2) == 0 }) == [2, 4, 6] | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_013" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,13 @@ | |||
| pub fn unzip(xs: List<#(a, b)>) -> #(List<a>, List<b>) { | ||||
|   when xs is { | ||||
|     [] -> #([], []) | ||||
|     [#(a, b), ..rest] -> { | ||||
|       let #(a_tail, b_tail) = unzip(rest) | ||||
|       #([a, ..a_tail], [b, ..b_tail]) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| test unzip_1() { | ||||
|   unzip([#(1, "a"), #(2, "b")]) == #([1, 2], ["a", "b"]) | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_014" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,7 @@ | |||
| // NOTE: | ||||
| // Somehow, the left-hand evaluates to: [#00, #20, #21] | ||||
| // whereas the right-hand evaluates to: [#21, #20, #00] | ||||
| // | ||||
| test foo() { | ||||
|   [0 - 2, 0 - 1, 0] == [-2, -1, 0] | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| name = "acceptance_test_015" | ||||
| version = "0.0.0" | ||||
|  | @ -0,0 +1,11 @@ | |||
| pub opaque type Map<key, value> { | ||||
|   inner: List<#(key, value)>, | ||||
| } | ||||
| 
 | ||||
| pub fn new() { | ||||
|   Map { inner: [] } | ||||
| } | ||||
| 
 | ||||
| test new_1() { | ||||
|   new() == Map { inner: [] } | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| all: | ||||
| 	@for t in $(shell find . -regex ".*[0-9]\{3\}" -type d | sort); do \
 | ||||
| 		cargo run --quiet -- check -d $${t}; \
 | ||||
| 		echo ""; \
 | ||||
| 	done | ||||
		Loading…
	
		Reference in New Issue
	
	 Matthias Benkort
						Matthias Benkort