1
//! Reader tests covering nil/bool/numbers/strings/symbols/lists/quasi/byte-vectors.
2

            
3
use base64::Engine;
4
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
5

            
6
use crate::ast::{Expr, Fraction};
7

            
8
use super::Reader;
9

            
10
#[test]
11
1
fn test_parse_nil() {
12
1
    let program = Reader::parse("nil").unwrap();
13
1
    assert_eq!(program.exprs, vec![Expr::Nil]);
14
1
}
15

            
16
#[test]
17
1
fn test_parse_t_case_insensitive() {
18
1
    let program = Reader::parse("t T").unwrap();
19
1
    assert_eq!(program.exprs, vec![Expr::T, Expr::T]);
20
1
}
21

            
22
#[test]
23
1
fn test_parse_nil_case_insensitive() {
24
1
    assert_eq!(Reader::parse("nil").unwrap().exprs, vec![Expr::Nil]);
25
1
    assert_eq!(Reader::parse("nil").unwrap().exprs, vec![Expr::Nil]);
26
1
    assert_eq!(Reader::parse("Nil").unwrap().exprs, vec![Expr::Nil]);
27
1
}
28

            
29
#[test]
30
1
fn test_parse_integer() {
31
1
    let program = Reader::parse("42").unwrap();
32
1
    assert_eq!(
33
        program.exprs,
34
1
        vec![Expr::Number(Fraction::from_integer(42))]
35
    );
36
1
}
37

            
38
#[test]
39
1
fn test_parse_negative_number() {
40
1
    let program = Reader::parse("-17").unwrap();
41
1
    assert_eq!(
42
        program.exprs,
43
1
        vec![Expr::Number(Fraction::from_integer(-17))]
44
    );
45
1
}
46

            
47
#[test]
48
1
fn test_parse_decimal() {
49
1
    let program = Reader::parse("0.1").unwrap();
50
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(1, 10))]);
51
1
}
52

            
53
#[test]
54
1
fn test_parse_rational_simple() {
55
1
    let program = Reader::parse("1/2").unwrap();
56
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(1, 2))]);
57
1
}
58

            
59
#[test]
60
1
fn test_parse_rational_negative() {
61
1
    let program = Reader::parse("-3/4").unwrap();
62
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(-3, 4))]);
63
1
}
64

            
65
#[test]
66
1
fn test_parse_rational_reduces() {
67
1
    let program = Reader::parse("4/8").unwrap();
68
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(1, 2))]);
69
1
}
70

            
71
#[test]
72
1
fn test_parse_rational_in_list() {
73
1
    let program = Reader::parse("(+ 1/2 1/3)").unwrap();
74
1
    assert_eq!(
75
        program.exprs,
76
1
        vec![Expr::List(vec![
77
1
            Expr::Symbol("+".into()),
78
1
            Expr::Number(Fraction::new(1, 2)),
79
1
            Expr::Number(Fraction::new(1, 3)),
80
1
        ])]
81
    );
82
1
}
83

            
84
#[test]
85
1
fn test_parse_rational_zero_denominator_rejected() {
86
1
    let result = Reader::parse("1/0");
87
1
    assert!(result.is_err());
88
1
}
89

            
90
#[test]
91
1
fn test_parse_integer_then_division_call() {
92
1
    let program = Reader::parse("(/ 1 2)").unwrap();
93
1
    assert_eq!(
94
        program.exprs,
95
1
        vec![Expr::List(vec![
96
1
            Expr::Symbol("/".into()),
97
1
            Expr::Number(Fraction::from_integer(1)),
98
1
            Expr::Number(Fraction::from_integer(2)),
99
1
        ])]
100
    );
101
1
}
102

            
103
#[test]
104
1
fn test_parse_symbol() {
105
1
    let program = Reader::parse("foo").unwrap();
106
1
    assert_eq!(program.exprs, vec![Expr::Symbol("FOO".into())]);
107
1
}
108

            
109
#[test]
110
1
fn test_parse_symbol_case_insensitive() {
111
1
    assert_eq!(
112
1
        Reader::parse("foo").unwrap().exprs,
113
1
        Reader::parse("FOO").unwrap().exprs
114
    );
115
1
    assert_eq!(
116
1
        Reader::parse("Sum").unwrap().exprs,
117
1
        Reader::parse("SUM").unwrap().exprs
118
    );
119
1
}
120

            
121
#[test]
122
1
fn test_parse_keyword() {
123
1
    let program = Reader::parse(":foo").unwrap();
124
1
    assert_eq!(program.exprs, vec![Expr::Keyword("FOO".into())]);
125
1
}
126

            
127
#[test]
128
1
fn test_parse_keyword_case_insensitive() {
129
1
    assert_eq!(
130
1
        Reader::parse(":foo").unwrap().exprs,
131
1
        Reader::parse(":FOO").unwrap().exprs
132
    );
133
1
    assert_eq!(
134
1
        Reader::parse(":Name").unwrap().exprs,
135
1
        Reader::parse(":NAME").unwrap().exprs
136
    );
137
1
}
138

            
139
// ADR-0029 colon-namespace grammar.
140

            
141
#[test]
142
1
fn qualified_symbol_single_colon() {
143
1
    let program = Reader::parse("finance:add-money").unwrap();
144
1
    assert_eq!(
145
        program.exprs,
146
1
        vec![Expr::Symbol("FINANCE:ADD-MONEY".into())]
147
    );
148
1
}
149

            
150
#[test]
151
1
fn qualified_symbol_double_colon_folds_to_single() {
152
    // `::` is accepted as input syntax but folds to the canonical single-colon
153
    // key — same binding (the `:`/`::` distinction is reserved for the future).
154
1
    assert_eq!(
155
1
        Reader::parse("finance::add-money").unwrap().exprs,
156
1
        Reader::parse("finance:add-money").unwrap().exprs
157
    );
158
1
    assert_eq!(
159
1
        Reader::parse("finance::add-money").unwrap().exprs,
160
1
        vec![Expr::Symbol("FINANCE:ADD-MONEY".into())]
161
    );
162
1
}
163

            
164
#[test]
165
1
fn qualified_symbol_case_insensitive() {
166
1
    assert_eq!(
167
1
        Reader::parse("Finance:Add-Money").unwrap().exprs,
168
1
        Reader::parse("finance:add-money").unwrap().exprs
169
    );
170
1
}
171

            
172
#[test]
173
1
fn qualified_symbol_as_list_head() {
174
1
    let program = Reader::parse("(split:list-for-transaction tx)").unwrap();
175
1
    assert_eq!(
176
        program.exprs,
177
1
        vec![Expr::List(vec![
178
1
            Expr::Symbol("SPLIT:LIST-FOR-TRANSACTION".into()),
179
1
            Expr::Symbol("TX".into()),
180
1
        ])]
181
    );
182
1
}
183

            
184
#[test]
185
1
fn leading_colon_still_keyword() {
186
    // Regression: a leading colon is a keyword, never a qualified symbol.
187
1
    assert_eq!(
188
1
        Reader::parse(":foo").unwrap().exprs,
189
1
        vec![Expr::Keyword("FOO".into())]
190
    );
191
1
}
192

            
193
#[test]
194
1
fn qualified_symbol_under_quote_and_quasiquote() {
195
1
    assert_eq!(
196
1
        Reader::parse("'finance:x").unwrap().exprs,
197
1
        vec![Expr::Quote(Box::new(Expr::Symbol("FINANCE:X".into())))]
198
    );
199
1
    assert_eq!(
200
1
        Reader::parse("`finance:x").unwrap().exprs,
201
1
        vec![Expr::Quasiquote(Box::new(Expr::Symbol("FINANCE:X".into())))]
202
    );
203
1
}
204

            
205
#[test]
206
1
fn mixed_qualified_and_keyword_in_list() {
207
1
    let program = Reader::parse("(a finance:b :c)").unwrap();
208
1
    assert_eq!(
209
        program.exprs,
210
1
        vec![Expr::List(vec![
211
1
            Expr::Symbol("A".into()),
212
1
            Expr::Symbol("FINANCE:B".into()),
213
1
            Expr::Keyword("C".into()),
214
1
        ])]
215
    );
216
1
}
217

            
218
#[test]
219
1
fn trailing_colon_is_error() {
220
1
    assert!(Reader::parse("foo:").is_err());
221
1
    assert!(Reader::parse("foo::").is_err());
222
1
}
223

            
224
#[test]
225
1
fn multiple_separators_are_error() {
226
1
    assert!(Reader::parse("a:b:c").is_err());
227
1
    assert!(Reader::parse("a::b::c").is_err());
228
1
    assert!(Reader::parse("a:b::c").is_err());
229
1
}
230

            
231
#[test]
232
1
fn leading_double_colon_and_bare_colon_are_error() {
233
1
    assert!(Reader::parse("::foo").is_err());
234
1
    assert!(Reader::parse(":").is_err());
235
1
}
236

            
237
#[test]
238
1
fn test_parse_string() {
239
1
    let program = Reader::parse(r#""hello""#).unwrap();
240
1
    assert_eq!(program.exprs, vec![Expr::String("hello".into())]);
241
1
}
242

            
243
#[test]
244
1
fn test_parse_string_with_escapes() {
245
1
    let program = Reader::parse(r#""hello\nworld""#).unwrap();
246
1
    assert_eq!(program.exprs, vec![Expr::String("hello\nworld".into())]);
247
1
}
248

            
249
#[test]
250
1
fn test_parse_list() {
251
1
    let program = Reader::parse("(+ 1 2)").unwrap();
252
1
    assert_eq!(
253
        program.exprs,
254
1
        vec![Expr::List(vec![
255
1
            Expr::Symbol("+".into()),
256
1
            Expr::Number(Fraction::from_integer(1)),
257
1
            Expr::Number(Fraction::from_integer(2)),
258
1
        ])]
259
    );
260
1
}
261

            
262
#[test]
263
1
fn test_parse_nested_list() {
264
1
    let program = Reader::parse("(define (square x) (* x x))").unwrap();
265
1
    assert_eq!(
266
        program.exprs,
267
1
        vec![Expr::List(vec![
268
1
            Expr::Symbol("DEFINE".into()),
269
1
            Expr::List(vec![
270
1
                Expr::Symbol("SQUARE".into()),
271
1
                Expr::Symbol("X".into()),
272
1
            ]),
273
1
            Expr::List(vec![
274
1
                Expr::Symbol("*".into()),
275
1
                Expr::Symbol("X".into()),
276
1
                Expr::Symbol("X".into()),
277
1
            ]),
278
1
        ])]
279
    );
280
1
}
281

            
282
#[test]
283
1
fn test_parse_quote() {
284
1
    let program = Reader::parse("'foo").unwrap();
285
1
    assert_eq!(
286
        program.exprs,
287
1
        vec![Expr::Quote(Box::new(Expr::Symbol("FOO".into())))]
288
    );
289
1
}
290

            
291
#[test]
292
1
fn test_parse_quoted_list() {
293
1
    let program = Reader::parse("'(1 2 3)").unwrap();
294
1
    assert_eq!(
295
        program.exprs,
296
1
        vec![Expr::Quote(Box::new(Expr::List(vec![
297
1
            Expr::Number(Fraction::from_integer(1)),
298
1
            Expr::Number(Fraction::from_integer(2)),
299
1
            Expr::Number(Fraction::from_integer(3)),
300
1
        ])))]
301
    );
302
1
}
303

            
304
#[test]
305
1
fn test_parse_multiple_exprs() {
306
1
    let program = Reader::parse("(DEFINE x 10) (+ x 5)").unwrap();
307
1
    assert_eq!(program.exprs.len(), 2);
308
1
}
309

            
310
#[test]
311
1
fn test_parse_line_comment() {
312
1
    let program = Reader::parse("; this is a comment\n42").unwrap();
313
1
    assert_eq!(
314
        program.exprs,
315
1
        vec![Expr::Number(Fraction::from_integer(42))]
316
    );
317
1
}
318

            
319
#[test]
320
1
fn test_parse_inline_comment() {
321
1
    let program = Reader::parse("42 ; inline comment").unwrap();
322
1
    assert_eq!(
323
        program.exprs,
324
1
        vec![Expr::Number(Fraction::from_integer(42))]
325
    );
326
1
}
327

            
328
#[test]
329
1
fn test_parse_comment_in_list() {
330
1
    let program = Reader::parse("(+ 1 ; add one\n   2)").unwrap();
331
1
    assert_eq!(
332
        program.exprs,
333
1
        vec![Expr::List(vec![
334
1
            Expr::Symbol("+".into()),
335
1
            Expr::Number(Fraction::from_integer(1)),
336
1
            Expr::Number(Fraction::from_integer(2)),
337
1
        ])]
338
    );
339
1
}
340

            
341
#[test]
342
1
fn test_parse_multiple_comments() {
343
1
    let code = "; comment 1
344
1
; comment 2
345
1
42
346
1
; comment 3";
347
1
    let program = Reader::parse(code).unwrap();
348
1
    assert_eq!(
349
        program.exprs,
350
1
        vec![Expr::Number(Fraction::from_integer(42))]
351
    );
352
1
}
353

            
354
#[test]
355
1
fn test_parse_only_comments() {
356
1
    let program = Reader::parse("; just a comment").unwrap();
357
1
    assert!(program.exprs.is_empty());
358
1
}
359

            
360
#[test]
361
1
fn test_parse_annotation() {
362
1
    let code = "; @test (= (count 'defun) 5)\n42";
363
1
    let program = Reader::parse(code).unwrap();
364
1
    assert_eq!(program.exprs.len(), 1);
365
1
    assert_eq!(program.annotations.len(), 1);
366
1
    assert_eq!(program.annotations[0].name, "test");
367
1
    assert!(matches!(&program.annotations[0].value, Expr::List(_)));
368
1
}
369

            
370
#[test]
371
1
fn test_parse_multiple_annotations() {
372
1
    let code = "; @test (= (count 'defun) 5)\n; @test (= (count 'defvar) 2)\n42";
373
1
    let program = Reader::parse(code).unwrap();
374
1
    assert_eq!(program.annotations.len(), 2);
375
1
}
376

            
377
#[test]
378
1
fn test_annotation_with_simple_expr() {
379
1
    let code = "; @version 1\n42";
380
1
    let program = Reader::parse(code).unwrap();
381
1
    assert_eq!(program.annotations.len(), 1);
382
1
    assert_eq!(program.annotations[0].name, "version");
383
1
    assert_eq!(
384
1
        program.annotations[0].value,
385
1
        Expr::Number(Fraction::from_integer(1))
386
    );
387
1
}
388

            
389
#[test]
390
1
fn test_regular_comment_no_annotation() {
391
1
    let code = "; just a comment\n42";
392
1
    let program = Reader::parse(code).unwrap();
393
1
    assert!(program.annotations.is_empty());
394
1
}
395

            
396
#[test]
397
1
fn test_parse_cons() {
398
1
    let program = Reader::parse("(a . b)").unwrap();
399
1
    assert_eq!(
400
        program.exprs,
401
1
        vec![Expr::cons(
402
1
            Expr::Symbol("A".into()),
403
1
            Expr::Symbol("B".into())
404
        )]
405
    );
406
1
}
407

            
408
#[test]
409
1
fn test_parse_cons_proper_list() {
410
1
    let program = Reader::parse("(a . (b . nil))").unwrap();
411
1
    assert_eq!(
412
        program.exprs,
413
1
        vec![Expr::cons(
414
1
            Expr::Symbol("A".into()),
415
1
            Expr::cons(Expr::Symbol("B".into()), Expr::Nil)
416
        )]
417
    );
418
1
}
419

            
420
#[test]
421
1
fn test_parse_improper_list() {
422
1
    let program = Reader::parse("(1 2 . 3)").unwrap();
423
1
    assert_eq!(
424
        program.exprs,
425
1
        vec![Expr::cons(
426
1
            Expr::Number(Fraction::from_integer(1)),
427
1
            Expr::cons(
428
1
                Expr::Number(Fraction::from_integer(2)),
429
1
                Expr::Number(Fraction::from_integer(3))
430
            )
431
        )]
432
    );
433
1
}
434

            
435
#[test]
436
1
fn test_invalid_dotted_pair_multiple_exprs_after_dot() {
437
1
    let result = Reader::parse("(1 . (2 . 3) (3 . nil))");
438
1
    assert!(result.is_err());
439
1
    let err = result.unwrap_err();
440
1
    let msg = err.to_string();
441
1
    assert!(
442
1
        msg.contains("closing paren") || msg.contains("one expression after dot"),
443
        "Error should mention closing paren or one expression after dot, got: {msg}"
444
    );
445
1
}
446

            
447
#[test]
448
1
fn test_parse_quasiquote() {
449
1
    let program = Reader::parse("`foo").unwrap();
450
1
    assert_eq!(
451
        program.exprs,
452
1
        vec![Expr::Quasiquote(Box::new(Expr::Symbol("FOO".into())))]
453
    );
454
1
}
455

            
456
#[test]
457
1
fn test_parse_unquote() {
458
1
    let program = Reader::parse(",foo").unwrap();
459
1
    assert_eq!(
460
        program.exprs,
461
1
        vec![Expr::Unquote(Box::new(Expr::Symbol("FOO".into())))]
462
    );
463
1
}
464

            
465
#[test]
466
1
fn test_parse_unquote_splicing() {
467
1
    let program = Reader::parse(",@foo").unwrap();
468
1
    assert_eq!(
469
        program.exprs,
470
1
        vec![Expr::UnquoteSplicing(Box::new(Expr::Symbol("FOO".into())))]
471
    );
472
1
}
473

            
474
#[test]
475
1
fn test_parse_quasiquoted_list() {
476
1
    let program = Reader::parse("`(if ,test ,body)").unwrap();
477
1
    assert_eq!(
478
        program.exprs,
479
1
        vec![Expr::Quasiquote(Box::new(Expr::List(vec![
480
1
            Expr::Symbol("IF".into()),
481
1
            Expr::Unquote(Box::new(Expr::Symbol("TEST".into()))),
482
1
            Expr::Unquote(Box::new(Expr::Symbol("BODY".into()))),
483
1
        ])))]
484
    );
485
1
}
486

            
487
#[test]
488
1
fn test_parse_byte_vector_empty() {
489
1
    let program = Reader::parse("#u8()").unwrap();
490
1
    assert_eq!(program.exprs, vec![Expr::Bytes(Vec::new())]);
491
1
}
492

            
493
#[test]
494
1
fn test_parse_byte_vector_simple() {
495
1
    let program = Reader::parse("#u8(0 1 255)").unwrap();
496
1
    assert_eq!(program.exprs, vec![Expr::Bytes(vec![0, 1, 255])]);
497
1
}
498

            
499
#[test]
500
1
fn test_parse_byte_vector_with_whitespace() {
501
1
    let program = Reader::parse("#u8(  10  20  30  )").unwrap();
502
1
    assert_eq!(program.exprs, vec![Expr::Bytes(vec![10, 20, 30])]);
503
1
}
504

            
505
#[test]
506
1
fn test_parse_byte_vector_full_byte_range() {
507
1
    let program = Reader::parse("#u8(0 127 128 255)").unwrap();
508
1
    assert_eq!(program.exprs, vec![Expr::Bytes(vec![0, 127, 128, 255])]);
509
1
}
510

            
511
#[test]
512
1
fn test_parse_byte_vector_overflow_rejected() {
513
1
    let result = Reader::parse("#u8(256)");
514
1
    assert!(result.is_err());
515
1
}
516

            
517
#[test]
518
1
fn test_parse_byte_vector_unclosed_rejected() {
519
1
    let result = Reader::parse("#u8(1 2 3");
520
1
    assert!(result.is_err());
521
1
}
522

            
523
#[test]
524
1
fn test_parse_base64_empty() {
525
1
    let program = Reader::parse("#\"\"").unwrap();
526
1
    assert_eq!(program.exprs, vec![Expr::Bytes(Vec::new())]);
527
1
}
528

            
529
#[test]
530
1
fn test_parse_base64_simple() {
531
1
    let program = Reader::parse("#\"aGVsbG8=\"").unwrap();
532
1
    assert_eq!(program.exprs, vec![Expr::Bytes(b"hello".to_vec())]);
533
1
}
534

            
535
#[test]
536
1
fn test_parse_base64_full_byte_range() {
537
1
    let mut bytes: Vec<u8> = (0u8..=255).collect();
538
1
    let encoded = BASE64_STANDARD.encode(&bytes);
539
1
    let source = format!("#\"{encoded}\"");
540
1
    let program = Reader::parse(&source).unwrap();
541
1
    let parsed = match program.exprs.as_slice() {
542
1
        [Expr::Bytes(b)] => b.clone(),
543
        other => panic!("expected single bytes expression, got {other:?}"),
544
    };
545
1
    bytes.sort();
546
1
    let mut parsed_sorted = parsed.clone();
547
1
    parsed_sorted.sort();
548
1
    assert_eq!(parsed_sorted, bytes);
549
1
}
550

            
551
#[test]
552
1
fn test_parse_base64_invalid_rejected() {
553
1
    let result = Reader::parse("#\"not!base64!\"");
554
1
    assert!(result.is_err());
555
1
}
556

            
557
#[test]
558
1
fn test_parse_base64_unclosed_rejected() {
559
1
    let result = Reader::parse("#\"aGVsbG8=");
560
1
    assert!(result.is_err());
561
1
}
562

            
563
#[test]
564
1
fn test_byte_vector_in_list() {
565
1
    let program = Reader::parse("(blob #u8(1 2 3) :tag)").unwrap();
566
1
    assert_eq!(
567
        program.exprs,
568
1
        vec![Expr::List(vec![
569
1
            Expr::Symbol("BLOB".into()),
570
1
            Expr::Bytes(vec![1, 2, 3]),
571
1
            Expr::Keyword("TAG".into()),
572
1
        ])]
573
    );
574
1
}