use rowan::{NodeOrToken, TextRange};
use crate::formatter::ir::Ir;
use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
pub fn lower(root: &SyntaxNode) -> Ir {
if root.kind() == SyntaxKind::ROOT {
lower_root(root)
} else {
lower_node(root)
}
}
fn lower_node(node: &SyntaxNode) -> Ir {
match node.kind() {
SyntaxKind::BINARY_EXPR | SyntaxKind::ASSIGNMENT_EXPR => lower_binary(node),
SyntaxKind::ARROW_EXPR => lower_arrow(node),
SyntaxKind::WHERE_EXPR => lower_where(node),
SyntaxKind::COMPARISON_EXPR => lower_comparison(node),
SyntaxKind::TERNARY_EXPR => lower_ternary(node),
SyntaxKind::RANGE_EXPR => lower_range(node),
SyntaxKind::UNARY_EXPR => lower_unary(node),
SyntaxKind::SPLAT_EXPR => lower_splat(node),
SyntaxKind::TYPE_ANNOTATION => lower_type_annotation(node),
SyntaxKind::MATRIX_EXPR | SyntaxKind::BRACESCAT_EXPR => lower_matrix(node),
SyntaxKind::BEGIN_EXPR | SyntaxKind::QUOTE_EXPR => lower_block_expr(node),
SyntaxKind::LET_EXPR => lower_let(node),
SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => lower_loop(node),
SyntaxKind::STRUCT_DEF => lower_struct(node),
SyntaxKind::FUNCTION_DEF | SyntaxKind::MACRO_DEF => lower_function(node),
SyntaxKind::DO_EXPR => lower_do(node),
SyntaxKind::ABSTRACT_DEF | SyntaxKind::PRIMITIVE_DEF => lower_type_decl(node),
SyntaxKind::MODULE_DEF => lower_module(node),
SyntaxKind::IF_EXPR => lower_if(node),
SyntaxKind::TRY_EXPR => lower_try(node),
SyntaxKind::ARG_LIST => lower_arg_list(node),
SyntaxKind::CALL_EXPR => lower_call(node),
SyntaxKind::INDEX_EXPR => lower_index(node),
SyntaxKind::MACRO_CALL => lower_macro_call(node),
SyntaxKind::TUPLE_EXPR | SyntaxKind::VECT_EXPR | SyntaxKind::BRACES => {
lower_collection(node)
}
SyntaxKind::COMPREHENSION | SyntaxKind::GENERATOR | SyntaxKind::BRACES_COMPREHENSION => {
lower_comprehension(node)
}
SyntaxKind::PAREN_EXPR => lower_paren(node),
SyntaxKind::PAREN_BLOCK => lower_paren_block(node),
SyntaxKind::BARE_TUPLE_EXPR | SyntaxKind::LET_BINDINGS => lower_comma_list(node),
SyntaxKind::KEYWORD_ARG => lower_keyword_arg(node),
SyntaxKind::PARAMETERS => lower_parameters(node),
SyntaxKind::FOR_BINDING => lower_for_binding(node),
SyntaxKind::RETURN_EXPR
| SyntaxKind::CONST_STMT
| SyntaxKind::GLOBAL_STMT
| SyntaxKind::LOCAL_STMT => lower_keyword_stmt(node),
SyntaxKind::USING_STMT | SyntaxKind::IMPORT_STMT => lower_import_stmt(node),
SyntaxKind::EXPORT_STMT | SyntaxKind::PUBLIC_STMT => lower_export_stmt(node),
SyntaxKind::LITERAL => lower_literal(node),
SyntaxKind::STRING_LITERAL | SyntaxKind::CMD_LITERAL => lower_string_literal(node),
_ => lower_transparent(node),
}
}
fn lower_transparent(node: &SyntaxNode) -> Ir {
let mut parts = Vec::new();
let mut iter = node.children_with_tokens().peekable();
while let Some(el) = iter.next() {
match el {
NodeOrToken::Node(child) => parts.push(lower_node(&child)),
NodeOrToken::Token(tok) => parts.push(lower_trivia(&tok, iter.peek())),
}
}
Ir::concat(parts)
}
fn lower_trivia(tok: &SyntaxToken, next: Option<&SyntaxElement>) -> Ir {
match tok.kind() {
SyntaxKind::WHITESPACE
if matches!(
next,
Some(NodeOrToken::Token(t)) if t.kind() == SyntaxKind::NEWLINE
) =>
{
Ir::text("")
}
SyntaxKind::COMMENT => Ir::text(tok.text().trim_end_matches([' ', '\t'])),
_ => Ir::text(tok.text().to_string()),
}
}
fn lower_binary(node: &SyntaxNode) -> Ir {
if let Some(ir) = try_lower_chain(node) {
return ir;
}
let is_assignment = node.kind() == SyntaxKind::ASSIGNMENT_EXPR;
let Some(op_kind) = binary_op_kind(node) else {
return lower_transparent(node);
};
let flatten = !is_assignment && !is_tight_binop(op_kind);
let mut items: Vec<SyntaxElement> = Vec::new();
if !collect_binary_chain(node, op_kind, flatten, &mut items) {
return lower_transparent(node);
}
let mut first: Option<Ir> = None;
let mut rest: Vec<Ir> = Vec::new();
let mut next_sep: Option<Ir> = None;
let mut expect_operand = true;
let mut operand_count = 0usize;
let mut op_count = 0usize;
let mut prev_operand: Option<SyntaxNode> = None;
for el in items {
match el {
NodeOrToken::Node(child) => {
if !expect_operand {
return lower_transparent(node);
}
let ir = lower_node(&child);
if operand_count == 0 {
first = Some(ir);
} else {
if let Some(sep) = next_sep.take() {
rest.push(sep);
}
rest.push(ir);
}
prev_operand = Some(child);
operand_count += 1;
expect_operand = false;
}
NodeOrToken::Token(tok) => {
if expect_operand {
return lower_transparent(node);
}
let tight = !is_assignment
&& is_tight_binop(tok.kind())
&& !(tok.kind() == SyntaxKind::DOT_CARET
&& prev_operand
.as_ref()
.is_some_and(dot_caret_snug_retokenizes));
if tight {
rest.push(Ir::text(tok.text().to_string()));
next_sep = None;
} else {
rest.push(Ir::text(format!(" {}", tok.text())));
next_sep = Some(if is_assignment {
Ir::text(" ")
} else {
Ir::Line
});
}
op_count += 1;
expect_operand = true;
}
}
}
let Some(first) = first else {
return lower_transparent(node);
};
if expect_operand || operand_count < 2 || op_count + 1 != operand_count {
return lower_transparent(node);
}
if is_assignment {
let mut parts = vec![first];
parts.extend(rest);
return Ir::concat(parts);
}
Ir::group(Ir::concat([first, Ir::indent(Ir::concat(rest))]))
}
fn binary_op_kind(node: &SyntaxNode) -> Option<SyntaxKind> {
let mut expect_operand = true;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(_) => expect_operand = false,
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::COMMENT
| SyntaxKind::BLOCK_COMMENT => {}
_ if !expect_operand => return Some(tok.kind()),
_ => {}
},
}
}
None
}
fn collect_binary_chain(
node: &SyntaxNode,
op_kind: SyntaxKind,
flatten: bool,
items: &mut Vec<SyntaxElement>,
) -> bool {
let mut expect_operand = true;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if !expect_operand {
return false;
}
if flatten
&& child.kind() == SyntaxKind::BINARY_EXPR
&& binary_op_kind(&child).is_some_and(|k| same_break_tier(k, op_kind))
{
if !collect_binary_chain(&child, op_kind, flatten, items) {
return false;
}
} else {
items.push(NodeOrToken::Node(child));
}
expect_operand = false;
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => return false,
_ if !expect_operand => {
items.push(NodeOrToken::Token(tok));
expect_operand = true;
}
_ => return false,
},
}
}
!expect_operand
}
fn same_break_tier(a: SyntaxKind, b: SyntaxKind) -> bool {
a == b || binary_prec_class(a).is_some_and(|c| Some(c) == binary_prec_class(b))
}
fn binary_prec_class(kind: SyntaxKind) -> Option<u8> {
use SyntaxKind::*;
Some(match kind {
PLUS | MINUS | PIPE | DOT_PLUS | DOT_MINUS | DOT_PIPE => 0,
STAR | SLASH | BACKSLASH | PERCENT | AMP | DOT_STAR | DOT_SLASH | DOT_BACKSLASH
| DOT_PERCENT | DOT_AMP => 1,
SHL | SHR | USHR => 2,
FAT_ARROW | LONG_ARROW | LEFT_RIGHT_ARROW | LEFT_LONG_ARROW | DOT_FAT_ARROW
| DOT_LONG_ARROW | DOT_LEFT_LONG_ARROW | DOT_LEFT_RIGHT_ARROW => 3,
_ => return None,
})
}
const MIN_CHAIN_CALLS: usize = 2;
struct ChainLink {
name: SyntaxNode,
args: Option<SyntaxNode>,
}
fn try_lower_chain(node: &SyntaxNode) -> Option<Ir> {
let (base, links) = collect_chain(node)?;
let call_count = links.iter().filter(|l| l.args.is_some()).count();
if call_count < MIN_CHAIN_CALLS {
return None;
}
let mut inner: Vec<Ir> = Vec::new();
for link in &links {
let name_ir = lower_node(&link.name);
match &link.args {
Some(args) => inner.push(Ir::concat([
Ir::text("."),
Ir::SoftLine,
name_ir,
lower_arg_list(args),
])),
None => inner.push(Ir::concat([Ir::text("."), name_ir])),
}
}
Some(Ir::group(Ir::concat([
lower_node(&base),
Ir::indent(Ir::concat(inner)),
])))
}
fn collect_chain(node: &SyntaxNode) -> Option<(SyntaxNode, Vec<ChainLink>)> {
let mut links: Vec<ChainLink> = Vec::new(); let mut cur = node.clone();
loop {
match cur.kind() {
SyntaxKind::CALL_EXPR => {
let (callee, arg_list) = call_parts(&cur)?;
let Some((inner, name)) = dot_access_parts(&callee) else {
break;
};
links.push(ChainLink {
name,
args: Some(arg_list),
});
cur = inner;
}
SyntaxKind::BINARY_EXPR => {
let Some((inner, name)) = dot_access_parts(&cur) else {
break;
};
links.push(ChainLink { name, args: None });
cur = inner;
}
_ => break,
}
}
links.reverse();
Some((cur, links))
}
fn call_parts(node: &SyntaxNode) -> Option<(SyntaxNode, SyntaxNode)> {
let mut nodes: Vec<SyntaxNode> = Vec::new();
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => nodes.push(child),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return None,
},
}
}
let [callee, arg_list] = nodes.as_slice() else {
return None;
};
if arg_list.kind() != SyntaxKind::ARG_LIST {
return None;
}
Some((callee.clone(), arg_list.clone()))
}
fn dot_access_parts(node: &SyntaxNode) -> Option<(SyntaxNode, SyntaxNode)> {
if node.kind() != SyntaxKind::BINARY_EXPR {
return None;
}
let mut lhs: Option<SyntaxNode> = None;
let mut rhs: Option<SyntaxNode> = None;
let mut saw_dot = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
let slot = if saw_dot { &mut rhs } else { &mut lhs };
if slot.is_some() {
return None;
}
*slot = Some(child);
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::DOT if !saw_dot => saw_dot = true,
_ => return None,
},
}
}
match (lhs, saw_dot, rhs) {
(Some(l), true, Some(r)) => Some((l, r)),
_ => None,
}
}
fn lower_call(node: &SyntaxNode) -> Ir {
try_lower_chain(node).unwrap_or_else(|| lower_transparent(node))
}
fn lower_unary(node: &SyntaxNode) -> Ir {
let mut op: Option<String> = None;
let mut operand: Option<SyntaxNode> = None;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => {
return lower_transparent(node);
}
_ if op.is_none() && operand.is_none() => op = Some(tok.text().to_string()),
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => {
if op.is_none() || operand.is_some() {
return lower_transparent(node);
}
operand = Some(child);
}
}
}
let (Some(op), Some(operand)) = (op, operand) else {
return lower_transparent(node);
};
if operand.kind() == SyntaxKind::UNARY_EXPR || operand_leads_with_operator(&operand) {
return lower_transparent(node);
}
Ir::concat([Ir::text(op), lower_node(&operand)])
}
fn operand_leads_with_operator(node: &SyntaxNode) -> bool {
node.first_token()
.and_then(|t| t.text().chars().next())
.is_some_and(|c| "+-*/\\^%!~<>=&|:$?".contains(c))
}
fn lower_splat(node: &SyntaxNode) -> Ir {
let mut operand: Option<SyntaxNode> = None;
let mut dots: Option<String> = None;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::DOT_DOT_DOT if operand.is_some() && dots.is_none() => {
dots = Some(tok.text().to_string());
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => {
if operand.is_some() {
return lower_transparent(node);
}
operand = Some(child);
}
}
}
let (Some(operand), Some(dots)) = (operand, dots) else {
return lower_transparent(node);
};
let trails_with_dot = operand.kind() != SyntaxKind::LITERAL
&& operand
.last_token()
.is_some_and(|t| t.text().ends_with('.'));
let ends_in_bracket = operand.last_token().is_some_and(|t| {
matches!(
t.kind(),
SyntaxKind::RPAREN | SyntaxKind::RBRACKET | SyntaxKind::RBRACE
)
});
if trails_with_dot || ends_in_bracket {
return lower_transparent(node);
}
Ir::concat([lower_node(&operand), Ir::text(dots)])
}
fn lower_arrow(node: &SyntaxNode) -> Ir {
let mut operands: Vec<SyntaxNode> = Vec::new();
let mut op: Option<SyntaxToken> = None;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => operands.push(child),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::ARROW if op.is_none() => op = Some(tok),
_ => return lower_transparent(node),
},
}
}
let (Some(_), [lhs, rhs]) = (op, operands.as_slice()) else {
return lower_transparent(node);
};
Ir::concat([lower_node(lhs), Ir::text(" -> "), lower_node(rhs)])
}
fn lower_paren(node: &SyntaxNode) -> Ir {
match paren_reflow_body(node) {
Some(body) => Ir::group(body),
None => lower_transparent(node),
}
}
fn paren_reflow_body(node: &SyntaxNode) -> Option<Ir> {
let mut inner: Option<SyntaxNode> = None;
let mut extra_operand = false;
let mut saw_lparen = false;
let mut saw_rparen = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if inner.is_some() {
extra_operand = true;
}
inner = Some(child);
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::LPAREN if !saw_lparen => saw_lparen = true,
SyntaxKind::RPAREN if !saw_rparen => saw_rparen = true,
_ => return None,
},
}
}
let (true, true, false, Some(inner)) = (saw_lparen, saw_rparen, extra_operand, inner) else {
return None;
};
Some(Ir::concat([
Ir::text("("),
Ir::indent(Ir::concat([Ir::SoftLine, lower_node(&inner)])),
Ir::SoftLine,
Ir::text(")"),
]))
}
fn lower_paren_block(node: &SyntaxNode) -> Ir {
let mut statements: Vec<Ir> = Vec::new();
let mut saw_lparen = false;
let mut saw_rparen = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if child.kind() == SyntaxKind::PARAMETERS {
match paren_block_statement(&child) {
Ok(Some(ir)) => statements.push(ir),
Ok(None) => {} Err(()) => return lower_transparent(node),
}
} else {
statements.push(lower_node(&child));
}
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::LPAREN if !saw_lparen => saw_lparen = true,
SyntaxKind::RPAREN if !saw_rparen => saw_rparen = true,
_ => return lower_transparent(node),
},
}
}
if !saw_lparen || !saw_rparen || statements.len() < 2 {
return lower_transparent(node);
}
let last = statements.len() - 1;
let mut body: Vec<Ir> = Vec::with_capacity(statements.len() * 2);
body.push(Ir::SoftLine);
for (i, stmt) in statements.into_iter().enumerate() {
body.push(stmt);
if i < last {
body.push(Ir::text(";"));
body.push(Ir::Line);
}
}
Ir::group(Ir::concat([
Ir::text("("),
Ir::indent(Ir::concat(body)),
Ir::SoftLine,
Ir::text(")"),
]))
}
fn paren_block_statement(params: &SyntaxNode) -> Result<Option<Ir>, ()> {
let mut statement: Option<Ir> = None;
let mut saw_semicolon = false;
for el in params.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if statement.is_some() {
return Err(());
}
statement = Some(lower_node(&child));
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::SEMICOLON if !saw_semicolon => saw_semicolon = true,
_ => return Err(()),
},
}
}
if !saw_semicolon {
return Err(());
}
Ok(statement)
}
fn lower_where(node: &SyntaxNode) -> Ir {
let mut operands: Vec<SyntaxNode> = Vec::new();
let mut kw: Option<SyntaxToken> = None;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => operands.push(child),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::WHERE_KW if kw.is_none() => kw = Some(tok),
_ => return lower_transparent(node),
},
}
}
let (Some(_), [lhs, rhs]) = (kw, operands.as_slice()) else {
return lower_transparent(node);
};
let bound = if rhs.kind() == SyntaxKind::BRACES {
lower_node(rhs)
} else {
Ir::concat([Ir::text("{"), lower_node(rhs), Ir::text("}")])
};
Ir::concat([lower_node(lhs), Ir::text(" where "), bound])
}
fn lower_keyword_stmt(node: &SyntaxNode) -> Ir {
let mut kw: Option<SyntaxToken> = None;
let mut rest: Vec<NodeOrToken<SyntaxNode, SyntaxToken>> = Vec::new();
for el in node.children_with_tokens() {
match &el {
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::WHITESPACE => {}
NodeOrToken::Token(tok) if kw.is_none() => kw = Some(tok.clone()),
_ => rest.push(el),
}
}
let Some(kw) = kw else {
return lower_transparent(node);
};
match rest.as_slice() {
[] => return Ir::text(kw.text().to_string()),
[NodeOrToken::Node(operand)] => {
return Ir::concat([
Ir::text(kw.text().to_string()),
Ir::text(" "),
lower_node(operand),
]);
}
_ => {}
}
let mut parts: Vec<Ir> = vec![Ir::text(kw.text().to_string()), Ir::text(" ")];
let mut expect_item = true;
for el in &rest {
match el {
NodeOrToken::Node(child) if expect_item => {
parts.push(lower_node(child));
expect_item = false;
}
NodeOrToken::Token(tok) if expect_item && tok.kind() == SyntaxKind::IDENT => {
parts.push(Ir::text(tok.text().to_string()));
expect_item = false;
}
NodeOrToken::Token(tok) if !expect_item && tok.kind() == SyntaxKind::COMMA => {
parts.push(Ir::text(", "));
expect_item = true;
}
_ => return lower_transparent(node),
}
}
if expect_item {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_import_stmt(node: &SyntaxNode) -> Ir {
let mut kw: Option<SyntaxToken> = None;
let mut rest_els: Vec<NodeOrToken<SyntaxNode, SyntaxToken>> = Vec::new();
for el in node.children_with_tokens() {
match &el {
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::WHITESPACE => {}
NodeOrToken::Token(tok) if kw.is_none() => kw = Some(tok.clone()),
_ => rest_els.push(el),
}
}
let Some(kw) = kw else {
return lower_transparent(node);
};
let mut first: Vec<Ir> = vec![Ir::text(kw.text().to_string()), Ir::text(" ")];
let mut rest: Vec<Ir> = Vec::new();
let mut seen_comma = false;
let mut expect_item = true;
for el in &rest_els {
match el {
NodeOrToken::Node(child) if expect_item => {
let ir = lower_node(child);
if seen_comma {
rest.push(ir);
} else {
first.push(ir);
}
expect_item = false;
}
NodeOrToken::Token(tok) if !expect_item && tok.kind() == SyntaxKind::COMMA => {
rest.push(Ir::text(","));
rest.push(Ir::Line);
seen_comma = true;
expect_item = true;
}
NodeOrToken::Token(tok) if !expect_item && tok.kind() == SyntaxKind::COLON => {
if seen_comma {
rest.push(Ir::text(": "));
} else {
first.push(Ir::text(": "));
}
expect_item = true;
}
NodeOrToken::Token(tok)
if matches!(tok.kind(), SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE) => {}
_ => return lower_transparent(node),
}
}
if expect_item {
return lower_transparent(node);
}
if rest.is_empty() {
return Ir::concat(first);
}
Ir::group(Ir::concat([
Ir::concat(first),
Ir::indent(Ir::concat(rest)),
]))
}
fn lower_export_stmt(node: &SyntaxNode) -> Ir {
let mut kw: Option<SyntaxToken> = None;
let mut rest_els: Vec<NodeOrToken<SyntaxNode, SyntaxToken>> = Vec::new();
for el in node.children_with_tokens() {
match &el {
NodeOrToken::Token(tok)
if matches!(tok.kind(), SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE) => {}
NodeOrToken::Token(tok)
if matches!(tok.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT) =>
{
return lower_transparent(node);
}
NodeOrToken::Token(tok) if kw.is_none() => kw = Some(tok.clone()),
_ => rest_els.push(el),
}
}
let Some(kw) = kw else {
return lower_transparent(node);
};
let mut first: Vec<Ir> = vec![Ir::text(kw.text().to_string())];
let mut rest: Vec<Ir> = Vec::new();
let mut seen_comma = false;
let mut expect_item = true;
for el in &rest_els {
match el {
NodeOrToken::Token(tok) if !expect_item && tok.kind() == SyntaxKind::COMMA => {
rest.push(Ir::text(","));
rest.push(Ir::Line);
seen_comma = true;
expect_item = true;
}
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::COMMA => {
return lower_transparent(node);
}
_ => {
let piece = match el {
NodeOrToken::Node(child) => lower_node(child),
NodeOrToken::Token(tok) => Ir::text(tok.text().to_string()),
};
let bucket = if seen_comma { &mut rest } else { &mut first };
if expect_item {
if !seen_comma {
bucket.push(Ir::text(" "));
}
expect_item = false;
}
bucket.push(piece);
}
}
}
if expect_item {
return lower_transparent(node);
}
if rest.is_empty() {
return Ir::concat(first);
}
Ir::group(Ir::concat([
Ir::concat(first),
Ir::indent(Ir::concat(rest)),
]))
}
fn lower_literal(node: &SyntaxNode) -> Ir {
Ir::concat(node.children_with_tokens().map(|el| match el {
NodeOrToken::Node(child) => lower_node(&child),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::FLOAT | SyntaxKind::FLOAT32 => {
Ir::text(normalize_float(tok.text()).unwrap_or_else(|| tok.text().to_string()))
}
SyntaxKind::HEX_INT => {
Ir::text(normalize_hex(tok.text()).unwrap_or_else(|| tok.text().to_string()))
}
_ => Ir::text(tok.text().to_string()),
},
}))
}
fn lower_string_literal(node: &SyntaxNode) -> Ir {
string_literal_body(node).unwrap_or_else(|| Ir::text(node.text().to_string()))
}
fn string_literal_body(node: &SyntaxNode) -> Option<Ir> {
let mut parts = Vec::new();
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => parts.push(Ir::text(tok.text().to_string())),
NodeOrToken::Node(child) => {
if child.kind() != SyntaxKind::INTERPOLATION {
return None;
}
parts.push(Ir::text(render_flat(&lower_node(&child))?));
}
}
}
Some(Ir::concat(parts))
}
fn render_flat(ir: &Ir) -> Option<String> {
let mut out = String::new();
render_flat_into(ir, &mut out).then_some(out)
}
fn render_flat_into(ir: &Ir, out: &mut String) -> bool {
match ir {
Ir::Text(s) => {
if s.contains('\n') {
return false;
}
out.push_str(s);
true
}
Ir::Concat(items) => items.iter().all(|item| render_flat_into(item, out)),
Ir::Indent(inner) | Ir::Group(inner) => render_flat_into(inner, out),
Ir::Line => {
out.push(' ');
true
}
Ir::SoftLine => true,
Ir::HardLine | Ir::BlankLine => false,
Ir::IfBreak(_, flat) => {
out.push_str(flat);
true
}
Ir::HugGroup {
prefix,
body,
close,
..
} => {
render_flat_into(prefix, out)
&& render_flat_into(body, out)
&& render_flat_into(close, out)
}
}
}
fn normalize_hex(text: &str) -> Option<String> {
const TARGETS: [usize; 5] = [4, 6, 10, 18, 34];
let rest = text.strip_prefix("0x")?;
let span = text.len();
if span >= 34 || TARGETS.contains(&span) {
return None;
}
let target = *TARGETS.iter().find(|&&t| t > span)?;
let mut out = String::with_capacity(target);
out.push_str("0x");
out.extend(std::iter::repeat_n('0', target - span));
out.push_str(rest);
Some(out)
}
fn normalize_float(text: &str) -> Option<String> {
if text.contains('_') || text.contains("0x") || text.contains("0X") {
return None;
}
let mut chars = text.chars().peekable();
let mut out = String::new();
match chars.peek() {
Some('+') => {
out.push('+');
chars.next();
}
Some('-') | Some('\u{2212}') => {
out.push('-');
chars.next();
}
_ => {}
}
let mut int_part = String::new();
while chars.peek().is_some_and(char::is_ascii_digit) {
int_part.push(chars.next().unwrap());
}
let mut frac_part = String::new();
if chars.peek() == Some(&'.') {
chars.next();
while chars.peek().is_some_and(char::is_ascii_digit) {
frac_part.push(chars.next().unwrap());
}
}
let mut marker = String::new();
let mut exp_part = String::new();
if matches!(chars.peek(), Some('e' | 'E' | 'f')) {
let m = chars.next().unwrap();
marker.push(if m == 'E' { 'e' } else { m });
match chars.peek() {
Some('+') => {
marker.push('+');
chars.next();
}
Some('-') | Some('\u{2212}') => {
marker.push('-');
chars.next();
}
_ => {}
}
while chars.peek().is_some_and(char::is_ascii_digit) {
exp_part.push(chars.next().unwrap());
}
}
if chars.next().is_some() {
return None;
}
out.push_str(&strip_leading_zeros(&int_part));
out.push('.');
out.push_str(&strip_trailing_zeros(&frac_part));
if !marker.is_empty() {
out.push_str(&marker);
out.push_str(&strip_leading_zeros(&exp_part));
}
Some(out)
}
fn strip_leading_zeros(s: &str) -> String {
let t = s.trim_start_matches('0');
if t.is_empty() {
"0".to_string()
} else {
t.to_string()
}
}
fn strip_trailing_zeros(s: &str) -> String {
let t = s.trim_end_matches('0');
if t.is_empty() {
"0".to_string()
} else {
t.to_string()
}
}
fn lower_comparison(node: &SyntaxNode) -> Ir {
let mut first: Option<Ir> = None;
let mut rest: Vec<Ir> = Vec::new();
let mut next_sep: Option<Ir> = None;
let mut expect_operand = true;
let mut operand_count = 0usize;
let mut op_count = 0usize;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if !expect_operand {
return lower_transparent(node);
}
let ir = lower_node(&child);
if operand_count == 0 {
first = Some(ir);
} else {
if let Some(sep) = next_sep.take() {
rest.push(sep);
}
rest.push(ir);
}
operand_count += 1;
expect_operand = false;
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => {
return lower_transparent(node);
}
_ => {
if expect_operand {
return lower_transparent(node);
}
rest.push(Ir::text(format!(" {}", tok.text())));
next_sep = Some(Ir::Line);
op_count += 1;
expect_operand = true;
}
},
}
}
let Some(first) = first else {
return lower_transparent(node);
};
if expect_operand || operand_count < 2 || op_count + 1 != operand_count {
return lower_transparent(node);
}
Ir::group(Ir::concat([first, Ir::indent(Ir::concat(rest))]))
}
fn lower_ternary(node: &SyntaxNode) -> Ir {
let mut first: Option<Ir> = None;
let mut rest: Vec<Ir> = Vec::new();
let mut next_sep: Option<Ir> = None;
let mut expect_operand = true;
let mut operand_count = 0usize;
let mut op_count = 0usize;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if !expect_operand {
return lower_transparent(node);
}
let ir = lower_node(&child);
if operand_count == 0 {
first = Some(ir);
} else {
if let Some(sep) = next_sep.take() {
rest.push(sep);
}
rest.push(ir);
}
operand_count += 1;
expect_operand = false;
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::QUESTION | SyntaxKind::COLON if !expect_operand => {
rest.push(Ir::text(format!(" {}", tok.text())));
next_sep = Some(Ir::Line);
op_count += 1;
expect_operand = true;
}
_ => return lower_transparent(node),
},
}
}
let Some(first) = first else {
return lower_transparent(node);
};
if expect_operand || operand_count != 3 || op_count != 2 {
return lower_transparent(node);
}
Ir::group(Ir::concat([first, Ir::indent(Ir::concat(rest))]))
}
fn lower_range(node: &SyntaxNode) -> Ir {
let mut parts: Vec<Ir> = Vec::new();
let mut expect_operand = true;
let mut operand_count = 0usize;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if !expect_operand {
return lower_transparent(node);
}
parts.push(lower_node(&child));
operand_count += 1;
expect_operand = false;
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::COLON if !expect_operand => {
parts.push(Ir::text(":"));
expect_operand = true;
}
_ => return lower_transparent(node),
},
}
}
if expect_operand || operand_count < 2 {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_type_annotation(node: &SyntaxNode) -> Ir {
let mut parts: Vec<Ir> = Vec::new();
let mut seen_colons = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => parts.push(lower_node(&child)),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::COLON_COLON if !seen_colons => {
seen_colons = true;
parts.push(Ir::text("::"));
}
_ => return lower_transparent(node),
},
}
}
if !seen_colons {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_arg_list(node: &SyntaxNode) -> Ir {
if bracket_has_comment(node) {
return lower_multiline_bracket(node);
}
let Some(ArgListParts {
open,
close,
mut items,
params: params_node,
last_huggable,
}) = collect_arg_list(node)
else {
return lower_transparent(node);
};
if let Some(pnode) = params_node {
if let Some((pitems, last_param_huggable)) = collect_param_items(&pnode) {
let hug = last_param_huggable.then(|| {
let mut prefix = params_hug_prefix(&open, &items, &pitems);
let mut body = pitems.last().expect("hug requires a last item").clone();
if let Some((extra, value_body)) = last_list_item(&pnode)
.as_ref()
.and_then(pair_hug_grouped_parts)
{
prefix.push(extra);
body = value_body;
}
(Ir::concat(prefix), body)
});
let close_text = Ir::text(close.clone());
let grouped = Ir::group(arg_list_params_body(&open, items, pitems, &close));
if let Some((prefix, body)) = hug {
return Ir::hug_group(prefix, body, close_text, grouped);
}
return grouped;
}
let mut parts: Vec<Ir> = vec![Ir::text(open)];
for (i, item) in items.into_iter().enumerate() {
if i > 0 {
parts.push(Ir::text(", "));
}
parts.push(item);
}
parts.push(lower_node(&pnode));
parts.push(Ir::text(close));
return Ir::concat(parts);
}
if items.is_empty() {
return Ir::concat([Ir::text(open), Ir::text(close)]);
}
if last_huggable {
let explode = arg_list_explode_group(&open, &items, &close);
let mut body = items.pop().expect("hug requires a last item");
let mut prefix: Vec<Ir> = vec![Ir::text(open)];
for item in items {
prefix.push(item);
prefix.push(Ir::text(", "));
}
if let Some((extra, value_body)) = last_list_item(node)
.as_ref()
.and_then(pair_hug_grouped_parts)
{
prefix.push(extra);
body = value_body;
}
return Ir::hug_group(Ir::concat(prefix), body, Ir::text(close), explode);
}
arg_list_explode_group(&open, &items, &close)
}
struct ArgListParts {
open: String,
close: String,
items: Vec<Ir>,
params: Option<SyntaxNode>,
last_huggable: bool,
}
fn collect_arg_list(node: &SyntaxNode) -> Option<ArgListParts> {
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut items: Vec<Ir> = Vec::new();
let mut params: Option<SyntaxNode> = None;
let mut pending_comma = false;
let mut last_huggable = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LPAREN | SyntaxKind::LBRACKET | SyntaxKind::LBRACE => {
open = Some(tok.text().to_string())
}
SyntaxKind::RPAREN | SyntaxKind::RBRACKET | SyntaxKind::RBRACE => {
close = Some(tok.text().to_string())
}
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMA => {
if pending_comma || items.is_empty() {
return None;
}
pending_comma = true;
}
_ => return None,
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG => {
if params.is_some() || (!items.is_empty() && !pending_comma) {
return None;
}
last_huggable = item_is_huggable(&child);
items.push(lower_node(&child));
pending_comma = false;
}
SyntaxKind::PARAMETERS => {
if pending_comma || params.is_some() {
return None;
}
params = Some(child);
}
_ => return None,
},
}
}
Some(ArgListParts {
open: open?,
close: close?,
items,
params,
last_huggable,
})
}
fn arg_list_explode_group(open: &str, items: &[Ir], close: &str) -> Ir {
Ir::group(arg_list_explode_body(open, items, close))
}
fn arg_list_explode_body(open: &str, items: &[Ir], close: &str) -> Ir {
bracket_explode_body(open, items, close, Ir::if_break(",", ""))
}
fn arg_list_params_body(open: &str, items: Vec<Ir>, pitems: Vec<Ir>, close: &str) -> Ir {
let mut group_parts: Vec<Ir> = vec![Ir::text(open)];
let mut inner: Vec<Ir> = Vec::new();
if items.is_empty() {
group_parts.push(Ir::text(";"));
} else {
inner.push(Ir::SoftLine);
for (i, item) in items.into_iter().enumerate() {
if i > 0 {
inner.push(Ir::text(","));
inner.push(Ir::Line);
}
inner.push(item);
}
inner.push(Ir::text(";"));
}
for (j, p) in pitems.into_iter().enumerate() {
if j > 0 {
inner.push(Ir::text(","));
}
inner.push(Ir::Line);
inner.push(p);
}
inner.push(Ir::if_break(",", ""));
group_parts.push(Ir::indent(Ir::concat(inner)));
group_parts.push(Ir::SoftLine);
group_parts.push(Ir::text(close));
Ir::concat(group_parts)
}
fn params_hug_prefix(open: &str, items: &[Ir], pitems: &[Ir]) -> Vec<Ir> {
let mut prefix: Vec<Ir> = vec![Ir::text(open.to_string())];
for (i, item) in items.iter().enumerate() {
if i > 0 {
prefix.push(Ir::text(", "));
}
prefix.push(item.clone());
}
prefix.push(Ir::text("; "));
for p in &pitems[..pitems.len() - 1] {
prefix.push(p.clone());
prefix.push(Ir::text(", "));
}
prefix
}
fn collection_explode_body(open: &str, items: &[Ir], close: &str, singleton_comma: bool) -> Ir {
let trailing = if singleton_comma {
Ir::text(",")
} else {
Ir::if_break(",", "")
};
bracket_explode_body(open, items, close, trailing)
}
fn bracket_explode_body(open: &str, items: &[Ir], close: &str, trailing: Ir) -> Ir {
let mut inner: Vec<Ir> = vec![Ir::SoftLine];
for (i, item) in items.iter().enumerate() {
if i > 0 {
inner.push(Ir::text(","));
inner.push(Ir::Line);
}
inner.push(item.clone());
}
inner.push(trailing);
Ir::concat([
Ir::text(open),
Ir::indent(Ir::concat(inner)),
Ir::SoftLine,
Ir::text(close),
])
}
fn item_is_huggable(item: &SyntaxNode) -> bool {
match item.kind() {
SyntaxKind::ARG => {
let mut children = item.children();
let (Some(child), None) = (children.next(), children.next()) else {
return false;
};
value_is_huggable(&child)
}
SyntaxKind::KEYWORD_ARG => {
let mut nodes = 0usize;
let mut value: Option<SyntaxNode> = None;
let mut seen_eq = false;
for el in item.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
nodes += 1;
value = Some(child);
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::EQ if !seen_eq => seen_eq = true,
_ => return false,
},
}
}
nodes == 2 && seen_eq && value.as_ref().is_some_and(value_is_huggable)
}
_ => false,
}
}
fn value_is_huggable(value: &SyntaxNode) -> bool {
huggable_kind(value.kind()) || pair_hug_chain(value).is_some()
}
fn pair_operands(node: &SyntaxNode) -> Option<(SyntaxNode, String, SyntaxNode)> {
if node.kind() != SyntaxKind::BINARY_EXPR {
return None;
}
let mut lhs: Option<SyntaxNode> = None;
let mut op: Option<String> = None;
let mut rhs: Option<SyntaxNode> = None;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match (&lhs, &op, &rhs) {
(None, None, None) => lhs = Some(child),
(Some(_), Some(_), None) => rhs = Some(child),
_ => return None,
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::FAT_ARROW | SyntaxKind::DOT_FAT_ARROW
if lhs.is_some() && op.is_none() =>
{
op = Some(tok.text().to_string());
}
_ => return None,
},
}
}
Some((lhs?, op?, rhs?))
}
fn pair_hug_chain(node: &SyntaxNode) -> Option<(Ir, SyntaxNode)> {
let (lhs, op, rhs) = pair_operands(node)?;
let head = Ir::concat([lower_node(&lhs), Ir::text(format!(" {op} "))]);
if huggable_kind(rhs.kind()) {
return Some((head, rhs));
}
let (tail, construct) = pair_hug_chain(&rhs)?;
Some((Ir::concat([head, tail]), construct))
}
fn huggable_kind(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::CALL_EXPR
| SyntaxKind::INDEX_EXPR
| SyntaxKind::CURLY_EXPR
| SyntaxKind::VECT_EXPR
| SyntaxKind::TUPLE_EXPR
| SyntaxKind::BRACES
| SyntaxKind::COMPREHENSION
| SyntaxKind::GENERATOR
| SyntaxKind::BRACES_COMPREHENSION
| SyntaxKind::MATRIX_EXPR
)
}
fn bracket_has_comment(node: &SyntaxNode) -> bool {
node.children_with_tokens()
.any(|el| matches!(el.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT))
}
fn lower_collection(node: &SyntaxNode) -> Ir {
if bracket_has_comment(node) {
return lower_multiline_bracket(node);
}
let Some(parts) = collect_collection_items(node) else {
return lower_transparent(node);
};
if parts.last_huggable {
let singleton_comma = collection_singleton_comma(node, &parts.items);
let CollectionParts {
open,
close,
mut items,
..
} = parts;
let close_text = if singleton_comma {
format!(",{close}")
} else {
close.clone()
};
let explode = Ir::group(collection_explode_body(
&open,
&items,
&close,
singleton_comma,
));
let mut body = items.pop().expect("hug requires a last item");
let mut prefix: Vec<Ir> = vec![Ir::text(open)];
for item in items {
prefix.push(item);
prefix.push(Ir::text(", "));
}
if let Some((extra, value_body)) = last_list_item(node)
.as_ref()
.and_then(pair_hug_grouped_parts)
{
prefix.push(extra);
body = value_body;
}
return Ir::hug_group(Ir::concat(prefix), body, Ir::text(close_text), explode);
}
Ir::group(collection_body(node, parts))
}
fn collection_reflow_body(node: &SyntaxNode) -> Option<Ir> {
let parts = collect_collection_items(node)?;
if parts.last_huggable {
let singleton_comma = collection_singleton_comma(node, &parts.items);
let close_text = if singleton_comma {
format!(",{}", parts.close)
} else {
parts.close.clone()
};
let explode =
collection_explode_body(&parts.open, &parts.items, &parts.close, singleton_comma);
let mut prefix: Vec<Ir> = vec![Ir::text(parts.open.clone())];
for item in &parts.items[..parts.items.len() - 1] {
prefix.push(item.clone());
prefix.push(Ir::text(", "));
}
return reflow_hug(prefix, &last_list_item(node)?, close_text, explode);
}
Some(collection_body(node, parts))
}
fn last_list_item(node: &SyntaxNode) -> Option<SyntaxNode> {
node.children()
.filter(|c| matches!(c.kind(), SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG))
.last()
}
fn reflow_hug(mut prefix: Vec<Ir>, last: &SyntaxNode, close: String, explode: Ir) -> Option<Ir> {
let (extra_prefix, body) = item_hug_parts(last)?;
if let Some(extra) = extra_prefix {
prefix.push(extra);
}
Some(Ir::hug_group(
Ir::concat(prefix),
body,
Ir::text(close),
explode,
))
}
fn item_hug_parts(item: &SyntaxNode) -> Option<(Option<Ir>, Ir)> {
match item.kind() {
SyntaxKind::ARG => {
let mut children = item.children();
let (Some(child), None) = (children.next(), children.next()) else {
return None;
};
hug_value_parts(&child)
}
SyntaxKind::KEYWORD_ARG => {
let mut children = item.children();
let (Some(name), Some(value), None) =
(children.next(), children.next(), children.next())
else {
return None;
};
let (extra, body) = hug_value_parts(&value)?;
let mut prefix = vec![lower_node(&name), Ir::text(" = ")];
prefix.extend(extra);
Some((Some(Ir::concat(prefix)), body))
}
_ => None,
}
}
fn pair_hug_grouped_parts(item: &SyntaxNode) -> Option<(Ir, Ir)> {
match item.kind() {
SyntaxKind::ARG => {
let mut children = item.children();
let (Some(child), None) = (children.next(), children.next()) else {
return None;
};
let (prefix, construct) = pair_hug_chain(&child)?;
Some((prefix, lower_node(&construct)))
}
SyntaxKind::KEYWORD_ARG => {
let mut children = item.children();
let (Some(name), Some(value), None) =
(children.next(), children.next(), children.next())
else {
return None;
};
let (prefix, construct) = pair_hug_chain(&value)?;
Some((
Ir::concat([lower_node(&name), Ir::text(" = "), prefix]),
lower_node(&construct),
))
}
_ => None,
}
}
fn hug_value_parts(value: &SyntaxNode) -> Option<(Option<Ir>, Ir)> {
if huggable_kind(value.kind()) {
return Some((None, construct_reflow_body(value)?));
}
let (prefix, construct) = pair_hug_chain(value)?;
let body = construct_reflow_body(&construct)?;
Some((Some(prefix), body))
}
fn construct_reflow_body(node: &SyntaxNode) -> Option<Ir> {
match node.kind() {
SyntaxKind::TUPLE_EXPR | SyntaxKind::VECT_EXPR | SyntaxKind::BRACES
if !bracket_has_comment(node) =>
{
collection_reflow_body(node)
}
SyntaxKind::MATRIX_EXPR | SyntaxKind::BRACESCAT_EXPR if !matrix_has_comment(node) => {
matrix_reflow_body(node)
}
SyntaxKind::PAREN_EXPR => paren_reflow_body(node),
SyntaxKind::CALL_EXPR | SyntaxKind::CURLY_EXPR => call_reflow_body(node),
SyntaxKind::INDEX_EXPR => index_reflow_body(node),
SyntaxKind::COMPREHENSION | SyntaxKind::GENERATOR | SyntaxKind::BRACES_COMPREHENSION => {
comprehension_reflow_body(node)
}
SyntaxKind::TYPED_COMPREHENSION => typed_comprehension_reflow_body(node),
_ => None,
}
}
fn collection_body(node: &SyntaxNode, parts: CollectionParts) -> Ir {
let singleton_comma = collection_singleton_comma(node, &parts.items);
let CollectionParts {
open, close, items, ..
} = parts;
if items.is_empty() {
return Ir::concat([Ir::text(open), Ir::text(close)]);
}
collection_explode_body(&open, &items, &close, singleton_comma)
}
fn collection_singleton_comma(node: &SyntaxNode, items: &[Ir]) -> bool {
node.kind() == SyntaxKind::TUPLE_EXPR && items.len() == 1
}
struct CollectionParts {
open: String,
close: String,
items: Vec<Ir>,
last_huggable: bool,
}
fn collect_collection_items(node: &SyntaxNode) -> Option<CollectionParts> {
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut items: Vec<Ir> = Vec::new();
let mut pending_comma = false;
let mut last_huggable = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LPAREN | SyntaxKind::LBRACKET | SyntaxKind::LBRACE => {
open = Some(tok.text().to_string())
}
SyntaxKind::RPAREN | SyntaxKind::RBRACKET | SyntaxKind::RBRACE => {
close = Some(tok.text().to_string())
}
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMA => {
if pending_comma || items.is_empty() {
return None;
}
pending_comma = true;
}
_ => return None,
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG => {
if !items.is_empty() && !pending_comma {
return None;
}
last_huggable = item_is_huggable(&child);
items.push(lower_node(&child));
pending_comma = false;
}
_ => return None,
},
}
}
Some(CollectionParts {
open: open?,
close: close?,
items,
last_huggable,
})
}
fn call_reflow_body(node: &SyntaxNode) -> Option<Ir> {
let mut parts = node.children_with_tokens();
let (Some(first), Some(second), None) = (parts.next(), parts.next(), parts.next()) else {
return None;
};
let (NodeOrToken::Node(callee), NodeOrToken::Node(args)) = (first, second) else {
return None;
};
let callee_has_comment = callee
.descendants_with_tokens()
.any(|el| matches!(el.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT));
if args.kind() != SyntaxKind::ARG_LIST || callee_has_comment || bracket_has_comment(&args) {
return None;
}
applied_args_body(lower_node(&callee), &args)
}
fn applied_args_body(prefix: Ir, args: &SyntaxNode) -> Option<Ir> {
let ArgListParts {
open,
close,
items,
params,
last_huggable,
} = collect_arg_list(args)?;
if let Some(pnode) = params {
let (pitems, last_param_huggable) = collect_param_items(&pnode)?;
if last_param_huggable {
let hug_prefix = params_hug_prefix(&open, &items, &pitems);
let explode = arg_list_params_body(&open, items, pitems, &close);
let hug = reflow_hug(hug_prefix, &last_list_item(&pnode)?, close, explode)?;
return Some(Ir::concat([prefix, hug]));
}
return Some(Ir::concat([
prefix,
arg_list_params_body(&open, items, pitems, &close),
]));
}
if items.is_empty() {
return Some(Ir::concat([prefix, Ir::text(open), Ir::text(close)]));
}
if last_huggable {
let explode = arg_list_explode_body(&open, &items, &close);
let mut hug_prefix: Vec<Ir> = vec![Ir::text(open.clone())];
for item in &items[..items.len() - 1] {
hug_prefix.push(item.clone());
hug_prefix.push(Ir::text(", "));
}
let hug = reflow_hug(hug_prefix, &last_list_item(args)?, close, explode)?;
return Some(Ir::concat([prefix, hug]));
}
Some(Ir::concat([
prefix,
arg_list_explode_body(&open, &items, &close),
]))
}
fn lower_index(node: &SyntaxNode) -> Ir {
match index_reflow_body(node) {
Some(body) => Ir::group(body),
None => lower_transparent(node),
}
}
fn index_reflow_body(node: &SyntaxNode) -> Option<Ir> {
let mut parts = node.children_with_tokens();
let (Some(first), Some(second), None) = (parts.next(), parts.next(), parts.next()) else {
return None;
};
let (NodeOrToken::Node(subject), NodeOrToken::Node(args)) = (first, second) else {
return None;
};
if args.kind() != SyntaxKind::ARG_LIST || bracket_has_comment(&args) {
return None;
}
if let Some(body) = construct_reflow_body(&subject) {
return Some(Ir::concat([body, lower_arg_list(&args)]));
}
if !chain_root_is_name(&subject) {
return None;
}
applied_args_body(lower_node(&subject), &args)
}
fn chain_root_is_name(subject: &SyntaxNode) -> bool {
let dotted = subject.kind() == SyntaxKind::BINARY_EXPR
&& subject
.children_with_tokens()
.any(|el| el.kind() == SyntaxKind::DOT);
if subject.kind() != SyntaxKind::NAME && !dotted {
return false;
}
!subject
.descendants_with_tokens()
.any(|el| matches!(el.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT))
}
fn lower_comprehension(node: &SyntaxNode) -> Ir {
match comprehension_reflow_body(node) {
Some(body) => Ir::group(body),
None => lower_transparent(node),
}
}
fn comprehension_reflow_body(node: &SyntaxNode) -> Option<Ir> {
if node
.descendants_with_tokens()
.any(|el| matches!(el.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT))
{
return None;
}
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut element: Option<Ir> = None;
let mut clauses: Vec<Ir> = Vec::new();
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LBRACKET | SyntaxKind::LPAREN | SyntaxKind::LBRACE => {
open = Some(tok.text().to_string())
}
SyntaxKind::RBRACKET | SyntaxKind::RPAREN | SyntaxKind::RBRACE => {
close = Some(tok.text().to_string())
}
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return None,
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::FOR_BINDING => clauses.push(lower_for_binding(&child)),
SyntaxKind::COMPREHENSION_IF => clauses.push(lower_comprehension_if(&child)?),
_ => {
if element.is_some() || !clauses.is_empty() {
return None;
}
element = Some(lower_node(&child));
}
},
}
}
let (open, close, element) = (open?, close?, element?);
if clauses.is_empty() {
return None;
}
let mut inner: Vec<Ir> = vec![Ir::SoftLine, element];
for clause in clauses {
inner.push(Ir::Line);
inner.push(clause);
}
Some(Ir::concat([
Ir::text(open),
Ir::indent(Ir::concat(inner)),
Ir::SoftLine,
Ir::text(close),
]))
}
fn typed_comprehension_reflow_body(node: &SyntaxNode) -> Option<Ir> {
let mut parts = node.children_with_tokens();
let (Some(first), Some(second), None) = (parts.next(), parts.next(), parts.next()) else {
return None;
};
let (NodeOrToken::Node(ty), NodeOrToken::Node(generator)) = (first, second) else {
return None;
};
let ty_has_comment = ty
.descendants_with_tokens()
.any(|el| matches!(el.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT));
if ty_has_comment {
return None;
}
Some(Ir::concat([
lower_node(&ty),
comprehension_reflow_body(&generator)?,
]))
}
fn lower_comprehension_if(node: &SyntaxNode) -> Option<Ir> {
let mut if_kw = false;
let mut filter: Option<SyntaxNode> = None;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::IF_KW if !if_kw => if_kw = true,
_ => return None,
},
NodeOrToken::Node(child) => {
if filter.is_some() {
return None;
}
filter = Some(child);
}
}
}
let filter = filter?;
if !if_kw {
return None;
}
Some(Ir::concat([Ir::text("if "), lower_node(&filter)]))
}
fn lower_macro_call(node: &SyntaxNode) -> Ir {
let mut parts: Vec<Ir> = Vec::new();
let mut saw_name = false;
let mut had_gap = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => had_gap = true,
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::MACRO_NAME if !saw_name => {
let Some(name) = lower_macro_name(&child) else {
return lower_transparent(node);
};
parts.push(name);
saw_name = true;
had_gap = false;
}
_ if saw_name => {
let call_form = child.kind() == SyntaxKind::ARG_LIST && !had_gap;
if !call_form {
parts.push(Ir::text(" "));
}
parts.push(lower_node(&child));
had_gap = false;
}
_ => return lower_transparent(node),
},
}
}
if !saw_name {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_macro_name(node: &SyntaxNode) -> Option<Ir> {
let mut text = String::new();
for el in node.descendants_with_tokens() {
if let NodeOrToken::Token(tok) = el {
match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => return None,
_ => text.push_str(tok.text()),
}
}
}
Some(Ir::text(text))
}
fn lower_comma_list(node: &SyntaxNode) -> Ir {
let mut first: Option<Ir> = None;
let mut rest: Vec<Ir> = Vec::new();
let mut item_count = 0usize;
let mut pending_comma = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMA => {
if pending_comma || item_count == 0 {
return lower_transparent(node);
}
pending_comma = true;
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => {
if item_count > 0 && !pending_comma {
return lower_transparent(node);
}
let ir = lower_node(&child);
if item_count == 0 {
first = Some(ir);
} else {
rest.push(Ir::text(","));
rest.push(Ir::Line);
rest.push(ir);
}
item_count += 1;
pending_comma = false;
}
}
}
if pending_comma {
return lower_transparent(node);
}
let Some(first) = first else {
return lower_transparent(node);
};
if rest.is_empty() {
return first;
}
Ir::group(Ir::concat([first, Ir::indent(Ir::concat(rest))]))
}
fn lower_keyword_arg(node: &SyntaxNode) -> Ir {
let mut parts: Vec<Ir> = Vec::new();
let mut seen_eq = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => parts.push(lower_node(&child)),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::EQ if !seen_eq => {
seen_eq = true;
parts.push(Ir::text(" = "));
}
_ => return lower_transparent(node),
},
}
}
if !seen_eq {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn collect_param_items(node: &SyntaxNode) -> Option<(Vec<Ir>, bool)> {
let mut items: Vec<Ir> = Vec::new();
let mut pending_comma = false;
let mut seen_semi = false;
let mut last_huggable = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::SEMICOLON if !seen_semi => seen_semi = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::COMMA => {
if pending_comma || items.is_empty() {
return None;
}
pending_comma = true;
}
_ => return None,
},
NodeOrToken::Node(child) => {
if !matches!(child.kind(), SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG) {
return None;
}
if !items.is_empty() && !pending_comma {
return None;
}
last_huggable = item_is_huggable(&child);
items.push(lower_node(&child));
pending_comma = false;
}
}
}
if !seen_semi || items.is_empty() {
return None;
}
Some((items, last_huggable))
}
fn lower_parameters(node: &SyntaxNode) -> Ir {
let mut parts: Vec<Ir> = Vec::new();
let mut first_item = true;
let mut pending_comma = false;
let mut seen_semi = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::SEMICOLON if !seen_semi => {
seen_semi = true;
parts.push(Ir::text(";"));
}
SyntaxKind::WHITESPACE => {}
SyntaxKind::COMMA => {
if pending_comma {
return lower_transparent(node);
}
pending_comma = true;
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => {
if !matches!(child.kind(), SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG) {
return lower_transparent(node);
}
if first_item {
parts.push(Ir::text(" "));
} else {
if !pending_comma {
return lower_transparent(node);
}
parts.push(Ir::text(", "));
}
parts.push(lower_node(&child));
first_item = false;
pending_comma = false;
}
}
}
if !seen_semi {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_for_binding(node: &SyntaxNode) -> Ir {
let mut for_kw = false;
let mut els: Vec<SyntaxElement> = Vec::new();
for el in node.children_with_tokens() {
match &el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::FOR_KW if !for_kw && els.is_empty() => for_kw = true,
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => {
return lower_transparent(node);
}
_ => els.push(el),
},
NodeOrToken::Node(_) => els.push(el),
}
}
let mut groups: Vec<Vec<SyntaxElement>> = vec![Vec::new()];
let mut filter: Option<SyntaxNode> = None;
let mut iter = els.into_iter();
while let Some(el) = iter.next() {
match &el {
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::COMMA => {
groups.push(Vec::new());
}
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::IF_KW => {
let rest: Vec<SyntaxElement> = iter.collect();
match rest.as_slice() {
[NodeOrToken::Node(f)] => filter = Some(f.clone()),
_ => return lower_transparent(node),
}
break;
}
_ => groups.last_mut().unwrap().push(el),
}
}
let mut specs: Vec<Ir> = Vec::with_capacity(groups.len());
for group in &groups {
match lower_for_spec(group) {
Some(ir) => specs.push(ir),
None => return lower_transparent(node),
}
}
if specs.is_empty() {
return lower_transparent(node);
}
let mut parts: Vec<Ir> = Vec::with_capacity(specs.len() * 2 + 2);
if for_kw {
parts.push(Ir::text("for "));
}
for (i, spec) in specs.into_iter().enumerate() {
if i > 0 {
parts.push(Ir::text(", "));
}
parts.push(spec);
}
if let Some(filter) = filter {
parts.push(Ir::text(" if "));
parts.push(lower_node(&filter));
}
Ir::concat(parts)
}
fn lower_for_spec(group: &[SyntaxElement]) -> Option<Ir> {
match group {
[NodeOrToken::Node(node)]
if matches!(
node.kind(),
SyntaxKind::ASSIGNMENT_EXPR | SyntaxKind::BINARY_EXPR
) =>
{
let (lhs, rhs) = for_iteration_operands(node)?;
Some(Ir::concat([lhs, Ir::text(" in "), rhs]))
}
[
NodeOrToken::Node(target),
NodeOrToken::Token(kw),
NodeOrToken::Node(iterable),
] if kw.kind() == SyntaxKind::IDENT && kw.text() == "in" => Some(Ir::concat([
lower_node(target),
Ir::text(" in "),
lower_node(iterable),
])),
_ => None,
}
}
fn for_iteration_operands(node: &SyntaxNode) -> Option<(Ir, Ir)> {
let mut operands: Vec<SyntaxNode> = Vec::new();
let mut op_count = 0usize;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => operands.push(child),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
SyntaxKind::EQ => op_count += 1,
SyntaxKind::UNICODE_OP if tok.text() == "∈" => op_count += 1,
_ => return None,
},
}
}
let (1, [lhs, rhs]) = (op_count, operands.as_slice()) else {
return None;
};
Some((lower_node(lhs), lower_node(rhs)))
}
fn has_newline_token(node: &SyntaxNode) -> bool {
node.descendants_with_tokens()
.any(|el| el.kind() == SyntaxKind::NEWLINE)
}
const MAX_BLANK_LINES: usize = 1;
fn lower_multiline_bracket(node: &SyntaxNode) -> Ir {
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut items: Vec<Ir> = Vec::new();
let mut item_comments: Vec<Option<String>> = Vec::new();
let mut gaps: Vec<Vec<String>> = Vec::new();
let mut gap: Vec<String> = Vec::new();
let mut leading: Vec<String> = Vec::new();
let mut header_comment: Option<String> = None;
let mut on_line = true;
let mut comma = false;
let mut leading_comma = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LPAREN | SyntaxKind::LBRACKET | SyntaxKind::LBRACE => {
open = Some(tok.text().to_string())
}
SyntaxKind::RPAREN | SyntaxKind::RBRACKET | SyntaxKind::RBRACE => {
close = Some(tok.text().to_string())
}
SyntaxKind::WHITESPACE => {}
SyntaxKind::NEWLINE => on_line = false,
SyntaxKind::COMMA => {
if comma {
return lower_transparent(node);
}
if items.is_empty() {
leading_comma = true;
}
comma = true;
}
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => {
let text = tok.text().trim_end_matches([' ', '\t']).to_string();
if on_line {
let slot = match items.last_mut() {
Some(_) => item_comments.last_mut().unwrap(),
None => &mut header_comment,
};
if slot.is_some() {
return lower_transparent(node);
}
*slot = Some(text);
} else {
gap.push(text);
on_line = true;
}
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG => {
if items.is_empty() {
if leading_comma {
return lower_transparent(node);
}
leading = std::mem::take(&mut gap);
} else {
if !comma {
return lower_transparent(node);
}
gaps.push(std::mem::take(&mut gap));
}
items.push(lower_node(&child));
item_comments.push(None);
comma = false;
on_line = true;
}
_ => return lower_transparent(node),
},
}
}
let trailing = gap;
let (Some(open), Some(close)) = (open, close) else {
return lower_transparent(node);
};
if items.is_empty() {
return lower_transparent(node);
}
fn render_gap(inner: &mut Vec<Ir>, comments: &[String]) {
for text in comments {
inner.push(Ir::HardLine);
inner.push(Ir::text(text.clone()));
}
}
let n = items.len();
let mut inner: Vec<Ir> = Vec::new();
render_gap(&mut inner, &leading);
inner.push(Ir::HardLine); for (i, item) in items.into_iter().enumerate() {
inner.push(item);
inner.push(Ir::text(","));
if let Some(text) = &item_comments[i] {
inner.push(Ir::text(" "));
inner.push(Ir::text(text.clone()));
}
if i + 1 < n {
render_gap(&mut inner, &gaps[i]);
inner.push(Ir::HardLine);
}
}
render_gap(&mut inner, &trailing);
let mut out: Vec<Ir> = vec![Ir::text(open)];
if let Some(text) = header_comment {
out.push(Ir::text(" "));
out.push(Ir::text(text));
}
out.push(Ir::indent(Ir::concat(inner)));
out.push(Ir::HardLine); out.push(Ir::text(close));
Ir::concat(out)
}
fn lower_matrix(node: &SyntaxNode) -> Ir {
if matrix_has_comment(node) {
return if has_newline_token(node) {
lower_matrix_multiline(node)
} else {
lower_transparent(node)
};
}
lower_matrix_reflow(node)
}
fn lower_matrix_reflow(node: &SyntaxNode) -> Ir {
match matrix_reflow_body(node) {
Some(body) => Ir::group(body),
None => lower_transparent(node),
}
}
fn matrix_reflow_body(node: &SyntaxNode) -> Option<Ir> {
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut rows: Vec<Vec<Ir>> = vec![Vec::new()];
let mut prev_was_semicolon = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LBRACKET | SyntaxKind::LBRACE => open = Some(tok.text().to_string()),
SyntaxKind::RBRACKET | SyntaxKind::RBRACE => close = Some(tok.text().to_string()),
SyntaxKind::WHITESPACE => continue,
SyntaxKind::SEMICOLON => {
if prev_was_semicolon {
return None;
}
rows.push(Vec::new());
prev_was_semicolon = true;
continue;
}
SyntaxKind::NEWLINE => rows.push(Vec::new()),
_ => return None,
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::ARG => rows.last_mut().unwrap().push(lower_node(&child)),
SyntaxKind::MATRIX_ROW => {
let row = rows.last_mut().unwrap();
for sub in child.children_with_tokens() {
match sub {
NodeOrToken::Token(t) if t.kind() == SyntaxKind::WHITESPACE => {}
NodeOrToken::Node(arg) if arg.kind() == SyntaxKind::ARG => {
row.push(lower_node(&arg))
}
_ => return None,
}
}
}
_ => return None,
},
}
prev_was_semicolon = false;
}
let (open, close) = (open?, close?);
rows.retain(|row| !row.is_empty());
if rows.is_empty() {
return Some(Ir::concat([Ir::text(open), Ir::text(close)]));
}
let mut inner: Vec<Ir> = vec![Ir::SoftLine];
for (i, row) in rows.into_iter().enumerate() {
if i > 0 {
inner.push(Ir::if_break("", ";"));
inner.push(Ir::Line);
}
for (j, elem) in row.into_iter().enumerate() {
if j > 0 {
inner.push(Ir::text(" "));
}
inner.push(elem);
}
}
Some(Ir::concat([
Ir::text(open),
Ir::indent(Ir::concat(inner)),
Ir::SoftLine,
Ir::text(close),
]))
}
fn matrix_has_comment(node: &SyntaxNode) -> bool {
node.children_with_tokens().any(|el| match el {
NodeOrToken::Token(t) => {
matches!(t.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT)
}
NodeOrToken::Node(child) => {
child.kind() == SyntaxKind::MATRIX_ROW
&& child
.children_with_tokens()
.any(|e| matches!(e.kind(), SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT))
}
})
}
fn lower_matrix_multiline(node: &SyntaxNode) -> Ir {
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut items: Vec<Ir> = Vec::new();
let mut item_comments: Vec<Option<String>> = Vec::new();
let mut gaps: Vec<Vec<String>> = Vec::new();
let mut gap: Vec<String> = Vec::new();
let mut leading: Vec<String> = Vec::new();
let mut header_comment: Option<String> = None;
let mut on_line = true;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LBRACKET | SyntaxKind::LBRACE => open = Some(tok.text().to_string()),
SyntaxKind::RBRACKET | SyntaxKind::RBRACE => close = Some(tok.text().to_string()),
SyntaxKind::WHITESPACE => {}
SyntaxKind::SEMICOLON => {}
SyntaxKind::NEWLINE => on_line = false,
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT => {
let text = tok.text().trim_end_matches([' ', '\t']).to_string();
if on_line {
let slot = match items.last_mut() {
Some(_) => item_comments.last_mut().unwrap(),
None => &mut header_comment,
};
if slot.is_some() {
return lower_transparent(node);
}
*slot = Some(text);
} else {
gap.push(text);
on_line = true;
}
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::MATRIX_ROW | SyntaxKind::ARG => {
let row = if child.kind() == SyntaxKind::ARG {
lower_node(&child)
} else {
match lower_matrix_row(&child) {
Some(row) => row,
None => return lower_transparent(node),
}
};
if items.is_empty() {
leading = std::mem::take(&mut gap);
} else {
gaps.push(std::mem::take(&mut gap));
}
items.push(row);
item_comments.push(None);
on_line = true;
}
_ => return lower_transparent(node),
},
}
}
let trailing = gap;
let (Some(open), Some(close)) = (open, close) else {
return lower_transparent(node);
};
if items.is_empty() {
return lower_transparent(node);
}
fn render_gap(inner: &mut Vec<Ir>, comments: &[String]) {
for text in comments {
inner.push(Ir::HardLine);
inner.push(Ir::text(text.clone()));
}
}
let n = items.len();
let mut inner: Vec<Ir> = Vec::new();
render_gap(&mut inner, &leading);
inner.push(Ir::HardLine); for (i, item) in items.into_iter().enumerate() {
inner.push(item);
if let Some(text) = &item_comments[i] {
inner.push(Ir::text(" "));
inner.push(Ir::text(text.clone()));
}
if i + 1 < n {
render_gap(&mut inner, &gaps[i]);
inner.push(Ir::HardLine);
}
}
render_gap(&mut inner, &trailing);
let mut out: Vec<Ir> = vec![Ir::text(open)];
if let Some(text) = header_comment {
out.push(Ir::text(" "));
out.push(Ir::text(text));
}
out.push(Ir::indent(Ir::concat(inner)));
out.push(Ir::HardLine); out.push(Ir::text(close));
Ir::concat(out)
}
fn lower_matrix_row(node: &SyntaxNode) -> Option<Ir> {
let mut elems: Vec<Ir> = Vec::new();
for sub in node.children_with_tokens() {
match sub {
NodeOrToken::Token(t) if t.kind() == SyntaxKind::WHITESPACE => {}
NodeOrToken::Node(arg) if arg.kind() == SyntaxKind::ARG => {
elems.push(lower_node(&arg));
}
_ => return None,
}
}
if elems.is_empty() {
return None;
}
let mut row: Vec<Ir> = Vec::new();
for (j, elem) in elems.into_iter().enumerate() {
if j > 0 {
row.push(Ir::text(" "));
}
row.push(elem);
}
Some(Ir::concat(row))
}
fn lower_block_expr(node: &SyntaxNode) -> Ir {
let kw = match node.kind() {
SyntaxKind::BEGIN_EXPR => "begin",
SyntaxKind::QUOTE_EXPR => "quote",
_ => return lower_transparent(node),
};
let mut block: Option<SyntaxNode> = None;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) if child.kind() == SyntaxKind::BLOCK => {
if block.is_some() {
return lower_transparent(node);
}
block = Some(child);
}
NodeOrToken::Node(_) => return lower_transparent(node),
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::BEGIN_KW | SyntaxKind::QUOTE_KW => {}
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (Some(block), true) = (block, saw_end) else {
return lower_transparent(node);
};
let mut parts = vec![Ir::text(kw)];
if !push_block_body(&mut parts, &block, || lower_block_body(&block)) {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn push_block_body(
parts: &mut Vec<Ir>,
block: &SyntaxNode,
render: impl FnOnce() -> Option<Ir>,
) -> bool {
match render() {
Some(body) => {
parts.push(body);
parts.push(Ir::HardLine);
parts.push(Ir::text("end"));
true
}
None if block_is_empty(block) => {
parts.push(Ir::text(" end"));
true
}
None => false,
}
}
fn lower_struct(node: &SyntaxNode) -> Ir {
let mut mutable = false;
let mut signature: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_struct = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::SIGNATURE if signature.is_none() && block.is_none() => {
signature = Some(child)
}
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::MUTABLE_KW if !mutable => mutable = true,
SyntaxKind::STRUCT_KW if !saw_struct => saw_struct = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(signature), Some(block)) = (saw_struct, saw_end, signature, block) else {
return lower_transparent(node);
};
let mut parts: Vec<Ir> = Vec::new();
if mutable {
parts.push(Ir::text("mutable "));
}
parts.push(Ir::text("struct "));
parts.push(lower_node(&signature));
if !push_block_body(&mut parts, &block, || lower_block_body(&block)) {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn block_is_empty(block: &SyntaxNode) -> bool {
block.children_with_tokens().all(|el| {
matches!(
el,
NodeOrToken::Token(tok) if matches!(
tok.kind(),
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE | SyntaxKind::SEMICOLON
)
)
})
}
fn lower_body_allow_empty(block: &SyntaxNode) -> Option<Option<Ir>> {
match lower_block_body(block) {
Some(ir) => Some(Some(ir)),
None if block_is_empty(block) => Some(None),
None => None,
}
}
fn lower_function(node: &SyntaxNode) -> Ir {
let kw = match node.kind() {
SyntaxKind::FUNCTION_DEF => "function",
SyntaxKind::MACRO_DEF => "macro",
_ => return lower_transparent(node),
};
let mut signature: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_kw = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::SIGNATURE if signature.is_none() && block.is_none() => {
signature = Some(child)
}
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::FUNCTION_KW | SyntaxKind::MACRO_KW if !saw_kw => saw_kw = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(signature), Some(block)) = (saw_kw, saw_end, signature, block) else {
return lower_transparent(node);
};
let mut parts = vec![Ir::text(kw), Ir::text(" "), lower_node(&signature)];
if !push_block_body(&mut parts, &block, || lower_block_body(&block)) {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_do(node: &SyntaxNode) -> Ir {
let mut head: Option<SyntaxNode> = None;
let mut params: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_do = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if !saw_do && head.is_none() {
head = Some(child);
} else if saw_do
&& child.kind() == SyntaxKind::DO_PARAMS
&& params.is_none()
&& block.is_none()
{
params = Some(child);
} else if saw_do && child.kind() == SyntaxKind::BLOCK && block.is_none() {
block = Some(child);
} else {
return lower_transparent(node);
}
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::DO_KW if !saw_do => saw_do = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(head), Some(block)) = (saw_do, saw_end, head, block) else {
return lower_transparent(node);
};
let mut parts = vec![lower_node(&head), Ir::text(" do")];
if let Some(params) = params {
let Some(params_ir) = lower_do_params(¶ms) else {
return lower_transparent(node);
};
parts.push(Ir::text(" "));
parts.push(params_ir);
}
if !push_block_body(&mut parts, &block, || lower_block_body(&block)) {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_do_params(node: &SyntaxNode) -> Option<Ir> {
let mut parts: Vec<Ir> = Vec::new();
let mut expect_item = true;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) if expect_item => {
parts.push(lower_node(&child));
expect_item = false;
}
NodeOrToken::Node(_) => return None,
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::COMMA if !expect_item => {
parts.push(Ir::text(", "));
expect_item = true;
}
SyntaxKind::WHITESPACE => {}
_ => return None,
},
}
}
if parts.is_empty() || expect_item {
return None;
}
Some(Ir::concat(parts))
}
fn lower_type_decl(node: &SyntaxNode) -> Ir {
let mut parts: Vec<Ir> = Vec::new();
let mut kw_count = 0u8;
let mut seen_sig = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) if child.kind() == SyntaxKind::SIGNATURE && !seen_sig => {
parts.push(lower_node(&child));
seen_sig = true;
}
NodeOrToken::Node(child) if seen_sig => parts.push(lower_node(&child)),
NodeOrToken::Node(_) => return lower_transparent(node),
NodeOrToken::Token(tok) if seen_sig => match tok.kind() {
SyntaxKind::WHITESPACE => parts.push(Ir::text(" ")),
SyntaxKind::END_KW => parts.push(Ir::text(tok.text().to_string())),
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::IDENT if kw_count < 2 => {
parts.push(Ir::text(tok.text().to_string()));
kw_count += 1;
}
SyntaxKind::WHITESPACE => parts.push(Ir::text(" ")),
_ => return lower_transparent(node),
},
}
}
if kw_count == 2 && seen_sig {
Ir::concat(parts)
} else {
lower_transparent(node)
}
}
fn lower_module(node: &SyntaxNode) -> Ir {
let mut kw: Option<&'static str> = None;
let mut signature: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::SIGNATURE if signature.is_none() && block.is_none() => {
signature = Some(child)
}
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::MODULE_KW if kw.is_none() => kw = Some("module "),
SyntaxKind::BAREMODULE_KW if kw.is_none() => kw = Some("baremodule "),
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (Some(kw), Some(signature), Some(block), true) = (kw, signature, block, saw_end) else {
return lower_transparent(node);
};
let mut parts = vec![Ir::text(kw), lower_node(&signature)];
let rendered = push_block_body(&mut parts, &block, || {
if module_should_indent(node) {
lower_block_body(&block)
} else {
build_block_body(&block)
}
});
if !rendered {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn module_should_indent(node: &SyntaxNode) -> bool {
if node
.ancestors()
.skip(1)
.any(|a| a.kind() == SyntaxKind::MODULE_DEF)
{
return true;
}
match node.parent() {
Some(parent) if parent.kind() == SyntaxKind::ROOT => parent.children().count() > 1,
_ => false,
}
}
fn lower_let(node: &SyntaxNode) -> Ir {
let mut bindings: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_let = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::LET_BINDINGS if bindings.is_none() && block.is_none() => {
bindings = Some(child)
}
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::LET_KW if !saw_let => saw_let = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(block)) = (saw_let, saw_end, block) else {
return lower_transparent(node);
};
let mut parts: Vec<Ir> = vec![Ir::text("let")];
if let Some(bindings) = bindings {
parts.push(Ir::text(" "));
parts.push(lower_node(&bindings));
}
if !push_block_body(&mut parts, &block, || lower_block_body(&block)) {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_control_header(header: &SyntaxNode) -> Ir {
let ir = lower_node(header);
if matches!(
header.kind(),
SyntaxKind::CONDITION | SyntaxKind::FOR_BINDING
) {
Ir::indent(ir)
} else {
ir
}
}
fn lower_loop(node: &SyntaxNode) -> Ir {
let kw = match node.kind() {
SyntaxKind::WHILE_EXPR => "while",
SyntaxKind::FOR_EXPR => "for",
_ => return lower_transparent(node),
};
let mut header: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_kw = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::CONDITION | SyntaxKind::FOR_BINDING
if header.is_none() && block.is_none() =>
{
header = Some(child)
}
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHILE_KW | SyntaxKind::FOR_KW if !saw_kw => saw_kw = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(header), Some(block)) = (saw_kw, saw_end, header, block) else {
return lower_transparent(node);
};
let mut parts = vec![Ir::text(kw), Ir::text(" "), lower_control_header(&header)];
if !push_block_body(&mut parts, &block, || lower_block_body(&block)) {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_if(node: &SyntaxNode) -> Ir {
let mut condition: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut clauses: Vec<SyntaxNode> = Vec::new();
let mut saw_if = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::CONDITION if condition.is_none() && block.is_none() => {
condition = Some(child)
}
SyntaxKind::BLOCK if condition.is_some() && block.is_none() => block = Some(child),
SyntaxKind::ELSEIF_CLAUSE | SyntaxKind::ELSE_CLAUSE if block.is_some() => {
clauses.push(child)
}
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::IF_KW if !saw_if => saw_if = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(condition), Some(block)) = (saw_if, saw_end, condition, block) else {
return lower_transparent(node);
};
let Some(body) = lower_body_allow_empty(&block) else {
return lower_transparent(node);
};
let body_empty = body.is_none();
let mut parts: Vec<Ir> = vec![
Ir::text("if"),
Ir::text(" "),
lower_control_header(&condition),
];
if let Some(body) = body {
parts.push(body);
}
for clause in &clauses {
let Some(clause_ir) = lower_branch_clause(clause) else {
return lower_transparent(node);
};
parts.push(clause_ir);
}
if body_empty && clauses.is_empty() {
parts.push(Ir::text(" end"));
} else {
parts.push(Ir::HardLine);
parts.push(Ir::text("end"));
}
Ir::concat(parts)
}
fn lower_try(node: &SyntaxNode) -> Ir {
let mut block: Option<SyntaxNode> = None;
let mut clauses: Vec<SyntaxNode> = Vec::new();
let mut saw_try = false;
let mut saw_end = false;
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
SyntaxKind::CATCH_CLAUSE | SyntaxKind::ELSE_CLAUSE | SyntaxKind::FINALLY_CLAUSE
if block.is_some() =>
{
clauses.push(child)
}
_ => return lower_transparent(node),
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::TRY_KW if !saw_try => saw_try = true,
SyntaxKind::END_KW => saw_end = true,
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return lower_transparent(node),
},
}
}
let (true, true, Some(block)) = (saw_try, saw_end, block) else {
return lower_transparent(node);
};
if clauses.is_empty() {
return lower_transparent(node);
}
let Some(body) = lower_body_allow_empty(&block) else {
return lower_transparent(node);
};
let mut parts: Vec<Ir> = vec![Ir::text("try")];
if let Some(body) = body {
parts.push(body);
}
for clause in &clauses {
let Some(clause_ir) = lower_branch_clause(clause) else {
return lower_transparent(node);
};
parts.push(clause_ir);
}
parts.push(Ir::HardLine);
parts.push(Ir::text("end"));
Ir::concat(parts)
}
fn lower_branch_clause(clause: &SyntaxNode) -> Option<Ir> {
let kw = match clause.kind() {
SyntaxKind::ELSEIF_CLAUSE => "elseif",
SyntaxKind::ELSE_CLAUSE => "else",
SyntaxKind::CATCH_CLAUSE => "catch",
SyntaxKind::FINALLY_CLAUSE => "finally",
_ => return None,
};
let mut header: Option<SyntaxNode> = None;
let mut block: Option<SyntaxNode> = None;
let mut saw_kw = false;
for el in clause.children_with_tokens() {
match el {
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::BLOCK if block.is_none() => block = Some(child),
_ if block.is_none() && header.is_none() => header = Some(child),
_ => return None,
},
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::ELSEIF_KW
| SyntaxKind::ELSE_KW
| SyntaxKind::CATCH_KW
| SyntaxKind::FINALLY_KW
if !saw_kw =>
{
saw_kw = true
}
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE => {}
_ => return None,
},
}
}
let (true, Some(block)) = (saw_kw, block) else {
return None;
};
let body = lower_body_allow_empty(&block)?;
let mut parts: Vec<Ir> = vec![Ir::HardLine, Ir::text(kw)];
if let Some(header) = header {
parts.push(Ir::text(" "));
parts.push(lower_control_header(&header));
}
if let Some(body) = body {
parts.push(body);
}
Some(Ir::concat(parts))
}
#[derive(Default)]
struct BodyLine {
stmts: Vec<Ir>,
comment: Option<Ir>,
range: Option<TextRange>,
}
impl BodyLine {
fn is_blank(&self) -> bool {
self.stmts.is_empty() && self.comment.is_none()
}
fn cover(&mut self, range: TextRange) {
self.range = Some(match self.range {
Some(current) => current.cover(range),
None => range,
});
}
}
fn collect_body_lines(node: &SyntaxNode) -> Option<Vec<BodyLine>> {
let mut lines: Vec<BodyLine> = vec![BodyLine::default()];
let mut expect_sep = false;
collect_body_elements(node, &mut lines, &mut expect_sep)?;
Some(lines)
}
fn collect_body_elements(
node: &SyntaxNode,
lines: &mut Vec<BodyLine>,
expect_sep: &mut bool,
) -> Option<()> {
for el in node.children_with_tokens() {
match el {
NodeOrToken::Node(child) => {
if child.kind() == SyntaxKind::TOPLEVEL_SEMICOLON {
collect_body_elements(&child, lines, expect_sep)?;
continue;
}
if *expect_sep {
return None;
}
let line = lines.last_mut().unwrap();
line.stmts.push(lower_node(&child));
line.cover(child.text_range());
*expect_sep = true;
}
NodeOrToken::Token(tok) => match tok.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::SEMICOLON => {
if lines.last().unwrap().comment.is_some() {
return None;
}
*expect_sep = false;
}
SyntaxKind::NEWLINE => {
lines.push(BodyLine::default());
*expect_sep = false;
}
SyntaxKind::COMMENT => {
let line = lines.last_mut().unwrap();
if line.comment.is_some() {
return None;
}
let text = tok.text().trim_end_matches([' ', '\t']);
line.comment = Some(Ir::text(text));
line.cover(tok.text_range());
*expect_sep = true;
}
SyntaxKind::BLOCK_COMMENT => {
let line = lines.last_mut().unwrap();
if line.comment.is_some() {
return None;
}
line.comment = Some(Ir::text(tok.text()));
line.cover(tok.text_range());
*expect_sep = true;
}
_ => return None,
},
}
}
Some(())
}
fn lower_root(root: &SyntaxNode) -> Ir {
let Some(lines) = collect_body_lines(root) else {
return lower_transparent(root);
};
let Some(first) = lines.iter().position(|l| !l.is_blank()) else {
return Ir::text("");
};
let last = lines.iter().rposition(|l| !l.is_blank()).unwrap();
let content = &lines[first..=last];
let mut inner: Vec<Ir> = Vec::new();
let mut pending_blanks = 0usize;
for line in content {
if line.is_blank() {
pending_blanks += 1;
continue;
}
for _ in 0..pending_blanks.min(MAX_BLANK_LINES) {
inner.push(Ir::BlankLine);
}
pending_blanks = 0;
if line.stmts.is_empty() {
inner.push(Ir::HardLine);
if let Some(comment) = &line.comment {
inner.push(comment.clone());
}
} else {
let last_stmt = line.stmts.len() - 1;
for (j, stmt) in line.stmts.iter().enumerate() {
inner.push(Ir::HardLine);
inner.push(stmt.clone());
if j == last_stmt
&& let Some(comment) = &line.comment
{
inner.push(Ir::text(" "));
inner.push(comment.clone());
}
}
}
}
if matches!(inner.first(), Some(Ir::HardLine)) {
inner.remove(0);
}
inner.push(Ir::HardLine);
Ir::concat(inner)
}
fn lower_block_body(block: &SyntaxNode) -> Option<Ir> {
build_block_body(block).map(Ir::indent)
}
fn build_block_body(block: &SyntaxNode) -> Option<Ir> {
let lines = collect_body_lines(block)?;
let first = lines.iter().position(|l| !l.is_blank())?;
let last = lines.iter().rposition(|l| !l.is_blank()).unwrap();
let leading_blanks = first.saturating_sub(1).min(MAX_BLANK_LINES);
let trailing_blanks = (lines.len() - 1 - last)
.saturating_sub(1)
.min(MAX_BLANK_LINES);
let content = &lines[first..=last];
let mut inner: Vec<Ir> =
Vec::with_capacity(content.len() * 2 + leading_blanks + trailing_blanks);
for _ in 0..leading_blanks {
inner.push(Ir::BlankLine);
}
let mut pending_blanks = 0usize;
for line in content {
if line.is_blank() {
pending_blanks += 1;
continue;
}
for _ in 0..pending_blanks.min(MAX_BLANK_LINES) {
inner.push(Ir::BlankLine);
}
pending_blanks = 0;
if line.stmts.is_empty() {
inner.push(Ir::HardLine);
if let Some(comment) = &line.comment {
inner.push(comment.clone());
}
} else {
let last = line.stmts.len() - 1;
for (j, stmt) in line.stmts.iter().enumerate() {
inner.push(Ir::HardLine); inner.push(stmt.clone());
if j == last
&& let Some(comment) = &line.comment
{
inner.push(Ir::text(" "));
inner.push(comment.clone());
}
}
}
}
for _ in 0..trailing_blanks {
inner.push(Ir::BlankLine);
}
Some(Ir::concat(inner))
}
pub(crate) fn lower_body_range(
container: &SyntaxNode,
range: TextRange,
) -> Option<(Ir, TextRange)> {
let lines = collect_body_lines(container)?;
let mut window: Option<(usize, usize)> = None;
for (idx, line) in lines.iter().enumerate() {
if let Some(span) = line.range
&& span.start() <= range.end()
&& range.start() <= span.end()
{
let start = window.map_or(idx, |(start, _)| start);
window = Some((start, idx));
}
}
let (first, last) = window?;
let mut inner: Vec<Ir> = Vec::new();
let mut pending_blanks = 0usize;
let mut emitted = false;
for line in &lines[first..=last] {
if line.is_blank() {
pending_blanks += 1;
continue;
}
for _ in 0..pending_blanks.min(MAX_BLANK_LINES) {
inner.push(Ir::BlankLine);
}
pending_blanks = 0;
if line.stmts.is_empty() {
if emitted {
inner.push(Ir::HardLine);
}
if let Some(comment) = &line.comment {
inner.push(comment.clone());
}
emitted = true;
} else {
let last_stmt = line.stmts.len() - 1;
for (j, stmt) in line.stmts.iter().enumerate() {
if emitted {
inner.push(Ir::HardLine);
}
inner.push(stmt.clone());
if j == last_stmt
&& let Some(comment) = &line.comment
{
inner.push(Ir::text(" "));
inner.push(comment.clone());
}
emitted = true;
}
}
}
let span = lines[first].range?.cover(lines[last].range?);
Some((Ir::concat(inner), span))
}
pub(crate) fn base_indent_level(container: &SyntaxNode) -> usize {
container
.ancestors()
.filter(|node| node.kind() == SyntaxKind::BLOCK)
.filter(|block| {
block.parent().is_none_or(|parent| {
parent.kind() != SyntaxKind::MODULE_DEF || module_should_indent(&parent)
})
})
.count()
}
fn is_tight_binop(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::CARET | SyntaxKind::DOT_CARET | SyntaxKind::COLON | SyntaxKind::DOT
)
}
fn dot_caret_snug_retokenizes(operand: &SyntaxNode) -> bool {
operand
.last_token()
.is_some_and(|tok| matches!(tok.kind(), SyntaxKind::INTEGER | SyntaxKind::HEX_INT))
}