use crate::ast::{Expression, FirstToken, Item, Parameter, Term, Token, Trivia, TriviaPiece};
use crate::doc::{Doc, Elem, Emit, GroupKind, Spacing, line};
#[derive(Default, Clone, Copy)]
pub(super) struct AppCtx<'a> {
pub indent_function: bool,
pub pre: &'a [Elem],
pub has_post: bool,
}
impl AppCtx<'static> {
pub const PAREN: Self = Self {
indent_function: true,
pre: &[],
has_post: true,
};
pub const RHS: Self = Self {
indent_function: false,
pre: &[Elem::Spacing(Spacing::Space)],
has_post: false,
};
}
use super::absorb::absorb_paren;
use super::term::render_list;
fn absorb_inner(doc: &mut Doc, arg: &Expression) {
if let Expression::Term(Term::List { open, items, close }) = arg {
let all_simple = items.0.iter().all(|item| match item {
Item::Item(t) => t.is_simple(),
Item::Comments(_) => true,
});
if items.0.len() <= 6 && all_simple {
render_list(doc, &line(), open, items, close);
return;
}
}
arg.emit(doc);
}
fn first_token_comment(expr: &Expression) -> Trivia {
let slot = expr.first_token();
let mut t = slot.pre_trivia.clone();
if let Some(tc) = slot.trail_comment {
t.push(tc.into());
}
t
}
fn strip_first_comment(expr: &Expression) -> Expression {
let mut e = expr.clone();
let slot = e.first_token_mut();
*slot.pre_trivia = Trivia::new();
*slot.trail_comment = None;
e
}
fn absorb_head(doc: &mut Doc, expr: &Expression, indent_function: bool, comment: &Trivia) {
if !comment.is_empty() {
strip_first_comment(expr).emit(doc);
} else if indent_function {
doc.nested(|n| {
n.group(|g| {
g.linebreak();
expr.emit(g);
});
});
} else {
expr.emit(doc);
}
}
fn list_pair<'a>(
f: &'a Expression,
a: &'a Expression,
head: Option<&'a Expression>,
) -> Option<(&'a Expression, &'a Expression, Option<&'a Expression>)> {
if !matches!(a, Expression::Term(Term::List { .. })) {
return None;
}
if let Expression::Apply { func: f2, arg: l1 } = f
&& matches!(**l1, Expression::Term(Term::List { .. }))
{
return Some((f2, l1, head));
}
if let Some(h) = head
&& matches!(f, Expression::Term(Term::List { .. }))
{
return Some((h, f, None));
}
None
}
fn absorb_arg(doc: &mut Doc, a: &Expression) {
doc.line();
let arg_ann = if matches!(a, Expression::Term(Term::Selection { .. })) {
GroupKind::Regular
} else {
GroupKind::Priority
};
doc.nested(|n| {
n.group_with(arg_ann, |g| absorb_inner(g, a));
});
}
fn absorb_app(
doc: &mut Doc,
expr: &Expression,
indent_function: bool,
comment: &Trivia,
head: Option<&Expression>,
) {
let recurse_head = |doc: &mut Doc, f: &Expression, head| {
doc.transparent_group(|g| {
absorb_app(g, f, indent_function, comment, head);
});
};
let Expression::Apply { func: f, arg: a } = expr else {
match head {
None => absorb_head(doc, expr, indent_function, comment),
Some(h) => {
recurse_head(doc, h, None);
absorb_arg(doc, expr);
}
}
return;
};
if let Some((f2, l1, h2)) = list_pair(f, a, head) {
doc.transparent_group(|outer| {
recurse_head(outer, f2, h2);
outer.nested(|n| {
n.group(|g| {
g.line();
g.group(|inner| absorb_inner(inner, l1));
g.line();
g.group(|inner| absorb_inner(inner, a));
});
});
});
return;
}
recurse_head(doc, f, head);
absorb_arg(doc, a);
}
fn priority_nest(doc: &mut Doc, f: impl FnOnce(&mut Doc)) {
doc.priority_group(|g| {
g.nested(f);
});
}
fn absorb_last(doc: &mut Doc, arg: &Expression) {
if let Expression::Term(t) = arg
&& t.is_absorbable()
{
return priority_nest(doc, |n| t.emit_bare(n));
}
if let Expression::Term(Term::Parenthesized {
open,
expr: inner,
close,
}) = arg
{
if let Expression::Lambda {
param: Parameter::Id(name),
colon,
body,
} = &**inner
&& let Expression::Term(body_term) = &**body
&& body_term.is_absorbable()
&& !open.has_trivia()
&& !name.has_trivia()
&& !colon.has_trivia()
{
return priority_nest(doc, |n| {
open.emit(n);
name.emit(n);
colon.emit(n);
n.hardspace();
body_term.emit_wide(n);
close.emit(n);
});
}
if let Expression::Apply { func: f, arg: a } = &**inner
&& let Expression::Term(Term::Token(ident)) = &**f
&& matches!(ident.value, Token::Identifier(_))
&& let Expression::Term(body_term) = &**a
&& body_term.is_absorbable()
&& !open.has_trivia()
&& !ident.has_trivia()
&& !close.has_trivia()
{
return priority_nest(doc, |n| {
open.emit(n);
ident.emit(n);
n.hardspace();
body_term.emit_wide(n);
close.emit(n);
});
}
return absorb_paren(doc, open, inner, close);
}
doc.group(|g| {
g.nested(|n| arg.emit(n));
});
}
pub(super) fn emit_app(doc: &mut Doc, ctx: AppCtx<'_>, expr: &Expression) {
let Expression::Apply { func: f, arg: a } = expr else {
unreachable!("emit_app requires an Apply");
};
emit_app_parts(doc, ctx, f, a, None);
}
pub(super) fn emit_app_parts(
doc: &mut Doc,
ctx: AppCtx<'_>,
f: &Expression,
a: &Expression,
head: Option<&Expression>,
) {
let mut comment = first_token_comment(head.unwrap_or(f));
if let [TriviaPiece::LanguageAnnotation(_)] = &*comment {
comment = Trivia::new();
}
let post_hardline = |doc: &mut Doc| {
if ctx.has_post && !comment.is_empty() {
doc.hardline();
}
};
comment.emit(doc);
if let Some((f2, l1, h2)) = list_pair(f, a, head) {
doc.group(|g| {
g.0.extend_from_slice(ctx.pre);
g.transparent_group(|inner| {
absorb_app(inner, f2, ctx.indent_function, &comment, h2);
});
g.line();
g.nested(|n| {
n.group(|gr| absorb_inner(gr, l1));
});
g.line();
g.nested(|n| {
n.group(|gr| absorb_inner(gr, a));
});
if ctx.has_post {
g.linebreak();
}
});
post_hardline(doc);
return;
}
let mut rendered_f = Doc::from(ctx.pre.to_vec());
rendered_f.transparent_group(|g| {
absorb_app(g, f, ctx.indent_function, &comment, head);
});
if head.is_none()
&& Expression::app_is_simple(f, a)
&& let Some(unexpanded) = rendered_f.try_compact(None)
{
doc.group(|g| {
g.extend(unexpanded);
g.hardspace();
absorb_last(g, a);
});
post_hardline(doc);
return;
}
doc.group(|g| {
g.extend(rendered_f);
g.line();
absorb_last(g, a);
if ctx.has_post {
g.linebreak();
}
});
post_hardline(doc);
}