mod block_comment;
mod comment_slots;
mod decl_emission;
mod doc_markdown;
mod import_layout;
mod pipeline_layout;
use block_comment::reindent_block_comment;
use comment_slots::CommentSlots;
use doc_markdown::*;
use import_layout::build_import_plan;
use pipeline_layout::*;
use crate::comment::Comment;
use crate::declaration::{CustomType, Declaration, InfixDef, TypeAlias, ValueConstructor};
use crate::exposing::{ExposedItem, Exposing};
use crate::expr::{
CaseBranch, Expr, Function, FunctionImplementation, LetDeclaration, RecordSetter, Signature,
};
use crate::file::ElmModule;
use crate::import::Import;
use crate::literal::Literal;
use crate::module_header::ModuleHeader;
use crate::node::Spanned;
use crate::operator::InfixDirection;
use crate::pattern::Pattern;
use crate::span::Span;
use crate::type_annotation::{RecordField, TypeAnnotation};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PrintStyle {
#[default]
Compact,
ElmFormat,
}
#[derive(Clone, Debug)]
pub struct PrintConfig {
pub indent_width: usize,
pub style: PrintStyle,
}
impl Default for PrintConfig {
fn default() -> Self {
Self {
indent_width: 4,
style: PrintStyle::default(),
}
}
}
pub struct Printer {
config: PrintConfig,
buf: String,
indent: usize,
indent_extra: u32,
indent_extra_stack: Vec<u32>,
doc_groups: Vec<Vec<String>>,
}
impl Printer {
pub fn new(config: PrintConfig) -> Self {
Self {
config,
buf: String::new(),
indent: 0,
indent_extra: 0,
indent_extra_stack: Vec::new(),
doc_groups: Vec::new(),
}
}
pub fn print_module(mut self, module: &ElmModule) -> String {
self.write_module(module);
self.buf
}
pub fn finish(self) -> String {
self.buf
}
fn is_pretty(&self) -> bool {
self.config.style == PrintStyle::ElmFormat
}
fn spans_cross_lines(a: Span, b: Span) -> bool {
a.start.line != 0 && b.end.line != 0 && b.end.line > a.start.line
}
fn spans_multi_lines<T>(items: &[Spanned<T>]) -> bool {
match (items.first(), items.last()) {
(Some(first), Some(last)) => Self::spans_cross_lines(first.span, last.span),
_ => false,
}
}
fn is_multiline(&self, expr: &Expr) -> bool {
match expr {
Expr::IfElse {
branches,
else_branch,
..
} => {
if self.is_pretty() {
return true;
}
if branches.len() == 1 {
let (c, b) = &branches[0];
if !self.is_multiline(&c.value)
&& !self.is_multiline(&b.value)
&& !self.is_multiline(&else_branch.value)
{
return false;
}
}
true
}
Expr::CaseOf { .. } | Expr::LetIn { .. } => true,
Expr::Lambda { body, .. } => self.is_multiline(&body.value),
Expr::Application(args) => args.iter().any(|a| self.is_multiline(&a.value)),
Expr::List(elems) => elems.iter().any(|e| self.is_multiline(&e.value)),
Expr::Tuple(elems) => elems.iter().any(|e| self.is_multiline(&e.value)),
Expr::Record(fields) => fields
.iter()
.any(|f| self.is_multiline(&f.value.value.value)),
Expr::RecordUpdate { updates, .. } => updates
.iter()
.any(|f| self.is_multiline(&f.value.value.value)),
Expr::OperatorApplication { left, right, .. } => {
self.is_multiline(&left.value) || self.is_multiline(&right.value)
}
Expr::Parenthesized(inner) => self.is_multiline(&inner.value),
Expr::Negation(inner) => self.is_multiline(&inner.value),
Expr::Literal(Literal::MultilineString(s)) if s.contains('\n') => true,
_ => false,
}
}
fn write(&mut self, s: &str) {
self.buf.push_str(s);
}
fn write_char(&mut self, c: char) {
self.buf.push(c);
}
fn newline(&mut self) {
self.buf.push('\n');
}
fn write_indent(&mut self) {
for _ in 0..self.indent * self.config.indent_width + self.indent_extra as usize {
self.buf.push(' ');
}
}
fn indent(&mut self) {
self.indent += 1;
self.indent_extra_stack.push(self.indent_extra);
self.indent_extra = 0;
}
fn dedent(&mut self) {
self.indent = self.indent.saturating_sub(1);
self.indent_extra = self.indent_extra_stack.pop().unwrap_or(0);
}
fn current_column(&self) -> usize {
match self.buf.rfind('\n') {
Some(pos) => self.buf.len() - pos - 1,
None => self.buf.len(),
}
}
fn newline_indent(&mut self) {
self.newline();
self.write_indent();
}
pub fn write_module(&mut self, module: &ElmModule) {
if self.is_pretty()
&& let Some(doc) = &module.module_documentation
{
self.doc_groups = parse_docs_groups(&doc.value);
}
let mut slots = CommentSlots::build(module, self.is_pretty());
if self.is_pretty() {
slots.hoist_import_trailing_comments(&module.imports);
}
let num_imports = slots.num_imports;
let total_anchors = slots.trailing_slot();
self.write_module_header(&module.header.value);
self.newline();
if let Some(doc) = &module.module_documentation {
self.newline();
self.write_doc_comment_text(&doc.value);
self.newline();
}
if !module.imports.is_empty() {
self.newline();
if self.is_pretty() {
for group in build_import_plan(&module.imports) {
let mut had_comments = false;
for &idx in &group.src_indices {
if !slots.slots[idx].is_empty() {
had_comments = true;
for c in &slots.slots[idx] {
self.write_comment(&c.value);
self.newline();
}
}
}
if had_comments {
self.newline();
}
if group.src_indices.len() == 1 {
self.write_import(&module.imports[group.src_indices[0]].value);
} else {
self.write_merged_imports(
&group
.src_indices
.iter()
.map(|&idx| &module.imports[idx].value)
.collect::<Vec<_>>(),
);
}
self.newline();
}
} else {
for (i, imp) in module.imports.iter().enumerate() {
if !slots.slots[i].is_empty() {
for c in &slots.slots[i] {
self.write_comment(&c.value);
self.newline();
}
}
self.write_import(&imp.value);
self.newline();
}
}
}
self.write_declarations_with_comments(module, &slots, num_imports);
self.write_trailing_orphan_comments(module, &slots, total_anchors);
self.newline();
}
fn write_comment(&mut self, comment: &Comment) {
match comment {
Comment::Line(text) => {
self.write("--");
self.write(text);
}
Comment::Block(text) => {
if self.is_pretty() && text.contains('\n') {
let brace_col = self.current_column();
self.write("{-");
let reindented = reindent_block_comment(text, brace_col);
let reindented = if self.is_pretty() && reindented.starts_with("- ") {
format!("-{}", &reindented[2..])
} else {
reindented
};
self.write(&reindented);
self.write("-}");
} else {
self.write("{-");
self.write(text);
self.write("-}");
}
}
Comment::Doc(text) => {
self.write_doc_comment_text(text);
}
}
}
fn write_doc_comment_text(&mut self, text: &str) {
if self.is_pretty() {
let normalized = normalize_doc_comment(text);
let normalized = collapse_blank_lines_in_doc(&normalized);
let normalized = normalize_doc_char_literals(&normalized);
let normalized = normalize_emphasis(&normalized);
let normalized = normalize_empty_link_refs(&normalized);
let normalized = normalize_markdown_lists(&normalized);
let normalized = normalize_fenced_code_blocks(&normalized);
let normalized = normalize_code_block_indent(&normalized);
let normalized = ensure_blank_before_code_block_with_trailing_comment(&normalized);
let normalized = ensure_blank_before_docs_after_prose(&normalized);
let normalized = normalize_docs_lines(&normalized);
let normalized = strip_paragraph_leading_whitespace(&normalized);
let normalized = collapse_prose_internal_spaces(&normalized);
let normalized = strip_trailing_whitespace_in_doc(&normalized);
self.write("{-|");
self.write(&normalized);
self.write("-}");
} else {
self.write("{-|");
self.write(text);
self.write("-}");
}
}
fn write_module_header(&mut self, header: &ModuleHeader) {
match header {
ModuleHeader::Normal { name, exposing } => {
self.write("module ");
self.write_module_name(&name.value);
self.write(" exposing ");
self.write_exposing(&exposing.value, true);
}
ModuleHeader::Port { name, exposing } => {
self.write("port module ");
self.write_module_name(&name.value);
self.write(" exposing ");
self.write_exposing(&exposing.value, true);
}
ModuleHeader::Effect {
name,
exposing,
command,
subscription,
} => {
self.write("effect module ");
self.write_module_name(&name.value);
self.write(" where { ");
let mut entries = Vec::new();
if let Some(cmd) = command {
entries.push(format!("command = {}", cmd.value));
}
if let Some(sub) = subscription {
entries.push(format!("subscription = {}", sub.value));
}
self.write(&entries.join(", "));
self.write(" } exposing ");
self.write_exposing(&exposing.value, true);
}
}
}
fn write_module_name(&mut self, parts: &[String]) {
self.write(&parts.join("."));
}
fn write_exposing(&mut self, exposing: &Exposing, is_module_header: bool) {
match exposing {
Exposing::All(_) => self.write("(..)"),
Exposing::Explicit(items) => {
if self.is_pretty() {
self.write_exposing_pretty(items, is_module_header);
} else {
self.write_char('(');
for (i, item) in items.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_exposed_item(&item.value);
}
self.write_char(')');
}
}
}
}
fn write_exposing_pretty(&mut self, items: &[Spanned<ExposedItem>], is_module_header: bool) {
let doc_groups = self.doc_groups.clone();
if is_module_header && !doc_groups.is_empty() {
let item_map: std::collections::HashMap<String, &ExposedItem> = items
.iter()
.map(|i| (exposed_item_name(&i.value), &i.value))
.collect();
let mut resolved_groups: Vec<Vec<&ExposedItem>> = Vec::new();
let mut emitted: std::collections::HashSet<String> = std::collections::HashSet::new();
for group in &doc_groups {
let group_items: Vec<&ExposedItem> = group
.iter()
.filter_map(|name| {
if emitted.contains(name.as_str()) {
return None;
}
let item = item_map.get(name.as_str()).copied()?;
if let ExposedItem::Infix(op) = item
&& op.len() >= 3
{
return None;
}
Some(item)
})
.collect();
if !group_items.is_empty() {
for item in &group_items {
emitted.insert(exposed_item_name(item));
}
resolved_groups.push(group_items);
}
}
let mut leftovers: Vec<&ExposedItem> = items
.iter()
.filter(|i| !emitted.contains(&exposed_item_name(&i.value)))
.map(|i| &i.value)
.collect();
leftovers.sort_by_key(|a| exposed_item_sort_key(a));
if !leftovers.is_empty() {
resolved_groups.push(leftovers);
}
if resolved_groups.len() <= 1 {
let all_items: Vec<&ExposedItem> = resolved_groups
.into_iter()
.flat_map(|g| g.into_iter())
.collect();
self.write_char('(');
for (j, item) in all_items.iter().enumerate() {
if j > 0 {
self.write(", ");
}
self.write_exposed_item(item);
}
self.write_char(')');
} else {
if self.buf.ends_with(' ') {
self.buf.pop();
}
self.indent();
let mut is_first = true;
for group_items in &resolved_groups {
self.newline_indent();
if is_first {
self.write("( ");
is_first = false;
} else {
self.write(", ");
}
for (j, item) in group_items.iter().enumerate() {
if j > 0 {
self.write(", ");
}
self.write_exposed_item(item);
}
}
self.newline_indent();
self.write_char(')');
self.dedent();
}
} else if !is_module_header {
self.write_char('(');
for (i, item) in items.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_exposed_item(&item.value);
}
self.write_char(')');
} else {
let mut sorted_items: Vec<&ExposedItem> = items.iter().map(|i| &i.value).collect();
sorted_items.sort_by_key(|a| exposed_item_sort_key(a));
let single_line: String = {
let parts: Vec<String> = sorted_items
.iter()
.map(|item| exposed_item_to_string(item))
.collect();
format!("({})", parts.join(", "))
};
let line_start = self.buf.rfind('\n').map_or(0, |p| p + 1);
let current_col = self.buf.len() - line_start;
if current_col + single_line.len() <= 200 {
self.write(&single_line);
} else {
if self.buf.ends_with(' ') {
self.buf.pop();
}
self.indent();
for (i, item) in sorted_items.iter().enumerate() {
self.newline_indent();
if i == 0 {
self.write("( ");
} else {
self.write(", ");
}
self.write_exposed_item(item);
}
self.newline_indent();
self.write_char(')');
self.dedent();
}
}
}
fn write_exposed_item(&mut self, item: &ExposedItem) {
match item {
ExposedItem::Function(name) => self.write(name),
ExposedItem::TypeOrAlias(name) => self.write(name),
ExposedItem::TypeExpose { name, open } => {
self.write(name);
if open.is_some() {
self.write("(..)");
}
}
ExposedItem::Infix(op) => {
self.write_char('(');
self.write(op);
self.write_char(')');
}
}
}
fn write_import(&mut self, import: &Import) {
self.write("import ");
self.write_module_name(&import.module_name.value);
if let Some(alias) = &import.alias {
let is_redundant = self.is_pretty() && alias.value == import.module_name.value;
if !is_redundant {
self.write(" as ");
self.write_module_name(&alias.value);
}
}
if let Some(exposing) = &import.exposing {
self.write(" exposing ");
if self.is_pretty() {
self.write_import_exposing_sorted(&exposing.value);
} else {
self.write_exposing(&exposing.value, false);
}
}
}
fn write_merged_imports(&mut self, imports: &[&Import]) {
assert!(!imports.is_empty());
let first = imports[0];
self.write("import ");
self.write_module_name(&first.module_name.value);
let merged_alias = imports.iter().find_map(|imp| {
imp.alias.as_ref().and_then(|a| {
if a.value == imp.module_name.value {
None } else {
Some(&a.value)
}
})
});
if let Some(alias) = merged_alias {
self.write(" as ");
self.write_module_name(alias);
}
let has_expose_all = imports
.iter()
.any(|imp| matches!(&imp.exposing, Some(e) if matches!(e.value, Exposing::All(_))));
if has_expose_all {
self.write(" exposing (..)");
} else {
let mut all_items: Vec<&ExposedItem> = Vec::new();
for imp in imports {
if let Some(exposing) = &imp.exposing
&& let Exposing::Explicit(items) = &exposing.value
{
for item in items {
all_items.push(&item.value);
}
}
}
if !all_items.is_empty() {
all_items.sort_by_key(|a| exposed_item_sort_key(a));
all_items.dedup_by(|a, b| exposed_item_sort_key(a) == exposed_item_sort_key(b));
self.write(" exposing (");
for (i, item) in all_items.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_exposed_item(item);
}
self.write_char(')');
}
}
}
fn write_import_exposing_sorted(&mut self, exposing: &Exposing) {
match exposing {
Exposing::All(_) => self.write("(..)"),
Exposing::Explicit(items) => {
let mut sorted: Vec<&ExposedItem> = items.iter().map(|i| &i.value).collect();
sorted.sort_by_key(|a| exposed_item_sort_key(a));
self.write_char('(');
for (i, item) in sorted.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_exposed_item(item);
}
self.write_char(')');
}
}
}
pub fn write_declaration(&mut self, decl: &Declaration) {
match decl {
Declaration::FunctionDeclaration(func) => self.write_function(func),
Declaration::AliasDeclaration(alias) => self.write_type_alias(alias),
Declaration::CustomTypeDeclaration(ct) => self.write_custom_type(ct),
Declaration::PortDeclaration(sig) => {
self.write("port ");
self.write_signature(sig);
}
Declaration::InfixDeclaration(infix) => self.write_infix_decl(infix),
Declaration::Destructuring { pattern, body } => {
self.write_pattern(&pattern.value);
self.write(" =");
self.indent();
self.newline_indent();
self.write_expr(&body.value);
self.dedent();
}
}
}
fn write_function(&mut self, func: &Function) {
if let Some(doc) = &func.documentation {
self.write_doc_comment_text(&doc.value);
self.newline();
}
if let Some(sig) = &func.signature {
self.write_signature(&sig.value);
self.newline();
}
self.write_function_impl(&func.declaration.value);
}
fn write_signature(&mut self, sig: &Signature) {
self.write(&sig.name.value);
self.write(" : ");
self.write_type(&sig.type_annotation.value);
}
fn write_function_impl(&mut self, imp: &FunctionImplementation) {
self.write(&imp.name.value);
for arg in &imp.args {
self.write_char(' ');
self.write_pattern_atomic(&arg.value);
}
self.write(" =");
self.indent();
self.newline_indent();
let body = if self.is_pretty() {
unwrap_parens(&imp.body.value)
} else {
&imp.body.value
};
self.write_expr(body);
self.dedent();
}
fn write_type_alias(&mut self, alias: &TypeAlias) {
if let Some(doc) = &alias.documentation {
self.write_doc_comment_text(&doc.value);
self.newline();
}
self.write("type alias ");
self.write(&alias.name.value);
for g in &alias.generics {
self.write_char(' ');
self.write(&g.value);
}
self.write(" =");
self.indent();
self.newline_indent();
if self.is_pretty() {
self.write_type_pretty_toplevel(&alias.type_annotation.value);
} else {
self.write_type(&alias.type_annotation.value);
}
self.dedent();
}
fn write_custom_type(&mut self, ct: &CustomType) {
if let Some(doc) = &ct.documentation {
self.write_doc_comment_text(&doc.value);
self.newline();
}
self.write("type ");
self.write(&ct.name.value);
for g in &ct.generics {
self.write_char(' ');
self.write(&g.value);
}
self.indent();
for (i, ctor) in ct.constructors.iter().enumerate() {
self.newline_indent();
if i == 0 {
self.write("= ");
} else {
self.write("| ");
}
self.write_value_constructor(&ctor.value);
}
self.dedent();
}
fn write_value_constructor(&mut self, ctor: &ValueConstructor) {
self.write(&ctor.name.value);
for arg in &ctor.args {
self.write_char(' ');
self.write_type_atomic(&arg.value);
}
}
fn write_infix_decl(&mut self, infix: &InfixDef) {
self.write("infix ");
if self.is_pretty() {
match infix.direction.value {
InfixDirection::Left => self.write("left "),
InfixDirection::Right => self.write("right "),
InfixDirection::Non => self.write("non "),
}
} else {
match infix.direction.value {
InfixDirection::Left => self.write("left"),
InfixDirection::Right => self.write("right"),
InfixDirection::Non => self.write("non"),
}
self.write_char(' ');
}
self.write(&infix.precedence.value.to_string());
self.write(" (");
self.write(&infix.operator.value);
self.write(") = ");
self.write(&infix.function.value);
}
pub fn write_type(&mut self, ty: &TypeAnnotation) {
match ty {
TypeAnnotation::FunctionType { from, to } => {
self.write_type_non_arrow(&from.value);
self.write(" -> ");
self.write_type(&to.value);
}
_ => self.write_type_non_arrow(ty),
}
}
fn write_type_pretty_toplevel(&mut self, ty: &TypeAnnotation) {
match ty {
TypeAnnotation::Record(fields) if fields.len() >= 2 => {
let spans_multi_lines = if fields.len() >= 2 {
let first_line = fields.first().map(|f| f.span.start.line).unwrap_or(0);
let last_line = fields.last().map(|f| f.span.end.line).unwrap_or(0);
last_line > first_line
} else {
false
};
if spans_multi_lines {
self.write_record_type_fields_multiline(fields, None);
} else {
self.write_type(ty);
}
}
_ => self.write_type(ty),
}
}
fn write_record_type_fields_multiline(
&mut self,
fields: &[Spanned<RecordField>],
base: Option<&str>,
) {
if let Some(base_name) = base {
self.write("{ ");
self.write(base_name);
self.indent();
self.newline_indent();
self.write("| ");
for (i, field) in fields.iter().enumerate() {
if i > 0 {
self.newline_indent();
self.write(", ");
}
self.write(&field.value.name.value);
self.write(" : ");
self.write_type(&field.value.type_annotation.value);
}
self.dedent();
} else {
self.write("{ ");
self.write(&fields[0].value.name.value);
self.write(" : ");
self.write_type(&fields[0].value.type_annotation.value);
for field in &fields[1..] {
self.newline_indent();
self.write(", ");
self.write(&field.value.name.value);
self.write(" : ");
self.write_type(&field.value.type_annotation.value);
}
}
self.newline_indent();
self.write("}");
}
fn write_type_non_arrow(&mut self, ty: &TypeAnnotation) {
match ty {
TypeAnnotation::FunctionType { .. } => {
self.write_char('(');
self.write_type(ty);
self.write_char(')');
}
TypeAnnotation::Typed {
module_name,
name,
args,
} => {
if !module_name.is_empty() {
self.write(&module_name.join("."));
self.write_char('.');
}
self.write(&name.value);
for arg in args {
self.write_char(' ');
self.write_type_atomic(&arg.value);
}
}
_ => self.write_type_atomic(ty),
}
}
fn write_type_atomic(&mut self, ty: &TypeAnnotation) {
match ty {
TypeAnnotation::GenericType(name) => self.write(name),
TypeAnnotation::Unit => self.write("()"),
TypeAnnotation::Typed {
module_name,
name,
args,
} if args.is_empty() => {
if !module_name.is_empty() {
self.write(&module_name.join("."));
self.write_char('.');
}
self.write(&name.value);
}
TypeAnnotation::Tupled(elems) => {
self.write("( ");
for (i, elem) in elems.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_type(&elem.value);
}
self.write(" )");
}
TypeAnnotation::Record(fields) => {
self.write_record_type_fields(fields, None);
}
TypeAnnotation::GenericRecord { base, fields } => {
self.write_record_type_fields(fields, Some(&base.value));
}
_ => {
self.write_char('(');
self.write_type(ty);
self.write_char(')');
}
}
}
fn write_record_type_fields(&mut self, fields: &[Spanned<RecordField>], base: Option<&str>) {
if fields.is_empty() && base.is_none() {
self.write("{}");
return;
}
self.write("{ ");
if let Some(base_name) = base {
self.write(base_name);
self.write(" | ");
}
for (i, field) in fields.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write(&field.value.name.value);
self.write(" : ");
self.write_type(&field.value.type_annotation.value);
}
self.write(" }");
}
pub fn write_pattern(&mut self, pat: &Pattern) {
match pat {
Pattern::As { pattern, name } => {
let needs_parens = self.is_pretty()
&& matches!(
&pattern.value,
Pattern::Constructor { args, .. } if !args.is_empty()
);
if needs_parens {
self.write_char('(');
}
self.write_pattern_cons(&pattern.value);
if needs_parens {
self.write_char(')');
}
self.write(" as ");
self.write(&name.value);
}
_ => self.write_pattern_cons(pat),
}
}
fn write_pattern_cons(&mut self, pat: &Pattern) {
match pat {
Pattern::Cons { head, tail } => {
let needs_parens = self.is_pretty()
&& matches!(
&head.value,
Pattern::Constructor { args, .. } if !args.is_empty()
);
if needs_parens {
self.write_char('(');
}
self.write_pattern_app(&head.value);
if needs_parens {
self.write_char(')');
}
self.write(" :: ");
self.write_pattern_cons(&tail.value);
}
_ => self.write_pattern_app(pat),
}
}
fn write_pattern_app(&mut self, pat: &Pattern) {
match pat {
Pattern::Constructor {
module_name,
name,
args,
} if !args.is_empty() => {
if !module_name.is_empty() {
self.write(&module_name.join("."));
self.write_char('.');
}
self.write(name);
for arg in args {
self.write_char(' ');
self.write_pattern_atomic(&arg.value);
}
}
_ => self.write_pattern_atomic(pat),
}
}
fn write_pattern_atomic(&mut self, pat: &Pattern) {
match pat {
Pattern::Anything => self.write_char('_'),
Pattern::Var(name) => self.write(name),
Pattern::Literal(lit) => self.write_literal(lit),
Pattern::Unit => self.write("()"),
Pattern::Hex(n) => {
self.write("0x");
self.write(&format!("{n:X}"));
}
Pattern::Tuple(elems) => {
self.write("( ");
for (i, elem) in elems.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_pattern(&elem.value);
}
self.write(" )");
}
Pattern::Constructor {
module_name,
name,
args,
} if args.is_empty() => {
if !module_name.is_empty() {
self.write(&module_name.join("."));
self.write_char('.');
}
self.write(name);
}
Pattern::Record(fields) => {
self.write("{ ");
for (i, field) in fields.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write(&field.value);
}
self.write(" }");
}
Pattern::List(elems) => {
if elems.is_empty() {
self.write("[]");
} else {
self.write("[ ");
for (i, elem) in elems.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_pattern(&elem.value);
}
self.write(" ]");
}
}
Pattern::Parenthesized(inner) => {
self.write_char('(');
self.write_pattern(&inner.value);
self.write_char(')');
}
Pattern::Constructor { .. } | Pattern::Cons { .. } | Pattern::As { .. } => {
self.write_char('(');
self.write_pattern(pat);
self.write_char(')');
}
}
}
pub fn write_expr(&mut self, expr: &Expr) {
self.write_expr_inner(expr);
}
fn write_leading_comments(&mut self, comments: &[Spanned<Comment>]) {
for c in comments {
self.write_comment(&c.value);
self.newline();
self.write_indent();
}
}
fn write_expr_inner(&mut self, expr: &Expr) {
let expr = if self.is_pretty() {
unwrap_parens_non_block(expr)
} else {
expr
};
match expr {
Expr::OperatorApplication {
operator,
left,
right,
..
} => {
let right_expr = if self.is_pretty() && operator == "<|" {
unwrap_parens(&right.value)
} else {
&right.value
};
if self.is_pretty()
&& matches!(operator.as_str(), "|>" | "|." | "|=")
&& let Some((head, rest)) = flatten_mixed_pipe_chain(expr)
{
let any_ml = self.is_multiline(head)
|| rest.iter().any(|(_, op)| self.is_multiline(op))
|| Self::spans_cross_lines(left.span, right.span);
if any_ml {
self.write_expr_operand(head, operator, true);
self.indent();
for (op, operand) in &rest {
self.newline_indent();
self.write(op);
self.write_char(' ');
self.write_expr_operand(operand, op, false);
}
self.dedent();
return;
}
}
if self.is_pretty()
&& matches!(operator.as_str(), "::" | "++")
&& let Some((head, rest)) = flatten_mixed_cons_append_chain(expr)
{
let any_ml = self.is_multiline(head)
|| rest.iter().any(|(_, e)| self.is_multiline(e))
|| Self::spans_cross_lines(left.span, right.span);
if any_ml {
self.write_expr_operand(head, operator, true);
self.indent();
for (op, operand) in &rest {
self.newline_indent();
self.write(op);
self.write_char(' ');
self.write_expr_operand(operand, op, false);
}
self.dedent();
return;
}
}
if self.is_pretty()
&& matches!(operator.as_str(), ">>" | "<<")
&& let Some(chain) = flatten_right_assoc_chain(expr, operator)
{
let any_ml = chain.iter().any(|op| self.is_multiline(op))
|| Self::spans_cross_lines(left.span, right.span);
if any_ml {
self.write_expr_operand(chain[0], operator, true);
self.indent();
for operand in &chain[1..] {
self.newline_indent();
self.write(operator);
self.write_char(' ');
self.write_expr_operand(operand, operator, false);
}
self.dedent();
return;
}
}
if self.is_pretty() && matches!(operator.as_str(), "+" | "-") {
let op_owned = operator.clone();
if let Some((head, rest)) =
flatten_left_assoc_pred(expr, &|o: &str| o == op_owned)
{
let any_ml = self.is_multiline(head)
|| rest.iter().any(|(_, op)| self.is_multiline(op))
|| Self::spans_cross_lines(left.span, right.span);
if any_ml {
self.write_expr_operand(head, operator, true);
self.indent();
for (op, operand) in &rest {
self.newline_indent();
self.write(op);
self.write_char(' ');
self.write_expr_operand(operand, op, false);
}
self.dedent();
return;
}
}
}
let use_vertical = if self.is_pretty() {
self.is_multiline(&left.value)
|| self.is_multiline(right_expr)
|| Self::spans_cross_lines(left.span, right.span)
} else {
self.is_multiline(right_expr)
};
self.write_leading_comments(&left.comments);
self.write_expr_operand(&left.value, operator, true);
if use_vertical && operator == "<|" {
self.write(" <|");
self.indent();
self.newline_indent();
self.write_leading_comments(&right.comments);
self.write_expr_inner(right_expr);
self.dedent();
} else if use_vertical {
self.indent();
self.newline_indent();
self.write(operator);
self.write_char(' ');
self.write_leading_comments(&right.comments);
self.write_expr_operand(right_expr, operator, false);
self.dedent();
} else {
self.write_char(' ');
self.write(operator);
self.write_char(' ');
self.write_leading_comments(&right.comments);
if operator == "<|" {
self.write_expr_inner(right_expr);
} else {
self.write_expr_operand(right_expr, operator, false);
}
}
}
Expr::IfElse {
branches,
else_branch,
} => {
self.write_if_expr(branches, &else_branch.value);
}
Expr::CaseOf {
expr: subject,
branches,
} => {
self.write_case_expr(&subject.value, branches);
}
Expr::LetIn { declarations, body } => {
self.write_let_expr(declarations, &body.value);
}
Expr::Lambda { args, body } => {
self.write_lambda(args, &body.value);
}
Expr::BinOps {
operands_and_operators,
final_operand,
} => {
if self.is_pretty() {
let any_ml = operands_and_operators
.iter()
.any(|(op, _)| self.is_multiline(&op.value))
|| self.is_multiline(&final_operand.value);
if any_ml {
self.write_expr_app(&operands_and_operators[0].0.value);
self.indent();
for (i, (_operand, op)) in operands_and_operators.iter().enumerate() {
self.newline_indent();
self.write(&op.value);
self.write_char(' ');
if i + 1 < operands_and_operators.len() {
self.write_expr_app(&operands_and_operators[i + 1].0.value);
} else {
self.write_expr_app(&final_operand.value);
}
}
self.dedent();
} else {
for (operand, op) in operands_and_operators {
self.write_expr_app(&operand.value);
self.write_char(' ');
self.write(&op.value);
self.write_char(' ');
}
self.write_expr_app(&final_operand.value);
}
} else {
for (operand, op) in operands_and_operators {
self.write_expr_app(&operand.value);
self.write_char(' ');
self.write(&op.value);
self.write_char(' ');
}
self.write_expr_app(&final_operand.value);
}
}
_ => self.write_expr_app(expr),
}
}
fn write_expr_operand(&mut self, expr: &Expr, parent_op: &str, is_left: bool) {
let expr = if self.is_pretty() {
unwrap_parens_non_block(expr)
} else {
expr
};
match expr {
Expr::OperatorApplication { operator, .. } => {
let parent_prec = op_precedence(parent_op);
let child_prec = op_precedence(operator);
let needs_parens = child_prec < parent_prec
|| (child_prec == parent_prec
&& ((is_left && is_right_assoc(parent_op))
|| (!is_left && !is_right_assoc(parent_op))));
if needs_parens {
self.write_char('(');
self.write_expr_inner(expr);
self.write_char(')');
} else {
self.write_expr_inner(expr);
}
}
_ => self.write_expr_app(expr),
}
}
fn write_expr_app(&mut self, expr: &Expr) {
match expr {
Expr::Application(args) => {
let any_arg_ml =
args.len() > 1 && args.iter().skip(1).any(|a| self.is_multiline(&a.value));
if any_arg_ml {
self.write_expr_atomic(&args[0].value);
self.indent();
for arg in &args[1..] {
self.newline_indent();
self.write_expr_atomic(&arg.value);
}
self.dedent();
} else {
for (i, arg) in args.iter().enumerate() {
if i > 0 {
self.write_char(' ');
}
self.write_expr_atomic(&arg.value);
}
}
}
Expr::Negation(inner) => {
self.write_char('-');
self.write_expr_atomic(&inner.value);
}
_ => self.write_expr_atomic(expr),
}
}
fn write_expr_atomic(&mut self, expr: &Expr) {
match expr {
Expr::Unit => self.write("()"),
Expr::Literal(lit) => self.write_literal(lit),
Expr::FunctionOrValue { module_name, name } => {
if !module_name.is_empty() {
self.write(&module_name.join("."));
self.write_char('.');
}
self.write(name);
}
Expr::PrefixOperator(op) => {
self.write_char('(');
self.write(op);
self.write_char(')');
}
Expr::Parenthesized(inner) => {
if self.is_pretty() && is_naturally_atomic(&inner.value) {
self.write_expr_atomic(&inner.value);
} else if self.is_pretty() && matches!(inner.value, Expr::Negation(_)) {
self.write_expr_app(&inner.value);
} else if self.is_pretty() && self.is_multiline(&inner.value) {
let is_block = matches!(
inner.value,
Expr::IfElse { .. }
| Expr::CaseOf { .. }
| Expr::LetIn { .. }
| Expr::Lambda { .. }
);
if is_block {
let saved_indent = self.indent;
let saved_extra = self.indent_extra;
let saved_stack = self.indent_extra_stack.clone();
self.write_char('(');
let col = self.current_column();
let w = self.config.indent_width;
self.indent = col / w;
self.indent_extra = (col % w) as u32;
self.write_expr(&inner.value);
self.indent = saved_indent;
self.indent_extra = saved_extra;
self.indent_extra_stack = saved_stack;
self.newline();
for _ in 0..(col - 1) {
self.buf.push(' ');
}
self.write_char(')');
} else {
let saved_indent = self.indent;
let saved_extra = self.indent_extra;
let saved_stack = self.indent_extra_stack.clone();
self.write_char('(');
let col = self.current_column();
let w = self.config.indent_width;
self.indent = col / w;
self.indent_extra = (col % w) as u32;
self.write_expr(&inner.value);
self.indent = saved_indent;
self.indent_extra = saved_extra;
self.indent_extra_stack = saved_stack;
self.newline();
for _ in 0..(col - 1) {
self.buf.push(' ');
}
self.write_char(')');
}
} else {
self.write_char('(');
self.write_expr(&inner.value);
self.write_char(')');
}
}
Expr::Tuple(elems) => {
self.write_comma_sep("( ", " )", elems);
}
Expr::List(elems) => {
if elems.is_empty() {
self.write("[]");
} else {
self.write_comma_sep("[ ", " ]", elems);
}
}
Expr::Record(fields) => {
if fields.is_empty() {
self.write("{}");
} else {
let any_ml = fields
.iter()
.any(|f| self.is_multiline(&f.value.value.value))
|| (self.is_pretty() && Self::spans_multi_lines(fields));
if any_ml {
self.write("{ ");
self.write_record_setter(&fields[0].value);
for field in &fields[1..] {
self.newline_indent();
self.write(", ");
self.write_record_setter(&field.value);
}
self.newline_indent();
self.write("}");
} else {
self.write("{ ");
for (i, field) in fields.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_record_setter(&field.value);
}
self.write(" }");
}
}
}
Expr::RecordUpdate { base, updates } => {
let any_ml = updates
.iter()
.any(|f| self.is_multiline(&f.value.value.value))
|| (self.is_pretty() && Self::spans_multi_lines(updates));
if any_ml {
self.write("{ ");
self.write(&base.value);
self.indent();
for (i, field) in updates.iter().enumerate() {
self.newline_indent();
if i == 0 {
self.write("| ");
} else {
self.write(", ");
}
self.write_record_setter(&field.value);
}
self.dedent();
self.newline_indent();
self.write("}");
} else {
self.write("{ ");
self.write(&base.value);
self.write(" | ");
for (i, field) in updates.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_record_setter(&field.value);
}
self.write(" }");
}
}
Expr::RecordAccess { record, field } => {
self.write_expr_atomic(&record.value);
self.write_char('.');
self.write(&field.value);
}
Expr::RecordAccessFunction(name) => {
self.write_char('.');
self.write(name);
}
Expr::GLSLExpression(src) => {
self.write("[glsl|");
self.write(src);
self.write("|]");
}
Expr::OperatorApplication { .. }
| Expr::Application(_)
| Expr::Negation(_)
| Expr::BinOps { .. } => {
self.write_char('(');
self.write_expr_inner(expr);
self.write_char(')');
}
Expr::IfElse { .. }
| Expr::CaseOf { .. }
| Expr::LetIn { .. }
| Expr::Lambda { .. } => {
if self.is_pretty() {
let saved_indent = self.indent;
let saved_extra = self.indent_extra;
let saved_stack = self.indent_extra_stack.clone();
self.write_char('(');
let col = self.current_column();
let w = self.config.indent_width;
self.indent = col / w;
self.indent_extra = (col % w) as u32;
self.write_expr_inner(expr);
self.indent = saved_indent;
self.indent_extra = saved_extra;
self.indent_extra_stack = saved_stack;
self.newline();
for _ in 0..(col - 1) {
self.buf.push(' ');
}
self.write_char(')');
} else {
self.write_char('(');
self.write_expr_inner(expr);
self.write_char(')');
}
}
}
}
fn write_comma_sep(&mut self, open: &str, close: &str, elems: &[Spanned<Expr>]) {
let any_multiline = elems.iter().any(|e| self.is_multiline(&e.value))
|| (self.is_pretty() && Self::spans_multi_lines(elems));
if any_multiline && self.is_pretty() {
let open_col = self.current_column();
let standard_indent =
self.indent * self.config.indent_width + self.indent_extra as usize;
let bump_indent = open_col > standard_indent;
let saved_extra = self.indent_extra;
self.write(open);
if bump_indent {
self.indent();
}
self.indent_extra = saved_extra + 2;
self.write_expr(&elems[0].value);
self.indent_extra = saved_extra;
if bump_indent {
self.dedent();
}
for elem in &elems[1..] {
self.newline();
for _ in 0..open_col {
self.buf.push(' ');
}
self.write(", ");
if bump_indent {
self.indent();
}
self.indent_extra = saved_extra + 2;
self.write_expr(&elem.value);
self.indent_extra = saved_extra;
if bump_indent {
self.dedent();
}
}
self.newline();
for _ in 0..open_col {
self.buf.push(' ');
}
self.write(close.trim_start());
} else if any_multiline {
self.write(open.trim_end());
self.indent();
for (i, elem) in elems.iter().enumerate() {
self.newline_indent();
if i == 0 {
self.write(" ");
} else {
self.write(", ");
}
self.write_expr(&elem.value);
}
self.newline_indent();
self.write(close.trim_start());
self.dedent();
} else {
self.write(open);
for (i, elem) in elems.iter().enumerate() {
if i > 0 {
self.write(", ");
}
self.write_expr(&elem.value);
}
self.write(close);
}
}
fn write_record_setter(&mut self, setter: &RecordSetter) {
self.write(&setter.field.value);
if self.is_multiline(&setter.value.value) {
self.write(" =");
self.indent();
self.newline_indent();
self.write_expr(&setter.value.value);
self.dedent();
} else {
self.write(" = ");
self.write_expr(&setter.value.value);
}
}
fn write_if_expr(&mut self, branches: &[(Spanned<Expr>, Spanned<Expr>)], else_branch: &Expr) {
let all_simple = !self.is_pretty()
&& branches.len() == 1
&& branches
.iter()
.all(|(c, b)| !self.is_multiline(&c.value) && !self.is_multiline(&b.value))
&& !self.is_multiline(else_branch);
if all_simple {
let (cond, body) = &branches[0];
self.write("if ");
self.write_expr(&cond.value);
self.write(" then ");
self.write_expr(&body.value);
self.write(" else ");
self.write_expr(else_branch);
} else if self.is_pretty() {
let if_col = self.current_column();
let saved_indent = self.indent;
let saved_extra = self.indent_extra;
let saved_stack = self.indent_extra_stack.clone();
let w = self.config.indent_width;
self.indent = if_col / w;
self.indent_extra = (if_col % w) as u32;
for (i, (cond, body)) in branches.iter().enumerate() {
if i == 0 {
self.write("if ");
} else {
self.write("else if ");
}
self.write_expr(&cond.value);
self.write(" then");
self.indent();
self.newline_indent();
self.write_expr(&body.value);
self.dedent();
self.newline();
self.newline_indent();
}
if let Expr::IfElse {
branches: nested_branches,
else_branch: nested_else,
} = else_branch
{
for (cond, body) in nested_branches {
self.write("else if ");
self.write_expr(&cond.value);
self.write(" then");
self.indent();
self.newline_indent();
self.write_expr(&body.value);
self.dedent();
self.newline();
self.newline_indent();
}
self.write_if_else_tail(&nested_else.value);
} else {
self.write("else");
self.indent();
self.newline_indent();
self.write_expr(else_branch);
self.dedent();
}
self.indent = saved_indent;
self.indent_extra = saved_extra;
self.indent_extra_stack = saved_stack;
} else {
for (i, (cond, body)) in branches.iter().enumerate() {
if i == 0 {
self.write("if ");
} else {
self.write("else if ");
}
self.write_expr(&cond.value);
self.write(" then");
self.indent();
self.newline_indent();
self.write_expr(&body.value);
self.dedent();
self.newline();
self.newline_indent();
}
self.write("else");
self.indent();
self.newline_indent();
self.write_expr(else_branch);
self.dedent();
}
}
fn write_if_else_tail(&mut self, else_branch: &Expr) {
if let Expr::IfElse {
branches: nested_branches,
else_branch: nested_else,
} = else_branch
{
for (cond, body) in nested_branches {
self.write("else if ");
self.write_expr(&cond.value);
self.write(" then");
self.indent();
self.newline_indent();
self.write_expr(&body.value);
self.dedent();
self.newline();
self.newline_indent();
}
self.write_if_else_tail(&nested_else.value);
} else {
self.write("else");
self.indent();
self.newline_indent();
self.write_expr(else_branch);
self.dedent();
}
}
fn write_case_expr(&mut self, subject: &Expr, branches: &[CaseBranch]) {
if self.is_pretty() {
let case_col = self.current_column();
let saved_indent = self.indent;
let saved_extra = self.indent_extra;
let saved_stack = self.indent_extra_stack.clone();
let w = self.config.indent_width;
self.indent = case_col / w;
self.indent_extra = (case_col % w) as u32;
let hanging = matches!(
subject,
Expr::IfElse { .. } | Expr::LetIn { .. } | Expr::CaseOf { .. }
);
if hanging {
self.write("case");
self.indent();
self.newline_indent();
self.write_expr(subject);
self.dedent();
self.newline_indent();
self.write("of");
} else {
self.write("case ");
self.write_expr(subject);
self.write(" of");
}
self.indent();
for (i, branch) in branches.iter().enumerate() {
if i > 0 {
self.newline();
}
self.newline_indent();
self.write_leading_comments(&branch.pattern.comments);
self.write_pattern(&branch.pattern.value);
self.write(" ->");
self.indent();
self.newline_indent();
self.write_leading_comments(&branch.body.comments);
self.write_expr(&branch.body.value);
self.dedent();
}
self.dedent();
self.indent = saved_indent;
self.indent_extra = saved_extra;
self.indent_extra_stack = saved_stack;
return;
}
self.write("case ");
self.write_expr(subject);
self.write(" of");
self.indent();
for (i, branch) in branches.iter().enumerate() {
if self.is_pretty() && i > 0 {
self.newline();
}
self.newline_indent();
self.write_leading_comments(&branch.pattern.comments);
self.write_pattern(&branch.pattern.value);
self.write(" ->");
self.indent();
self.newline_indent();
self.write_leading_comments(&branch.body.comments);
self.write_expr(&branch.body.value);
self.dedent();
}
self.dedent();
}
fn write_let_expr(&mut self, declarations: &[Spanned<LetDeclaration>], body: &Expr) {
if self.is_pretty() {
let let_col = self.current_column();
let saved_indent = self.indent;
let saved_extra = self.indent_extra;
let saved_stack = self.indent_extra_stack.clone();
let w = self.config.indent_width;
self.indent = let_col / w;
self.indent_extra = (let_col % w) as u32;
self.write("let");
self.indent();
for (i, decl) in declarations.iter().enumerate() {
if i > 0 {
self.newline();
}
self.newline_indent();
self.write_leading_comments(&decl.comments);
self.write_let_declaration(&decl.value);
}
self.dedent();
self.newline_indent();
self.write("in");
self.newline_indent();
self.write_expr(body);
self.indent = saved_indent;
self.indent_extra = saved_extra;
self.indent_extra_stack = saved_stack;
return;
}
self.write("let");
self.indent();
for decl in declarations {
self.newline_indent();
self.write_leading_comments(&decl.comments);
self.write_let_declaration(&decl.value);
}
self.dedent();
self.newline_indent();
self.write("in");
self.newline_indent();
self.write_expr(body);
}
fn write_let_declaration(&mut self, decl: &LetDeclaration) {
match decl {
LetDeclaration::Function(func) => {
if let Some(sig) = &func.signature {
self.write_signature(&sig.value);
self.newline_indent();
}
self.write_function_impl(&func.declaration.value);
}
LetDeclaration::Destructuring { pattern, body } => {
self.write_pattern(&pattern.value);
self.write(" =");
self.indent();
self.newline_indent();
self.write_expr(&body.value);
self.dedent();
}
}
}
fn write_lambda(&mut self, args: &[Spanned<Pattern>], body: &Expr) {
self.write("\\");
for (i, arg) in args.iter().enumerate() {
if i > 0 {
self.write_char(' ');
}
self.write_pattern_atomic(&arg.value);
}
if self.is_multiline(body) {
self.write(" ->");
self.indent();
self.newline_indent();
self.write_expr(body);
self.dedent();
} else {
self.write(" -> ");
self.write_expr(body);
}
}
fn write_literal(&mut self, lit: &Literal) {
match lit {
Literal::Char(c) => {
self.write_char('\'');
match c {
'"' => self.write_char('"'),
_ => self.write_escaped_char(*c),
}
self.write_char('\'');
}
Literal::String(s) => {
self.write_char('"');
self.write_escaped_string(s);
self.write_char('"');
}
Literal::MultilineString(s) => {
self.write("\"\"\"");
self.write(s);
self.write("\"\"\"");
}
Literal::Int(n) => self.write(&n.to_string()),
Literal::Hex(n) => {
if self.is_pretty() {
let abs = n.unsigned_abs();
let prefix = if *n < 0 { "-0x" } else { "0x" };
if abs <= 0xFF {
self.write(&format!("{prefix}{abs:02X}"));
} else if abs <= 0xFFFF {
self.write(&format!("{prefix}{abs:04X}"));
} else if abs <= 0xFFFF_FFFF {
self.write(&format!("{prefix}{abs:08X}"));
} else {
self.write(&format!("{prefix}{abs:016X}"));
}
} else {
self.write("0x");
self.write(&format!("{n:02X}"));
}
}
Literal::Float(f) => {
let s = f.to_string();
if s.contains('.') {
self.write(&s);
} else if let Some(e_pos) = s.find(['e', 'E']) {
self.write(&s[..e_pos]);
self.write(".0");
self.write(&s[e_pos..]);
} else {
self.write(&s);
self.write(".0");
}
}
}
}
fn write_escaped_char(&mut self, c: char) {
match c {
'\n' => self.write("\\n"),
'\t' => self.write("\\t"),
'\\' => self.write("\\\\"),
'\'' => self.write("\\'"),
'"' => self.write("\\\""),
c if should_unicode_escape(c) => {
self.write(&format!("\\u{{{:04X}}}", c as u32));
}
c => self.write_char(c),
}
}
fn write_escaped_string(&mut self, s: &str) {
for c in s.chars() {
match c {
'\'' => self.write_char('\''),
_ => self.write_escaped_char(c),
}
}
}
}
pub(super) fn should_unicode_escape(c: char) -> bool {
if c.is_control() {
return true;
}
let cp = c as u32;
matches!(
cp,
0x00A0 | 0x1680 | 0x2000..=0x200F | 0x2028..=0x202F | 0x205F..=0x206F | 0x2E5E..=0x2E7F | 0x3000 | 0xFEFF )
}
fn op_precedence(op: &str) -> u8 {
match op {
"<|" | "|>" => 0,
"||" => 2,
"&&" => 3,
"==" | "/=" | "<" | ">" | "<=" | ">=" => 4,
"::" | "++" => 5,
"+" | "-" => 6,
"*" | "/" | "//" => 7,
"^" => 8,
"<<" | ">>" => 9,
_ => 9,
}
}
fn is_right_assoc(op: &str) -> bool {
matches!(op, "<|" | "||" | "&&" | "::" | "++" | "^" | ">>")
}
fn exposed_item_name(item: &ExposedItem) -> String {
match item {
ExposedItem::Function(name) | ExposedItem::TypeOrAlias(name) => name.clone(),
ExposedItem::TypeExpose { name, .. } => name.clone(),
ExposedItem::Infix(op) => format!("({})", op),
}
}
fn exposed_item_sort_key(item: &ExposedItem) -> String {
match item {
ExposedItem::Function(name) => name.clone(),
ExposedItem::TypeOrAlias(name) => name.clone(),
ExposedItem::TypeExpose { name, .. } => name.clone(),
ExposedItem::Infix(op) => format!("({})", op),
}
}
fn exposed_item_to_string(item: &ExposedItem) -> String {
match item {
ExposedItem::Function(name) => name.clone(),
ExposedItem::TypeOrAlias(name) => name.clone(),
ExposedItem::TypeExpose { name, open } => {
if open.is_some() {
format!("{name}(..)")
} else {
name.clone()
}
}
ExposedItem::Infix(op) => format!("({op})"),
}
}
fn is_naturally_atomic(expr: &Expr) -> bool {
matches!(
expr,
Expr::Unit
| Expr::Literal(_)
| Expr::FunctionOrValue { .. }
| Expr::PrefixOperator(_)
| Expr::Parenthesized(_)
| Expr::Tuple(_)
| Expr::List(_)
| Expr::Record(_)
| Expr::RecordUpdate { .. }
| Expr::RecordAccess { .. }
| Expr::RecordAccessFunction(_)
| Expr::GLSLExpression(_)
)
}
fn unwrap_parens(expr: &Expr) -> &Expr {
match expr {
Expr::Parenthesized(inner) => &inner.value,
other => other,
}
}
fn unwrap_parens_non_block(expr: &Expr) -> &Expr {
match expr {
Expr::Parenthesized(inner)
if !matches!(
inner.value,
Expr::OperatorApplication { .. }
| Expr::BinOps { .. }
| Expr::IfElse { .. }
| Expr::CaseOf { .. }
| Expr::LetIn { .. }
| Expr::Lambda { .. }
) =>
{
&inner.value
}
other => other,
}
}
pub fn print(module: &ElmModule) -> String {
Printer::new(PrintConfig::default()).print_module(module)
}
pub fn pretty_print(module: &ElmModule) -> String {
Printer::new(PrintConfig {
style: PrintStyle::ElmFormat,
..PrintConfig::default()
})
.print_module(module)
}