Skip to main content

vla_lang/
printer.rs

1//! The printer: VLA.bas's `WriteDatum` and `WritePretty`.
2//!
3//! `WriteDatum` writes a form flat: a symbol as it stands, a string literal
4//! quoted with `\` and `"` escaped (backslashes first), a list in
5//! parentheses with one space between elements, and `()` for an empty one.
6//! `WritePretty` is `VlaExpandText`'s and `VlaFormat`'s layout: a form that
7//! fits within 90 columns at its indent prints flat; a wider list opens with
8//! its head and gives every remaining element its own line four columns in,
9//! closing on the last. Width is measured as VBA's `Len` measures it, in
10//! UTF-16 code units.
11
12use crate::form::Form;
13
14/// VBA `WriteDatum`.
15pub fn write_datum(f: &Form) -> String {
16    let mut out = String::new();
17    write_into(f, &mut out);
18    out
19}
20
21fn write_into(f: &Form, out: &mut String) {
22    match f {
23        Form::Sym(s) => out.push_str(s),
24        Form::Str(s) => {
25            out.push('"');
26            out.push_str(&s.replace('\\', "\\\\").replace('"', "\\\""));
27            out.push('"');
28        }
29        Form::List(l) => {
30            out.push('(');
31            for (i, item) in l.items.iter().enumerate() {
32                if i > 0 {
33                    out.push(' ');
34                }
35                write_into(item, out);
36            }
37            out.push(')');
38        }
39    }
40}
41
42/// VBA `Len`: the width of a text in UTF-16 code units.
43fn vba_len(s: &str) -> usize {
44    s.encode_utf16().count()
45}
46
47/// VBA `WritePretty`, with lines joined by CRLF as the VBA joins them.
48pub fn write_pretty(f: &Form, indent: usize) -> String {
49    let pad = " ".repeat(indent);
50    let flat = write_datum(f);
51    let Form::List(l) = f else {
52        return format!("{pad}{flat}");
53    };
54    if indent + vba_len(&flat) <= 90 {
55        return format!("{pad}{flat}");
56    }
57    if l.items.is_empty() {
58        return format!("{pad}()");
59    }
60    let mut out = format!("{pad}({}", write_datum(&l.items[0]));
61    for item in &l.items[1..] {
62        out.push_str("\r\n");
63        out.push_str(&write_pretty(item, indent + 4));
64    }
65    out.push(')');
66    out
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::form::List;
73
74    #[test]
75    fn write_datum_escapes_as_the_reader_reads() {
76        assert_eq!(write_datum(&Form::sym("set!")), "set!");
77        assert_eq!(
78            write_datum(&Form::string("a \"q\" \\ b")),
79            "\"a \\\"q\\\" \\\\ b\""
80        );
81        assert_eq!(write_datum(&Form::list(vec![])), "()");
82        let f = Form::list(vec![
83            Form::sym("a"),
84            Form::list(vec![Form::sym("b"), Form::sym("c")]),
85            Form::string("d"),
86        ]);
87        assert_eq!(write_datum(&f), "(a (b c) \"d\")");
88    }
89
90    #[test]
91    fn write_pretty_breaks_a_wide_list_after_its_head() {
92        let short = Form::list(vec![Form::sym("a"), Form::sym("b")]);
93        assert_eq!(write_pretty(&short, 4), "    (a b)");
94        let wide = Form::List(List::new(
95            vec![
96                Form::sym("begin"),
97                Form::list(vec![
98                    Form::sym("set!"),
99                    Form::sym("x"),
100                    Form::string(&"y".repeat(60)),
101                ]),
102                Form::list(vec![
103                    Form::sym("set!"),
104                    Form::sym("z"),
105                    Form::string(&"w".repeat(60)),
106                ]),
107            ],
108            0,
109        ));
110        let text = write_pretty(&wide, 0);
111        let lines: Vec<&str> = text.split("\r\n").collect();
112        assert_eq!(lines.len(), 3);
113        assert_eq!(lines[0], "(begin");
114        assert!(lines[1].starts_with("    (set! x \"yyy"));
115        assert!(lines[2].ends_with("\"))"));
116        assert_eq!(write_pretty(&Form::sym("x"), 2), "  x");
117    }
118}