feat: typecheck If expressions

This commit is contained in:
rvcas 2022-10-23 19:34:51 -04:00 committed by Lucas
parent 5244e58c9f
commit 825783ca61
12 changed files with 971 additions and 32 deletions

View File

@ -8,7 +8,11 @@ use clap::{Parser, Subcommand};
#[clap(propagate_version = true)] #[clap(propagate_version = true)]
pub enum Args { pub enum Args {
/// Build an aiken project /// Build an aiken project
Build, Build {
/// Path to project
#[clap(short, long)]
directory: Option<PathBuf>,
},
/// Typecheck a project project /// Typecheck a project project
Check { Check {
/// Path to project /// Path to project

View File

@ -55,7 +55,14 @@ impl Error {
} }
pub fn report(&self) { pub fn report(&self) {
eprintln!("Error: {:?}", self) match self {
Error::List(errors) => {
for error in errors {
eprintln!("Error: {:?}", error)
}
}
rest => eprintln!("Error: {:?}", rest),
}
} }
} }

View File

@ -16,7 +16,7 @@ use uplc::{
}, },
}; };
use aiken::{config::Config, error, project::Project}; use aiken::{config::Config, project::Project};
mod args; mod args;
@ -28,14 +28,35 @@ fn main() -> miette::Result<()> {
let args = Args::default(); let args = Args::default();
match args { match args {
Args::Build => { Args::Build { directory } => {
// 1. load and parse modules let project_path = if let Some(d) = directory {
// * lib - contains modules, types, and functions d
// * contracts - contains validators } else {
// * scripts - contains native scripts dsl env::current_dir().into_diagnostic()?
// 2. type check everything };
// 3. generate uplc and policy/address if relevant
todo!() 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 } => { Args::Check { directory } => {
@ -49,24 +70,24 @@ fn main() -> miette::Result<()> {
let mut project = Project::new(config, project_path); 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 { for warning in project.warnings {
warning.report() warning.report()
} }
if let Err(err) = build_result { if let Err(err) = build_result {
match &err { err.report();
error::Error::List(errors) => {
for error in errors {
eprintln!("Error: {:?}", error)
}
}
rest => eprintln!("Error: {:?}", rest),
}
// 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 => { Args::Dev => {

View File

@ -50,15 +50,21 @@ impl Project {
} }
pub fn build(&mut self) -> Result<(), Error> { 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()?; self.read_source_files()?;
let parsed_modules = self.parse_sources()?; let parsed_modules = self.parse_sources()?;
let processing_sequence = parsed_modules.sequence()?; let processing_sequence = parsed_modules.sequence()?;
let checked_modules = self.type_check(parsed_modules, processing_sequence)?; let _checked_modules = self.type_check(parsed_modules, processing_sequence)?;
println!("{:?}", checked_modules);
Ok(()) Ok(())
} }
@ -143,7 +149,8 @@ impl Project {
path, path,
code, code,
kind, kind,
package, // TODO: come back and figure out where to use this
package: _package,
ast, ast,
}) = parsed_modules.remove(&name) }) = parsed_modules.remove(&name)
{ {

View File

@ -9,6 +9,7 @@ pub mod error;
pub mod expr; pub mod expr;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
pub mod pretty;
pub mod tipo; pub mod tipo;
pub mod token; pub mod token;

436
crates/lang/src/pretty.rs Normal file
View File

@ -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<Document<'a>> {
fn to_doc(self) -> Document<'a> {
Document::Vec(self)
}
}
impl<'a, D: Documentable<'a>> Documentable<'a> for Option<D> {
fn to_doc(self) -> Document<'a> {
self.map(Documentable::to_doc).unwrap_or_else(nil)
}
}
pub fn concat<'a>(docs: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
Document::Vec(docs.into_iter().collect())
}
pub fn join<'a>(
docs: impl IntoIterator<Item = Document<'a>>,
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<Self>),
/// Renders `broken` if group is broken, `unbroken` otherwise
Break {
broken: &'a str,
unbroken: &'a str,
kind: BreakKind,
},
/// Join multiple documents together
Vec(Vec<Self>),
/// Nests the given document by the given indent
Nest(isize, Box<Self>),
/// Nests the given document to the current cursor position
NestCurrent(Box<Self>),
/// Nests the given document to the current cursor position
Group(Box<Self>),
/// 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()),
}
}
}

