diff --git a/crates/cli/src/cmd/eval.rs b/crates/cli/src/cmd/eval.rs new file mode 100644 index 00000000..15dfabb7 --- /dev/null +++ b/crates/cli/src/cmd/eval.rs @@ -0,0 +1,27 @@ +use aiken_project::options::{CodeGenMode, Options}; +use std::path::PathBuf; + +#[derive(clap::Args)] +/// Evaluate a chosen function with no argument. +pub struct Args { + /// Path to project + #[clap(short, long)] + directory: Option, + + /// Evaluate the given function + #[clap(short, long)] + function_name: String, +} + +pub fn exec( + Args { + directory, + function_name, + }: Args, +) -> miette::Result<()> { + crate::with_project(directory, |p| { + p.compile(Options { + code_gen_mode: CodeGenMode::Eval(function_name.clone()), + }) + }) +} diff --git a/crates/cli/src/cmd/mod.rs b/crates/cli/src/cmd/mod.rs index 75b5fe74..4fc06408 100644 --- a/crates/cli/src/cmd/mod.rs +++ b/crates/cli/src/cmd/mod.rs @@ -1,6 +1,7 @@ pub mod build; pub mod check; pub mod error; +pub mod eval; pub mod fmt; pub mod lsp; pub mod new; diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 0034bf3e..615fb7f7 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -2,7 +2,7 @@ use std::{env, path::PathBuf}; use aiken_project::{ config::Config, - telemetry::{self, TestInfo}, + telemetry::{self, EvalInfo}, Project, }; use miette::IntoDiagnostic; @@ -76,37 +76,30 @@ 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 eval_info in &tests { + println!("{}", fmt_test(eval_info, max_mem, max_cpu)) } let (n_passed, n_failed) = tests .iter() .fold((0, 0), |(n_passed, n_failed), test_info| { - if test_info.is_passing { + if test_info.success { (n_passed + 1, n_failed) } else { (n_passed, n_failed + 1) @@ -128,26 +121,72 @@ impl telemetry::EventListener for Terminal { } } -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: i32, max_cpu: i32) -> String { + let EvalInfo { + success, + script, spent_budget, - } = test_info; + .. + } = eval_info; let ExBudget { mem, cpu } = spent_budget; format!( " [{}] [mem: {}, cpu: {}] {}::{}", - if *is_passing { + if *success { "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() + script.module.blue(), + script.name.bright_blue() + ) +} + +fn fmt_eval(eval_info: &EvalInfo, max_mem: i32, max_cpu: i32) -> String { + let EvalInfo { + output, + script, + spent_budget, + .. + } = eval_info; + + let ExBudget { mem, cpu } = spent_budget; + + format!( + " {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}", + script.module.blue(), + script.name.bright_blue(), + pad_left(mem.to_string(), max_mem, " "), + pad_left(cpu.to_string(), max_cpu, " "), + output + .as_ref() + .map(|x| format!("{}", x)) + .unwrap_or("Error.".to_string()), + ) +} + +fn find_max_execution_units(xs: &Vec) -> (i32, i32) { + 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() as i32, + max_cpu.to_string().len() as i32, ) } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index d624ce6d..3cda6690 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,4 +1,4 @@ -use aiken::cmd::{build, check, fmt, lsp, new, tx, uplc}; +use aiken::cmd::{build, check, eval, fmt, lsp, new, tx, uplc}; use clap::Parser; /// Aiken: a smart-contract language and toolchain for Cardano @@ -11,6 +11,7 @@ pub enum Cmd { Fmt(fmt::Args), Build(build::Args), Check(check::Args), + Eval(eval::Args), #[clap(hide = true)] Lsp(lsp::Args), @@ -35,6 +36,7 @@ fn main() -> miette::Result<()> { Cmd::Fmt(args) => fmt::exec(args), Cmd::Build(args) => build::exec(args), Cmd::Check(args) => check::exec(args), + Cmd::Eval(args) => eval::exec(args), Cmd::Lsp(args) => lsp::exec(args), Cmd::Tx(sub_cmd) => tx::exec(sub_cmd), Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd), diff --git a/crates/lang/src/uplc.rs b/crates/lang/src/uplc.rs index b4b20f52..23054ff7 100644 --- a/crates/lang/src/uplc.rs +++ b/crates/lang/src/uplc.rs @@ -96,7 +96,12 @@ impl<'a> CodeGenerator<'a> { } } - pub fn generate(&mut self, body: TypedExpr, arguments: Vec) -> Program { + pub fn generate( + &mut self, + body: TypedExpr, + arguments: Vec, + wrap_as_validator: bool, + ) -> Program { let mut ir_stack = vec![]; let scope = vec![self.id_gen.next()]; @@ -113,7 +118,11 @@ impl<'a> CodeGenerator<'a> { } // Wrap the validator body if ifThenElse term unit error - term = builder::final_wrapper(term); + term = if wrap_as_validator { + builder::final_wrapper(term) + } else { + term + }; for arg in arguments.iter().rev() { term = Term::Lambda { diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index ba483096..16ca71ae 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -13,7 +13,7 @@ pub mod script; pub mod telemetry; use aiken_lang::{ - ast::{Definition, Function, ModuleKind, TypedFunction}, + ast::{Definition, Function, ModuleKind, TypedDefinition, TypedFunction}, builtins, tipo::TypeInfo, uplc::{CodeGenerator, DataTypeKey, FunctionAccessKey}, @@ -28,9 +28,9 @@ use pallas::{ use pallas_traverse::ComputeHash; use script::Script; use serde_json::json; -use telemetry::{EventListener, TestInfo}; +use telemetry::{EvalInfo, EventListener}; use uplc::{ - ast::{DeBruijn, Program}, + ast::{Constant, DeBruijn, Program, Term}, machine::cost_model::ExBudget, }; @@ -146,8 +146,25 @@ where self.write_build_outputs(programs, uplc_dump)?; } CodeGenMode::Test(match_tests) => { - let tests = self.test_gen(&checked_modules)?; - self.run_tests(tests, match_tests); + let tests = self.scripts_gen(&checked_modules, |def| match def { + Definition::Test(..) => true, + _ => false, + })?; + if !tests.is_empty() { + self.event_listener.handle_event(Event::RunningTests); + } + let results = self.eval_scripts(tests, match_tests); + self.event_listener + .handle_event(Event::FinishedTests { tests: results }); + } + CodeGenMode::Eval(func_name) => { + let scripts = self.scripts_gen(&checked_modules, |def| match def { + Definition::Fn(..) => true, + _ => false, + })?; + let results = self.eval_scripts(scripts, Some(func_name)); + self.event_listener + .handle_event(Event::EvaluatingFunction { results }); } CodeGenMode::NoOp => (), } @@ -427,7 +444,7 @@ 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()); @@ -438,7 +455,11 @@ where } // TODO: revisit ownership and lifetimes of data in this function - fn test_gen(&mut self, checked_modules: &CheckedModules) -> Result, Error> { + fn scripts_gen( + &mut self, + checked_modules: &CheckedModules, + should_collect: fn(&TypedDefinition) -> bool, + ) -> Result, Error> { let mut programs = Vec::new(); let mut functions = HashMap::new(); let mut type_aliases = HashMap::new(); @@ -447,7 +468,7 @@ where let mut constants = HashMap::new(); // 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() { @@ -461,9 +482,14 @@ where }, func, ); + if should_collect(def) { + scripts.push((module.name.clone(), func)); + } } Definition::Test(func) => { - tests.push((module.name.clone(), func)); + if should_collect(def) { + scripts.push((module.name.clone(), func)); + } // indices_to_remove.push(index); } Definition::TypeAlias(ta) => { @@ -492,7 +518,7 @@ where // } } - for (module_name, func_def) in tests { + for (module_name, func_def) in scripts { let Function { arguments, name, @@ -509,7 +535,7 @@ where &self.module_types, ); - let program = generator.generate(body.clone(), arguments.clone()); + let program = generator.generate(body.clone(), arguments.clone(), false); let script = Script::new(module_name, name.to_string(), program.try_into().unwrap()); @@ -519,7 +545,7 @@ where Ok(programs) } - fn run_tests(&self, tests: Vec