diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 484624d8..c6bf8bb0 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -8,7 +8,11 @@ use clap::{Parser, Subcommand}; #[clap(propagate_version = true)] pub enum Args { /// Build an aiken project - Build, + Build { + /// Path to project + #[clap(short, long)] + directory: Option, + }, /// Typecheck a project project Check { /// Path to project diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 35c3ec59..cd4ecf39 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -55,7 +55,14 @@ impl Error { } pub fn report(&self) { - eprintln!("Error: {:?}", self) + match self { + Error::List(errors) => { + for error in errors { + eprintln!("Error: {:?}", error) + } + } + rest => eprintln!("Error: {:?}", rest), + } } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index ddd3d8a1..6faa2f2b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -16,7 +16,7 @@ use uplc::{ }, }; -use aiken::{config::Config, error, project::Project}; +use aiken::{config::Config, project::Project}; mod args; @@ -28,14 +28,35 @@ fn main() -> miette::Result<()> { let args = Args::default(); match args { - Args::Build => { - // 1. load and parse modules - // * lib - contains modules, types, and functions - // * contracts - contains validators - // * scripts - contains native scripts dsl - // 2. type check everything - // 3. generate uplc and policy/address if relevant - todo!() + Args::Build { directory } => { + let project_path = if let Some(d) = directory { + d + } else { + env::current_dir().into_diagnostic()? + }; + + let config = Config::load(project_path.clone()).into_diagnostic()?; + + let mut project = Project::new(config, project_path); + + let build_result = project.build(); + + let warning_count = project.warnings.len(); + + for warning in project.warnings { + warning.report() + } + + if let Err(err) = build_result { + err.report(); + + miette::bail!( + "failed: {} error(s), {warning_count} warning(s)", + err.total(), + ); + }; + + println!("finished with {warning_count} warning(s)") } Args::Check { directory } => { @@ -49,24 +70,24 @@ fn main() -> miette::Result<()> { let mut project = Project::new(config, project_path); - let build_result = project.build(); + let build_result = project.check(); + + let warning_count = project.warnings.len(); for warning in project.warnings { warning.report() } if let Err(err) = build_result { - match &err { - error::Error::List(errors) => { - for error in errors { - eprintln!("Error: {:?}", error) - } - } - rest => eprintln!("Error: {:?}", rest), - } + err.report(); - // miette::bail!("failed: {} errors", err.total()); + miette::bail!( + "failed: {} error(s), {warning_count} warning(s)", + err.total(), + ); }; + + println!("finished with {warning_count} warning(s)") } Args::Dev => { diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs index 76a37ede..438d9b4a 100644 --- a/crates/cli/src/project.rs +++ b/crates/cli/src/project.rs @@ -50,15 +50,21 @@ impl Project { } pub fn build(&mut self) -> Result<(), Error> { + self.compile(true) + } + + pub fn check(&mut self) -> Result<(), Error> { + self.compile(false) + } + + pub fn compile(&mut self, _uplc_gen: bool) -> Result<(), Error> { self.read_source_files()?; let parsed_modules = self.parse_sources()?; let processing_sequence = parsed_modules.sequence()?; - let checked_modules = self.type_check(parsed_modules, processing_sequence)?; - - println!("{:?}", checked_modules); + let _checked_modules = self.type_check(parsed_modules, processing_sequence)?; Ok(()) } @@ -143,7 +149,8 @@ impl Project { path, code, kind, - package, + // TODO: come back and figure out where to use this + package: _package, ast, }) = parsed_modules.remove(&name) { diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index 460229ab..6aa1ea26 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -9,6 +9,7 @@ pub mod error; pub mod expr; pub mod lexer; pub mod parser; +pub mod pretty; pub mod tipo; pub mod token; diff --git a/crates/lang/src/pretty.rs b/crates/lang/src/pretty.rs new file mode 100644 index 00000000..2eec272d --- /dev/null +++ b/crates/lang/src/pretty.rs @@ -0,0 +1,436 @@ +//! This module implements the functionality described in +//! ["Strictly Pretty" (2000) by Christian Lindig][0], with a few +//! extensions. +//! +//! This module is heavily influenced by Elixir's Inspect.Algebra and +//! JavaScript's Prettier. +//! +//! [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200 +//! +//! ## Extensions +//! +//! - `ForceBreak` from Prettier. +//! - `FlexBreak` from Elixir. +#![allow(clippy::wrong_self_convention)] + +// #[cfg(test)] +// mod tests; + +use std::collections::VecDeque; + +use itertools::Itertools; + +#[macro_export] +macro_rules! docvec { + () => { + Document::Vec(Vec::new()) + }; + + ($($x:expr),+ $(,)?) => { + Document::Vec(vec![$($x.to_doc()),+]) + }; +} + +#[derive(Debug)] +pub enum Error {} + +/// Coerce a value into a Document. +/// Note we do not implement this for String as a slight pressure to favour str +/// over String. +pub trait Documentable<'a> { + fn to_doc(self) -> Document<'a>; +} + +impl<'a> Documentable<'a> for char { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for &'a str { + fn to_doc(self) -> Document<'a> { + Document::Str(self) + } +} + +impl<'a> Documentable<'a> for isize { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for i64 { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for usize { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for f64 { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{:?}", self)) + } +} + +impl<'a> Documentable<'a> for u64 { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{:?}", self)) + } +} + +impl<'a> Documentable<'a> for u32 { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for u16 { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for u8 { + fn to_doc(self) -> Document<'a> { + Document::String(format!("{}", self)) + } +} + +impl<'a> Documentable<'a> for Document<'a> { + fn to_doc(self) -> Document<'a> { + self + } +} + +impl<'a> Documentable<'a> for Vec> { + fn to_doc(self) -> Document<'a> { + Document::Vec(self) + } +} + +impl<'a, D: Documentable<'a>> Documentable<'a> for Option { + fn to_doc(self) -> Document<'a> { + self.map(Documentable::to_doc).unwrap_or_else(nil) + } +} + +pub fn concat<'a>(docs: impl IntoIterator>) -> Document<'a> { + Document::Vec(docs.into_iter().collect()) +} + +pub fn join<'a>( + docs: impl IntoIterator>, + separator: Document<'a>, +) -> Document<'a> { + concat(Itertools::intersperse(docs.into_iter(), separator)) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Document<'a> { + /// A mandatory linebreak + Line(usize), + + /// Forces contained groups to break + ForceBreak, + + /// May break contained document based on best fit, thus flex break + FlexBreak(Box), + + /// Renders `broken` if group is broken, `unbroken` otherwise + Break { + broken: &'a str, + unbroken: &'a str, + kind: BreakKind, + }, + + /// Join multiple documents together + Vec(Vec), + + /// Nests the given document by the given indent + Nest(isize, Box), + + /// Nests the given document to the current cursor position + NestCurrent(Box), + + /// Nests the given document to the current cursor position + Group(Box), + + /// A string to render + String(String), + + /// A str to render + Str(&'a str), +} + +#[derive(Debug, Clone)] +enum Mode { + Broken, + Unbroken, +} + +fn fits( + mut limit: isize, + mut current_width: isize, + mut docs: VecDeque<(isize, Mode, &Document<'_>)>, +) -> bool { + loop { + if current_width > limit { + return false; + }; + + let (indent, mode, document) = match docs.pop_front() { + Some(x) => x, + None => return true, + }; + + match document { + Document::Line(_) => return true, + + Document::ForceBreak => return false, + + Document::Nest(i, doc) => docs.push_front((i + indent, mode, doc)), + + // TODO: Remove + Document::NestCurrent(doc) => docs.push_front((indent, mode, doc)), + + Document::Group(doc) => docs.push_front((indent, Mode::Unbroken, doc)), + + Document::Str(s) => limit -= s.len() as isize, + Document::String(s) => limit -= s.len() as isize, + + Document::Break { unbroken, .. } => match mode { + Mode::Broken => return true, + Mode::Unbroken => current_width += unbroken.len() as isize, + }, + + Document::FlexBreak(doc) => docs.push_front((indent, mode, doc)), + + Document::Vec(vec) => { + for doc in vec.iter().rev() { + docs.push_front((indent, mode.clone(), doc)); + } + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BreakKind { + Flex, + Strict, +} + +fn format( + writer: &mut String, + limit: isize, + mut width: isize, + mut docs: VecDeque<(isize, Mode, &Document<'_>)>, +) -> Result<(), Error> { + while let Some((indent, mode, document)) = docs.pop_front() { + match document { + Document::ForceBreak => (), + + Document::Line(i) => { + for _ in 0..*i { + writer.push('\n'); + } + + for _ in 0..indent { + writer.push(' '); + } + + width = indent; + } + + // Flex breaks are NOT conditional to the mode + Document::Break { + broken, + unbroken, + kind: BreakKind::Flex, + } => { + let unbroken_width = width + unbroken.len() as isize; + + if fits(limit, unbroken_width, docs.clone()) { + writer.push_str(unbroken); + + width = unbroken_width; + } else { + writer.push_str(broken); + + writer.push('\n'); + + for _ in 0..indent { + writer.push(' '); + } + width = indent; + } + } + + // Strict breaks are conditional to the mode + Document::Break { + broken, + unbroken, + kind: BreakKind::Strict, + } => { + width = match mode { + Mode::Unbroken => { + writer.push_str(unbroken); + + width + unbroken.len() as isize + } + Mode::Broken => { + writer.push_str(broken); + + writer.push('\n'); + + for _ in 0..indent { + writer.push(' '); + } + + indent + } + }; + } + + Document::String(s) => { + width += s.len() as isize; + + writer.push_str(s); + } + + Document::Str(s) => { + width += s.len() as isize; + + writer.push_str(s); + } + + Document::Vec(vec) => { + for doc in vec.iter().rev() { + docs.push_front((indent, mode.clone(), doc)); + } + } + + Document::Nest(i, doc) => { + docs.push_front((indent + i, mode, doc)); + } + + Document::NestCurrent(doc) => { + docs.push_front((width, mode, doc)); + } + + Document::Group(doc) | Document::FlexBreak(doc) => { + // TODO: don't clone the doc + let mut group_docs = VecDeque::new(); + + group_docs.push_back((indent, Mode::Unbroken, doc.as_ref())); + + if fits(limit, width, group_docs) { + docs.push_front((indent, Mode::Unbroken, doc)); + } else { + docs.push_front((indent, Mode::Broken, doc)); + } + } + } + } + Ok(()) +} + +pub fn nil<'a>() -> Document<'a> { + Document::Vec(vec![]) +} + +pub fn line<'a>() -> Document<'a> { + Document::Line(1) +} + +pub fn lines<'a>(i: usize) -> Document<'a> { + Document::Line(i) +} + +pub fn force_break<'a>() -> Document<'a> { + Document::ForceBreak +} + +pub fn break_<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> { + Document::Break { + broken, + unbroken, + kind: BreakKind::Strict, + } +} + +pub fn flex_break<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> { + Document::Break { + broken, + unbroken, + kind: BreakKind::Flex, + } +} + +impl<'a> Document<'a> { + pub fn group(self) -> Self { + Self::Group(Box::new(self)) + } + + pub fn nest(self, indent: isize) -> Self { + Self::Nest(indent, Box::new(self)) + } + + pub fn nest_current(self) -> Self { + Self::NestCurrent(Box::new(self)) + } + + pub fn append(self, second: impl Documentable<'a>) -> Self { + match self { + Self::Vec(mut vec) => { + vec.push(second.to_doc()); + Self::Vec(vec) + } + first => Self::Vec(vec![first, second.to_doc()]), + } + } + + pub fn to_pretty_string(self, limit: isize) -> String { + let mut buffer = String::new(); + + self.pretty_print(limit, &mut buffer) + .expect("Writing to string buffer failed"); + + buffer + } + + pub fn surround(self, open: impl Documentable<'a>, closed: impl Documentable<'a>) -> Self { + open.to_doc().append(self).append(closed) + } + + pub fn pretty_print(&self, limit: isize, writer: &mut String) -> Result<(), Error> { + let mut docs = VecDeque::new(); + + docs.push_back((0, Mode::Unbroken, self)); + + format(writer, limit, 0, docs)?; + + Ok(()) + } + + /// Returns true when the document contains no printable characters + /// (whitespace and newlines are considered printable characters). + pub fn is_empty(&self) -> bool { + use Document::*; + match self { + Line(n) => *n == 0, + ForceBreak => true, + String(s) => s.is_empty(), + Str(s) => s.is_empty(), + // assuming `broken` and `unbroken` are equivalent + Break { broken, .. } => broken.is_empty(), + FlexBreak(d) | Nest(_, d) | NestCurrent(d) | Group(d) => d.is_empty(), + Vec(docs) => docs.iter().all(|d| d.is_empty()), + } + } +} diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index 84f5f584..25f7f7e7 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -5,7 +5,7 @@ use crate::{ tipo::fields::FieldMap, }; -use self::environment::Environment; +use self::{environment::Environment, pretty::Printer}; mod environment; pub mod error; @@ -15,6 +15,7 @@ mod hydrator; mod infer; mod pattern; mod pipe; +mod pretty; #[derive(Debug, Clone, PartialEq)] pub enum Type { @@ -203,6 +204,18 @@ impl Type { _ => None, } } + + pub fn to_pretty(&self, indent: usize) -> String { + Printer::new().pretty_print(self, indent) + } + + pub fn to_pretty_with_names(&self, names: HashMap, indent: usize) -> String { + let mut printer = Printer::new(); + + printer.with_names(names); + + printer.pretty_print(self, indent) + } } #[derive(Debug, Clone, PartialEq)] diff --git a/crates/lang/src/tipo/error.rs b/crates/lang/src/tipo/error.rs index 5ff77280..0a800dde 100644 --- a/crates/lang/src/tipo/error.rs +++ b/crates/lang/src/tipo/error.rs @@ -231,7 +231,11 @@ pub enum Error { location: Span, }, - #[error("")] + #[error( + "Type Mismatch\n\nExpected type:\n\n{}\n\nFound type:\n\n{}\n", + expected.to_pretty_with_names(rigid_type_names.clone(), 4), + given.to_pretty_with_names(rigid_type_names.clone(), 4) + )] CouldNotUnify { #[label] location: Span, diff --git a/crates/lang/src/tipo/expr.rs b/crates/lang/src/tipo/expr.rs index d05a8e2d..c16daabe 100644 --- a/crates/lang/src/tipo/expr.rs +++ b/crates/lang/src/tipo/expr.rs @@ -6,9 +6,9 @@ use crate::{ ast::{ Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant, RecordUpdateSpread, Span, SrcId, TodoKind, TypedArg, TypedCallArg, TypedClause, - TypedClauseGuard, TypedConstant, TypedMultiPattern, TypedRecordUpdateArg, UntypedArg, - UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedMultiPattern, UntypedPattern, - UntypedRecordUpdateArg, + TypedClauseGuard, TypedConstant, TypedIfBranch, TypedMultiPattern, TypedRecordUpdateArg, + UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedIfBranch, + UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg, }, builtins::{bool, byte_array, function, int, list, result, string}, expr::{TypedExpr, UntypedExpr}, @@ -267,7 +267,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, branches, final_else, - } => todo!(), + } => self.infer_if(branches, *final_else, location), UntypedExpr::Assignment { location, @@ -1422,6 +1422,60 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + fn infer_if( + &mut self, + branches: Vec1, + final_else: UntypedExpr, + location: Span, + ) -> Result { + let first = branches.first(); + + let condition = self.infer(first.condition.clone())?; + + self.unify(bool(), condition.tipo(), condition.type_defining_location())?; + + let body = self.infer(first.body.clone())?; + + let tipo = body.tipo(); + + let mut typed_branches = Vec1::new(TypedIfBranch { + body, + condition, + location: first.location, + }); + + for branch in &branches[1..] { + let condition = self.infer(branch.condition.clone())?; + + self.unify(bool(), condition.tipo(), condition.type_defining_location())?; + + let body = self.infer(first.body.clone())?; + + self.unify(tipo.clone(), body.tipo(), body.type_defining_location())?; + + typed_branches.push(TypedIfBranch { + body, + condition, + location: branch.location, + }); + } + + let typed_final_else = self.infer(final_else)?; + + self.unify( + tipo.clone(), + typed_final_else.tipo(), + typed_final_else.type_defining_location(), + )?; + + Ok(TypedExpr::If { + location, + branches: typed_branches, + final_else: Box::new(typed_final_else), + tipo, + }) + } + fn infer_fn( &mut self, args: Vec, diff --git a/crates/lang/src/tipo/pretty.rs b/crates/lang/src/tipo/pretty.rs new file mode 100644 index 00000000..d0c0232b --- /dev/null +++ b/crates/lang/src/tipo/pretty.rs @@ -0,0 +1,382 @@ +use std::{collections::HashMap, sync::Arc}; + +use itertools::Itertools; + +use super::{Type, TypeVar}; +use crate::{ + docvec, + pretty::{nil, *}, +}; + +const INDENT: isize = 2; + +// TODO: use references instead of cloning strings and vectors +#[derive(Debug, Default)] +pub struct Printer { + names: HashMap, + uid: u64, + // A mapping of printd type names to the module that they are defined in. + printed_types: HashMap, +} + +impl Printer { + pub fn new() -> Self { + Default::default() + } + + pub fn with_names(&mut self, names: HashMap) { + self.names = names; + } + + /// Render a Type as a well formatted string. + /// + pub fn pretty_print(&mut self, typ: &Type, initial_indent: usize) -> String { + let mut buffer = String::with_capacity(initial_indent); + + for _ in 0..initial_indent { + buffer.push(' '); + } + + buffer + .to_doc() + .append(self.print(typ)) + .nest(initial_indent as isize) + .to_pretty_string(80) + } + + // TODO: have this function return a Document that borrows from the Type. + // Is this possible? The lifetime would have to go through the Arc> + // for TypeVar::Link'd types. + pub fn print<'a>(&mut self, typ: &Type) -> Document<'a> { + match typ { + Type::App { + name, args, module, .. + } => { + let doc = if self.name_clashes_if_unqualified(name, module) { + qualify_type_name(module, name) + } else { + self.printed_types.insert(name.clone(), module.clone()); + Document::String(name.clone()) + }; + if args.is_empty() { + doc + } else { + doc.append("(") + .append(self.args_to_aiken_doc(args)) + .append(")") + } + } + + Type::Fn { args, ret } => "fn(" + .to_doc() + .append(self.args_to_aiken_doc(args)) + .append(") ->") + .append(break_("", " ").append(self.print(ret)).nest(INDENT).group()), + + Type::Var { tipo: typ, .. } => self.type_var_doc(&typ.borrow()), + } + } + + fn name_clashes_if_unqualified(&mut self, tipo: &String, module: &String) -> bool { + match self.printed_types.get(tipo) { + None => false, + Some(previous_module) if module == previous_module => false, + Some(_different_module) => true, + } + } + + fn type_var_doc<'a>(&mut self, typ: &TypeVar) -> Document<'a> { + match typ { + TypeVar::Link { tipo: ref typ, .. } => self.print(typ), + TypeVar::Unbound { id, .. } | TypeVar::Generic { id, .. } => self.generic_type_var(*id), + } + } + + pub fn generic_type_var<'a>(&mut self, id: u64) -> Document<'a> { + match self.names.get(&id) { + Some(n) => { + let typ_name = n.clone(); + + self.printed_types.insert(typ_name, "".to_string()); + + Document::String(n.clone()) + } + None => { + let n = self.next_letter(); + + self.names.insert(id, n.clone()); + + self.printed_types.insert(n.clone(), "".to_string()); + + Document::String(n) + } + } + } + + fn next_letter(&mut self) -> String { + let alphabet_length = 26; + let char_offset = 97; + let mut chars = vec![]; + let mut n; + let mut rest = self.uid; + + loop { + n = rest % alphabet_length; + + rest /= alphabet_length; + + chars.push((n as u8 + char_offset) as char); + + if rest == 0 { + break; + } + + rest -= 1 + } + + self.uid += 1; + + chars.into_iter().rev().collect() + } + + fn args_to_aiken_doc<'a>(&mut self, args: &[Arc]) -> Document<'a> { + if args.is_empty() { + return nil(); + } + + let args = concat(Itertools::intersperse( + args.iter().map(|t| self.print(t).group()), + break_(",", ", "), + )); + + break_("", "") + .append(args) + .nest(INDENT) + .append(break_(",", "")) + .group() + } +} + +fn qualify_type_name(module: &String, typ_name: &str) -> Document<'static> { + if module.is_empty() { + docvec!["aiken.", Document::String(typ_name.to_string())] + } else { + Document::String([module, typ_name].join(".")) + } +} + +#[cfg(test)] +mod test { + use std::cell::RefCell; + + use pretty_assertions::assert_eq; + + use crate::builtins::{function, int}; + + use super::*; + + #[test] + fn next_letter_test() { + let mut printer = Printer::new(); + assert_eq!(printer.next_letter(), "a".to_string()); + assert_eq!(printer.next_letter(), "b".to_string()); + assert_eq!(printer.next_letter(), "c".to_string()); + assert_eq!(printer.next_letter(), "d".to_string()); + assert_eq!(printer.next_letter(), "e".to_string()); + assert_eq!(printer.next_letter(), "f".to_string()); + assert_eq!(printer.next_letter(), "g".to_string()); + assert_eq!(printer.next_letter(), "h".to_string()); + assert_eq!(printer.next_letter(), "i".to_string()); + assert_eq!(printer.next_letter(), "j".to_string()); + assert_eq!(printer.next_letter(), "k".to_string()); + assert_eq!(printer.next_letter(), "l".to_string()); + assert_eq!(printer.next_letter(), "m".to_string()); + assert_eq!(printer.next_letter(), "n".to_string()); + assert_eq!(printer.next_letter(), "o".to_string()); + assert_eq!(printer.next_letter(), "p".to_string()); + assert_eq!(printer.next_letter(), "q".to_string()); + assert_eq!(printer.next_letter(), "r".to_string()); + assert_eq!(printer.next_letter(), "s".to_string()); + assert_eq!(printer.next_letter(), "t".to_string()); + assert_eq!(printer.next_letter(), "u".to_string()); + assert_eq!(printer.next_letter(), "v".to_string()); + assert_eq!(printer.next_letter(), "w".to_string()); + assert_eq!(printer.next_letter(), "x".to_string()); + assert_eq!(printer.next_letter(), "y".to_string()); + assert_eq!(printer.next_letter(), "z".to_string()); + assert_eq!(printer.next_letter(), "aa".to_string()); + assert_eq!(printer.next_letter(), "ab".to_string()); + assert_eq!(printer.next_letter(), "ac".to_string()); + assert_eq!(printer.next_letter(), "ad".to_string()); + assert_eq!(printer.next_letter(), "ae".to_string()); + assert_eq!(printer.next_letter(), "af".to_string()); + assert_eq!(printer.next_letter(), "ag".to_string()); + assert_eq!(printer.next_letter(), "ah".to_string()); + assert_eq!(printer.next_letter(), "ai".to_string()); + assert_eq!(printer.next_letter(), "aj".to_string()); + assert_eq!(printer.next_letter(), "ak".to_string()); + assert_eq!(printer.next_letter(), "al".to_string()); + assert_eq!(printer.next_letter(), "am".to_string()); + assert_eq!(printer.next_letter(), "an".to_string()); + assert_eq!(printer.next_letter(), "ao".to_string()); + assert_eq!(printer.next_letter(), "ap".to_string()); + assert_eq!(printer.next_letter(), "aq".to_string()); + assert_eq!(printer.next_letter(), "ar".to_string()); + assert_eq!(printer.next_letter(), "as".to_string()); + assert_eq!(printer.next_letter(), "at".to_string()); + assert_eq!(printer.next_letter(), "au".to_string()); + assert_eq!(printer.next_letter(), "av".to_string()); + assert_eq!(printer.next_letter(), "aw".to_string()); + assert_eq!(printer.next_letter(), "ax".to_string()); + assert_eq!(printer.next_letter(), "ay".to_string()); + assert_eq!(printer.next_letter(), "az".to_string()); + assert_eq!(printer.next_letter(), "ba".to_string()); + assert_eq!(printer.next_letter(), "bb".to_string()); + assert_eq!(printer.next_letter(), "bc".to_string()); + assert_eq!(printer.next_letter(), "bd".to_string()); + assert_eq!(printer.next_letter(), "be".to_string()); + assert_eq!(printer.next_letter(), "bf".to_string()); + assert_eq!(printer.next_letter(), "bg".to_string()); + assert_eq!(printer.next_letter(), "bh".to_string()); + assert_eq!(printer.next_letter(), "bi".to_string()); + assert_eq!(printer.next_letter(), "bj".to_string()); + assert_eq!(printer.next_letter(), "bk".to_string()); + assert_eq!(printer.next_letter(), "bl".to_string()); + assert_eq!(printer.next_letter(), "bm".to_string()); + assert_eq!(printer.next_letter(), "bn".to_string()); + assert_eq!(printer.next_letter(), "bo".to_string()); + assert_eq!(printer.next_letter(), "bp".to_string()); + assert_eq!(printer.next_letter(), "bq".to_string()); + assert_eq!(printer.next_letter(), "br".to_string()); + assert_eq!(printer.next_letter(), "bs".to_string()); + assert_eq!(printer.next_letter(), "bt".to_string()); + assert_eq!(printer.next_letter(), "bu".to_string()); + assert_eq!(printer.next_letter(), "bv".to_string()); + assert_eq!(printer.next_letter(), "bw".to_string()); + assert_eq!(printer.next_letter(), "bx".to_string()); + assert_eq!(printer.next_letter(), "by".to_string()); + assert_eq!(printer.next_letter(), "bz".to_string()); + } + + #[test] + fn pretty_print_test() { + macro_rules! assert_string { + ($src:expr, $typ:expr $(,)?) => { + let mut printer = Printer::new(); + assert_eq!($typ.to_string(), printer.pretty_print(&$src, 0),); + }; + } + + assert_string!( + Type::App { + module: "whatever".to_string(), + name: "Int".to_string(), + public: true, + args: vec![], + }, + "Int", + ); + assert_string!( + Type::App { + module: "".to_string(), + name: "Pair".to_string(), + public: true, + args: vec![ + Arc::new(Type::App { + module: "whatever".to_string(), + name: "Int".to_string(), + public: true, + args: vec![], + }), + Arc::new(Type::App { + module: "whatever".to_string(), + name: "Bool".to_string(), + public: true, + args: vec![], + }), + ], + }, + "Pair(Int, Bool)", + ); + assert_string!( + Type::Fn { + args: vec![ + Arc::new(Type::App { + args: vec![], + module: "whatever".to_string(), + name: "Int".to_string(), + public: true, + }), + Arc::new(Type::App { + args: vec![], + module: "whatever".to_string(), + name: "Bool".to_string(), + public: true, + }), + ], + ret: Arc::new(Type::App { + args: vec![], + module: "whatever".to_string(), + name: "Bool".to_string(), + public: true, + }), + }, + "fn(Int, Bool) -> Bool", + ); + assert_string!( + Type::Var { + tipo: Arc::new(RefCell::new(TypeVar::Link { + tipo: Arc::new(Type::App { + args: vec![], + module: "whatever".to_string(), + name: "Int".to_string(), + public: true, + }), + })), + }, + "Int", + ); + assert_string!( + Type::Var { + tipo: Arc::new(RefCell::new(TypeVar::Unbound { id: 2231 })), + }, + "a", + ); + assert_string!( + function( + vec![Arc::new(Type::Var { + tipo: Arc::new(RefCell::new(TypeVar::Unbound { id: 78 })), + })], + Arc::new(Type::Var { + tipo: Arc::new(RefCell::new(TypeVar::Unbound { id: 2 })), + }), + ), + "fn(a) -> b", + ); + assert_string!( + function( + vec![Arc::new(Type::Var { + tipo: Arc::new(RefCell::new(TypeVar::Generic { id: 78 })), + })], + Arc::new(Type::Var { + tipo: Arc::new(RefCell::new(TypeVar::Generic { id: 2 })), + }), + ), + "fn(a) -> b", + ); + } + + #[test] + fn function_test() { + assert_eq!(pretty_print(function(vec![], int())), "fn() -> Int"); + + assert_eq!( + pretty_print(function(vec![int(), int(), int()], int())), + "fn(Int, Int, Int) -> Int" + ); + } + + fn pretty_print(typ: Arc) -> String { + Printer::new().pretty_print(&typ, 0) + } +} diff --git a/examples/sample/lib/syntax.ak b/examples/sample/lib/syntax.ak index f111995a..00bac8d3 100644 --- a/examples/sample/lib/syntax.ak +++ b/examples/sample/lib/syntax.ak @@ -1,3 +1,7 @@ +pub type ScriptContext { + idk: Int +} + pub fn append(a: ByteArray, b: ByteArray) -> ByteArray { todo } \ No newline at end of file diff --git a/examples/sample/scripts/swap.ak b/examples/sample/scripts/swap.ak index ba4d7ae6..27aa3d81 100644 --- a/examples/sample/scripts/swap.ak +++ b/examples/sample/scripts/swap.ak @@ -9,7 +9,13 @@ pub type Redeemer { Sell } -pub fn validate(datum: Datum, rdmr: Redeemer, ctx: ScriptContext) -> Bool { +pub fn validate(datum: Datum, rdmr: Redeemer, ctx: syntax.ScriptContext) -> Bool { + let thing = if True { + 3 + } else { + "thing" + } + when rdmr is { Buy -> True Sell -> datum.something == "Aiken"