From b6962ba9d335a0c958115352dfa1a280e6806094 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 14 Dec 2022 00:31:14 +0100 Subject: [PATCH] Add 'eval' command to evaluate target aiken function Pretty useful for debbugging. Though, on second-thoughts, this is something we may want to review later and maybe have that done by default for tests. At the moment, we expects tests to unify to `bool`, and treat `false` values as failing tests. Yet, on failures, this gives little information about what's wrong with the test. It'd be nice to either have better way to assert in tests, or, to simply accept non-bool tests, and show whatever the test evaluates to as a debug output. --- crates/cli/src/cmd/eval.rs | 27 +++++++++ crates/cli/src/cmd/mod.rs | 1 + crates/cli/src/lib.rs | 97 +++++++++++++++++++++++---------- crates/cli/src/main.rs | 4 +- crates/lang/src/uplc.rs | 13 ++++- crates/project/src/lib.rs | 90 ++++++++++++++++++------------ crates/project/src/options.rs | 1 + crates/project/src/telemetry.rs | 13 +++-- 8 files changed, 176 insertions(+), 70 deletions(-) create mode 100644 crates/cli/src/cmd/eval.rs 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