use crate::formatter::{Formatter, Sep};
use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
use rowan::NodeOrToken;
pub(crate) fn format_node(f: &mut Formatter, node: &SyntaxNode) {
use SyntaxKind::*;
match node.kind() {
SOURCE_FILE => source_file(f, node),
COMPONENT_BODY | UDP_BODY | ENUM_BODY | ENUM_ENTRY_BODY | STRUCT_BODY | CONSTRAINT_BODY => {
braced_body(f, node)
}
COMPONENT_DEF
| COMPONENT_NAMED_DEF
| COMPONENT_ANON_DEF
| UDP_DEF
| ENUM_DEF
| ENUM_ENTRY
| STRUCT_DEF
| CONSTRAINT_DEF
| CONSTRAINT_NAMED_DEF
| CONSTRAINT_ANON_DEF
| CONSTRAINT_INSTS
| LOCAL_PROPERTY_ASSIGNMENT
| NORMAL_PROP_ASSIGN
| ENCODE_PROP_ASSIGN
| PROP_MOD_ASSIGN
| PROP_KEYWORD
| PROP_MOD
| ENUM_PROP_ASSIGN
| EXPLICIT_COMPONENT_INST
| COMPONENT_INSTS
| COMPONENT_INST
| COMPONENT_INST_ALIAS
| COMPONENT_TYPE
| COMPONENT_INST_TYPE
| FIELD_INST_RESET
| INST_ADDR_FIXED
| INST_ADDR_STRIDE
| INST_ADDR_ALIGN
| DATA_TYPE
| BASIC_DATA_TYPE
| STRUCT_ELEM
| UDP_TYPE
| UDP_USAGE
| UDP_DEFAULT
| UDP_CONSTRAINT
| UDP_DATA_TYPE
| UDP_COMP_TYPE
| BINARY_EXPR
| TERNARY_EXPR
| PARAM_DEF_ELEM
| CONSTR_RELATIONAL
| CONSTR_PROP_ASSIGN
| CONSTR_INSIDE_ENUM => spaced(f, node),
INSTANCE_REF
| INSTANCE_REF_ELEMENT
| PROP_REF
| DYNAMIC_PROPERTY_ASSIGNMENT
| ARRAY_SUFFIX
| RANGE_SUFFIX
| ARRAY_TYPE_SUFFIX
| UNARY_EXPR
| PAREN_EXPR
| LITERAL
| ENUM_LITERAL
| CAST_TYPE
| CAST_WIDTH
| PARAM_ASSIGNMENT
| CONSTR_LHS
| CONSTR_INSIDE_VALUE => tight(f, node),
STRUCT_KV => struct_kv(f, node),
CONSTR_INSIDE_VALUES => inside_values(f, node),
CONCATENATE | REPLICATE | ARRAY_LITERAL | STRUCT_LITERAL | MACRO_CALL => {
flat_list(f, node)
}
PARAM_DEF | PARAM_INST => param_list(f, node),
_ => f.verbatim(node),
}
}
fn source_file(f: &mut Formatter, node: &SyntaxNode) {
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) => {
f.request(Sep::Newline);
f.token(&tok);
}
NodeOrToken::Node(item) => {
f.request(Sep::Newline);
format_node(f, &item);
}
}
}
}
fn braced_body(f: &mut Formatter, node: &SyntaxNode) {
f.request(Sep::Space);
if is_empty(node) {
for child in node.children_with_tokens() {
if let NodeOrToken::Token(tok) = child
&& !tok.kind().is_trivia()
{
f.token(&tok);
}
}
return;
}
let mut prev: Option<SyntaxNode> = None;
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::L_BRACE => {
f.token(&tok);
f.indent();
f.settle_width();
}
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::R_BRACE => {
f.dedent();
f.pin(Sep::Newline);
f.token(&tok);
}
NodeOrToken::Token(tok) => {
f.request(Sep::Newline);
f.token(&tok);
}
NodeOrToken::Node(item) => {
f.request(if shares_line_with(&item, prev.as_ref()) {
Sep::Space
} else {
Sep::Newline
});
format_node(f, &item);
prev = Some(item);
}
}
}
}
fn spaced(f: &mut Formatter, node: &SyntaxNode) {
let mut first = true;
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) => {
if !first && !is_terminator(&tok) {
f.request(Sep::Space);
}
f.token(&tok);
first = false;
}
NodeOrToken::Node(child) => {
if !first && !is_suffix(child.kind()) {
f.request(Sep::Space);
}
format_node(f, &child);
first = false;
}
}
}
}
fn tight(f: &mut Formatter, node: &SyntaxNode) {
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) => f.token(&tok),
NodeOrToken::Node(child) => format_node(f, &child),
}
}
}
fn inside_values(f: &mut Formatter, node: &SyntaxNode) {
let mut first = true;
let mut braced = false;
let mut after_comma = false;
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) => {
match tok.kind() {
SyntaxKind::L_BRACE => {
f.request(Sep::Space);
f.token(&tok);
f.request(Sep::Space);
braced = true;
}
SyntaxKind::R_BRACE => {
f.request(Sep::Space);
f.token(&tok);
braced = false;
}
_ => {
if !first && !braced && !is_terminator(&tok) {
f.request(Sep::Space);
}
f.token(&tok);
}
}
after_comma = tok.kind() == SyntaxKind::COMMA;
first = false;
}
NodeOrToken::Node(child) => {
if after_comma || (!first && !braced) {
f.request(Sep::Space);
}
format_node(f, &child);
after_comma = false;
first = false;
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Layout {
Flat,
Broken,
}
fn param_list_layout(node: &SyntaxNode) -> Layout {
let elements = node
.children()
.filter(|child| {
matches!(
child.kind(),
SyntaxKind::PARAM_DEF_ELEM | SyntaxKind::PARAM_ASSIGNMENT
)
})
.count();
if elements > 1 || forces_break(node) {
Layout::Broken
} else {
Layout::Flat
}
}
fn forces_break(node: &SyntaxNode) -> bool {
node.descendants_with_tokens().any(|child| match child {
NodeOrToken::Node(_) => false,
NodeOrToken::Token(tok) => {
tok.kind() == SyntaxKind::LINE_COMMENT
|| tok.kind().is_directive()
|| (tok.kind().is_comment() && tok.text().contains('\n'))
}
})
}
fn param_list(f: &mut Formatter, node: &SyntaxNode) {
match param_list_layout(node) {
Layout::Flat => flat_list(f, node),
Layout::Broken => broken_list(f, node),
}
}
fn flat_list(f: &mut Formatter, node: &SyntaxNode) {
let padded = node.children().next().is_some();
let mut after_comma = false;
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) => {
if padded && tok.kind() == SyntaxKind::R_BRACE {
f.request(Sep::Space);
}
f.token(&tok);
if padded && tok.kind() == SyntaxKind::L_BRACE {
f.request(Sep::Space);
}
after_comma = tok.kind() == SyntaxKind::COMMA;
}
NodeOrToken::Node(element) => {
if after_comma {
f.request(Sep::Space);
}
format_node(f, &element);
after_comma = false;
}
}
}
}
fn struct_kv(f: &mut Formatter, node: &SyntaxNode) {
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) => {
f.token(&tok);
if tok.kind() == SyntaxKind::COLON {
f.request(Sep::Space);
}
}
NodeOrToken::Node(value) => format_node(f, &value),
}
}
}
fn broken_list(f: &mut Formatter, node: &SyntaxNode) {
let outer = f.allow_blank_lines(false);
for child in node.children_with_tokens() {
match child {
NodeOrToken::Token(tok) if tok.kind().is_trivia() => f.trivia(&tok),
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::L_PAREN => {
f.token(&tok);
f.indent();
f.settle_width();
}
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::R_PAREN => {
f.dedent();
f.pin(Sep::Newline);
f.token(&tok);
}
NodeOrToken::Token(tok) => f.token(&tok),
NodeOrToken::Node(element) => {
f.request(Sep::Newline);
format_node(f, &element);
}
}
}
f.allow_blank_lines(outer);
}
fn is_terminator(tok: &SyntaxToken) -> bool {
matches!(tok.kind(), SyntaxKind::SEMICOLON | SyntaxKind::COMMA)
}
fn is_suffix(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::ARRAY_SUFFIX | SyntaxKind::RANGE_SUFFIX | SyntaxKind::ARRAY_TYPE_SUFFIX
)
}
fn is_empty(node: &SyntaxNode) -> bool {
node.children_with_tokens().all(|child| match child {
NodeOrToken::Node(_) => false,
NodeOrToken::Token(tok) => matches!(
tok.kind(),
SyntaxKind::WHITESPACE | SyntaxKind::L_BRACE | SyntaxKind::R_BRACE
),
})
}
fn shares_line_with(item: &SyntaxNode, prev: Option<&SyntaxNode>) -> bool {
prev.is_some_and(|prev| is_sw_or_hw(prev) && is_sw_or_hw(item) && !preceded_by_newline(item))
}
fn is_sw_or_hw(node: &SyntaxNode) -> bool {
node.kind() == SyntaxKind::LOCAL_PROPERTY_ASSIGNMENT
&& first_significant(node)
.is_some_and(|tok| matches!(tok.kind(), SyntaxKind::SW_KW | SyntaxKind::HW_KW))
}
fn first_significant(node: &SyntaxNode) -> Option<SyntaxToken> {
tokens_of(node).find(|tok| !tok.kind().is_trivia())
}
fn preceded_by_newline(node: &SyntaxNode) -> bool {
tokens_of(node)
.take_while(|tok| tok.kind().is_trivia())
.any(|tok| tok.text().contains('\n'))
}
fn tokens_of(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> {
let end = node.text_range().end();
std::iter::successors(node.first_token(), |tok: &SyntaxToken| tok.next_token())
.take_while(move |tok| tok.text_range().end() <= end)
}