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
18385
pub fn format_value(value: &Value) -> String {
10
18385
    let mut out = String::new();
11
18385
    write_value(&mut out, value);
12
18385
    out
13
18385
}
14

            
15
18533
fn write_value(out: &mut String, value: &Value) {
16
3279
    match value {
17
1361
        Value::Nil => out.push_str("NIL"),
18
138
        Value::Bool(true) => out.push_str("#t"),
19
1
        Value::Bool(false) => out.push_str("#f"),
20
3279
        Value::Number(n) if *n.denom() == 1 => {
21
3138
            out.push_str(&n.numer().to_string());
22
3138
        }
23
141
        Value::Number(n) => {
24
141
            out.push_str(&format!("{}/{}", n.numer(), n.denom()));
25
141
        }
26
13397
        Value::String(s) => write_string_literal(out, s),
27
4
        Value::Symbol(s) => out.push_str(s),
28
75
        Value::Bytes(b) => write_bytes_literal(out, b),
29
71
        Value::Pair(pair) => write_pair_or_list(out, pair),
30
1
        Value::Vector(items) => write_vector(out, items),
31
1
        Value::Closure(c) => {
32
1
            out.push_str(&format!("#<closure:{}>", c.code_id));
33
1
        }
34
1
        Value::Struct { name, fields } => write_struct(out, name, fields),
35
        Value::Commodity {
36
204
            amount,
37
204
            commodity_id,
38
        } => {
39
            // Plist envelope keeps the wire shape uniform with native
40
            // results: emacs `(read)` recovers `(:commodity <ratio> :id "<uuid>")`.
41
204
            out.push_str("(:commodity ");
42
204
            if *amount.denom() == 1 {
43
204
                out.push_str(&amount.numer().to_string());
44
204
            } else {
45
                out.push_str(&format!("{}/{}", amount.numer(), amount.denom()));
46
            }
47
204
            out.push_str(" :id \"");
48
204
            out.push_str(&commodity_id.to_string());
49
204
            out.push_str("\")");
50
        }
51
    }
52
18533
}
53

            
54
13397
fn write_string_literal(out: &mut String, s: &str) {
55
13397
    out.push('"');
56
732367
    for ch in s.chars() {
57
732367
        match ch {
58
817
            '\\' => out.push_str("\\\\"),
59
4353
            '"' => out.push_str("\\\""),
60
3265
            '\n' => out.push_str("\\n"),
61
            '\t' => out.push_str("\\t"),
62
            '\r' => out.push_str("\\r"),
63
723932
            other => out.push(other),
64
        }
65
    }
66
13397
    out.push('"');
67
13397
}
68

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

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

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

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

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

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

            
147
    #[test]
148
1
    fn formats_booleans() {
149
1
        assert_eq!(format_value(&Value::Bool(true)), "#t");
150
1
        assert_eq!(format_value(&Value::Bool(false)), "#f");
151
1
    }
152

            
153
    #[test]
154
1
    fn formats_integer_number() {
155
1
        assert_eq!(
156
1
            format_value(&Value::Number(Fraction::from_integer(42))),
157
            "42"
158
        );
159
1
    }
160

            
161
    #[test]
162
1
    fn formats_negative_integer() {
163
1
        assert_eq!(
164
1
            format_value(&Value::Number(Fraction::from_integer(-7))),
165
            "-7"
166
        );
167
1
    }
168

            
169
    #[test]
170
1
    fn formats_proper_fraction() {
171
1
        assert_eq!(format_value(&Value::Number(Fraction::new(3, 4))), "3/4");
172
1
    }
173

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

            
180
    #[test]
181
1
    fn formats_symbol_verbatim() {
182
1
        assert_eq!(format_value(&Value::Symbol("FOO".into())), "FOO");
183
1
    }
184

            
185
    #[test]
186
1
    fn formats_short_bytes_as_u8_literal() {
187
1
        let v = Value::Bytes(vec![0, 1, 255]);
188
1
        assert_eq!(format_value(&v), "#u8(0 1 255)");
189
1
    }
190

            
191
    #[test]
192
1
    fn formats_empty_bytes_as_u8_literal() {
193
1
        assert_eq!(format_value(&Value::Bytes(Vec::new())), "#u8()");
194
1
    }
195

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

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

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

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

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

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

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

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

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

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

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

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