Skip to main content

hara_native/kernel/
form.rs

1#[derive(Debug, Clone, PartialEq)]
2pub enum Form {
3    Nil,
4    Bool(bool),
5    Number(i64),
6    Float(f64),
7    BigInteger(BigInt),
8    Character(char),
9    Regex(String),
10    Tagged(String, Box<Form>),
11    Metadata(Box<Form>, Box<Form>),
12    Symbol(String),
13    Keyword(String),
14    String(String),
15    Map(Vec<(Form, Form)>),
16    Set(Vec<Form>),
17    Vector(Vec<Form>),
18    List(Vec<Form>),
19}
20
21pub(crate) fn display_string(value: &str) -> String {
22    let mut output = String::from("\"");
23    for ch in value.chars() {
24        match ch {
25            '\n' => output.push_str("\\n"),
26            '\r' => output.push_str("\\r"),
27            '\t' => output.push_str("\\t"),
28            '\u{0008}' => output.push_str("\\b"),
29            '\u{000c}' => output.push_str("\\f"),
30            '\\' => output.push_str("\\\\"),
31            '"' => output.push_str("\\\""),
32            ch if ch.is_control() => output.push_str(&format!("\\u{:04X}", ch as u32)),
33            ch => output.push(ch),
34        }
35    }
36    output.push('"');
37    output
38}
39
40pub(crate) fn display_regex(value: &str) -> String {
41    let mut output = String::from("#\"");
42    let mut backslashes = 0usize;
43    for ch in value.chars() {
44        if ch == '"' && backslashes % 2 == 0 {
45            output.push('\\');
46        }
47        output.push(ch);
48        backslashes = if ch == '\\' { backslashes + 1 } else { 0 };
49    }
50    output.push('"');
51    output
52}
53
54fn display_forms(values: &[Form], start: &str, end: &str) -> String {
55    format!(
56        "{start}{}{end}",
57        values
58            .iter()
59            .map(ToString::to_string)
60            .collect::<Vec<_>>()
61            .join(" ")
62    )
63}
64
65impl std::fmt::Display for Form {
66    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        let output = match self {
68            Self::Nil => "nil".into(),
69            Self::Bool(value) => value.to_string(),
70            Self::Number(value) => value.to_string(),
71            Self::Float(value) if value.is_finite() && value.fract() == 0.0 => {
72                format!("(double {value:.1})")
73            }
74            Self::Float(value) if value.is_finite() => format!("(double {value})"),
75            Self::Float(_) => panic!("non-finite number"),
76            Self::BigInteger(value) => value.to_string(),
77            Self::Character('\n') => "\\newline".into(),
78            Self::Character(' ') => "\\space".into(),
79            Self::Character('\t') => "\\tab".into(),
80            Self::Character('\u{0008}') => "\\backspace".into(),
81            Self::Character('\u{000c}') => "\\formfeed".into(),
82            Self::Character('\r') => "\\return".into(),
83            Self::Character(value) if value.is_control() => format!("\\u{:04X}", *value as u32),
84            Self::Character(value) => format!("\\{value}"),
85            Self::Regex(value) => display_regex(value),
86            Self::Tagged(tag, value) => format!("#{tag}{value}"),
87            Self::Metadata(_, value) => value.to_string(),
88            Self::Symbol(value) => value.clone(),
89            Self::Keyword(value) => format!(":{value}"),
90            Self::String(value) => display_string(value),
91            Self::Map(entries) => {
92                let values = entries
93                    .iter()
94                    .flat_map(|(key, value)| [key.to_string(), value.to_string()])
95                    .collect::<Vec<_>>();
96                format!("{{{}}}", values.join(" "))
97            }
98            Self::Set(values) => display_forms(values, "#{", "}"),
99            Self::Vector(values) => display_forms(values, "[", "]"),
100            Self::List(values) => display_forms(values, "(", ")"),
101        };
102        formatter.write_str(&output)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::Form;
109    use crate::kernel::parse;
110
111    #[test]
112    fn canonical_readable_forms_round_trip() {
113        let sources = [
114            "nil",
115            "true",
116            "-42",
117            "\\newline",
118            "\\u0000",
119            "\"line\\nvalue\"",
120            ":hara/name",
121            "hara/name",
122            "(quote [1 2 3])",
123            "{:message \"value\" :flags #{:a :b}}",
124            "#math[:tensor 42]",
125            "#\"\\d+\"",
126        ];
127        for regex in [Form::Regex(r#"\""#.into()), Form::Regex(r#"\\\""#.into())] {
128            let readable = regex.to_string();
129            assert_eq!(parse(&readable).unwrap(), regex, "{readable}");
130        }
131
132        for source in sources {
133            let form = parse(source).unwrap();
134            let readable = form.to_string();
135            assert_eq!(parse(&readable).unwrap(), form, "{source} -> {readable}");
136        }
137    }
138
139    #[test]
140    fn metadata_printing_is_canonical() {
141        let metadata = parse("^:private [1]").unwrap();
142        assert_eq!(metadata.to_string(), "[1]");
143        assert_eq!(parse(&metadata.to_string()).unwrap(), parse("[1]").unwrap());
144    }
145}
146use num_bigint::BigInt;