diff --git a/Cargo.lock b/Cargo.lock index 493fc30a..860c4b89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,6 @@ name = "aiken-lang" version = "0.0.24" dependencies = [ "chumsky", - "internment", "itertools", "miette", "pretty_assertions", @@ -85,6 +84,7 @@ name = "aiken-project" version = "0.0.24" dependencies = [ "aiken-lang", + "ignore", "miette", "petgraph", "regex", @@ -445,16 +445,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "internment" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a798d7677f07d6f1e77be484ea8626ddb1566194de399f1206306820c406371" -dependencies = [ - "hashbrown", - "parking_lot", -] - [[package]] name = "is_ci" version = "1.1.1" @@ -488,16 +478,6 @@ version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.17" @@ -688,29 +668,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - [[package]] name = "peg" version = "0.8.0" @@ -971,12 +928,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "serde" version = "1.0.145" @@ -1008,12 +959,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smallvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" - [[package]] name = "smawk" version = "0.3.1" @@ -1302,49 +1247,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "yansi" version = "0.5.1" diff --git a/crates/cli/src/cmd/fmt.rs b/crates/cli/src/cmd/fmt.rs new file mode 100644 index 00000000..9706a3a3 --- /dev/null +++ b/crates/cli/src/cmd/fmt.rs @@ -0,0 +1,31 @@ +#[derive(clap::Args)] +/// Create a new Aiken project +pub struct Args { + /// Files to format + #[clap(default_value = ".")] + files: Vec, + + /// Read source from STDIN + #[clap(long)] + stdin: bool, + + /// Check if inputs are formatted without changing them + #[clap(long)] + check: bool, +} + +pub fn exec( + Args { + check, + stdin, + files, + }: Args, +) -> miette::Result<()> { + if let Err(err) = aiken_project::format::run(stdin, check, files) { + err.report(); + + miette::bail!("failed: {} error(s)", err.total()); + }; + + Ok(()) +} diff --git a/crates/cli/src/cmd/mod.rs b/crates/cli/src/cmd/mod.rs index f7fa2e10..43c895bc 100644 --- a/crates/cli/src/cmd/mod.rs +++ b/crates/cli/src/cmd/mod.rs @@ -1,5 +1,6 @@ pub mod build; pub mod check; +pub mod fmt; pub mod new; pub mod tx; pub mod uplc; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 70d150e7..11bee196 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,4 +1,4 @@ -use aiken::cmd::{build, check, new, tx, uplc}; +use aiken::cmd::{build, check, fmt, new, tx, uplc}; use clap::Parser; /// Aiken: a smart-contract language and toolchain for Cardano @@ -8,6 +8,7 @@ use clap::Parser; #[clap(setting(clap::AppSettings::DeriveDisplayOrder))] pub enum Cmd { New(new::Args), + Fmt(fmt::Args), Build(build::Args), Check(check::Args), @@ -28,6 +29,7 @@ fn main() -> miette::Result<()> { miette::set_panic_hook(); match Cmd::default() { Cmd::New(args) => new::exec(args), + Cmd::Fmt(args) => fmt::exec(args), Cmd::Build(args) => build::exec(args), Cmd::Check(args) => check::exec(args), Cmd::Tx(sub_cmd) => tx::exec(sub_cmd), diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index 01914f71..aa13e1d5 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -12,7 +12,6 @@ authors = ["Lucas Rosa ", "Kasey White "] [dependencies] chumsky = "0.8.0" -internment = "0.7.0" itertools = "0.10.5" miette = "5.2.0" strum = "0.24.1" diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index a5c27d5c..31eecbb8 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -1,7 +1,5 @@ use std::{fmt, ops::Range, sync::Arc}; -use internment::Intern; - use crate::{ builtins::{self, bool}, expr::{TypedExpr, UntypedExpr}, @@ -80,6 +78,7 @@ pub enum Definition { public: bool, return_annotation: Option, return_type: T, + end_position: usize, }, TypeAlias { @@ -122,6 +121,30 @@ pub enum Definition { }, } +impl Definition { + pub fn location(&self) -> Span { + match self { + Definition::Fn { location, .. } + | Definition::Use { location, .. } + | Definition::TypeAlias { location, .. } + | Definition::DataType { location, .. } + | Definition::ModuleConstant { location, .. } => *location, + } + } + + pub fn put_doc(&mut self, new_doc: String) { + match self { + Definition::Use { .. } => (), + Definition::Fn { doc, .. } + | Definition::TypeAlias { doc, .. } + | Definition::DataType { doc, .. } + | Definition::ModuleConstant { doc, .. } => { + let _ = std::mem::replace(doc, Some(new_doc)); + } + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct DefinitionLocation<'module> { pub module: Option<&'module str>, @@ -215,6 +238,15 @@ pub struct CallArg { pub value: A, } +impl CallArg { + pub fn is_capture_hole(&self) -> bool { + match &self.value { + UntypedExpr::Var { ref name, .. } => name == CAPTURE_VARIABLE, + _ => false, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct RecordConstructor { pub location: Span, @@ -451,6 +483,26 @@ pub enum BinOp { ModInt, } +impl BinOp { + pub fn precedence(&self) -> u8 { + // Ensure that this matches the other precedence function for guards + match self { + Self::Or => 1, + + Self::And => 2, + + Self::Eq | Self::NotEq => 3, + + Self::LtInt | Self::LtEqInt | Self::GtEqInt | Self::GtInt => 4, + + // Pipe is 5 + Self::AddInt | Self::SubInt => 6, + + Self::MultInt | Self::DivInt | Self::ModInt => 7, + } + } +} + pub type UntypedPattern = Pattern<(), ()>; pub type TypedPattern = Pattern>; @@ -542,7 +594,7 @@ impl Pattern { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum AssignmentKind { Let, Assert, @@ -568,7 +620,6 @@ pub struct Clause { impl TypedClause { pub fn location(&self) -> Span { Span { - src: SrcId::empty(), start: self .pattern .get(0) @@ -663,6 +714,23 @@ impl ClauseGuard { | ClauseGuard::LtEqInt { location, .. } => *location, } } + + pub fn precedence(&self) -> u8 { + // Ensure that this matches the other precedence function for guards + match self { + ClauseGuard::Or { .. } => 1, + ClauseGuard::And { .. } => 2, + + ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } => 3, + + ClauseGuard::GtInt { .. } + | ClauseGuard::GtEqInt { .. } + | ClauseGuard::LtInt { .. } + | ClauseGuard::LtEqInt { .. } => 4, + + ClauseGuard::Constant(_) | ClauseGuard::Var { .. } => 5, + } + } } impl TypedClauseGuard { @@ -721,18 +789,8 @@ pub enum TodoKind { EmptyFunction, } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct SrcId(Intern>); - -impl SrcId { - pub fn empty() -> Self { - SrcId(Intern::new(Vec::new())) - } -} - #[derive(Copy, Clone, PartialEq, Eq)] pub struct Span { - pub src: SrcId, pub start: usize, pub end: usize, } @@ -747,11 +805,7 @@ impl Span { pub fn empty() -> Self { use chumsky::Span; - Self::new(SrcId::empty(), 0..0) - } - - pub fn src(&self) -> SrcId { - self.src + Self::new((), 0..0) } pub fn range(&self) -> Range { @@ -763,43 +817,34 @@ impl Span { pub fn union(self, other: Self) -> Self { use chumsky::Span; - assert_eq!( - self.src, other.src, - "attempted to union spans with different sources" - ); - Self { start: self.start().min(other.start()), end: self.end().max(other.end()), - ..self } } } impl fmt::Debug for Span { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}:{:?}", self.src, self.range()) + write!(f, "{:?}", self.range()) } } impl chumsky::Span for Span { - type Context = SrcId; + type Context = (); type Offset = usize; - fn new(context: Self::Context, range: Range) -> Self { + fn new(_context: Self::Context, range: Range) -> Self { assert!(range.start <= range.end); Self { - src: context, start: range.start, end: range.end, } } - fn context(&self) -> Self::Context { - self.src - } + fn context(&self) -> Self::Context {} fn start(&self) -> Self::Offset { self.start diff --git a/crates/lang/src/expr.rs b/crates/lang/src/expr.rs index c5ab2a45..80781de7 100644 --- a/crates/lang/src/expr.rs +++ b/crates/lang/src/expr.rs @@ -435,7 +435,6 @@ impl UntypedExpr { let location = Span { start: self.location().start, end: next.location().end, - ..self.location() }; match (self.clone(), next.clone()) { @@ -508,4 +507,35 @@ impl UntypedExpr { } => expressions.last().map(Self::location).unwrap_or(*location), } } + + pub fn start_byte_index(&self) -> usize { + match self { + Self::Sequence { + expressions, + location, + .. + } => expressions + .first() + .map(|e| e.start_byte_index()) + .unwrap_or(location.start), + Self::PipeLine { expressions, .. } => expressions.first().start_byte_index(), + Self::Try { location, .. } | Self::Assignment { location, .. } => location.start, + _ => self.location().start, + } + } + + pub fn binop_precedence(&self) -> u8 { + match self { + Self::BinOp { name, .. } => name.precedence(), + Self::PipeLine { .. } => 5, + _ => std::u8::MAX, + } + } + + pub fn is_simple_constant(&self) -> bool { + matches!( + self, + Self::String { .. } | Self::Int { .. } | Self::ByteArray { .. } + ) + } } diff --git a/crates/lang/src/format.rs b/crates/lang/src/format.rs new file mode 100644 index 00000000..53fca843 --- /dev/null +++ b/crates/lang/src/format.rs @@ -0,0 +1,1537 @@ +use itertools::Itertools; +use std::sync::Arc; +use vec1::Vec1; + +use crate::{ + ast::{ + Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, ClauseGuard, Constant, + Definition, Pattern, RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, + TypedArg, TypedConstant, UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, + UntypedDefinition, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, CAPTURE_VARIABLE, + }, + docvec, + expr::UntypedExpr, + parser::extra::{Comment, ModuleExtra}, + pretty::{break_, concat, flex_break, join, line, lines, nil, Document, Documentable}, + tipo::{self, Type}, +}; + +const INDENT: isize = 2; + +pub fn pretty(writer: &mut String, module: UntypedModule, extra: ModuleExtra, src: &str) { + let intermediate = Intermediate { + comments: extra + .comments + .iter() + .map(|span| Comment::from((span, src))) + .collect(), + doc_comments: extra + .doc_comments + .iter() + .map(|span| Comment::from((span, src))) + .collect(), + empty_lines: &extra.empty_lines, + module_comments: extra + .module_comments + .iter() + .map(|span| Comment::from((span, src))) + .collect(), + }; + + Formatter::with_comments(&intermediate) + .module(&module) + .pretty_print(80, writer); +} + +struct Intermediate<'a> { + comments: Vec>, + doc_comments: Vec>, + module_comments: Vec>, + empty_lines: &'a [usize], +} + +/// Hayleigh's bane +#[derive(Debug, Clone, Default)] +pub struct Formatter<'a> { + comments: &'a [Comment<'a>], + doc_comments: &'a [Comment<'a>], + module_comments: &'a [Comment<'a>], + empty_lines: &'a [usize], +} + +impl<'comments> Formatter<'comments> { + pub fn new() -> Self { + Default::default() + } + + fn with_comments(extra: &'comments Intermediate<'comments>) -> Self { + Self { + comments: &extra.comments, + doc_comments: &extra.doc_comments, + module_comments: &extra.module_comments, + empty_lines: extra.empty_lines, + } + } + + // Pop comments that occur before a byte-index in the source, consuming + // and retaining any empty lines contained within. + fn pop_comments(&mut self, limit: usize) -> impl Iterator> { + let (popped, rest, empty_lines) = + comments_before(self.comments, self.empty_lines, limit, true); + + self.comments = rest; + + self.empty_lines = empty_lines; + + popped + } + + // Pop doc comments that occur before a byte-index in the source, consuming + // and dropping any empty lines contained within. + fn pop_doc_comments(&mut self, limit: usize) -> impl Iterator> { + let (popped, rest, empty_lines) = + comments_before(self.doc_comments, self.empty_lines, limit, false); + + self.doc_comments = rest; + + self.empty_lines = empty_lines; + + popped + } + + // Remove between 0 and `limit` empty lines following the current position, + // returning true if any empty lines were removed. + fn pop_empty_lines(&mut self, limit: usize) -> bool { + let mut end = 0; + + for (i, &position) in self.empty_lines.iter().enumerate() { + if position > limit { + break; + } + end = i + 1; + } + + self.empty_lines = self + .empty_lines + .get(end..) + .expect("Pop empty lines slicing"); + + end != 0 + } + + fn definitions<'a>(&mut self, definitions: &'a [UntypedDefinition]) -> Vec> { + let mut has_imports = false; + let mut has_declarations = false; + let mut imports = Vec::new(); + let mut declarations = Vec::with_capacity(definitions.len()); + + for def in definitions { + let start = def.location().start; + + match def { + Definition::Use { .. } => { + has_imports = true; + + let comments = self.pop_comments(start); + + let def = self.definition(def); + + imports.push(commented(def, comments)) + } + + _other => { + has_declarations = true; + + let comments = self.pop_comments(start); + + let declaration = self.documented_definition(def); + + declarations.push(commented(declaration, comments)) + } + } + } + + let imports = join(imports.into_iter(), line()); + + let declarations = join(declarations.into_iter(), lines(2)); + + let sep = if has_imports && has_declarations { + lines(2) + } else { + nil() + }; + + vec![imports, sep, declarations] + } + + fn module<'a>(&mut self, module: &'a UntypedModule) -> Document<'a> { + let groups = join(self.definitions(&module.definitions), lines(2)); + + // Now that `groups` has been collected, only freestanding comments (//) + // and doc comments (///) remain. Freestanding comments aren't associated + // with any statement, and are moved to the bottom of the module. + let doc_comments = join( + self.doc_comments.iter().map(|comment| { + "///" + .to_doc() + .append(Document::String(comment.content.to_string())) + }), + line(), + ); + + let comments = match printed_comments(self.pop_comments(usize::MAX), false) { + Some(comments) => comments, + None => nil(), + }; + + let module_comments = if !self.module_comments.is_empty() { + let comments = self.module_comments.iter().map(|s| { + "////" + .to_doc() + .append(Document::String(s.content.to_string())) + }); + + join(comments, line()).append(line()) + } else { + nil() + }; + + let non_empty = vec![module_comments, groups, doc_comments, comments] + .into_iter() + .filter(|doc| !doc.is_empty()); + + join(non_empty, line()).append(line()) + } + + fn definition<'a>(&mut self, definition: &'a UntypedDefinition) -> Document<'a> { + match definition { + Definition::Fn { + name, + arguments: args, + body, + public, + return_annotation, + end_position, + .. + } => self.definition_fn(public, name, args, return_annotation, body, *end_position), + + Definition::TypeAlias { + alias, + parameters: args, + annotation: resolved_type, + public, + .. + } => self.type_alias(*public, alias, args, resolved_type), + + Definition::DataType { + name, + parameters, + public, + constructors, + location, + opaque, + .. + } => self.data_type(*public, *opaque, name, parameters, constructors, location), + + Definition::Use { + module, + as_name, + unqualified, + .. + } => "use " + .to_doc() + .append(Document::String(module.join("/"))) + .append(if unqualified.is_empty() { + nil() + } else { + let unqualified = Itertools::intersperse( + unqualified + .iter() + .sorted_by(|a, b| a.name.cmp(&b.name)) + .map(|e| e.to_doc()), + flex_break(",", ", "), + ); + let unqualified = break_("", "") + .append(concat(unqualified)) + .nest(INDENT) + .append(break_(",", "")) + .group(); + ".{".to_doc().append(unqualified).append("}") + }) + .append(if let Some(name) = as_name { + docvec![" as ", name] + } else { + nil() + }), + + Definition::ModuleConstant { + public, + name, + annotation, + value, + .. + } => { + let head = pub_(*public).append("const ").append(name.as_str()); + let head = match annotation { + None => head, + Some(t) => head.append(": ").append(self.type_ast(t)), + }; + head.append(" = ").append(self.const_expr(value)) + } + } + } + + fn const_expr<'a, A, B>(&mut self, value: &'a Constant) -> Document<'a> { + match value { + Constant::ByteArray { .. } => todo!(), + Constant::Int { value, .. } => value.to_doc(), + + Constant::String { value, .. } => self.string(value), + + Constant::List { elements, .. } => { + let comma: fn() -> Document<'a> = if elements.iter().all(Constant::is_simple) { + || flex_break(",", ", ") + } else { + || break_(",", ", ") + }; + let elements_document = join(elements.iter().map(|e| self.const_expr(e)), comma()); + list(elements_document, elements.len(), None) + } + + Constant::Record { + name, + args, + module: None, + .. + } if args.is_empty() => name.to_doc(), + + Constant::Record { + name, + args, + module: Some(m), + .. + } if args.is_empty() => m.to_doc().append(".").append(name.as_str()), + + Constant::Record { + name, + args, + module: None, + .. + } => name + .to_doc() + .append(wrap_args(args.iter().map(|a| self.constant_call_arg(a)))) + .group(), + + Constant::Record { + name, + args, + module: Some(m), + .. + } => m + .to_doc() + .append(".") + .append(name.as_str()) + .append(wrap_args(args.iter().map(|a| self.constant_call_arg(a)))) + .group(), + + Constant::Var { + name, module: None, .. + } => name.to_doc(), + + Constant::Var { + name, + module: Some(module), + .. + } => docvec![module, ".", name], + } + } + + pub fn docs_const_expr<'a>( + &mut self, + public: bool, + name: &'a str, + value: &'a TypedConstant, + ) -> Document<'a> { + let mut printer = tipo::pretty::Printer::new(); + + pub_(public) + .append("const ") + .append(name) + .append(": ") + .append(printer.print(&value.tipo())) + .append(" = ") + .append(self.const_expr(value)) + } + + fn documented_definition<'a>(&mut self, s: &'a UntypedDefinition) -> Document<'a> { + let comments = self.doc_comments(s.location().start); + comments.append(self.definition(s).group()).group() + } + + fn doc_comments<'a>(&mut self, limit: usize) -> Document<'a> { + let mut comments = self.pop_doc_comments(limit).peekable(); + match comments.peek() { + None => nil(), + Some(_) => join( + comments.map(|c| match c { + Some(c) => "///".to_doc().append(Document::String(c.to_string())), + None => unreachable!("empty lines dropped by pop_doc_comments"), + }), + line(), + ) + .append(line()) + .force_break(), + } + } + + fn type_ast_constructor<'a>( + &mut self, + module: &'a Option, + name: &'a str, + args: &'a [Annotation], + ) -> Document<'a> { + let head = module + .as_ref() + .map(|qualifier| qualifier.to_doc().append(".").append(name)) + .unwrap_or_else(|| name.to_doc()); + + if args.is_empty() { + head + } else { + head.append(self.type_arguments(args)) + } + } + + fn type_ast<'a>(&mut self, t: &'a Annotation) -> Document<'a> { + match t { + Annotation::Hole { name, .. } => name.to_doc(), + + Annotation::Constructor { + name, + arguments: args, + module, + .. + } => self.type_ast_constructor(module, name, args), + + Annotation::Fn { + arguments: args, + ret: retrn, + .. + } => "fn" + .to_doc() + .append(self.type_arguments(args)) + .group() + .append(" ->") + .append(break_("", " ").append(self.type_ast(retrn)).nest(INDENT)), + + Annotation::Var { name, .. } => name.to_doc(), + } + .group() + } + + fn type_arguments<'a>(&mut self, args: &'a [Annotation]) -> Document<'a> { + wrap_args(args.iter().map(|t| self.type_ast(t))) + } + + pub fn type_alias<'a>( + &mut self, + public: bool, + name: &'a str, + args: &'a [String], + typ: &'a Annotation, + ) -> Document<'a> { + let head = pub_(public).append("type ").append(name); + + let head = if args.is_empty() { + head + } else { + head.append(wrap_args(args.iter().map(|e| e.to_doc())).group()) + }; + + head.append(" =") + .append(line().append(self.type_ast(typ)).group().nest(INDENT)) + } + + fn fn_arg<'a, A>(&mut self, arg: &'a Arg) -> Document<'a> { + let comments = self.pop_comments(arg.location.start); + + let doc = match &arg.annotation { + None => arg.arg_name.to_doc(), + Some(a) => arg.arg_name.to_doc().append(": ").append(self.type_ast(a)), + } + .group(); + + commented(doc, comments) + } + + fn definition_fn<'a>( + &mut self, + public: &'a bool, + name: &'a str, + args: &'a [UntypedArg], + return_annotation: &'a Option, + body: &'a UntypedExpr, + end_location: usize, + ) -> Document<'a> { + // Fn name and args + let head = pub_(*public) + .append("fn ") + .append(name) + .append(wrap_args(args.iter().map(|e| self.fn_arg(e)))); + + // Add return annotation + let head = match return_annotation { + Some(anno) => head.append(" -> ").append(self.type_ast(anno)), + None => head, + } + .group(); + + // Format body + let body = self.expr(body); + + // Add any trailing comments + let body = match printed_comments(self.pop_comments(end_location), false) { + Some(comments) => body.append(line()).append(comments), + None => body, + }; + + // Stick it all together + head.append(" {") + .append(line().append(body).nest(INDENT).group()) + .append(line()) + .append("}") + } + + fn expr_fn<'a>( + &mut self, + args: &'a [UntypedArg], + return_annotation: Option<&'a Annotation>, + body: &'a UntypedExpr, + ) -> Document<'a> { + let args = wrap_args(args.iter().map(|e| self.fn_arg(e))).group(); + let body = match body { + UntypedExpr::When { .. } => self.expr(body).force_break(), + _ => self.expr(body), + }; + + let header = "fn".to_doc().append(args); + + let header = match return_annotation { + None => header, + Some(t) => header.append(" -> ").append(self.type_ast(t)), + }; + + header + .append( + break_(" {", " { ") + .append(body) + .nest(INDENT) + .append(break_("", " ")) + .append("}"), + ) + .group() + } + + fn sequence<'a>(&mut self, expressions: &'a [UntypedExpr]) -> Document<'a> { + let count = expressions.len(); + let mut documents = Vec::with_capacity(count * 2); + for (i, expression) in expressions.iter().enumerate() { + let preceeding_newline = self.pop_empty_lines(expression.start_byte_index()); + if i != 0 && preceeding_newline { + documents.push(lines(2)); + } else if i != 0 { + documents.push(lines(1)); + } + documents.push(self.expr(expression).group()); + } + documents.to_doc().force_break() + } + + fn assignment<'a>( + &mut self, + pattern: &'a UntypedPattern, + value: &'a UntypedExpr, + then: Option<&'a UntypedExpr>, + kind: Option, + annotation: &'a Option, + ) -> Document<'a> { + self.pop_empty_lines(pattern.location().end); + + let keyword = match kind { + Some(AssignmentKind::Let) => "let ", + Some(AssignmentKind::Assert) => "assert ", + None => "try ", + }; + + let pattern = self.pattern(pattern); + + let annotation = annotation + .as_ref() + .map(|a| ": ".to_doc().append(self.type_ast(a))); + + let doc = if then.is_some() { + keyword.to_doc().force_break() + } else { + keyword.to_doc() + } + .append(pattern.append(annotation).group()) + .append(" =") + .append(self.assigned_value(value)); + + if let Some(then) = then { + doc.append(if self.pop_empty_lines(then.start_byte_index()) { + lines(2) + } else { + line() + }) + .append(self.expr(then)) + } else { + doc + } + } + + fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { + let comments = self.pop_comments(expr.start_byte_index()); + + let document = match expr { + UntypedExpr::ByteArray { .. } => todo!(), + UntypedExpr::If { .. } => todo!(), + UntypedExpr::Todo { label: None, .. } => "todo".to_doc(), + + UntypedExpr::Todo { label: Some(l), .. } => docvec!["todo(\"", l, "\")"], + + UntypedExpr::PipeLine { expressions, .. } => self.pipeline(expressions), + + UntypedExpr::Int { value, .. } => value.to_doc(), + + UntypedExpr::String { value, .. } => self.string(value), + + UntypedExpr::Sequence { expressions, .. } => self.sequence(expressions), + + UntypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => "_".to_doc(), + + UntypedExpr::Var { name, .. } => name.to_doc(), + + UntypedExpr::Negate { value, .. } => self.negate(value), + + UntypedExpr::Fn { + is_capture: true, + body, + .. + } => self.fn_capture(body), + + UntypedExpr::Fn { + return_annotation, + arguments: args, + body, + .. + } => self.expr_fn(args, return_annotation.as_ref(), body), + + UntypedExpr::List { elements, tail, .. } => self.list(elements, tail.as_deref()), + + UntypedExpr::Call { + fun, + arguments: args, + .. + } => self.call(fun, args), + + UntypedExpr::BinOp { + name, left, right, .. + } => self.bin_op(name, left, right), + + UntypedExpr::Assignment { + value, + pattern, + annotation, + kind, + .. + } => self.assignment(pattern, value, None, Some(*kind), annotation), + + UntypedExpr::Try { + value, + pattern, + annotation, + then, + .. + } => self.assignment(pattern, value, Some(then), None, annotation), + + UntypedExpr::When { + subjects, clauses, .. + } => self.when(subjects, clauses), + + UntypedExpr::FieldAccess { + label, container, .. + } => self.expr(container).append(".").append(label.as_str()), + + UntypedExpr::RecordUpdate { + constructor, + spread, + arguments: args, + .. + } => self.record_update(constructor, spread, args), + }; + commented(document, comments) + } + + fn string<'a>(&self, string: &'a String) -> Document<'a> { + let doc = string.to_doc().surround("\"", "\""); + if string.contains('\n') { + doc.force_break() + } else { + doc + } + } + + fn pattern_constructor<'a>( + &mut self, + name: &'a str, + args: &'a [CallArg], + module: &'a Option, + with_spread: bool, + ) -> Document<'a> { + fn is_breakable(expr: &UntypedPattern) -> bool { + match expr { + Pattern::List { .. } => true, + Pattern::Constructor { + arguments: args, .. + } => !args.is_empty(), + _ => false, + } + } + + let name = match module { + Some(m) => m.to_doc().append(".").append(name), + None => name.to_doc(), + }; + + if args.is_empty() && with_spread { + name.append("(..)") + } else if args.is_empty() { + name + } else if with_spread { + name.append(wrap_args_with_spread( + args.iter().map(|a| self.pattern_call_arg(a)), + )) + } else { + match args { + [arg] if is_breakable(&arg.value) => name + .append("(") + .append(self.pattern_call_arg(arg)) + .append(")") + .group(), + + _ => name + .append(wrap_args(args.iter().map(|a| self.pattern_call_arg(a)))) + .group(), + } + } + } + + fn call<'a>(&mut self, fun: &'a UntypedExpr, args: &'a [CallArg]) -> Document<'a> { + match args { + [arg] if is_breakable_expr(&arg.value) => self + .expr(fun) + .append("(") + .append(self.call_arg(arg)) + .append(")") + .group(), + + _ => self + .expr(fun) + .append(wrap_args(args.iter().map(|a| self.call_arg(a)))) + .group(), + } + } + + pub fn when<'a>( + &mut self, + subjects: &'a [UntypedExpr], + clauses: &'a [UntypedClause], + ) -> Document<'a> { + let subjects_doc = break_("when", "when ") + .append(join( + subjects.iter().map(|s| self.wrap_expr(s)), + break_(",", ", "), + )) + .append(break_(" is", " is ")) + .nest(INDENT) + .append(break_("", " ")) + .append("{") + .group(); + + let clauses_doc = concat( + clauses + .iter() + .enumerate() + .map(|(i, c)| self.clause(c, i as u32)), + ); + + subjects_doc + .append(line().append(clauses_doc).nest(INDENT)) + .append(line()) + .append("}") + .force_break() + } + + pub fn record_update<'a>( + &mut self, + constructor: &'a UntypedExpr, + spread: &'a RecordUpdateSpread, + args: &'a [UntypedRecordUpdateArg], + ) -> Document<'a> { + use std::iter::once; + let constructor_doc = self.expr(constructor); + let spread_doc = "..".to_doc().append(self.expr(&spread.base)); + let arg_docs = args.iter().map(|a| self.record_update_arg(a)); + let all_arg_docs = once(spread_doc).chain(arg_docs); + constructor_doc.append(wrap_args(all_arg_docs)).group() + } + + pub fn bin_op<'a>( + &mut self, + name: &'a BinOp, + left: &'a UntypedExpr, + right: &'a UntypedExpr, + ) -> Document<'a> { + let precedence = name.precedence(); + + let left_precedence = left.binop_precedence(); + let right_precedence = right.binop_precedence(); + + let left = self.expr(left); + let right = self.expr(right); + + self.operator_side(left, precedence, left_precedence) + .append(name) + .append(self.operator_side(right, precedence, right_precedence - 1)) + } + + pub fn operator_side<'a>(&mut self, doc: Document<'a>, op: u8, side: u8) -> Document<'a> { + if op > side { + break_("{", "{ ") + .append(doc) + .nest(INDENT) + .append(break_("", " ")) + .append("}") + .group() + } else { + doc + } + } + + fn pipeline<'a>(&mut self, expressions: &'a Vec1) -> Document<'a> { + let mut docs = Vec::with_capacity(expressions.len() * 3); + let first = expressions.first(); + let first_precedence = first.binop_precedence(); + let first = self.wrap_expr(first); + docs.push(self.operator_side(first, 5, first_precedence)); + + for expr in expressions.iter().skip(1) { + let comments = self.pop_comments(expr.location().start); + let doc = match expr { + UntypedExpr::Fn { + is_capture: true, + body, + .. + } => self.pipe_capture_right_hand_side(body), + + _ => self.wrap_expr(expr), + }; + docs.push(line()); + docs.push(commented("|> ".to_doc(), comments)); + docs.push(self.operator_side(doc, 4, expr.binop_precedence())); + } + + docs.to_doc().force_break() + } + + fn pipe_capture_right_hand_side<'a>(&mut self, fun: &'a UntypedExpr) -> Document<'a> { + let (fun, args) = match fun { + UntypedExpr::Call { + fun, + arguments: args, + .. + } => (fun, args), + _ => panic!("Function capture found not to have a function call body when formatting"), + }; + + let hole_in_first_position = matches!( + args.first(), + Some(CallArg { + value: UntypedExpr::Var { name, .. }, + .. + }) if name == CAPTURE_VARIABLE + ); + + if hole_in_first_position && args.len() == 1 { + // x |> fun(_) + self.expr(fun) + } else if hole_in_first_position { + // x |> fun(_, 2, 3) + self.expr(fun) + .append(wrap_args(args.iter().skip(1).map(|a| self.call_arg(a))).group()) + } else { + // x |> fun(1, _, 3) + self.expr(fun) + .append(wrap_args(args.iter().map(|a| self.call_arg(a))).group()) + } + } + + fn fn_capture<'a>(&mut self, call: &'a UntypedExpr) -> Document<'a> { + match call { + UntypedExpr::Call { + fun, + arguments: args, + .. + } => match args.as_slice() { + [first, second] if is_breakable_expr(&second.value) && first.is_capture_hole() => { + self.expr(fun) + .append("(_, ") + .append(self.call_arg(second)) + .append(")") + .group() + } + + _ => self + .expr(fun) + .append(wrap_args(args.iter().map(|a| self.call_arg(a))).group()), + }, + + // The body of a capture being not a fn shouldn't be possible... + _ => panic!("Function capture body found not to be a call in the formatter",), + } + } + + pub fn record_constructor<'a, A>( + &mut self, + constructor: &'a RecordConstructor, + ) -> Document<'a> { + let comments = self.pop_comments(constructor.location.start); + let doc_comments = self.doc_comments(constructor.location.start); + + let doc = if constructor.arguments.is_empty() { + constructor.name.to_doc() + } else if constructor.sugar { + wrap_fields(constructor.arguments.iter().map( + |RecordConstructorArg { + label, + annotation, + location, + .. + }| { + let arg_comments = self.pop_comments(location.start); + + let arg = match label { + Some(l) => l.to_doc().append(": ").append(self.type_ast(annotation)), + None => self.type_ast(annotation), + }; + + commented( + self.doc_comments(location.start).append(arg).group(), + arg_comments, + ) + }, + )) + .group() + } else { + constructor + .name + .to_doc() + .append(wrap_args(constructor.arguments.iter().map( + |RecordConstructorArg { + label, + annotation, + location, + .. + }| { + let arg_comments = self.pop_comments(location.start); + + let arg = match label { + Some(l) => l.to_doc().append(": ").append(self.type_ast(annotation)), + None => self.type_ast(annotation), + }; + + commented( + self.doc_comments(location.start).append(arg).group(), + arg_comments, + ) + }, + ))) + .group() + }; + + commented(doc_comments.append(doc).group(), comments) + } + + pub fn data_type<'a, A>( + &mut self, + public: bool, + opaque: bool, + name: &'a str, + args: &'a [String], + constructors: &'a [RecordConstructor], + location: &'a Span, + ) -> Document<'a> { + self.pop_empty_lines(location.start); + + let mut is_sugar = false; + + pub_(public) + .to_doc() + .append(if opaque { "opaque type " } else { "type " }) + .append(if args.is_empty() { + name.to_doc() + } else { + name.to_doc() + .append(wrap_args(args.iter().map(|e| e.to_doc()))) + .group() + }) + .append(" {") + .append(if constructors.len() == 1 && constructors[0].sugar { + is_sugar = true; + + self.record_constructor(&constructors[0]) + } else { + concat(constructors.iter().map(|c| { + if self.pop_empty_lines(c.location.start) { + lines(2) + } else { + line() + } + .append(self.record_constructor(c)) + .nest(INDENT) + .group() + })) + }) + .append(if is_sugar { nil() } else { line() }) + .append("}") + } + + pub fn docs_opaque_custom_type<'a>( + &mut self, + public: bool, + name: &'a str, + args: &'a [String], + location: &'a Span, + ) -> Document<'a> { + self.pop_empty_lines(location.start); + pub_(public) + .to_doc() + .append("opaque type ") + .append(if args.is_empty() { + name.to_doc() + } else { + name.to_doc() + .append(wrap_args(args.iter().map(|e| e.to_doc()))) + }) + } + + pub fn docs_fn_signature<'a>( + &mut self, + public: bool, + name: &'a str, + args: &'a [TypedArg], + return_type: Arc, + ) -> Document<'a> { + let mut printer = tipo::pretty::Printer::new(); + + pub_(public) + .append("fn ") + .append(name) + .append(self.docs_fn_args(args, &mut printer)) + .append(" -> ".to_doc()) + .append(printer.print(&return_type)) + } + + // Will always print the types, even if they were implicit in the original source + pub fn docs_fn_args<'a>( + &mut self, + args: &'a [TypedArg], + printer: &mut tipo::pretty::Printer, + ) -> Document<'a> { + wrap_args(args.iter().map(|arg| { + arg.arg_name + .to_doc() + .append(": ".to_doc().append(printer.print(&arg.tipo))) + .group() + })) + } + + fn wrap_expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { + match expr { + UntypedExpr::Sequence { .. } + | UntypedExpr::Assignment { .. } + | UntypedExpr::Try { .. } => "{" + .to_doc() + .append(line().append(self.expr(expr)).nest(INDENT)) + .append(line()) + .append("}") + .force_break(), + + _ => self.expr(expr), + } + } + + fn call_arg<'a>(&mut self, arg: &'a CallArg) -> Document<'a> { + match &arg.label { + Some(s) => commented( + s.to_doc().append(": "), + self.pop_comments(arg.location.start), + ), + None => nil(), + } + .append(self.wrap_expr(&arg.value)) + } + + fn record_update_arg<'a>(&mut self, arg: &'a UntypedRecordUpdateArg) -> Document<'a> { + arg.label + .to_doc() + .append(": ") + .append(self.wrap_expr(&arg.value)) + } + + fn case_clause_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { + match expr { + UntypedExpr::Try { .. } + | UntypedExpr::Sequence { .. } + | UntypedExpr::Assignment { .. } => " {" + .to_doc() + .append(line().append(self.expr(expr)).nest(INDENT).group()) + .append(line()) + .append("}") + .force_break(), + + UntypedExpr::Fn { .. } | UntypedExpr::List { .. } => { + " ".to_doc().append(self.expr(expr)).group() + } + + UntypedExpr::When { .. } => line().append(self.expr(expr)).nest(INDENT).group(), + + _ => break_("", " ").append(self.expr(expr)).nest(INDENT).group(), + } + } + + fn assigned_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { + match expr { + UntypedExpr::When { .. } => " ".to_doc().append(self.expr(expr)).group(), + _ => self.case_clause_value(expr), + } + } + + fn clause<'a>(&mut self, clause: &'a UntypedClause, index: u32) -> Document<'a> { + let space_before = self.pop_empty_lines(clause.location.start); + let after_position = clause.location.end; + let clause_doc = join( + std::iter::once(&clause.pattern) + .chain(&clause.alternative_patterns) + .map(|p| join(p.iter().map(|p| self.pattern(p)), ", ".to_doc())), + " | ".to_doc(), + ); + let clause_doc = match &clause.guard { + None => clause_doc, + Some(guard) => clause_doc.append(" if ").append(self.clause_guard(guard)), + }; + + // Remove any unused empty lines within the clause + self.pop_empty_lines(after_position); + + if index == 0 { + clause_doc + } else if space_before { + lines(2).append(clause_doc) + } else { + lines(1).append(clause_doc) + } + .append(" ->") + .append(self.case_clause_value(&clause.then)) + } + + pub fn external_type<'a>( + &mut self, + public: bool, + name: &'a str, + args: &'a [String], + ) -> Document<'a> { + pub_(public) + .append("external type ") + .append(name) + .append(if args.is_empty() { + nil() + } else { + wrap_args(args.iter().map(|e| e.to_doc())) + }) + } + + fn list<'a>( + &mut self, + elements: &'a [UntypedExpr], + tail: Option<&'a UntypedExpr>, + ) -> Document<'a> { + let comma: fn() -> Document<'a> = + if tail.is_none() && elements.iter().all(UntypedExpr::is_simple_constant) { + || flex_break(",", ", ") + } else { + || break_(",", ", ") + }; + let elements_document = join(elements.iter().map(|e| self.wrap_expr(e)), comma()); + let tail = tail.map(|e| self.expr(e)); + list(elements_document, elements.len(), tail) + } + + fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { + let comments = self.pop_comments(pattern.location().start); + let doc = match pattern { + Pattern::Int { value, .. } => value.to_doc(), + + Pattern::String { value, .. } => self.string(value), + + Pattern::Var { name, .. } => name.to_doc(), + + Pattern::VarUsage { name, .. } => name.to_doc(), + + Pattern::Assign { name, pattern, .. } => { + self.pattern(pattern).append(" as ").append(name.as_str()) + } + + Pattern::Discard { name, .. } => name.to_doc(), + + Pattern::List { elements, tail, .. } => { + let elements_document = + join(elements.iter().map(|e| self.pattern(e)), break_(",", ", ")); + let tail = tail.as_ref().map(|e| { + if e.is_discard() { + nil() + } else { + self.pattern(e) + } + }); + list(elements_document, elements.len(), tail) + } + + Pattern::Constructor { + name, + arguments: args, + module, + with_spread, + .. + } => self.pattern_constructor(name, args, module, *with_spread), + }; + commented(doc, comments) + } + + fn pattern_call_arg<'a>(&mut self, arg: &'a CallArg) -> Document<'a> { + arg.label + .as_ref() + .map(|s| s.to_doc().append(": ")) + .unwrap_or_else(nil) + .append(self.pattern(&arg.value)) + } + + pub fn clause_guard_bin_op<'a>( + &mut self, + name: &'a str, + name_precedence: u8, + left: &'a UntypedClauseGuard, + right: &'a UntypedClauseGuard, + ) -> Document<'a> { + let left_precedence = left.precedence(); + let right_precedence = right.precedence(); + let left = self.clause_guard(left); + let right = self.clause_guard(right); + self.operator_side(left, name_precedence, left_precedence) + .append(name) + .append(self.operator_side(right, name_precedence, right_precedence - 1)) + } + + fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> { + match clause_guard { + ClauseGuard::And { left, right, .. } => { + self.clause_guard_bin_op(" && ", clause_guard.precedence(), left, right) + } + ClauseGuard::Or { left, right, .. } => { + self.clause_guard_bin_op(" || ", clause_guard.precedence(), left, right) + } + ClauseGuard::Equals { left, right, .. } => { + self.clause_guard_bin_op(" == ", clause_guard.precedence(), left, right) + } + + ClauseGuard::NotEquals { left, right, .. } => { + self.clause_guard_bin_op(" != ", clause_guard.precedence(), left, right) + } + ClauseGuard::GtInt { left, right, .. } => { + self.clause_guard_bin_op(" > ", clause_guard.precedence(), left, right) + } + + ClauseGuard::GtEqInt { left, right, .. } => { + self.clause_guard_bin_op(" >= ", clause_guard.precedence(), left, right) + } + ClauseGuard::LtInt { left, right, .. } => { + self.clause_guard_bin_op(" < ", clause_guard.precedence(), left, right) + } + + ClauseGuard::LtEqInt { left, right, .. } => { + self.clause_guard_bin_op(" <= ", clause_guard.precedence(), left, right) + } + + ClauseGuard::Var { name, .. } => name.to_doc(), + + ClauseGuard::Constant(constant) => self.const_expr(constant), + } + } + + fn constant_call_arg<'a, A, B>(&mut self, arg: &'a CallArg>) -> Document<'a> { + match &arg.label { + None => self.const_expr(&arg.value), + Some(s) => s.to_doc().append(": ").append(self.const_expr(&arg.value)), + } + } + + fn negate<'a>(&mut self, value: &'a UntypedExpr) -> Document<'a> { + docvec!["!", self.wrap_expr(value)] + } +} + +impl<'a> Documentable<'a> for &'a ArgName { + fn to_doc(self) -> Document<'a> { + match self { + ArgName::Named { name, .. } | ArgName::Discard { name, .. } => name.to_doc(), + ArgName::LabeledDiscard { label, name, .. } + | ArgName::NamedLabeled { label, name, .. } => { + docvec![label, " ", name] + } + } + } +} + +fn pub_(public: bool) -> Document<'static> { + if public { + "pub ".to_doc() + } else { + nil() + } +} + +impl<'a> Documentable<'a> for &'a UnqualifiedImport { + fn to_doc(self) -> Document<'a> { + self.name.to_doc().append(match &self.as_name { + None => nil(), + Some(s) => " as ".to_doc().append(s.as_str()), + }) + } +} + +impl<'a> Documentable<'a> for &'a BinOp { + fn to_doc(self) -> Document<'a> { + match self { + BinOp::And => " && ", + BinOp::Or => " || ", + BinOp::LtInt => " < ", + BinOp::LtEqInt => " <= ", + BinOp::Eq => " == ", + BinOp::NotEq => " != ", + BinOp::GtEqInt => " >= ", + BinOp::GtInt => " > ", + BinOp::AddInt => " + ", + BinOp::SubInt => " - ", + BinOp::MultInt => " * ", + BinOp::DivInt => " / ", + BinOp::ModInt => " % ", + } + .to_doc() + } +} + +pub fn wrap_args<'a, I>(args: I) -> Document<'a> +where + I: IntoIterator>, +{ + let mut args = args.into_iter().peekable(); + if args.peek().is_none() { + return "()".to_doc(); + } + break_("(", "(") + .append(join(args, break_(",", ", "))) + .nest(INDENT) + .append(break_(",", "")) + .append(")") +} + +pub fn wrap_fields<'a, I>(args: I) -> Document<'a> +where + I: IntoIterator>, +{ + let mut args = args.into_iter().peekable(); + if args.peek().is_none() { + return nil(); + } + break_(" ", " ") + .append(join(args, break_(",", ", "))) + .nest(INDENT) + .append(break_(",", "")) + .append(" ") +} + +pub fn wrap_args_with_spread<'a, I>(args: I) -> Document<'a> +where + I: IntoIterator>, +{ + let mut args = args.into_iter().peekable(); + if args.peek().is_none() { + return "()".to_doc(); + } + + break_("(", "(") + .append(join(args, break_(",", ", "))) + .append(break_(",", ", ")) + .append("..") + .nest(INDENT) + .append(break_(",", "")) + .append(")") + .group() +} + +fn list<'a>(elements: Document<'a>, length: usize, tail: Option>) -> Document<'a> { + if length == 0 { + return match tail { + Some(tail) => tail, + None => "[]".to_doc(), + }; + } + + let doc = break_("[", "[").append(elements); + + match tail { + None => doc.nest(INDENT).append(break_(",", "")), + + // Don't print tail if it is a discard + Some(Document::String(t)) if t == *"_" => doc + .append(break_(",", ", ")) + .append("..") + .nest(INDENT) + .append(break_("", "")), + + Some(final_tail) => doc + .append(break_(",", ", ")) + .append("..") + .append(final_tail) + .nest(INDENT) + .append(break_("", "")), + } + .append("]") + .group() +} + +fn printed_comments<'a, 'comments>( + comments: impl IntoIterator>, + trailing_newline: bool, +) -> Option> { + let mut comments = comments.into_iter().peekable(); + comments.peek()?; + + let mut doc = Vec::new(); + while let Some(c) = comments.next() { + // There will never be consecutive empty lines (None values), + // and whenever we peek a None, we advance past it. + let c = c.expect("no consecutive empty lines"); + doc.push("//".to_doc().append(Document::String(c.to_string()))); + match comments.peek() { + // Next line is a comment + Some(Some(_)) => doc.push(line()), + // Next line is empty + Some(None) => { + comments.next(); + match comments.peek() { + Some(_) => doc.push(lines(2)), + None => { + if trailing_newline { + doc.push(lines(2)); + } + } + } + } + // We've reached the end, there are no more lines + None => { + if trailing_newline { + doc.push(line()); + } + } + } + } + let doc = concat(doc); + if trailing_newline { + Some(doc.force_break()) + } else { + Some(doc) + } +} + +fn commented<'a, 'comments>( + doc: Document<'a>, + comments: impl IntoIterator>, +) -> Document<'a> { + match printed_comments(comments, true) { + Some(comments) => comments.append(doc.group()), + None => doc, + } +} + +pub fn comments_before<'a>( + comments: &'a [Comment<'a>], + empty_lines: &'a [usize], + limit: usize, + retain_empty_lines: bool, +) -> ( + impl Iterator>, + &'a [Comment<'a>], + &'a [usize], +) { + let end_comments = comments + .iter() + .position(|c| c.start > limit) + .unwrap_or(comments.len()); + let end_empty_lines = empty_lines + .iter() + .position(|l| *l > limit) + .unwrap_or(empty_lines.len()); + let popped_comments = comments + .get(0..end_comments) + .expect("0..end_comments is guaranteed to be in bounds") + .iter() + .map(|c| (c.start, Some(c.content))); + let popped_empty_lines = if retain_empty_lines { empty_lines } else { &[] } + .get(0..end_empty_lines) + .unwrap_or(&[]) + .iter() + .map(|i| (i, i)) + // compact consecutive empty lines into a single line + .coalesce(|(a_start, a_end), (b_start, b_end)| { + if *a_end + 1 == *b_start { + Ok((a_start, b_end)) + } else { + Err(((a_start, a_end), (b_start, b_end))) + } + }) + .map(|l| (*l.0, None)); + let popped = popped_comments + .merge_by(popped_empty_lines, |(a, _), (b, _)| a < b) + .skip_while(|(_, comment_or_line)| comment_or_line.is_none()) + .map(|(_, comment_or_line)| comment_or_line); + ( + popped, + comments.get(end_comments..).expect("in bounds"), + empty_lines.get(end_empty_lines..).expect("in bounds"), + ) +} + +fn is_breakable_expr(expr: &UntypedExpr) -> bool { + matches!( + expr, + UntypedExpr::Fn { .. } + | UntypedExpr::Sequence { .. } + | UntypedExpr::Assignment { .. } + | UntypedExpr::Call { .. } + | UntypedExpr::When { .. } + | UntypedExpr::List { .. } + | UntypedExpr::If { .. } + ) +} diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index 6aa1ea26..754caa7f 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -5,13 +5,11 @@ use std::sync::{ pub mod ast; pub mod builtins; -pub mod error; pub mod expr; -pub mod lexer; +pub mod format; pub mod parser; pub mod pretty; pub mod tipo; -pub mod token; #[derive(Debug, Default, Clone)] pub struct IdGenerator { diff --git a/crates/lang/src/parser.rs b/crates/lang/src/parser.rs index 276deb72..fa57ef84 100644 --- a/crates/lang/src/parser.rs +++ b/crates/lang/src/parser.rs @@ -1,45 +1,83 @@ use chumsky::prelude::*; use vec1::Vec1; +pub mod error; +pub mod extra; +pub mod lexer; +pub mod token; + use crate::{ - ast::{self, BinOp, Span, SrcId, TodoKind, CAPTURE_VARIABLE}, - error::ParseError, - expr, lexer, - token::Token, + ast::{self, BinOp, Span, TodoKind, CAPTURE_VARIABLE}, + expr, }; -pub fn script(src: &str) -> Result> { +use error::ParseError; +use extra::ModuleExtra; +use token::Token; + +enum DefinitionOrExtra { + Definition(Box), + ModuleComment(Span), + DocComment(Span), + Comment(Span), + EmptyLine(usize), +} + +pub fn module( + src: &str, + kind: ast::ModuleKind, +) -> Result<(ast::UntypedModule, ModuleExtra), Vec> { let len = src.chars().count(); - let span = |i| Span::new(SrcId::empty(), i..i + 1); + let span = |i| Span::new((), i..i + 1); let tokens = lexer::lexer().parse(chumsky::Stream::from_iter( span(len), src.chars().enumerate().map(|(i, c)| (c, span(i))), ))?; - module_parser(ast::ModuleKind::Script) - .parse(chumsky::Stream::from_iter(span(len), tokens.into_iter())) -} + let module_data = + module_parser().parse(chumsky::Stream::from_iter(span(len), tokens.into_iter()))?; -pub fn module_parser( - kind: ast::ModuleKind, -) -> impl Parser { - choice(( - import_parser(), - data_parser(), - type_alias_parser(), - fn_parser(), - )) - .repeated() - .then_ignore(end()) - .map(move |definitions| ast::UntypedModule { + let mut definitions = Vec::new(); + let mut extra = ModuleExtra::new(); + + for data in module_data { + match data { + DefinitionOrExtra::Definition(def) => definitions.push(*def), + DefinitionOrExtra::ModuleComment(c) => extra.module_comments.push(c), + DefinitionOrExtra::DocComment(c) => extra.doc_comments.push(c), + DefinitionOrExtra::Comment(c) => extra.comments.push(c), + DefinitionOrExtra::EmptyLine(e) => extra.empty_lines.push(e), + } + } + + let module = ast::UntypedModule { kind, definitions, docs: vec![], name: "".to_string(), type_info: (), - }) + }; + + Ok((module, extra)) +} + +fn module_parser() -> impl Parser, Error = ParseError> { + choice(( + import_parser() + .map(Box::new) + .map(DefinitionOrExtra::Definition), + data_parser() + .map(Box::new) + .map(DefinitionOrExtra::Definition), + type_alias_parser() + .map(Box::new) + .map(DefinitionOrExtra::Definition), + fn_parser().map(Box::new).map(DefinitionOrExtra::Definition), + )) + .repeated() + .then_ignore(end()) } pub fn import_parser() -> impl Parser { @@ -188,7 +226,8 @@ pub fn fn_parser() -> impl Parser impl Parser impl Parser, + pub doc_comments: Vec, + pub comments: Vec, + pub empty_lines: Vec, +} + +impl ModuleExtra { + pub fn new() -> Self { + Default::default() + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Comment<'a> { + pub start: usize, + pub content: &'a str, +} + +impl<'a> From<(&Span, &'a str)> for Comment<'a> { + fn from(src: (&Span, &'a str)) -> Comment<'a> { + let start = src.0.start; + let end = src.0.end as usize; + Comment { + start, + content: src + .1 + .get(start as usize..end) + .expect("From span to comment"), + } + } +} diff --git a/crates/lang/src/lexer.rs b/crates/lang/src/parser/lexer.rs similarity index 98% rename from crates/lang/src/lexer.rs rename to crates/lang/src/parser/lexer.rs index b826acbd..4933168d 100644 --- a/crates/lang/src/lexer.rs +++ b/crates/lang/src/parser/lexer.rs @@ -1,6 +1,8 @@ use chumsky::prelude::*; -use crate::{ast::Span, error::ParseError, token::Token}; +use crate::ast::Span; + +use super::{error::ParseError, token::Token}; pub fn lexer() -> impl Parser, Error = ParseError> { let int = text::int(10).map(|value| Token::Int { value }); diff --git a/crates/lang/src/token.rs b/crates/lang/src/parser/token.rs similarity index 100% rename from crates/lang/src/token.rs rename to crates/lang/src/parser/token.rs diff --git a/crates/lang/src/pretty.rs b/crates/lang/src/pretty.rs index 2eec272d..5c3e0d2a 100644 --- a/crates/lang/src/pretty.rs +++ b/crates/lang/src/pretty.rs @@ -9,13 +9,10 @@ //! //! ## Extensions //! -//! - `ForceBreak` from Prettier. +//! - `ForcedBreak` from Elixir. //! - `FlexBreak` from Elixir. #![allow(clippy::wrong_self_convention)] -// #[cfg(test)] -// mod tests; - use std::collections::VecDeque; use itertools::Itertools; @@ -31,9 +28,6 @@ macro_rules! docvec { }; } -#[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. @@ -136,7 +130,7 @@ pub enum Document<'a> { Line(usize), /// Forces contained groups to break - ForceBreak, + ForceBroken(Box), /// May break contained document based on best fit, thus flex break FlexBreak(Box), @@ -154,9 +148,6 @@ pub enum Document<'a> { /// 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), @@ -167,10 +158,24 @@ pub enum Document<'a> { Str(&'a str), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Mode { Broken, Unbroken, + + // + // These are used for the Fits variant, taken from Elixir's + // Inspect.Algebra's `fits` extension. + // + /// Broken and forced to remain broken + ForcedBroken, + // ForcedUnbroken, // Used for next_break_fits. Not yet implemented. +} + +impl Mode { + fn is_forced(&self) -> bool { + matches!(self, Mode::ForcedBroken) + } } fn fits( @@ -189,14 +194,15 @@ fn fits( }; match document { - Document::Line(_) => return true, + Document::ForceBroken(_) => { + return false; + } - Document::ForceBreak => return false, + Document::Line(_) => return true, 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) if mode.is_forced() => docs.push_front((indent, mode, doc)), Document::Group(doc) => docs.push_front((indent, Mode::Unbroken, doc)), @@ -204,7 +210,7 @@ fn fits( Document::String(s) => limit -= s.len() as isize, Document::Break { unbroken, .. } => match mode { - Mode::Broken => return true, + Mode::Broken | Mode::ForcedBroken => return true, Mode::Unbroken => current_width += unbroken.len() as isize, }, @@ -212,7 +218,7 @@ fn fits( Document::Vec(vec) => { for doc in vec.iter().rev() { - docs.push_front((indent, mode.clone(), doc)); + docs.push_front((indent, mode, doc)); } } } @@ -230,11 +236,9 @@ fn format( 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'); @@ -267,6 +271,7 @@ fn format( for _ in 0..indent { writer.push(' '); } + width = indent; } } @@ -283,7 +288,8 @@ fn format( width + unbroken.len() as isize } - Mode::Broken => { + + Mode::Broken | Mode::ForcedBroken => { writer.push_str(broken); writer.push('\n'); @@ -311,7 +317,7 @@ fn format( Document::Vec(vec) => { for doc in vec.iter().rev() { - docs.push_front((indent, mode.clone(), doc)); + docs.push_front((indent, mode, doc)); } } @@ -319,15 +325,10 @@ fn format( 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())); + group_docs.push_front((indent, Mode::Unbroken, doc.as_ref())); if fits(limit, width, group_docs) { docs.push_front((indent, Mode::Unbroken, doc)); @@ -335,9 +336,12 @@ fn format( docs.push_front((indent, Mode::Broken, doc)); } } + + Document::ForceBroken(document) => { + docs.push_front((indent, Mode::ForcedBroken, document)); + } } } - Ok(()) } pub fn nil<'a>() -> Document<'a> { @@ -352,10 +356,6 @@ 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, @@ -381,8 +381,8 @@ impl<'a> Document<'a> { Self::Nest(indent, Box::new(self)) } - pub fn nest_current(self) -> Self { - Self::NestCurrent(Box::new(self)) + pub fn force_break(self) -> Self { + Self::ForceBroken(Box::new(self)) } pub fn append(self, second: impl Documentable<'a>) -> Self { @@ -398,8 +398,7 @@ impl<'a> Document<'a> { 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"); + self.pretty_print(limit, &mut buffer); buffer } @@ -408,14 +407,12 @@ impl<'a> Document<'a> { open.to_doc().append(self).append(closed) } - pub fn pretty_print(&self, limit: isize, writer: &mut String) -> Result<(), Error> { + pub fn pretty_print(&self, limit: isize, writer: &mut String) { let mut docs = VecDeque::new(); - docs.push_back((0, Mode::Unbroken, self)); + docs.push_front((0, Mode::Unbroken, self)); - format(writer, limit, 0, docs)?; - - Ok(()) + format(writer, limit, 0, docs); } /// Returns true when the document contains no printable characters @@ -424,12 +421,11 @@ impl<'a> Document<'a> { 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(), + ForceBroken(d) | FlexBreak(d) | Nest(_, d) | Group(d) => d.is_empty(), Vec(docs) => docs.iter().all(|d| d.is_empty()), } } diff --git a/crates/lang/src/tests/lexer.rs b/crates/lang/src/tests/lexer.rs index 5c5f7239..cf0060e2 100644 --- a/crates/lang/src/tests/lexer.rs +++ b/crates/lang/src/tests/lexer.rs @@ -1,17 +1,13 @@ use chumsky::prelude::*; -use crate::{ - ast::{Span, SrcId}, - lexer, - token::Token, -}; +use crate::{ast::Span, parser::lexer, parser::token::Token}; #[test] fn tokens() { let code = "pub type |> >=\n{ Thing _na_thing name"; let len = code.chars().count(); - let span = |i| Span::new(SrcId::empty(), i..i + 1); + let span = |i| Span::new((), i..i + 1); assert_eq!( lexer::lexer() diff --git a/crates/lang/src/tests/parser.rs b/crates/lang/src/tests/parser.rs index 9c67ba9b..35ff168c 100644 --- a/crates/lang/src/tests/parser.rs +++ b/crates/lang/src/tests/parser.rs @@ -2,9 +2,8 @@ use chumsky::prelude::*; use pretty_assertions::assert_eq; use crate::{ - ast::{self, Span, SrcId}, - expr, lexer, - parser::module_parser, + ast::{self, Span}, + expr, parser, }; #[test] @@ -106,23 +105,11 @@ fn module() { } } "#; - let len = code.chars().count(); - let span = |i| Span::new(SrcId::empty(), i..i + 1); - - let tokens = lexer::lexer() - .parse(chumsky::Stream::from_iter( - span(len), - code.chars().enumerate().map(|(i, c)| (c, span(i))), - )) - .unwrap(); - - let res = module_parser(ast::ModuleKind::Script) - .parse(chumsky::Stream::from_iter(span(len), tokens.into_iter())) - .unwrap(); + let (module, _extra) = parser::module(code, ast::ModuleKind::Script).unwrap(); assert_eq!( - res, + module, ast::UntypedModule { docs: vec![], kind: ast::ModuleKind::Script, @@ -130,26 +117,26 @@ fn module() { type_info: (), definitions: vec![ ast::UntypedDefinition::Use { - location: Span::new(SrcId::empty(), 13..25), + location: Span::new((), 13..25), module: vec!["std".to_string(), "list".to_string()], as_name: None, unqualified: vec![], package: (), }, ast::UntypedDefinition::Use { - location: Span::new(SrcId::empty(), 38..80), + location: Span::new((), 38..80), module: vec!["std".to_string(), "address".to_string()], as_name: None, unqualified: vec![ ast::UnqualifiedImport { as_name: Some("A".to_string()), - location: Span::new(SrcId::empty(), 55..67), + location: Span::new((), 55..67), layer: Default::default(), name: "Address".to_string() }, ast::UnqualifiedImport { as_name: Some("w".to_string()), - location: Span::new(SrcId::empty(), 69..79), + location: Span::new((), 69..79), layer: Default::default(), name: "thing".to_string() } @@ -157,38 +144,38 @@ fn module() { package: (), }, ast::UntypedDefinition::Use { - location: Span::new(SrcId::empty(), 93..108), + location: Span::new((), 93..108), module: vec!["std".to_string(), "tx".to_string()], as_name: Some("t".to_string()), unqualified: vec![], package: (), }, ast::UntypedDefinition::DataType { - location: Span::new(SrcId::empty(), 122..240), + location: Span::new((), 122..240), constructors: vec![ ast::RecordConstructor { - location: Span::new(SrcId::empty(), 153..165), + location: Span::new((), 153..165), name: "Some".to_string(), arguments: vec![ ast::RecordConstructorArg { label: None, annotation: ast::Annotation::Var { - location: Span::new(SrcId::empty(), 158..159), + location: Span::new((), 158..159), name: "a".to_string(), }, - location: Span::new(SrcId::empty(), 158..159), + location: Span::new((), 158..159), tipo: (), doc: None, }, ast::RecordConstructorArg { label: None, annotation: ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 161..164), + location: Span::new((), 161..164), module: None, name: "Int".to_string(), arguments: vec![], }, - location: Span::new(SrcId::empty(), 161..164), + location: Span::new((), 161..164), tipo: (), doc: None, }, @@ -197,37 +184,37 @@ fn module() { sugar: false, }, ast::RecordConstructor { - location: Span::new(SrcId::empty(), 180..184), + location: Span::new((), 180..184), name: "None".to_string(), arguments: vec![], documentation: None, sugar: false, }, ast::RecordConstructor { - location: Span::new(SrcId::empty(), 199..226), + location: Span::new((), 199..226), name: "Wow".to_string(), arguments: vec![ ast::RecordConstructorArg { label: Some("name".to_string(),), annotation: ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 211..214), + location: Span::new((), 211..214), module: None, name: "Int".to_string(), arguments: vec![], }, - location: Span::new(SrcId::empty(), 205..214), + location: Span::new((), 205..214), tipo: (), doc: None, }, ast::RecordConstructorArg { label: Some("age".to_string(),), annotation: ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 221..224), + location: Span::new((), 221..224), module: None, name: "Int".to_string(), arguments: vec![], }, - location: Span::new(SrcId::empty(), 216..224), + location: Span::new((), 216..224), tipo: (), doc: None, }, @@ -244,17 +231,17 @@ fn module() { typed_parameters: vec![], }, ast::UntypedDefinition::DataType { - location: Span::new(SrcId::empty(), 254..313), + location: Span::new((), 254..313), constructors: vec![ast::RecordConstructor { - location: Span::new(SrcId::empty(), 275..313), + location: Span::new((), 275..313), name: "User".to_string(), arguments: vec![ast::RecordConstructorArg { label: Some("name".to_string()), annotation: ast::Annotation::Hole { - location: Span::new(SrcId::empty(), 297..299), + location: Span::new((), 297..299), name: "_w".to_string(), }, - location: Span::new(SrcId::empty(), 291..299), + location: Span::new((), 291..299), tipo: (), doc: None, },], @@ -271,18 +258,18 @@ fn module() { ast::UntypedDefinition::TypeAlias { alias: "Thing".to_string(), annotation: ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 340..351), + location: Span::new((), 340..351), module: None, name: "Option".to_string(), arguments: vec![ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 347..350), + location: Span::new((), 347..350), module: None, name: "Int".to_string(), arguments: vec![], },], }, doc: None, - location: Span::new(SrcId::empty(), 327..351), + location: Span::new((), 327..351), parameters: vec![], public: false, tipo: (), @@ -290,18 +277,18 @@ fn module() { ast::UntypedDefinition::TypeAlias { alias: "Me".to_string(), annotation: ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 379..393), + location: Span::new((), 379..393), module: None, name: "Option".to_string(), arguments: vec![ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 386..392), + location: Span::new((), 386..392), module: None, name: "String".to_string(), arguments: vec![], },], }, doc: None, - location: Span::new(SrcId::empty(), 365..393), + location: Span::new((), 365..393), parameters: vec![], public: true, tipo: (), @@ -310,46 +297,48 @@ fn module() { arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "a".to_string(), - location: Span::new(SrcId::empty(), 422..423), + location: Span::new((), 422..423), }, - location: Span::new(SrcId::empty(), 422..423), + location: Span::new((), 422..423), annotation: None, tipo: (), },], body: expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 448..453), + location: Span::new((), 448..453), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 448..449), + location: Span::new((), 448..449), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 452..453), + location: Span::new((), 452..453), value: "1".to_string(), }), }, doc: None, - location: Span::new(SrcId::empty(), 407..467), + location: Span::new((), 407..467), name: "add_one".to_string(), public: true, return_annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 428..431), + location: Span::new((), 428..431), module: None, name: "Int".to_string(), arguments: vec![], },), return_type: (), + end_position: 3, }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![ast::Arg { arg_name: ast::ArgName::NamedLabeled { name: "a".to_string(), label: "thing".to_string(), - location: Span::new(SrcId::empty(), 494..501), + location: Span::new((), 494..501), }, - location: Span::new(SrcId::empty(), 494..506), + location: Span::new((), 494..506), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 503..506), + location: Span::new((), 503..506), module: None, name: "Int".to_string(), arguments: vec![], @@ -359,43 +348,44 @@ fn module() { body: expr::UntypedExpr::PipeLine { expressions: vec1::vec1![ expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 526..531), + location: Span::new((), 526..531), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 526..527), + location: Span::new((), 526..527), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 530..531), + location: Span::new((), 530..531), value: "2".to_string(), }), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 551..558), + location: Span::new((), 551..558), name: "add_one".to_string(), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 578..585), + location: Span::new((), 578..585), name: "add_one".to_string(), }, ], }, doc: None, - location: Span::new(SrcId::empty(), 481..599), + location: Span::new((), 481..599), name: "thing".to_string(), public: true, return_annotation: None, return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "a".to_string(), - location: Span::new(SrcId::empty(), 624..625), + location: Span::new((), 624..625), }, - location: Span::new(SrcId::empty(), 624..630), + location: Span::new((), 624..630), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 627..630), + location: Span::new((), 627..630), module: None, name: "Int".to_string(), arguments: vec![], @@ -403,103 +393,104 @@ fn module() { tipo: (), },], body: expr::UntypedExpr::Sequence { - location: Span::new(SrcId::empty(), 648..826), + location: Span::new((), 648..826), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 648..731), + location: Span::new((), 648..731), value: Box::new(expr::UntypedExpr::PipeLine { expressions: vec1::vec1![ expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 672..677), + location: Span::new((), 672..677), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 672..673), + location: Span::new((), 672..673), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 676..677), + location: Span::new((), 676..677), value: "2".to_string(), }), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 697..704), + location: Span::new((), 697..704), name: "add_one".to_string(), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 724..731), + location: Span::new((), 724..731), name: "add_one".to_string(), }, ], }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 652..653), + location: Span::new((), 652..653), name: "x".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 755..778), + location: Span::new((), 755..778), value: Box::new(expr::UntypedExpr::List { - location: Span::new(SrcId::empty(), 767..778), + location: Span::new((), 767..778), elements: vec![ expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 769..770), + location: Span::new((), 769..770), value: "1".to_string(), }, expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 772..773), + location: Span::new((), 772..773), value: "2".to_string(), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 775..776), + location: Span::new((), 775..776), name: "a".to_string(), }, ], tail: None, }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 759..764), + location: Span::new((), 759..764), name: "thing".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 794..809), + location: Span::new((), 794..809), value: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 804..809), + location: Span::new((), 804..809), name: "thing".to_string(), }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 798..801), + location: Span::new((), 798..801), name: "idk".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 825..826), + location: Span::new((), 825..826), name: "y".to_string(), }, ], }, doc: None, - location: Span::new(SrcId::empty(), 613..840), + location: Span::new((), 613..840), name: "wow".to_string(), public: true, return_annotation: None, return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "a".to_string(), - location: Span::new(SrcId::empty(), 866..867), + location: Span::new((), 866..867), }, - location: Span::new(SrcId::empty(), 866..872), + location: Span::new((), 866..872), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 869..872), + location: Span::new((), 869..872), module: None, name: "Int".to_string(), arguments: vec![], @@ -507,148 +498,142 @@ fn module() { tipo: (), },], body: expr::UntypedExpr::Sequence { - location: Span::new(SrcId::empty(), 889..1225), + location: Span::new((), 889..1225), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 889..980), + location: Span::new((), 889..980), value: Box::new(expr::UntypedExpr::Sequence { - location: Span::new(SrcId::empty(), 915..964), + location: Span::new((), 915..964), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 915..924), + location: Span::new((), 915..924), value: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 923..924), + location: Span::new((), 923..924), value: "4".to_string(), }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 919..920), + location: Span::new((), 919..920), name: "x".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 959..964), + location: Span::new((), 959..964), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 959..960), + location: Span::new((), 959..960), name: "x".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 963..964), + location: Span::new((), 963..964), value: "5".to_string(), }), }, ], }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 893..894), + location: Span::new((), 893..894), name: "b".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::When { - location: Span::new(SrcId::empty(), 996..1225), + location: Span::new((), 996..1225), subjects: vec![ expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1001..1002), + location: Span::new((), 1001..1002), name: "a".to_string(), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1004..1005), + location: Span::new((), 1004..1005), name: "b".to_string(), }, ], clauses: vec![ ast::Clause { - location: Span::new(SrcId::empty(), 1027..1036), + location: Span::new((), 1027..1036), pattern: vec![ ast::Pattern::Int { - location: Span::new(SrcId::empty(), 1027..1028), + location: Span::new((), 1027..1028), value: "1".to_string(), }, ast::Pattern::Int { - location: Span::new(SrcId::empty(), 1030..1031), + location: Span::new((), 1030..1031), value: "2".to_string(), }, ], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1035..1036), + location: Span::new((), 1035..1036), value: "3".to_string(), }, }, ast::Clause { - location: Span::new(SrcId::empty(), 1053..1163), + location: Span::new((), 1053..1163), pattern: vec![ast::Pattern::Int { - location: Span::new(SrcId::empty(), 1053..1054), + location: Span::new((), 1053..1054), value: "1".to_string(), },], alternative_patterns: vec![vec![ ast::Pattern::Int { - location: Span::new(SrcId::empty(), 1057..1058), + location: Span::new((), 1057..1058), value: "4".to_string(), }, ast::Pattern::Int { - location: Span::new(SrcId::empty(), 1060..1061), + location: Span::new((), 1060..1061), value: "5".to_string(), }, ],], guard: None, then: expr::UntypedExpr::Sequence { - location: Span::new(SrcId::empty(), 1085..1145), + location: Span::new((), 1085..1145), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 1085..1100), + location: Span::new((), 1085..1100), value: Box::new(expr::UntypedExpr::Int { - location: Span::new( - SrcId::empty(), - 1099..1100 - ), + location: Span::new((), 1099..1100), value: "5".to_string(), }), pattern: ast::Pattern::Var { - location: Span::new( - SrcId::empty(), - 1089..1096 - ), + location: Span::new((), 1089..1096), name: "amazing".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1138..1145), + location: Span::new((), 1138..1145), name: "amazing".to_string(), }, ], }, }, ast::Clause { - location: Span::new(SrcId::empty(), 1180..1186), + location: Span::new((), 1180..1186), pattern: vec![ast::Pattern::Int { - location: Span::new(SrcId::empty(), 1180..1181), + location: Span::new((), 1180..1181), value: "3".to_string(), },], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1185..1186), + location: Span::new((), 1185..1186), value: "9".to_string(), }, }, ast::Clause { - location: Span::new(SrcId::empty(), 1203..1209), + location: Span::new((), 1203..1209), pattern: vec![ast::Pattern::Discard { name: "_".to_string(), - location: Span::new(SrcId::empty(), 1203..1204), + location: Span::new((), 1203..1204), },], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1208..1209), + location: Span::new((), 1208..1209), value: "4".to_string(), }, }, @@ -657,30 +642,31 @@ fn module() { ], }, doc: None, - location: Span::new(SrcId::empty(), 854..1239), + location: Span::new((), 854..1239), name: "wow2".to_string(), public: true, return_annotation: None, return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![], body: expr::UntypedExpr::Sequence { - location: Span::new(SrcId::empty(), 1292..1364), + location: Span::new((), 1292..1364), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 1292..1334), + location: Span::new((), 1292..1334), value: Box::new(expr::UntypedExpr::Fn { - location: Span::new(SrcId::empty(), 1306..1334), + location: Span::new((), 1306..1334), is_capture: false, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "a".to_string(), - location: Span::new(SrcId::empty(), 1310..1311), + location: Span::new((), 1310..1311), }, - location: Span::new(SrcId::empty(), 1310..1316), + location: Span::new((), 1310..1316), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1313..1316), + location: Span::new((), 1313..1316), module: None, name: "Int".to_string(), arguments: vec![], @@ -688,26 +674,26 @@ fn module() { tipo: (), },], body: Box::new(expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 1327..1332), + location: Span::new((), 1327..1332), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1327..1328), + location: Span::new((), 1327..1328), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1331..1332), + location: Span::new((), 1331..1332), value: "1".to_string(), }), }), return_annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1321..1324), + location: Span::new((), 1321..1324), module: None, name: "Int".to_string(), arguments: vec![], },), }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 1296..1303), + location: Span::new((), 1296..1303), name: "add_one".to_string(), }, kind: ast::AssignmentKind::Let, @@ -716,11 +702,11 @@ fn module() { expr::UntypedExpr::PipeLine { expressions: vec1::vec1![ expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1352..1353), + location: Span::new((), 1352..1353), value: "2".to_string(), }, expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1357..1364), + location: Span::new((), 1357..1364), name: "add_one".to_string(), }, ], @@ -728,11 +714,11 @@ fn module() { ], }, doc: None, - location: Span::new(SrcId::empty(), 1253..1378), + location: Span::new((), 1253..1378), name: "such".to_string(), public: true, return_annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1270..1273), + location: Span::new((), 1270..1273), module: None, name: "Int".to_string(), arguments: vec![], @@ -740,28 +726,30 @@ fn module() { return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![], body: expr::UntypedExpr::Todo { kind: ast::TodoKind::EmptyFunction, - location: Span::new(SrcId::empty(), 1392..1403), + location: Span::new((), 1392..1403), label: None, }, doc: None, - location: Span::new(SrcId::empty(), 1392..1403), + location: Span::new((), 1392..1403), name: "run".to_string(), public: false, return_annotation: None, return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "user".to_string(), - location: Span::new(SrcId::empty(), 1425..1429), + location: Span::new((), 1425..1429), }, - location: Span::new(SrcId::empty(), 1425..1435), + location: Span::new((), 1425..1435), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1431..1435), + location: Span::new((), 1431..1435), module: None, name: "User".to_string(), arguments: vec![], @@ -769,121 +757,107 @@ fn module() { tipo: (), },], body: expr::UntypedExpr::FieldAccess { - location: Span::new(SrcId::empty(), 1455..1464), + location: Span::new((), 1455..1464), label: "name".to_string(), container: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1455..1459), + location: Span::new((), 1455..1459), name: "user".to_string(), }), }, doc: None, - location: Span::new(SrcId::empty(), 1417..1478), + location: Span::new((), 1417..1478), name: "name".to_string(), public: false, return_annotation: None, return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![], body: expr::UntypedExpr::Sequence { - location: Span::new(SrcId::empty(), 1521..1642), + location: Span::new((), 1521..1642), expressions: vec![ expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 1521..1539), + location: Span::new((), 1521..1539), value: Box::new(expr::UntypedExpr::Call { - location: Span::new(SrcId::empty(), 1536..1539), + location: Span::new((), 1536..1539), fun: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1529..1536), + location: Span::new((), 1529..1536), name: "add_one".to_string(), }), arguments: vec![ast::CallArg { label: None, - location: Span::new(SrcId::empty(), 1537..1538), + location: Span::new((), 1537..1538), value: expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1537..1538), + location: Span::new((), 1537..1538), value: "3".to_string(), }, },], }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 1525..1526), + location: Span::new((), 1525..1526), name: "x".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Assignment { - location: Span::new(SrcId::empty(), 1557..1602), + location: Span::new((), 1557..1602), value: Box::new(expr::UntypedExpr::Fn { - location: Span::new(SrcId::empty(), 1581..1602), + location: Span::new((), 1581..1602), is_capture: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "_capture__0".to_string(), - location: Span::new(SrcId::empty(), 0..0), + location: Span::new((), 0..0), }, - location: Span::new(SrcId::empty(), 0..0), + location: Span::new((), 0..0), annotation: None, tipo: (), },], body: Box::new(expr::UntypedExpr::Call { - location: Span::new(SrcId::empty(), 1581..1602), + location: Span::new((), 1581..1602), fun: Box::new(expr::UntypedExpr::FieldAccess { - location: Span::new(SrcId::empty(), 1573..1581), + location: Span::new((), 1573..1581), label: "map".to_string(), container: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1573..1577), + location: Span::new((), 1573..1577), name: "list".to_string(), }), }), arguments: vec![ ast::CallArg { label: None, - location: Span::new(SrcId::empty(), 1582..1583), + location: Span::new((), 1582..1583), value: expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1582..1583), + location: Span::new((), 1582..1583), name: "_capture__0".to_string(), }, }, ast::CallArg { label: None, - location: Span::new(SrcId::empty(), 1585..1601), + location: Span::new((), 1585..1601), value: expr::UntypedExpr::Fn { - location: Span::new(SrcId::empty(), 1585..1601), + location: Span::new((), 1585..1601), is_capture: false, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "y".to_string(), - location: Span::new( - SrcId::empty(), - 1589..1590 - ), + location: Span::new((), 1589..1590), }, - location: Span::new( - SrcId::empty(), - 1589..1590 - ), + location: Span::new((), 1589..1590), annotation: None, tipo: (), },], body: Box::new(expr::UntypedExpr::BinOp { - location: Span::new( - SrcId::empty(), - 1594..1599 - ), + location: Span::new((), 1594..1599), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new( - SrcId::empty(), - 1594..1595 - ), + location: Span::new((), 1594..1595), name: "x".to_string(), }), right: Box::new(expr::UntypedExpr::Var { - location: Span::new( - SrcId::empty(), - 1598..1599 - ), + location: Span::new((), 1598..1599), name: "y".to_string(), }), }), @@ -895,34 +869,34 @@ fn module() { return_annotation: None, }), pattern: ast::Pattern::Var { - location: Span::new(SrcId::empty(), 1561..1570), + location: Span::new((), 1561..1570), name: "map_add_x".to_string(), }, kind: ast::AssignmentKind::Let, annotation: None, }, expr::UntypedExpr::Call { - location: Span::new(SrcId::empty(), 1629..1642), + location: Span::new((), 1629..1642), fun: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1620..1629), + location: Span::new((), 1620..1629), name: "map_add_x".to_string(), }), arguments: vec![ast::CallArg { label: None, - location: Span::new(SrcId::empty(), 1630..1641), + location: Span::new((), 1630..1641), value: expr::UntypedExpr::List { - location: Span::new(SrcId::empty(), 1630..1641), + location: Span::new((), 1630..1641), elements: vec![ expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1632..1633), + location: Span::new((), 1632..1633), value: "1".to_string(), }, expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1635..1636), + location: Span::new((), 1635..1636), value: "2".to_string(), }, expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1638..1639), + location: Span::new((), 1638..1639), value: "3".to_string(), }, ], @@ -933,22 +907,23 @@ fn module() { ], }, doc: None, - location: Span::new(SrcId::empty(), 1492..1656), + location: Span::new((), 1492..1656), name: "calls".to_string(), public: false, return_annotation: None, return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![ ast::Arg { arg_name: ast::ArgName::Named { name: "user".to_string(), - location: Span::new(SrcId::empty(), 1685..1689), + location: Span::new((), 1685..1689), }, - location: Span::new(SrcId::empty(), 1685..1695), + location: Span::new((), 1685..1695), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1691..1695), + location: Span::new((), 1691..1695), module: None, name: "User".to_string(), arguments: vec![], @@ -958,11 +933,11 @@ fn module() { ast::Arg { arg_name: ast::ArgName::Named { name: "name".to_string(), - location: Span::new(SrcId::empty(), 1697..1701), + location: Span::new((), 1697..1701), }, - location: Span::new(SrcId::empty(), 1697..1709), + location: Span::new((), 1697..1709), annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1703..1709), + location: Span::new((), 1703..1709), module: None, name: "String".to_string(), arguments: vec![], @@ -971,33 +946,33 @@ fn module() { }, ], body: expr::UntypedExpr::RecordUpdate { - location: Span::new(SrcId::empty(), 1737..1768), + location: Span::new((), 1737..1768), constructor: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1737..1741), + location: Span::new((), 1737..1741), name: "User".to_string(), }), spread: ast::RecordUpdateSpread { base: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1746..1750), + location: Span::new((), 1746..1750), name: "user".to_string(), }), - location: Span::new(SrcId::empty(), 1742..1768), + location: Span::new((), 1742..1768), }, arguments: vec![ast::UntypedRecordUpdateArg { label: "name".to_string(), - location: Span::new(SrcId::empty(), 1752..1765), + location: Span::new((), 1752..1765), value: expr::UntypedExpr::String { - location: Span::new(SrcId::empty(), 1758..1765), + location: Span::new((), 1758..1765), value: "Aiken".to_string(), }, },], }, doc: None, - location: Span::new(SrcId::empty(), 1670..1782), + location: Span::new((), 1670..1782), name: "update_name".to_string(), public: false, return_annotation: Some(ast::Annotation::Constructor { - location: Span::new(SrcId::empty(), 1714..1718), + location: Span::new((), 1714..1718), module: None, name: "User".to_string(), arguments: vec![], @@ -1005,75 +980,76 @@ fn module() { return_type: (), }, ast::UntypedDefinition::Fn { + end_position: 3, arguments: vec![], body: expr::UntypedExpr::If { - location: Span::new(SrcId::empty(), 1823..2036), + location: Span::new((), 1823..2036), branches: vec1::vec1![ ast::IfBranch { condition: expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1826..1830), + location: Span::new((), 1826..1830), name: "True".to_string(), }, body: expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 1853..1858), + location: Span::new((), 1853..1858), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1853..1854), + location: Span::new((), 1853..1854), value: "1".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1857..1858), + location: Span::new((), 1857..1858), value: "1".to_string(), }), }, - location: Span::new(SrcId::empty(), 1826..1876), + location: Span::new((), 1826..1876), }, ast::IfBranch { condition: expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 1885..1890), + location: Span::new((), 1885..1890), name: ast::BinOp::LtInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1885..1886), + location: Span::new((), 1885..1886), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1889..1890), + location: Span::new((), 1889..1890), value: "4".to_string(), }), }, body: expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1913..1914), + location: Span::new((), 1913..1914), value: "5".to_string(), }, - location: Span::new(SrcId::empty(), 1885..1932), + location: Span::new((), 1885..1932), }, ast::IfBranch { condition: expr::UntypedExpr::BinOp { - location: Span::new(SrcId::empty(), 1941..1947), + location: Span::new((), 1941..1947), name: ast::BinOp::Or, left: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1941..1942), + location: Span::new((), 1941..1942), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Var { - location: Span::new(SrcId::empty(), 1946..1947), + location: Span::new((), 1946..1947), name: "b".to_string(), }), }, body: expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 1970..1971), + location: Span::new((), 1970..1971), value: "6".to_string(), }, - location: Span::new(SrcId::empty(), 1941..1989), + location: Span::new((), 1941..1989), }, ], final_else: Box::new(expr::UntypedExpr::Int { - location: Span::new(SrcId::empty(), 2017..2018), + location: Span::new((), 2017..2018), value: "3".to_string(), }), }, doc: None, - location: Span::new(SrcId::empty(), 1796..2050), + location: Span::new((), 1796..2050), name: "ifs".to_string(), public: false, return_annotation: None, diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index ce0f4b00..b01f764f 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -17,7 +17,7 @@ mod hydrator; mod infer; mod pattern; mod pipe; -mod pretty; +pub mod pretty; #[derive(Debug, Clone, PartialEq)] pub enum Type { diff --git a/crates/lang/src/tipo/environment.rs b/crates/lang/src/tipo/environment.rs index f018b42c..1596f0f2 100644 --- a/crates/lang/src/tipo/environment.rs +++ b/crates/lang/src/tipo/environment.rs @@ -195,6 +195,7 @@ impl<'a> Environment<'a> { body, return_annotation, return_type, + end_position, } => { // Lookup the inferred function information let function = self @@ -238,6 +239,7 @@ impl<'a> Environment<'a> { return_annotation, return_type, body, + end_position, } } diff --git a/crates/lang/src/tipo/expr.rs b/crates/lang/src/tipo/expr.rs index c16daabe..733c567f 100644 --- a/crates/lang/src/tipo/expr.rs +++ b/crates/lang/src/tipo/expr.rs @@ -5,10 +5,10 @@ use vec1::Vec1; use crate::{ ast::{ Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant, - RecordUpdateSpread, Span, SrcId, TodoKind, TypedArg, TypedCallArg, TypedClause, - TypedClauseGuard, TypedConstant, TypedIfBranch, TypedMultiPattern, TypedRecordUpdateArg, - UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedIfBranch, - UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg, + RecordUpdateSpread, Span, TodoKind, TypedArg, TypedCallArg, TypedClause, 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}, @@ -605,7 +605,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .ok_or_else(|| Error::UnknownModuleValue { name: label.clone(), location: Span { - src: SrcId::empty(), start: module_location.end, end: select_location.end, }, diff --git a/crates/lang/src/tipo/infer.rs b/crates/lang/src/tipo/infer.rs index 90dbccec..a1cd071d 100644 --- a/crates/lang/src/tipo/infer.rs +++ b/crates/lang/src/tipo/infer.rs @@ -6,7 +6,7 @@ use crate::{ TypedModule, UntypedDefinition, UntypedModule, }, builtins::function, - token::Token, + parser::token::Token, IdGenerator, }; @@ -150,6 +150,7 @@ fn infer_definition( arguments: args, body, return_annotation, + end_position, .. } => { let preregistered_fn = environment @@ -227,6 +228,7 @@ fn infer_definition( .return_type() .expect("Could not find return type for fn"), body, + end_position, }) } diff --git a/crates/lang/src/tipo/pattern.rs b/crates/lang/src/tipo/pattern.rs index f9ea9b56..83cba23c 100644 --- a/crates/lang/src/tipo/pattern.rs +++ b/crates/lang/src/tipo/pattern.rs @@ -15,7 +15,7 @@ use super::{ PatternConstructor, Type, ValueConstructor, ValueConstructorVariant, }; use crate::{ - ast::{CallArg, Pattern, Span, SrcId, TypedPattern, UntypedMultiPattern, UntypedPattern}, + ast::{CallArg, Pattern, Span, TypedPattern, UntypedMultiPattern, UntypedPattern}, builtins::{int, list, string}, }; @@ -427,7 +427,6 @@ impl<'a, 'b> PatternTyper<'a, 'b> { if pattern_args.len() == field_map.arity as usize { return Err(Error::UnnecessarySpreadOperator { location: Span { - src: SrcId::empty(), start: location.end - 3, end: location.end - 1, }, @@ -437,7 +436,6 @@ impl<'a, 'b> PatternTyper<'a, 'b> { // The location of the spread operator itself let spread_location = Span { - src: SrcId::empty(), start: location.end - 3, end: location.end - 1, }; diff --git a/crates/lang/src/tipo/pipe.rs b/crates/lang/src/tipo/pipe.rs index e9fe3cee..95715a7a 100644 --- a/crates/lang/src/tipo/pipe.rs +++ b/crates/lang/src/tipo/pipe.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use vec1::Vec1; use crate::{ - ast::{AssignmentKind, CallArg, Pattern, Span, SrcId, PIPE_VARIABLE}, + ast::{AssignmentKind, CallArg, Pattern, Span, PIPE_VARIABLE}, builtins::function, expr::{TypedExpr, UntypedExpr}, }; @@ -48,7 +48,6 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { argument_type: first.tipo(), argument_location: first.location(), location: Span { - src: SrcId::empty(), start: first.location().start, end: *end, }, diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 9d850fd3..6c836a95 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -10,6 +10,7 @@ authors = ["Lucas Rosa ", "Kasey White "] [dependencies] aiken-lang = { path = "../lang", version = "0.0.24" } +ignore = "0.4.18" miette = { version = "5.3.0", features = ["fancy"] } petgraph = "0.6.2" regex = "1.6.0" diff --git a/crates/project/src/error.rs b/crates/project/src/error.rs index cd4ecf39..23173d85 100644 --- a/crates/project/src/error.rs +++ b/crates/project/src/error.rs @@ -1,10 +1,10 @@ use std::{ fmt::{Debug, Display}, io, - path::PathBuf, + path::{Path, PathBuf}, }; -use aiken_lang::{error::ParseError, tipo}; +use aiken_lang::{parser::error::ParseError, tipo}; use miette::{Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode}; #[allow(dead_code)] @@ -20,6 +20,12 @@ pub enum Error { #[error("file operation failed")] FileIo { error: io::Error, path: PathBuf }, + #[error("source code incorrectly formatted")] + Format { problem_files: Vec }, + + #[error(transparent)] + StandardIo(#[from] io::Error), + #[error("cyclical module imports")] ImportCycle { modules: Vec }, @@ -64,6 +70,20 @@ impl Error { rest => eprintln!("Error: {:?}", rest), } } + + pub fn from_parse_errors(errs: Vec, path: &Path, src: &str) -> Self { + let mut errors = Vec::with_capacity(errs.len()); + + for error in errs { + errors.push(Error::Parse { + path: path.into(), + src: src.to_string(), + error: error.into(), + }); + } + + Error::List(errors) + } } impl Debug for Error { @@ -94,6 +114,8 @@ impl Diagnostic for Error { Error::List(_) => None, Error::Parse { .. } => Some(Box::new("aiken::parser")), Error::Type { .. } => Some(Box::new("aiken::typecheck")), + Error::StandardIo(_) => None, + Error::Format { .. } => None, } } @@ -112,6 +134,8 @@ impl Diagnostic for Error { Error::List(_) => None, Error::Parse { error, .. } => error.kind.help(), Error::Type { error, .. } => error.help(), + Error::StandardIo(_) => None, + Error::Format { .. } => None, } } @@ -123,6 +147,8 @@ impl Diagnostic for Error { Error::List(_) => None, Error::Parse { error, .. } => error.labels(), Error::Type { error, .. } => error.labels(), + Error::StandardIo(_) => None, + Error::Format { .. } => None, } } @@ -134,6 +160,8 @@ impl Diagnostic for Error { Error::List(_) => None, Error::Parse { src, .. } => Some(src), Error::Type { src, .. } => Some(src), + Error::StandardIo(_) => None, + Error::Format { .. } => None, } } } @@ -196,3 +224,11 @@ impl Debug for Warning { Ok(()) } } + +#[derive(Debug, PartialEq, Eq)] +pub struct Unformatted { + pub source: PathBuf, + pub destination: PathBuf, + pub input: String, + pub output: String, +} diff --git a/crates/project/src/format.rs b/crates/project/src/format.rs new file mode 100644 index 00000000..6d8716b7 --- /dev/null +++ b/crates/project/src/format.rs @@ -0,0 +1,147 @@ +use std::{ + fs, + io::Read, + path::{Path, PathBuf}, + str::FromStr, +}; + +use aiken_lang::{ast::ModuleKind, parser}; + +use crate::{ + error::{Error, Unformatted}, + is_aiken_path, +}; + +pub fn run(stdin: bool, check: bool, files: Vec) -> Result<(), Error> { + if stdin { + process_stdin(check) + } else { + process_files(check, files) + } +} + +fn process_stdin(check: bool) -> Result<(), Error> { + let src = read_stdin()?; + + let mut out = String::new(); + + let (module, extra) = parser::module(&src, ModuleKind::Lib) + .map_err(|errs| Error::from_parse_errors(errs, Path::new(""), &src))?; + + aiken_lang::format::pretty(&mut out, module, extra, &src); + + if !check { + print!("{}", out); + return Ok(()); + } + + if src != out { + return Err(Error::Format { + problem_files: vec![Unformatted { + source: PathBuf::from(""), + destination: PathBuf::from(""), + input: src, + output: out, + }], + }); + } + + Ok(()) +} + +fn process_files(check: bool, files: Vec) -> Result<(), Error> { + if check { + check_files(files) + } else { + format_files(files) + } +} + +fn check_files(files: Vec) -> Result<(), Error> { + let problem_files = unformatted_files(files)?; + + if problem_files.is_empty() { + Ok(()) + } else { + Err(Error::Format { problem_files }) + } +} + +fn format_files(files: Vec) -> Result<(), Error> { + for file in unformatted_files(files)? { + fs::write(file.destination, file.output)?; + } + + Ok(()) +} + +fn unformatted_files(files: Vec) -> Result, Error> { + let mut problem_files = Vec::with_capacity(files.len()); + let mut errors = Vec::new(); + + for file_path in files { + let path = PathBuf::from_str(&file_path).unwrap(); + + if path.is_dir() { + for path in aiken_files_excluding_gitignore(&path) { + if let Err(err) = format_file(&mut problem_files, path) { + errors.push(err); + }; + } + } else if let Err(err) = format_file(&mut problem_files, path) { + println!("{:?}", err); + errors.push(err); + } + } + + if errors.is_empty() { + Ok(problem_files) + } else { + Err(Error::List(errors)) + } +} + +fn format_file(problem_files: &mut Vec, path: PathBuf) -> Result<(), Error> { + let src = fs::read_to_string(&path).map_err(|error| Error::FileIo { + error, + path: path.clone(), + })?; + + let mut output = String::new(); + + let (module, extra) = parser::module(&src, ModuleKind::Lib) + .map_err(|errs| Error::from_parse_errors(errs, &path, &src))?; + + aiken_lang::format::pretty(&mut output, module, extra, &src); + + if src != output { + problem_files.push(Unformatted { + source: path.clone(), + destination: path, + input: src, + output, + }); + } + + Ok(()) +} + +pub fn read_stdin() -> Result { + let mut src = String::new(); + + std::io::stdin().read_to_string(&mut src)?; + + Ok(src) +} + +pub fn aiken_files_excluding_gitignore(dir: &Path) -> impl Iterator + '_ { + ignore::WalkBuilder::new(dir) + .follow_links(true) + .require_git(false) + .build() + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false)) + .map(ignore::DirEntry::into_path) + .filter(move |d| is_aiken_path(d, dir)) +} diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index c7da293f..4b0b4c4c 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -6,6 +6,7 @@ use std::{ pub mod config; pub mod error; +pub mod format; pub mod module; use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator}; @@ -106,8 +107,8 @@ impl Project { kind, } in self.sources.drain(0..) { - match aiken_lang::parser::script(&code) { - Ok(mut ast) => { + match aiken_lang::parser::module(&code, kind) { + Ok((mut ast, _)) => { // Store the name ast.name = name.clone(); diff --git a/examples/sample/src/sample.ak b/examples/sample/src/sample.ak index 2e8de51c..4a01b793 100644 --- a/examples/sample/src/sample.ak +++ b/examples/sample/src/sample.ak @@ -1,3 +1,5 @@ -pub type ScriptContext { - thing: String -} \ No newline at end of file + + + + +pub type ScriptContext { thing: String } diff --git a/examples/sample/src/sample/context.ak b/examples/sample/src/sample/context.ak index 5a07d7e2..0b3f2838 100644 --- a/examples/sample/src/sample/context.ak +++ b/examples/sample/src/sample/context.ak @@ -1,8 +1,7 @@ -pub type ScriptContext(purpose) { - tx_info: TxInfo, - script_purpose: purpose -} -pub type TxInfo { - idk: Int -} \ No newline at end of file + + + +pub type ScriptContext(purpose) { tx_info: TxInfo, script_purpose: purpose } + +pub type TxInfo { idk: Int } diff --git a/examples/sample/src/sample/mint.ak b/examples/sample/src/sample/mint.ak index 2d54a356..b687e34c 100644 --- a/examples/sample/src/sample/mint.ak +++ b/examples/sample/src/sample/mint.ak @@ -1,7 +1,10 @@ use sample/context -pub type Mint { - currency_symbol: ByteArray -} -pub type ScriptContext = context.ScriptContext(Mint) \ No newline at end of file + + + +pub type Mint { currency_symbol: ByteArray } + +pub type ScriptContext = + context.ScriptContext(Mint) diff --git a/examples/sample/src/sample/spend.ak b/examples/sample/src/sample/spend.ak index 1f11f897..984923ed 100644 --- a/examples/sample/src/sample/spend.ak +++ b/examples/sample/src/sample/spend.ak @@ -1,7 +1,10 @@ use sample/context -pub type Spend { - idk: Int -} -pub type ScriptContext = context.ScriptContext(Spend) \ No newline at end of file + + + +pub type Spend { idk: Int } + +pub type ScriptContext = + context.ScriptContext(Spend) diff --git a/examples/sample/src/scripts/swap.ak b/examples/sample/src/scripts/swap.ak index 6893941b..f0f15a3a 100644 --- a/examples/sample/src/scripts/swap.ak +++ b/examples/sample/src/scripts/swap.ak @@ -1,9 +1,11 @@ use sample/mint use sample/spend -pub type Datum { - something: String, -} + + + + +pub type Datum { something: String } pub type Redeemer { Buy @@ -11,8 +13,8 @@ pub type Redeemer { } pub fn spend(datum: Datum, rdmr: Redeemer, ctx: spend.ScriptContext) -> Bool { - when rdmr is { + when rdmr is { Buy -> True Sell -> datum.something == "Aiken" } -} \ No newline at end of file +}