use crate::predoc::{
Doc, DocE, GroupAnn, Pretty, hardline, hardspace, line, line_prime, push_group, push_group_ann,
push_nested, unexpand_spacing_prime,
};
use crate::types::{Expression, FirstToken, Item, Parameter, Term, Token, Trivia};
use super::absorb::{is_absorbable_term, push_absorb_paren};
use super::term::{push_pretty_term, push_pretty_term_wide, push_render_list};
use super::util::{has_trivia, is_simple_expression, is_simple_term};
const fn is_list_arg(e: &Expression) -> bool {
matches!(e, Expression::Term(Term::List(_, _, _)))
}
const fn is_selection_arg(e: &Expression) -> bool {
matches!(e, Expression::Term(Term::Selection(_, _, _)))
}
fn push_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) => is_simple_term(t),
Item::Comments(_) => true,
});
if items.0.len() <= 6 && all_simple {
push_render_list(doc, &line(), open, items, close);
return;
}
}
arg.pretty(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 push_absorb_app(doc: &mut Doc, expr: &Expression, indent_function: bool, comment: &Trivia) {
let recurse_head = |doc: &mut Doc, head: &Expression| {
push_group_ann(doc, GroupAnn::Transparent, |g| {
push_absorb_app(g, head, indent_function, comment);
});
};
let Expression::Application(f, a) = expr else {
if !comment.is_empty() {
strip_first_comment(expr).pretty(doc);
} else if indent_function {
push_nested(doc, |n| {
push_group(n, |g| {
g.push(line_prime());
expr.pretty(g);
});
});
} else {
expr.pretty(doc);
}
return;
};
if is_list_arg(a)
&& let Expression::Application(f2, l1) = &**f
&& is_list_arg(l1)
{
push_group_ann(doc, GroupAnn::Transparent, |outer| {
recurse_head(outer, f2);
push_nested(outer, |n| {
push_group(n, |g| {
g.push(line());
push_group(g, |inner| push_absorb_inner(inner, l1));
g.push(line());
push_group(g, |inner| push_absorb_inner(inner, a));
});
});
});
return;
}
recurse_head(doc, f);
doc.push(line());
let arg_ann = if is_selection_arg(a) {
GroupAnn::RegularG
} else {
GroupAnn::Priority
};
push_nested(doc, |n| {
push_group_ann(n, arg_ann, |g| push_absorb_inner(g, a));
});
}
fn push_priority_nest(doc: &mut Doc, f: impl FnOnce(&mut Doc)) {
push_group_ann(doc, GroupAnn::Priority, |g| push_nested(g, f));
}
fn push_absorb_last(doc: &mut Doc, arg: &Expression) {
if let Expression::Term(t) = arg
&& is_absorbable_term(t)
{
return push_priority_nest(doc, |n| push_pretty_term(n, t));
}
if let Expression::Term(Term::Parenthesized(open, inner, close)) = arg {
if let Expression::Abstraction(Parameter::ID(name), colon, body) = &**inner
&& let Expression::Term(body_term) = &**body
&& is_absorbable_term(body_term)
&& !has_trivia(open)
&& !has_trivia(name)
&& !has_trivia(colon)
{
return push_priority_nest(doc, |n| {
open.pretty(n);
name.pretty(n);
colon.pretty(n);
n.push(hardspace());
push_pretty_term_wide(n, body_term);
close.pretty(n);
});
}
if let Expression::Application(f, a) = &**inner
&& let Expression::Term(Term::Token(ident)) = &**f
&& matches!(ident.value, Token::Identifier(_))
&& let Expression::Term(body_term) = &**a
&& is_absorbable_term(body_term)
&& !has_trivia(open)
&& !has_trivia(ident)
&& !has_trivia(close)
{
return push_priority_nest(doc, |n| {
open.pretty(n);
ident.pretty(n);
n.push(hardspace());
push_pretty_term_wide(n, body_term);
close.pretty(n);
});
}
return push_absorb_paren(doc, open, inner, close);
}
push_group(doc, |g| push_nested(g, |n| arg.pretty(n)));
}
pub(super) fn push_pretty_app(
doc: &mut Doc,
indent_function: bool,
pre: &[DocE],
has_post: bool,
expr: &Expression,
) {
let Expression::Application(f, a) = expr else {
unreachable!("push_pretty_app requires an Application");
};
let comment = first_token_comment(f);
let post_hardline = |doc: &mut Doc| {
if has_post && !comment.is_empty() {
doc.push(hardline());
}
};
comment.pretty(doc);
if is_list_arg(a)
&& let Expression::Application(f2, l1) = &**f
&& is_list_arg(l1)
{
push_group(doc, |g| {
g.extend_from_slice(pre);
push_group_ann(g, GroupAnn::Transparent, |inner| {
push_absorb_app(inner, f2, indent_function, &comment);
});
g.push(line());
push_nested(g, |n| push_group(n, |gr| push_absorb_inner(gr, l1)));
g.push(line());
push_nested(g, |n| push_group(n, |gr| push_absorb_inner(gr, a)));
if has_post {
g.push(line_prime());
}
});
post_hardline(doc);
return;
}
let mut rendered_f: Doc = pre.to_vec();
push_group_ann(&mut rendered_f, GroupAnn::Transparent, |g| {
push_absorb_app(g, f, indent_function, &comment);
});
if is_simple_expression(expr)
&& let Some(unexpanded) = unexpand_spacing_prime(None, &rendered_f)
{
push_group(doc, |g| {
g.extend(unexpanded);
g.push(hardspace());
push_absorb_last(g, a);
});
post_hardline(doc);
return;
}
push_group(doc, |g| {
g.extend(rendered_f);
g.push(line());
push_absorb_last(g, a);
if has_post {
g.push(line_prime());
}
});
post_hardline(doc);
}