scheme-edit 0.2.0

Lossless Scheme S-expression parser and editor (toml_edit-style CST)
Documentation
use crate::cst::{Item, ListKind, Node};

/// A symbol atom.
pub fn sym(s: &str) -> Node {
    Node::Atom(s.to_string())
}

/// Any atom rendered verbatim (numbers, booleans, keywords, ...).
pub fn atom(s: &str) -> Node {
    Node::Atom(s.to_string())
}

/// A string literal node; escapes `\` and `"` only.
pub fn string_lit(s: &str) -> Node {
    Node::Str(escape_string(s))
}

/// `'name`
pub fn quoted_sym(s: &str) -> Node {
    Node::Prefixed {
        prefix: "'".to_string(),
        inner: Box::new(sym(s)),
    }
}

/// A paren list with children separated by single spaces.
pub fn list(children: Vec<Node>) -> Node {
    let mut items = Vec::with_capacity(children.len() * 2);
    for (i, child) in children.into_iter().enumerate() {
        if i > 0 {
            items.push(Item::Ws(" ".to_string()));
        }
        items.push(Item::Node(child));
    }
    Node::List {
        kind: ListKind::Paren,
        items,
    }
}

/// The quoted, escaped literal as text: `a"b` -> `"a\"b"`.
pub fn escape_string(s: &str) -> String {
    let mut out = String::with_capacity(s.len() + 2);
    out.push('"');
    for c in s.chars() {
        if c == '"' || c == '\\' {
            out.push('\\');
        }
        out.push(c);
    }
    out.push('"');
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn string_lit_escaping_matches_libguix() {
        assert_eq!(escape_string("hello"), "\"hello\"");
        assert_eq!(escape_string("a\"b"), "\"a\\\"b\"");
        assert_eq!(escape_string("a\\b"), "\"a\\\\b\"");
        assert_eq!(string_lit("a\"b").to_source(), "\"a\\\"b\"");
    }

    #[test]
    fn built_list_prints_flat() {
        let n = list(vec![sym("name"), quoted_sym("guix")]);
        assert_eq!(n.to_source(), "(name 'guix)");
    }
}