pub mod config; pub mod deps; pub mod docs; pub mod error; pub mod format; pub mod module; pub mod options; pub mod package_name; pub mod paths; pub mod pretty; pub mod script; pub mod telemetry; use aiken_lang::{ ast::{Definition, Function, ModuleKind, TypedDataType, TypedDefinition, TypedFunction}, builder::{DataTypeKey, FunctionAccessKey}, builtins::{self, generic_var}, tipo::TypeInfo, uplc::CodeGenerator, IdGenerator, }; use deps::UseManifest; use miette::NamedSource; use options::{CodeGenMode, Options}; use package_name::PackageName; use pallas::{ codec::minicbor, ledger::{addresses::Address, primitives::babbage}, }; use pallas_traverse::ComputeHash; use script::{EvalHint, EvalInfo, Script}; use serde_json::json; use std::{ collections::HashMap, fs, path::{Path, PathBuf}, }; use telemetry::EventListener; use uplc::{ ast::{Constant, DeBruijn, Program, Term}, machine::cost_model::ExBudget, }; use crate::{ config::Config, error::{Error, Warning}, module::{ CheckedModule, CheckedModules, ParsedModule, ParsedModules, CERT, MINT, SPEND, VALIDATOR_NAMES, WITHDRAW, }, telemetry::Event, }; #[derive(Debug)] pub struct Source { pub path: PathBuf, pub name: String, pub code: String, pub kind: ModuleKind, } pub struct Project where T: EventListener, { config: Config, defined_modules: HashMap, checked_modules: CheckedModules, id_gen: IdGenerator, module_types: HashMap, root: PathBuf, sources: Vec, pub warnings: Vec, event_listener: T, } impl Project where T: EventListener, { pub fn new(root: PathBuf, event_listener: T) -> Result, Error> { let id_gen = IdGenerator::new(); 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 config = Config::load(root.clone())?; Ok(Project { config, checked_modules: CheckedModules::default(), defined_modules: HashMap::new(), id_gen, module_types, root, sources: vec![], warnings: vec![], event_listener, }) } pub fn build(&mut self, uplc: bool) -> Result<(), Error> { let options = Options { code_gen_mode: CodeGenMode::Build(uplc), }; self.compile(options) } pub fn docs(&mut self, destination: Option) -> Result<(), Error> { self.compile_deps()?; self.event_listener .handle_event(Event::BuildingDocumentation { root: self.root.clone(), name: self.config.name.to_string(), version: self.config.version.clone(), }); self.read_source_files()?; let destination = destination.unwrap_or_else(|| self.root.join("docs")); let mut parsed_modules = self.parse_sources(self.config.name.clone())?; for (_, module) in parsed_modules.iter_mut() { module.attach_doc_and_module_comments(); } self.type_check(parsed_modules)?; self.event_listener.handle_event(Event::GeneratingDocFiles { output_path: destination.clone(), }); let doc_files = docs::generate_all( &self.root, &self.config, self.checked_modules.values().collect(), ); for file in doc_files { let path = destination.join(file.path); fs::create_dir_all(path.parent().unwrap())?; fs::write(&path, file.content)?; } Ok(()) } pub fn check( &mut self, skip_tests: bool, match_tests: Option>, verbose: bool, exact_match: bool, ) -> Result<(), Error> { let options = Options { code_gen_mode: if skip_tests { CodeGenMode::NoOp } else { CodeGenMode::Test { match_tests, verbose, exact_match, } }, }; self.compile(options) } pub fn compile(&mut self, options: Options) -> Result<(), Error> { self.compile_deps()?; self.event_listener .handle_event(Event::StartingCompilation { root: self.root.clone(), name: self.config.name.to_string(), version: self.config.version.clone(), }); self.read_source_files()?; let parsed_modules = self.parse_sources(self.config.name.clone())?; self.type_check(parsed_modules)?; let validators = self.validate_validators()?; match options.code_gen_mode { CodeGenMode::Build(uplc_dump) => { if validators.is_empty() { self.warnings.push(Warning::NoValidators); } let programs = self.code_gen(validators)?; self.write_build_outputs(programs, uplc_dump)?; Ok(()) } CodeGenMode::Test { match_tests, verbose, exact_match, } => { let tests = self.collect_scripts(verbose, |def| matches!(def, Definition::Test(..)))?; if !tests.is_empty() { self.event_listener.handle_event(Event::RunningTests); } let results = self.eval_scripts(tests, match_tests, exact_match); let errors: Vec = 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(); self.event_listener .handle_event(Event::FinishedTests { tests: results }); if !errors.is_empty() { Err(Error::List(errors)) } else { Ok(()) } } CodeGenMode::NoOp => Ok(()), } } fn compile_deps(&mut self) -> Result<(), Error> { let manifest = deps::download( &self.event_listener, None, UseManifest::Yes, &self.root, &self.config, )?; for package in manifest.packages { let lib = self.root.join(paths::build_deps_package(&package.name)); self.event_listener .handle_event(Event::StartingCompilation { root: lib.clone(), name: package.name.to_string(), version: package.version.clone(), }); self.read_package_source_files(&lib.join("lib"))?; let parsed_modules = self.parse_sources(package.name)?; self.type_check(parsed_modules)?; } Ok(()) } fn read_source_files(&mut self) -> Result<(), Error> { let lib = self.root.join("lib"); let validators = self.root.join("validators"); self.aiken_files(&validators, ModuleKind::Validator)?; self.aiken_files(&lib, ModuleKind::Lib)?; Ok(()) } fn read_package_source_files(&mut self, lib: &Path) -> Result<(), Error> { self.aiken_files(lib, ModuleKind::Lib)?; Ok(()) } fn parse_sources(&mut self, package_name: PackageName) -> Result { let mut errors = Vec::new(); let mut parsed_modules = HashMap::with_capacity(self.sources.len()); for Source { path, name, code, kind, } in self.sources.drain(0..) { match aiken_lang::parser::module(&code, kind) { Ok((mut ast, extra)) => { // Store the name ast.name = name.clone(); let module = ParsedModule { kind, ast, code, name, path, extra, package: package_name.to_string(), }; if let Some(first) = self .defined_modules .insert(module.name.clone(), module.path.clone()) { return Err(Error::DuplicateModule { module: module.name.clone(), first, second: module.path, }); } parsed_modules.insert(module.name.clone(), module); } Err(errs) => { for error in errs { errors.push(Error::Parse { path: path.clone(), src: code.clone(), named: NamedSource::new(path.display().to_string(), code.clone()), error: Box::new(error), }) } } } } if errors.is_empty() { Ok(parsed_modules.into()) } else { Err(Error::List(errors)) } } fn type_check(&mut self, mut parsed_modules: ParsedModules) -> Result<(), Error> { let processing_sequence = parsed_modules.sequence()?; for name in processing_sequence { if let Some(ParsedModule { name, path, code, kind, extra, package, ast, }) = parsed_modules.remove(&name) { let mut type_warnings = Vec::new(); let ast = ast .infer( &self.id_gen, kind, &self.config.name.to_string(), &self.module_types, &mut type_warnings, ) .map_err(|error| Error::Type { path: path.clone(), src: code.clone(), named: NamedSource::new(path.display().to_string(), code.clone()), error, })?; // Register any warnings emitted as type warnings let type_warnings = type_warnings .into_iter() .map(|w| Warning::from_type_warning(w, path.clone(), code.clone())); self.warnings.extend(type_warnings); // Register the types from this module so they can be imported into // other modules. self.module_types .insert(name.clone(), ast.type_info.clone()); self.checked_modules.insert( name.clone(), CheckedModule { kind, extra, name, code, ast, package, input_path: path, }, ); } } Ok(()) } fn validate_validators(&self) -> Result, Error> { let mut errors = Vec::new(); let mut validators = Vec::new(); for module in self.checked_modules.validators() { for def in module.ast.definitions() { if let Definition::Fn(func_def) = def { if VALIDATOR_NAMES.contains(&func_def.name.as_str()) { // validators must return a Bool if !func_def.return_type.is_bool() { errors.push(Error::ValidatorMustReturnBool { location: func_def.location, src: module.code.clone(), path: module.input_path.clone(), named: NamedSource::new( module.input_path.display().to_string(), module.code.clone(), ), }) } // depending on name, validate the minimum number of arguments // if too low, push a new error on to errors if [MINT, CERT, WITHDRAW].contains(&func_def.name.as_str()) && func_def.arguments.len() < 2 { errors.push(Error::WrongValidatorArity { location: func_def.location, src: module.code.clone(), path: module.input_path.clone(), named: NamedSource::new( module.input_path.display().to_string(), module.code.clone(), ), name: func_def.name.clone(), at_least: 2, }) } if SPEND == func_def.name && func_def.arguments.len() < 3 { errors.push(Error::WrongValidatorArity { location: func_def.location, src: module.code.clone(), path: module.input_path.clone(), named: NamedSource::new( module.input_path.display().to_string(), module.code.clone(), ), name: func_def.name.clone(), at_least: 3, }) } validators.push(( module.input_path.clone(), module.name.clone(), func_def.clone(), )); } } } } if errors.is_empty() { Ok(validators) } else { Err(Error::List(errors)) } } fn code_gen( &mut self, validators: Vec<(PathBuf, String, TypedFunction)>, ) -> Result, Error> { let mut programs = Vec::new(); let mut functions = HashMap::new(); let mut type_aliases = HashMap::new(); let mut data_types = HashMap::new(); let prelude_functions = builtins::prelude_functions(); for (access_key, func) in prelude_functions.iter() { functions.insert(access_key.clone(), func); } let option_data_type = TypedDataType::option(generic_var(self.id_gen.next())); data_types.insert( DataTypeKey { module_name: "".to_string(), defined_type: "Option".to_string(), }, &option_data_type, ); for module in self.checked_modules.values() { for def in module.ast.definitions() { match def { Definition::Fn(func) => { functions.insert( FunctionAccessKey { module_name: module.name.clone(), function_name: func.name.clone(), variant_name: String::new(), }, func, ); } Definition::TypeAlias(ta) => { type_aliases.insert((module.name.clone(), ta.alias.clone()), ta); } Definition::DataType(dt) => { data_types.insert( DataTypeKey { module_name: module.name.clone(), defined_type: dt.name.clone(), }, dt, ); } Definition::ModuleConstant(_) | Definition::Test(_) | Definition::Use(_) => {} } } } for (input_path, module_name, func_def) in validators { let Function { arguments, name, body, .. } = func_def; let mut generator = CodeGenerator::new( &functions, // &type_aliases, &data_types, &self.module_types, ); self.event_listener.handle_event(Event::GeneratingUPLC { output_path: self.output_path().join(&module_name).join(&name), name: format!("{}.{}", module_name, name), }); let program = generator.generate(body, arguments, true); let script = Script::new( input_path, module_name, name, program.try_into().unwrap(), None, ); programs.push(script); } Ok(programs) } fn collect_scripts( &mut self, verbose: bool, should_collect: fn(&TypedDefinition) -> bool, ) -> Result, Error> { let mut programs = Vec::new(); let mut functions = HashMap::new(); let mut type_aliases = HashMap::new(); let mut data_types = HashMap::new(); let prelude_functions = builtins::prelude_functions(); for (access_key, func) in prelude_functions.iter() { functions.insert(access_key.clone(), func); } let option_data_type = TypedDataType::option(generic_var(self.id_gen.next())); data_types.insert( DataTypeKey { module_name: "".to_string(), defined_type: "Option".to_string(), }, &option_data_type, ); let mut scripts = Vec::new(); for module in self.checked_modules.values() { for def in module.ast.definitions() { match def { Definition::Fn(func) => { functions.insert( FunctionAccessKey { module_name: module.name.clone(), function_name: func.name.clone(), variant_name: String::new(), }, func, ); if should_collect(def) && module.package == self.config.name.to_string() { scripts.push((module.input_path.clone(), module.name.clone(), func)); } } Definition::Test(func) => { if should_collect(def) && module.package == self.config.name.to_string() { scripts.push((module.input_path.clone(), module.name.clone(), func)); } } Definition::TypeAlias(ta) => { type_aliases.insert((module.name.clone(), ta.alias.clone()), ta); } Definition::DataType(dt) => { data_types.insert( DataTypeKey { module_name: module.name.clone(), defined_type: dt.name.clone(), }, dt, ); } Definition::Use(_) | Definition::ModuleConstant(_) => (), } } } for (input_path, module_name, func_def) in scripts { let Function { arguments, name, body, .. } = func_def; if verbose { self.event_listener.handle_event(Event::GeneratingUPLCFor { name: name.clone(), path: input_path.clone(), }) } let mut generator = CodeGenerator::new( &functions, // &type_aliases, &data_types, &self.module_types, ); 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 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); } Ok(programs) } fn eval_scripts( &self, scripts: Vec