Skip to main content

links_notation/
nested_format.rs

1use crate::{LiNo, escape_reference};
2
3fn parts<T>(link: &LiNo<T>) -> (Option<&T>, &[LiNo<T>]) {
4    match link {
5        LiNo::Ref(id) => (Some(id), &[]),
6        LiNo::Link { id, values } => (id.as_ref(), values),
7    }
8}
9fn atom<T>(link: &LiNo<T>) -> bool {
10    let (id, values) = parts(link);
11    id.is_some() && values.is_empty()
12}
13fn can_nest<T>(link: &LiNo<T>) -> bool {
14    let (id, values) = parts(link);
15    !atom(link) && !(id.is_none() && values.len() == 1 && !atom(&values[0]))
16}
17fn line_holds<T>(link: &LiNo<T>, count: usize) -> bool {
18    let (id, values) = parts(link);
19    id.is_some() || count >= 2 || (count == 1 && atom(&values[0]))
20}
21fn write<T: ToString>(
22    link: &LiNo<T>,
23    pad: &str,
24    indent: &str,
25    under_name: bool,
26    lines: &mut Vec<String>,
27) {
28    let (id, values) = parts(link);
29    if !line_holds(link, values.len()) || (under_name && id.is_none() && values.len() == 1) {
30        lines.push(format!("{pad}{link}"));
31        return;
32    }
33    let mut count = values.len();
34    while count > 0 && can_nest(&values[count - 1]) {
35        count -= 1;
36    }
37    while count < values.len() && !line_holds(link, count) {
38        count += 1;
39    }
40    let mut head = Vec::new();
41    if let Some(id) = id {
42        head.push(format!("{}:", escape_reference(&id.to_string())));
43    }
44    for value in &values[..count] {
45        head.push(if atom(value) {
46            escape_reference(&parts(value).0.unwrap().to_string())
47        } else {
48            value.to_string()
49        });
50    }
51    lines.push(format!("{pad}{}", head.join(" ")));
52    let inner = format!("{pad}{indent}");
53    for value in &values[count..] {
54        write(
55            value,
56            &inner,
57            indent,
58            under_name || (id.is_some() && count == 0),
59            lines,
60        );
61    }
62}
63
64/// Write recursively nested lines with two spaces per level and no final newline.
65/// Read by folding groups in a group-aware binding; Rust's parser keeps path semantics.
66pub fn format_nested<T: ToString>(links: &[LiNo<T>]) -> String {
67    format_nested_with_indent(links, "  ")
68}
69
70/// Write nested lines using nonempty spaces. Panics for invalid indentation.
71pub fn format_nested_with_indent<T: ToString>(links: &[LiNo<T>], indent: &str) -> String {
72    assert!(
73        !indent.is_empty() && indent.chars().all(|c| c == ' '),
74        "indent must contain only spaces and be nonempty"
75    );
76    let mut lines = Vec::new();
77    for link in links {
78        write(link, "", indent, false, &mut lines);
79    }
80    lines.join("\n")
81}