use crate::analyzer::{Rule, TectParser};
use pest::Parser;
struct Block {
content: String,
start_pos: usize,
end_pos: usize,
}
pub fn format_tect_source(content: &str) -> Option<String> {
let mut blocks = Vec::new();
let parsed = match TectParser::parse(Rule::program, content) {
Ok(mut p) => p.next().unwrap(),
Err(_) => return None,
};
for pair in parsed.into_inner() {
if pair.as_rule() == Rule::EOI {
continue;
}
let span = pair.as_span();
let formatted_content = match pair.as_rule() {
Rule::func_def => format_function(pair),
Rule::import_stmt => pair.as_str().trim().to_string(),
Rule::comment | Rule::flow_step => pair.as_str().trim().to_string(),
_ => pair.as_str().trim().to_string(), };
if !formatted_content.is_empty() {
blocks.push(Block {
content: formatted_content,
start_pos: span.start(),
end_pos: span.end(),
});
}
}
if blocks.is_empty() {
return Some(String::new());
}
let mut result = String::new();
result.push_str(&blocks[0].content);
for i in 1..blocks.len() {
let prev = &blocks[i - 1];
let curr = &blocks[i];
let gap = &content[prev.end_pos..curr.start_pos];
let newline_count = gap.chars().filter(|&c| c == '\n').count();
let separator = if newline_count >= 2 { "\n\n" } else { "\n" };
result.push_str(separator);
result.push_str(&curr.content);
}
result.push('\n');
Some(result)
}
fn format_token_list(pair: pest::iterators::Pair<Rule>) -> String {
pair.into_inner()
.map(|t| {
let inner = t.into_inner().next().unwrap();
match inner.as_rule() {
Rule::collection => {
let name = inner.into_inner().next().unwrap().as_str().trim();
format!("[{}]", name)
}
_ => inner.as_str().trim().to_string(),
}
})
.collect::<Vec<_>>()
.join(", ")
}
fn format_function(pair: pest::iterators::Pair<Rule>) -> String {
let mut inner = pair.clone().into_inner();
let mut parts = Vec::new();
let mut last_inner_pos = None;
while let Some(p) = inner.peek() {
let pos = p.as_span().start();
if last_inner_pos == Some(pos) {
break;
}
last_inner_pos = Some(pos);
if p.as_rule() == Rule::doc_line {
parts.push(inner.next().unwrap().as_str().trim().to_string());
} else {
break;
}
}
let mut header = Vec::new();
while let Some(p) = inner.peek() {
if p.as_rule() == Rule::token_list {
header.push(format_token_list(inner.next().unwrap()));
} else if matches!(p.as_rule(), Rule::ident | Rule::kw_function) {
header.push(inner.next().unwrap().as_str().trim().to_string());
} else {
break;
}
}
if !header.is_empty() {
parts.push(header.join(" "));
}
if let Some(p) = inner.next() {
for child in p.into_inner() {
if child.as_rule() == Rule::output_line {
let raw = child.as_str().trim();
let symbol = if raw.starts_with('>') { ">" } else { "|" };
let mut output_parts = child.into_inner();
let tokens = output_parts
.next()
.map(format_token_list)
.unwrap_or_default();
parts.push(format!(" {} {}", symbol, tokens));
}
}
}
parts.join("\n")
}