Skip to main content

scheme_edit/
build.rs

1use crate::cst::{Item, ListKind, Node};
2
3/// A symbol atom.
4pub fn sym(s: &str) -> Node {
5    Node::Atom(s.to_string())
6}
7
8/// Any atom rendered verbatim (numbers, booleans, keywords, ...).
9pub fn atom(s: &str) -> Node {
10    Node::Atom(s.to_string())
11}
12
13/// A string literal node; escapes `\` and `"` only.
14pub fn string_lit(s: &str) -> Node {
15    Node::Str(escape_string(s))
16}
17
18/// `'name`
19pub fn quoted_sym(s: &str) -> Node {
20    Node::Prefixed {
21        prefix: "'".to_string(),
22        inner: Box::new(sym(s)),
23    }
24}
25
26/// A paren list with children separated by single spaces.
27pub fn list(children: Vec<Node>) -> Node {
28    let mut items = Vec::with_capacity(children.len() * 2);
29    for (i, child) in children.into_iter().enumerate() {
30        if i > 0 {
31            items.push(Item::Ws(" ".to_string()));
32        }
33        items.push(Item::Node(child));
34    }
35    Node::List {
36        kind: ListKind::Paren,
37        items,
38    }
39}
40
41/// The quoted, escaped literal as text: `a"b` -> `"a\"b"`.
42pub fn escape_string(s: &str) -> String {
43    let mut out = String::with_capacity(s.len() + 2);
44    out.push('"');
45    for c in s.chars() {
46        if c == '"' || c == '\\' {
47            out.push('\\');
48        }
49        out.push(c);
50    }
51    out.push('"');
52    out
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn string_lit_escaping_matches_libguix() {
61        assert_eq!(escape_string("hello"), "\"hello\"");
62        assert_eq!(escape_string("a\"b"), "\"a\\\"b\"");
63        assert_eq!(escape_string("a\\b"), "\"a\\\\b\"");
64        assert_eq!(string_lit("a\"b").to_source(), "\"a\\\"b\"");
65    }
66
67    #[test]
68    fn built_list_prints_flat() {
69        let n = list(vec![sym("name"), quoted_sym("guix")]);
70        assert_eq!(n.to_source(), "(name 'guix)");
71    }
72}