use rowan::NodeOrToken;
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::TYPE_ANNOTATION => lower_type_annotation(node),
SyntaxKind::MATRIX_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::TUPLE_EXPR | SyntaxKind::VECT_EXPR | SyntaxKind::BRACES => {
lower_collection(node)
}
SyntaxKind::PAREN_EXPR => lower_paren(node),
SyntaxKind::PAREN_BLOCK => lower_paren_block(node),
SyntaxKind::BARE_TUPLE_EXPR => lower_bare_tuple(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),
_ => 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 {
let is_assignment = node.kind() == SyntaxKind::ASSIGNMENT_EXPR;
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 => {
let tight = !is_assignment && is_tight_binop(tok.kind());
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;
}
_ => return lower_transparent(node),
},
}
}
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 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 {
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 lower_transparent(node),
},
}
}
let (true, true, false, Some(inner)) = (saw_lparen, saw_rparen, extra_operand, inner) else {
return lower_transparent(node);
};
Ir::group(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::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 mut parts: Vec<Ir> = Vec::with_capacity(statements.len() * 2 + 1);
parts.push(Ir::text("("));
for (i, stmt) in statements.into_iter().enumerate() {
if i > 0 {
parts.push(Ir::text("; "));
}
parts.push(stmt);
}
parts.push(Ir::text(")"));
Ir::concat(parts)
}
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::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: 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);
};
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::COMMA => {
parts.push(Ir::text(", "));
expect_item = true;
}
NodeOrToken::Token(tok) if !expect_item && tok.kind() == SyntaxKind::COLON => {
parts.push(Ir::text(": "));
expect_item = true;
}
_ => return lower_transparent(node),
}
}
if expect_item {
return lower_transparent(node);
}
Ir::concat(parts)
}
fn lower_export_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 matches!(
tok.kind(),
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT | SyntaxKind::NEWLINE
) =>
{
return lower_transparent(node);
}
NodeOrToken::Token(tok) if kw.is_none() => kw = Some(tok.clone()),
_ => rest.push(el),
}
}
let Some(kw) = kw else {
return lower_transparent(node);
};
let mut parts: Vec<Ir> = vec![Ir::text(kw.text().to_string())];
let mut expect_item = true;
for el in &rest {
match el {
NodeOrToken::Token(tok) if !expect_item && tok.kind() == SyntaxKind::COMMA => {
parts.push(Ir::text(","));
expect_item = true;
}
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::COMMA => {
return lower_transparent(node);
}
_ => {
if expect_item {
parts.push(Ir::text(" "));
expect_item = false;
}
parts.push(match el {
NodeOrToken::Node(child) => lower_node(child),
NodeOrToken::Token(tok) => Ir::text(tok.text().to_string()),
});
}
}
}
if expect_item {
return lower_transparent(node);
}
Ir::concat(parts)
}
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 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 mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut items: Vec<Ir> = Vec::new();
let mut params: Option<Ir> = None;
let mut pending_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 => {}
SyntaxKind::COMMA => {
if pending_comma || items.is_empty() {
return lower_transparent(node);
}
pending_comma = true;
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG => {
if params.is_some() || (!items.is_empty() && !pending_comma) {
return lower_transparent(node);
}
items.push(lower_node(&child));
pending_comma = false;
}
SyntaxKind::PARAMETERS => {
if pending_comma || params.is_some() {
return lower_transparent(node);
}
params = Some(lower_node(&child));
}
_ => return lower_transparent(node),
},
}
}
let (Some(open), Some(close)) = (open, close) else {
return lower_transparent(node);
};
if let Some(params) = params {
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(params);
parts.push(Ir::text(close));
return Ir::concat(parts);
}
if items.is_empty() {
return Ir::concat([Ir::text(open), Ir::text(close)]);
}
let mut inner: Vec<Ir> = vec![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::if_break(",", ""));
Ir::group(Ir::concat([
Ir::text(open),
Ir::indent(Ir::concat(inner)),
Ir::SoftLine,
Ir::text(close),
]))
}
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 keep_singleton_comma = node.kind() == SyntaxKind::TUPLE_EXPR;
let mut open: Option<String> = None;
let mut close: Option<String> = None;
let mut items: Vec<Ir> = Vec::new();
let mut pending_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 => {}
SyntaxKind::COMMA => {
if pending_comma || items.is_empty() {
return lower_transparent(node);
}
pending_comma = true;
}
_ => return lower_transparent(node),
},
NodeOrToken::Node(child) => match child.kind() {
SyntaxKind::ARG | SyntaxKind::KEYWORD_ARG => {
if !items.is_empty() && !pending_comma {
return lower_transparent(node);
}
items.push(lower_node(&child));
pending_comma = false;
}
_ => return lower_transparent(node),
},
}
}
let (Some(open), Some(close)) = (open, close) else {
return lower_transparent(node);
};
if items.is_empty() {
return Ir::concat([Ir::text(open), Ir::text(close)]);
}
let trailing = if keep_singleton_comma && items.len() == 1 {
Ir::text(",")
} else {
Ir::if_break(",", "")
};
let mut inner: Vec<Ir> = vec![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(trailing);
Ir::group(Ir::concat([
Ir::text(open),
Ir::indent(Ir::concat(inner)),
Ir::SoftLine,
Ir::text(close),
]))
}
fn lower_bare_tuple(node: &SyntaxNode) -> Ir {
let mut parts: 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::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 {
if !pending_comma {
return lower_transparent(node);
}
parts.push(Ir::text(", "));
}
parts.push(lower_node(&child));
item_count += 1;
pending_comma = false;
}
}
}
if pending_comma {
return lower_transparent(node);
}
Ir::concat(parts)
}
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 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::FOR_KW if !for_kw && els.is_empty() => for_kw = true,
SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT | SyntaxKind::NEWLINE => {
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::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 {
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 => open = Some(tok.text().to_string()),
SyntaxKind::RBRACKET => close = Some(tok.text().to_string()),
SyntaxKind::WHITESPACE => continue,
SyntaxKind::SEMICOLON => {
if prev_was_semicolon {
return lower_transparent(node);
}
rows.push(Vec::new());
prev_was_semicolon = true;
continue;
}
SyntaxKind::NEWLINE => rows.push(Vec::new()),
_ => return lower_transparent(node),
},
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 lower_transparent(node),
}
}
}
_ => return lower_transparent(node),
},
}
prev_was_semicolon = false;
}
let (Some(open), Some(close)) = (open, close) else {
return lower_transparent(node);
};
rows.retain(|row| !row.is_empty());
if rows.is_empty() {
return 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);
}
}
Ir::group(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 => open = Some(tok.text().to_string()),
SyntaxKind::RBRACKET => 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;
let mut iter = node.children_with_tokens().peekable();
while let Some(el) = iter.next() {
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 => parts.push(lower_trivia(&tok, iter.peek())),
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_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_node(&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_node(&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_node(&header));
}
if let Some(body) = body {
parts.push(body);
}
Some(Ir::concat(parts))
}
#[derive(Default)]
struct BodyLine {
stmts: Vec<Ir>,
comment: Option<Ir>,
}
impl BodyLine {
fn is_blank(&self) -> bool {
self.stmts.is_empty() && self.comment.is_none()
}
}
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;
}
lines.last_mut().unwrap().stmts.push(lower_node(&child));
*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));
*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()));
*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))
}
fn is_tight_binop(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::CARET | SyntaxKind::COLON | SyntaxKind::DOT
)
}