use crate::syntax::{Sk, sk};
use rowan::{GreenNode, GreenNodeBuilder};
pub(crate) fn build(src: &str) -> GreenNode {
let mut b = GreenNodeBuilder::new();
b.start_node(sk(Sk::Root));
for line in src.split_inclusive('\n') {
let (content, term) = split_terminator(line);
process_line(&mut b, content, term);
}
b.finish_node(); b.finish()
}
fn split_terminator(line: &str) -> (&str, &str) {
if let Some(rest) = line.strip_suffix("\r\n") {
(rest, &line[rest.len()..])
} else if let Some(rest) = line.strip_suffix('\n') {
(rest, &line[rest.len()..])
} else {
(line, "")
}
}
fn process_line(b: &mut GreenNodeBuilder<'static>, content: &str, term: &str) {
let rest = content.trim_start();
let indent = &content[..content.len() - rest.len()];
if rest.is_empty() {
emit_ws(b, indent);
emit_newline(b, term);
return;
}
if matches!(rest.as_bytes()[0], b'#' | b'!') {
emit_ws(b, indent);
b.token(sk(Sk::Comment), rest);
emit_newline(b, term);
return;
}
b.start_node(sk(Sk::Entry));
emit_ws(b, indent);
build_entry(b, rest);
emit_newline(b, term);
b.finish_node(); }
fn build_entry(b: &mut GreenNodeBuilder<'static>, rest: &str) {
let Some(sep_idx) = rest.find(['=', ':']) else {
b.token(sk(Sk::Error), rest);
return;
};
let key_region = &rest[..sep_idx];
let key = key_region.trim_end();
if !key.is_empty() {
b.token(sk(Sk::Key), key);
}
emit_ws(b, &key_region[key.len()..]);
b.token(sk(Sk::Sep), &rest[sep_idx..sep_idx + 1]);
let val_region = &rest[sep_idx + 1..];
let core = val_region.trim();
let lead_len = val_region.len() - val_region.trim_start().len();
emit_ws(b, &val_region[..lead_len]);
b.start_node(sk(Sk::Value));
if !core.is_empty() {
b.token(sk(Sk::ValStr), core);
}
b.finish_node(); emit_ws(b, &val_region[lead_len + core.len()..]);
}
fn emit_ws(b: &mut GreenNodeBuilder<'static>, ws: &str) {
if !ws.is_empty() {
b.token(sk(Sk::Ws), ws);
}
}
fn emit_newline(b: &mut GreenNodeBuilder<'static>, term: &str) {
if !term.is_empty() {
b.token(sk(Sk::Newline), term);
}
}