1
use base64::Engine;
2
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
3

            
4
use super::value::{Pair, Value};
5

            
6
const BYTES_INLINE_THRESHOLD: usize = 32;
7

            
8
#[must_use]
9
63852
pub fn format_value(value: &Value) -> String {
10
63852
    let mut out = String::new();
11
63852
    write_value(&mut out, value);
12
63852
    out
13
63852
}
14

            
15
71086
fn write_value(out: &mut String, value: &Value) {
16
14208
    match value {
17
6475
        Value::Nil => out.push_str("nil"),
18
2326
        Value::T => out.push('t'),
19
14208
        Value::Number(n) if *n.denom() == 1 => {
20
13871
            out.push_str(&n.numer().to_string());
21
13871
        }
22
337
        Value::Number(n) => {
23
337
            out.push_str(&format!("{}/{}", n.numer(), n.denom()));
24
337
        }
25
42580
        Value::String(s) => write_string_literal(out, s),
26
3989
        Value::Symbol(s) => out.push_str(s),
27
90
        Value::Bytes(b) => write_bytes_literal(out, b),
28
1166
        Value::Pair(pair) => write_pair_or_list(out, pair),
29
1
        Value::Vector(items) => write_vector(out, items),
30
1
        Value::Closure(c) => {
31
1
            out.push_str(&format!("#<closure:{}>", c.code_id));
32
1
        }
33
1
        Value::Struct { name, fields } => write_struct(out, name, fields),
34
        Value::Commodity {
35
249
            amount,
36
249
            commodity_id,
37
        } => {
38
            // Plist envelope keeps the wire shape uniform with native
39
            // results: emacs `(read)` recovers `(:commodity <ratio> :id "<uuid>")`.
40
249
            out.push_str("(:commodity ");
41
249
            if *amount.denom() == 1 {
42
249
                out.push_str(&amount.numer().to_string());
43
249
            } else {
44
                out.push_str(&format!("{}/{}", amount.numer(), amount.denom()));
45
            }
46
249
            out.push_str(" :id \"");
47
249
            out.push_str(&commodity_id.to_string());
48
249
            out.push_str("\")");
49
        }
50
    }
51
71086
}
52

            
53
42580
fn write_string_literal(out: &mut String, s: &str) {
54
42580
    out.push('"');
55
2176516
    for ch in s.chars() {
56
2176516
        match ch {
57
6309
            '\\' => out.push_str("\\\\"),
58
27723
            '"' => out.push_str("\\\""),
59
6475
            '\n' => out.push_str("\\n"),
60
            '\t' => out.push_str("\\t"),
61
            '\r' => out.push_str("\\r"),
62
2136009
            other => out.push(other),
63
        }
64
    }
65
42580
    out.push('"');
66
42580
}
67

            
68
90
fn write_bytes_literal(out: &mut String, bytes: &[u8]) {
69
90
    if bytes.len() <= BYTES_INLINE_THRESHOLD {
70
87
        out.push_str("#u8(");
71
87
        let mut first = true;
72
205
        for byte in bytes {
73
205
            if first {
74
86
                first = false;
75
119
            } else {
76
119
                out.push(' ');
77
119
            }
78
205
            out.push_str(&byte.to_string());
79
        }
80
87
        out.push(')');
81
3
    } else {
82
3
        out.push_str("#\"");
83
3
        out.push_str(&BASE64_STANDARD.encode(bytes));
84
3
        out.push('"');
85
3
    }
86
90
}
87

            
88
1166
fn write_pair_or_list(out: &mut String, pair: &Pair) {
89
1166
    out.push('(');
90
1166
    write_value(out, &pair.car);
91
1166
    let mut cdr = &pair.cdr;
92
    loop {
93
7228
        match cdr {
94
1164
            Value::Nil => break,
95
6062
            Value::Pair(next) => {
96
6062
                out.push(' ');
97
6062
                write_value(out, &next.car);
98
6062
                cdr = &next.cdr;
99
6062
            }
100
2
            other => {
101
2
                out.push_str(" . ");
102
2
                write_value(out, other);
103
2
                break;
104
            }
105
        }
106
    }
107
1166
    out.push(')');
108
1166
}
109

            
110
1
fn write_vector(out: &mut String, items: &[Value]) {
111
1
    out.push_str("#(");
112
1
    let mut first = true;
113
2
    for item in items {
114
2
        if first {
115
1
            first = false;
116
1
        } else {
117
1
            out.push(' ');
118
1
        }
119
2
        write_value(out, item);
120
    }
121
1
    out.push(')');
122
1
}
123

            
124
1
fn write_struct(out: &mut String, name: &str, fields: &[Value]) {
125
1
    out.push_str("#S(");
126
1
    out.push_str(name);
127
2
    for field in fields {
128
2
        out.push(' ');
129
2
        write_value(out, field);
130
2
    }
131
1
    out.push(')');
132
1
}
133

            
134
#[cfg(test)]
135
mod tests {
136
    use super::super::value::{Closure, Fraction, Pair};
137
    use super::*;
138
    use crate::ast::Expr;
139
    use crate::reader::Reader;
140

            
141
    #[test]
142
1
    fn formats_nil() {
143
1
        assert_eq!(format_value(&Value::Nil), "nil");
144
1
    }
145

            
146
    #[test]
147
1
    fn formats_t() {
148
1
        assert_eq!(format_value(&Value::T), "t");
149
1
    }
150

            
151
    #[test]
152
1
    fn a_list_ending_in_nil_prints_as_a_proper_list() {
153
1
        let list = Pair::cons(Value::Symbol("A".into()), Value::Nil);
154
1
        assert_eq!(format_value(&list), "(A)");
155
1
    }
156

            
157
    #[test]
158
1
    fn formats_integer_number() {
159
1
        assert_eq!(
160
1
            format_value(&Value::Number(Fraction::from_integer(42))),
161
            "42"
162
        );
163
1
    }
164

            
165
    #[test]
166
1
    fn formats_negative_integer() {
167
1
        assert_eq!(
168
1
            format_value(&Value::Number(Fraction::from_integer(-7))),
169
            "-7"
170
        );
171
1
    }
172

            
173
    #[test]
174
1
    fn formats_proper_fraction() {
175
1
        assert_eq!(format_value(&Value::Number(Fraction::new(3, 4))), "3/4");
176
1
    }
177

            
178
    #[test]
179
1
    fn formats_string_with_escapes() {
180
1
        let v = Value::String("a\nb\"c\\d".to_string());
181
1
        assert_eq!(format_value(&v), "\"a\\nb\\\"c\\\\d\"");
182
1
    }
183

            
184
    #[test]
185
1
    fn formats_symbol_verbatim() {
186
1
        assert_eq!(format_value(&Value::Symbol("FOO".into())), "FOO");
187
1
    }
188

            
189
    #[test]
190
1
    fn formats_short_bytes_as_u8_literal() {
191
1
        let v = Value::Bytes(vec![0, 1, 255]);
192
1
        assert_eq!(format_value(&v), "#u8(0 1 255)");
193
1
    }
194

            
195
    #[test]
196
1
    fn formats_empty_bytes_as_u8_literal() {
197
1
        assert_eq!(format_value(&Value::Bytes(Vec::new())), "#u8()");
198
1
    }
199

            
200
    #[test]
201
1
    fn formats_bytes_at_inline_threshold() {
202
1
        let bytes: Vec<u8> = (0..BYTES_INLINE_THRESHOLD as u8).collect();
203
1
        let formatted = format_value(&Value::Bytes(bytes.clone()));
204
1
        assert!(formatted.starts_with("#u8("));
205
1
        assert!(formatted.ends_with(')'));
206
1
    }
207

            
208
    #[test]
209
1
    fn formats_long_bytes_as_base64_literal() {
210
1
        let bytes: Vec<u8> = (0..=BYTES_INLINE_THRESHOLD as u8).collect();
211
1
        let formatted = format_value(&Value::Bytes(bytes.clone()));
212
1
        assert!(formatted.starts_with("#\""));
213
1
        assert!(formatted.ends_with('"'));
214
1
        let payload = &formatted[2..formatted.len() - 1];
215
1
        let decoded = BASE64_STANDARD.decode(payload).unwrap();
216
1
        assert_eq!(decoded, bytes);
217
1
    }
218

            
219
    #[test]
220
1
    fn formats_proper_list() {
221
1
        let list = Pair::cons(
222
1
            Value::Number(Fraction::from_integer(1)),
223
1
            Pair::cons(
224
1
                Value::Number(Fraction::from_integer(2)),
225
1
                Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
226
            ),
227
        );
228
1
        assert_eq!(format_value(&list), "(1 2 3)");
229
1
    }
230

            
231
    #[test]
232
1
    fn formats_dotted_pair() {
233
1
        let pair = Pair::cons(Value::Symbol("A".into()), Value::Symbol("B".into()));
234
1
        assert_eq!(format_value(&pair), "(A . B)");
235
1
    }
236

            
237
    #[test]
238
1
    fn formats_improper_list() {
239
1
        let list = Pair::cons(
240
1
            Value::Number(Fraction::from_integer(1)),
241
1
            Pair::cons(
242
1
                Value::Number(Fraction::from_integer(2)),
243
1
                Value::Number(Fraction::from_integer(3)),
244
            ),
245
        );
246
1
        assert_eq!(format_value(&list), "(1 2 . 3)");
247
1
    }
248

            
249
    #[test]
250
1
    fn formats_vector() {
251
1
        let v = Value::Vector(vec![Value::Number(Fraction::from_integer(1)), Value::T]);
252
1
        assert_eq!(format_value(&v), "#(1 t)");
253
1
    }
254

            
255
    #[test]
256
1
    fn formats_closure() {
257
1
        let c = Value::Closure(Closure::new(7, vec![]));
258
1
        assert_eq!(format_value(&c), "#<closure:7>");
259
1
    }
260

            
261
    #[test]
262
1
    fn formats_struct() {
263
1
        let s = Value::Struct {
264
1
            name: "ACCOUNT".into(),
265
1
            fields: vec![
266
1
                Value::Symbol("ID".into()),
267
1
                Value::Number(Fraction::from_integer(42)),
268
1
            ],
269
1
        };
270
1
        assert_eq!(format_value(&s), "#S(ACCOUNT ID 42)");
271
1
    }
272

            
273
    #[test]
274
1
    fn round_trip_short_bytes_through_reader() {
275
1
        let v = Value::Bytes(vec![0xCA, 0xFE, 0xBA, 0xBE]);
276
1
        let formatted = format_value(&v);
277
1
        let parsed = Reader::parse(&formatted).unwrap();
278
1
        assert_eq!(
279
            parsed.exprs,
280
1
            vec![Expr::Bytes(vec![0xCA, 0xFE, 0xBA, 0xBE])]
281
        );
282
1
    }
283

            
284
    #[test]
285
1
    fn round_trip_long_bytes_through_reader() {
286
1
        let bytes: Vec<u8> = (0..=255).chain(0..=255).collect();
287
1
        let formatted = format_value(&Value::Bytes(bytes.clone()));
288
1
        let parsed = Reader::parse(&formatted).unwrap();
289
1
        assert_eq!(parsed.exprs, vec![Expr::Bytes(bytes)]);
290
1
    }
291

            
292
    #[test]
293
1
    fn round_trip_full_byte_range_through_reader() {
294
1
        let bytes: Vec<u8> = (0..=255).collect();
295
1
        let formatted = format_value(&Value::Bytes(bytes.clone()));
296
1
        let parsed = Reader::parse(&formatted).unwrap();
297
1
        assert_eq!(parsed.exprs, vec![Expr::Bytes(bytes)]);
298
1
    }
299

            
300
    #[test]
301
1
    fn round_trip_rational_through_reader() {
302
4
        for (num, den) in [(1i64, 2i64), (-3, 4), (1, 3), (7, 11)] {
303
4
            let v = Value::Number(Fraction::new(num, den));
304
4
            let formatted = format_value(&v);
305
4
            let parsed = Reader::parse(&formatted).unwrap();
306
4
            assert_eq!(parsed.exprs, vec![Expr::Number(Fraction::new(num, den))]);
307
        }
308
1
    }
309
}