View File

@ -5,7 +5,7 @@ use crate::{
tipo::fields::FieldMap, tipo::fields::FieldMap,
}; };
use self::environment::Environment; use self::{environment::Environment, pretty::Printer};
mod environment; mod environment;
pub mod error; pub mod error;
@ -15,6 +15,7 @@ mod hydrator;
mod infer; mod infer;
mod pattern; mod pattern;
mod pipe; mod pipe;
mod pretty;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Type { pub enum Type {
@ -203,6 +204,18 @@ impl Type {
_ => None, _ => None,
} }
} }
pub fn to_pretty(&self, indent: usize) -> String {
Printer::new().pretty_print(self, indent)
}
pub fn to_pretty_with_names(&self, names: HashMap<u64, String>, indent: usize) -> String {
let mut printer = Printer::new();
printer.with_names(names);
printer.pretty_print(self, indent)
}
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View File

@ -231,7 +231,11 @@ pub enum Error {
location: Span, 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 { CouldNotUnify {
#[label] #[label]
location: Span, location: Span,

View File

@ -6,9 +6,9 @@ use crate::{
ast::{ ast::{
Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant, Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant,
RecordUpdateSpread, Span, SrcId, TodoKind, TypedArg, TypedCallArg, TypedClause, RecordUpdateSpread, Span, SrcId, TodoKind, TypedArg, TypedCallArg, TypedClause,
TypedClauseGuard, TypedConstant, TypedMultiPattern, TypedRecordUpdateArg, UntypedArg, TypedClauseGuard, TypedConstant, TypedIfBranch, TypedMultiPattern, TypedRecordUpdateArg,
UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedMultiPattern, UntypedPattern, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedIfBranch,
UntypedRecordUpdateArg, UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg,
}, },
builtins::{bool, byte_array, function, int, list, result, string}, builtins::{bool, byte_array, function, int, list, result, string},
expr::{TypedExpr, UntypedExpr}, expr::{TypedExpr, UntypedExpr},
@ -267,7 +267,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location, location,
branches, branches,
final_else, final_else,
} => todo!(), } => self.infer_if(branches, *final_else, location),
UntypedExpr::Assignment { UntypedExpr::Assignment {
location, location,
@ -1422,6 +1422,60 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}) })
} }
fn infer_if(
&mut self,
branches: Vec1<UntypedIfBranch>,
final_else: UntypedExpr,
location: Span,
) -> Result<TypedExpr, Error> {
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( fn infer_fn(
&mut self, &mut self,
args: Vec<UntypedArg>, args: Vec<UntypedArg>,

View File

@ -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<u64, String>,
uid: u64,
// A mapping of printd type names to the module that they are defined in.
printed_types: HashMap<String, String>,
}
impl Printer {
pub fn new() -> Self {
Default::default()
}
pub fn with_names(&mut self, names: HashMap<u64, String>) {
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<Refcell<Type>>
// 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<Type>]) -> 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<Type>) -> String {
Printer::new().pretty_print(&typ, 0)
}
}

View File

@ -1,3 +1,7 @@
pub type ScriptContext {
idk: Int
}
pub fn append(a: ByteArray, b: ByteArray) -> ByteArray { pub fn append(a: ByteArray, b: ByteArray) -> ByteArray {
todo todo
} }

View File

@ -9,7 +9,13 @@ pub type Redeemer {
Sell 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 { when rdmr is {
Buy -> True Buy -> True
Sell -> datum.something == "Aiken" Sell -> datum.something == "Aiken"