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_bool() {
18
1
    let program = Reader::parse("#t #T t T").unwrap();
19
1
    assert_eq!(
20
        program.exprs,
21
1
        vec![
22
1
            Expr::Bool(true),
23
1
            Expr::Bool(true),
24
1
            Expr::Bool(true),
25
1
            Expr::Bool(true),
26
        ]
27
    );
28
1
}
29

            
30
#[test]
31
1
fn test_parse_false_is_nil() {
32
1
    let program = Reader::parse("#f #F").unwrap();
33
1
    assert_eq!(program.exprs, vec![Expr::Nil, Expr::Nil]);
34
1
}
35

            
36
#[test]
37
1
fn test_parse_nil_case_insensitive() {
38
1
    assert_eq!(Reader::parse("nil").unwrap().exprs, vec![Expr::Nil]);
39
1
    assert_eq!(Reader::parse("NIL").unwrap().exprs, vec![Expr::Nil]);
40
1
    assert_eq!(Reader::parse("Nil").unwrap().exprs, vec![Expr::Nil]);
41
1
}
42

            
43
#[test]
44
1
fn test_parse_integer() {
45
1
    let program = Reader::parse("42").unwrap();
46
1
    assert_eq!(
47
        program.exprs,
48
1
        vec![Expr::Number(Fraction::from_integer(42))]
49
    );
50
1
}
51

            
52
#[test]
53
1
fn test_parse_negative_number() {
54
1
    let program = Reader::parse("-17").unwrap();
55
1
    assert_eq!(
56
        program.exprs,
57
1
        vec![Expr::Number(Fraction::from_integer(-17))]
58
    );
59
1
}
60

            
61
#[test]
62
1
fn test_parse_decimal() {
63
1
    let program = Reader::parse("0.1").unwrap();
64
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(1, 10))]);
65
1
}
66

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

            
73
#[test]
74
1
fn test_parse_rational_negative() {
75
1
    let program = Reader::parse("-3/4").unwrap();
76
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(-3, 4))]);
77
1
}
78

            
79
#[test]
80
1
fn test_parse_rational_reduces() {
81
1
    let program = Reader::parse("4/8").unwrap();
82
1
    assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(1, 2))]);
83
1
}
84

            
85
#[test]
86
1
fn test_parse_rational_in_list() {
87
1
    let program = Reader::parse("(+ 1/2 1/3)").unwrap();
88
1
    assert_eq!(
89
        program.exprs,
90
1
        vec![Expr::List(vec![
91
1
            Expr::Symbol("+".into()),
92
1
            Expr::Number(Fraction::new(1, 2)),
93
1
            Expr::Number(Fraction::new(1, 3)),
94
1
        ])]
95
    );
96
1
}
97

            
98
#[test]
99
1
fn test_parse_rational_zero_denominator_rejected() {
100
1
    let result = Reader::parse("1/0");
101
1
    assert!(result.is_err());
102
1
}
103

            
104
#[test]
105
1
fn test_parse_integer_then_division_call() {
106
1
    let program = Reader::parse("(/ 1 2)").unwrap();
107
1
    assert_eq!(
108
        program.exprs,
109
1
        vec![Expr::List(vec![
110
1
            Expr::Symbol("/".into()),
111
1
            Expr::Number(Fraction::from_integer(1)),
112
1
            Expr::Number(Fraction::from_integer(2)),
113
1
        ])]
114
    );
115
1
}
116

            
117
#[test]
118
1
fn test_parse_symbol() {
119
1
    let program = Reader::parse("foo").unwrap();
120
1
    assert_eq!(program.exprs, vec![Expr::Symbol("FOO".into())]);
121
1
}
122

            
123
#[test]
124
1
fn test_parse_symbol_case_insensitive() {
125
1
    assert_eq!(
126
1
        Reader::parse("foo").unwrap().exprs,
127
1
        Reader::parse("FOO").unwrap().exprs
128
    );
129
1
    assert_eq!(
130
1
        Reader::parse("Sum").unwrap().exprs,
131
1
        Reader::parse("SUM").unwrap().exprs
132
    );
133
1
}
134

            
135
#[test]
136
1
fn test_parse_keyword() {
137
1
    let program = Reader::parse(":foo").unwrap();
138
1
    assert_eq!(program.exprs, vec![Expr::Keyword("FOO".into())]);
139
1
}
140

            
141
#[test]
142
1
fn test_parse_keyword_case_insensitive() {
143
1
    assert_eq!(
144
1
        Reader::parse(":foo").unwrap().exprs,
145
1
        Reader::parse(":FOO").unwrap().exprs
146
    );
147
1
    assert_eq!(
148
1
        Reader::parse(":Name").unwrap().exprs,
149
1
        Reader::parse(":NAME").unwrap().exprs
150
    );
151
1
}
152

            
153
// ADR-0029 colon-namespace grammar.
154

            
155
#[test]
156
1
fn qualified_symbol_single_colon() {
157
1
    let program = Reader::parse("finance:add-money").unwrap();
158
1
    assert_eq!(
159
        program.exprs,
160
1
        vec![Expr::Symbol("FINANCE:ADD-MONEY".into())]
161
    );
162
1
}
163

            
164
#[test]
165
1
fn qualified_symbol_double_colon_folds_to_single() {
166
    // `::` is accepted as input syntax but folds to the canonical single-colon
167
    // key — same binding (the `:`/`::` distinction is reserved for the future).
168
1
    assert_eq!(
169
1
        Reader::parse("finance::add-money").unwrap().exprs,
170
1
        Reader::parse("finance:add-money").unwrap().exprs
171
    );
172
1
    assert_eq!(
173
1
        Reader::parse("finance::add-money").unwrap().exprs,
174
1
        vec![Expr::Symbol("FINANCE:ADD-MONEY".into())]
175
    );
176
1
}
177

            
178
#[test]
179
1
fn qualified_symbol_case_insensitive() {
180
1
    assert_eq!(
181
1
        Reader::parse("Finance:Add-Money").unwrap().exprs,
182
1
        Reader::parse("finance:add-money").unwrap().exprs
183
    );
184
1
}
185

            
186
#[test]
187
1
fn qualified_symbol_as_list_head() {
188
1
    let program = Reader::parse("(split:list-for-transaction tx)").unwrap();
189
1
    assert_eq!(
190
        program.exprs,
191
1
        vec![Expr::List(vec![
192
1
            Expr::Symbol("SPLIT:LIST-FOR-TRANSACTION".into()),
193
1
            Expr::Symbol("TX".into()),
194
1
        ])]
195
    );
196
1
}
197

            
198
#[test]
199
1
fn leading_colon_still_keyword() {
200
    // Regression: a leading colon is a keyword, never a qualified symbol.
201
1
    assert_eq!(
202
1
        Reader::parse(":foo").unwrap().exprs,
203
1
        vec![Expr::Keyword("FOO".into())]
204
    );
205
1
}
206

            
207
#[test]
208
1
fn qualified_symbol_under_quote_and_quasiquote() {
209
1
    assert_eq!(
210
1
        Reader::parse("'finance:x").unwrap().exprs,
211
1
        vec![Expr::Quote(Box::new(Expr::Symbol("FINANCE:X".into())))]
212
    );
213
1
    assert_eq!(
214
1
        Reader::parse("`finance:x").unwrap().exprs,
215
1
        vec![Expr::Quasiquote(Box::new(Expr::Symbol("FINANCE:X".into())))]
216
    );
217
1
}
218

            
219
#[test]
220
1
fn mixed_qualified_and_keyword_in_list() {
221
1
    let program = Reader::parse("(a finance:b :c)").unwrap();
222
1
    assert_eq!(
223
        program.exprs,
224
1
        vec![Expr::List(vec![
225
1
            Expr::Symbol("A".into()),
226
1
            Expr::Symbol("FINANCE:B".into()),
227
1
            Expr::Keyword("C".into()),
228
1
        ])]
229
    );
230
1
}
231

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

            
238
#[test]
239
1
fn multiple_separators_are_error() {
240
1
    assert!(Reader::parse("a:b:c").is_err());
241
1
    assert!(Reader::parse("a::b::c").is_err());
242
1
    assert!(Reader::parse("a:b::c").is_err());
243
1
}
244

            
245
#[test]
246
1
fn leading_double_colon_and_bare_colon_are_error() {
247
1
    assert!(Reader::parse("::foo").is_err());
248
1
    assert!(Reader::parse(":").is_err());
249
1
}
250

            
251
#[test]
252
1
fn test_parse_string() {
253
1
    let program = Reader::parse(r#""hello""#).unwrap();
254
1
    assert_eq!(program.exprs, vec![Expr::String("hello".into())]);
255
1
}
256

            
257
#[test]
258
1
fn test_parse_string_with_escapes() {
259
1
    let program = Reader::parse(r#""hello\nworld""#).unwrap();
260
1
    assert_eq!(program.exprs, vec![Expr::String("hello\nworld".into())]);
261
1
}
262

            
263
#[test]
264
1
fn test_parse_list() {
265
1
    let program = Reader::parse("(+ 1 2)").unwrap();
266
1
    assert_eq!(
267
        program.exprs,
268
1
        vec![Expr::List(vec![
269
1
            Expr::Symbol("+".into()),
270
1
            Expr::Number(Fraction::from_integer(1)),
271
1
            Expr::Number(Fraction::from_integer(2)),
272
1
        ])]
273
    );
274
1
}
275

            
276
#[test]
277
1
fn test_parse_nested_list() {
278
1
    let program = Reader::parse("(define (square x) (* x x))").unwrap();
279
1
    assert_eq!(
280
        program.exprs,
281
1
        vec![Expr::List(vec![
282
1
            Expr::Symbol("DEFINE".into()),
283
1
            Expr::List(vec![
284
1
                Expr::Symbol("SQUARE".into()),
285
1
                Expr::Symbol("X".into()),
286
1
            ]),
287
1
            Expr::List(vec![
288
1
                Expr::Symbol("*".into()),
289
1
                Expr::Symbol("X".into()),
290
1
                Expr::Symbol("X".into()),
291
1
            ]),
292
1
        ])]
293
    );
294
1
}
295

            
296
#[test]
297
1
fn test_parse_quote() {
298
1
    let program = Reader::parse("'foo").unwrap();
299
1
    assert_eq!(
300
        program.exprs,
301
1
        vec![Expr::Quote(Box::new(Expr::Symbol("FOO".into())))]
302
    );
303
1
}
304

            
305
#[test]
306
1
fn test_parse_quoted_list() {
307
1
    let program = Reader::parse("'(1 2 3)").unwrap();
308
1
    assert_eq!(
309
        program.exprs,
310
1
        vec![Expr::Quote(Box::new(Expr::List(vec![
311
1
            Expr::Number(Fraction::from_integer(1)),
312
1
            Expr::Number(Fraction::from_integer(2)),
313
1
            Expr::Number(Fraction::from_integer(3)),
314
1
        ])))]
315
    );
316
1
}
317

            
318
#[test]
319
1
fn test_parse_multiple_exprs() {
320
1
    let program = Reader::parse("(DEFINE x 10) (+ x 5)").unwrap();
321
1
    assert_eq!(program.exprs.len(), 2);
322
1
}
323

            
324
#[test]
325
1
fn test_parse_line_comment() {
326
1
    let program = Reader::parse("; this is a comment\n42").unwrap();
327
1
    assert_eq!(
328
        program.exprs,
329
1
        vec![Expr::Number(Fraction::from_integer(42))]
330
    );
331
1
}
332

            
333
#[test]
334
1
fn test_parse_inline_comment() {
335
1
    let program = Reader::parse("42 ; inline comment").unwrap();
336
1
    assert_eq!(
337
        program.exprs,
338
1
        vec![Expr::Number(Fraction::from_integer(42))]
339
    );
340
1
}
341

            
342
#[test]
343
1
fn test_parse_comment_in_list() {
344
1
    let program = Reader::parse("(+ 1 ; add one\n   2)").unwrap();
345
1
    assert_eq!(
346
        program.exprs,
347
1
        vec![Expr::List(vec![
348
1
            Expr::Symbol("+".into()),
349
1
            Expr::Number(Fraction::from_integer(1)),
350
1
            Expr::Number(Fraction::from_integer(2)),
351
1
        ])]
352
    );
353
1
}
354

            
355
#[test]
356
1
fn test_parse_multiple_comments() {
357
1
    let code = "; comment 1
358
1
; comment 2
359
1
42
360
1
; comment 3";
361
1
    let program = Reader::parse(code).unwrap();
362
1
    assert_eq!(
363
        program.exprs,
364
1
        vec![Expr::Number(Fraction::from_integer(42))]
365
    );
366
1
}
367

            
368
#[test]
369
1
fn test_parse_only_comments() {
370
1
    let program = Reader::parse("; just a comment").unwrap();
371
1
    assert!(program.exprs.is_empty());
372
1
}
373

            
374
#[test]
375
1
fn test_parse_annotation() {
376
1
    let code = "; @test (= (count 'defun) 5)\n42";
377
1
    let program = Reader::parse(code).unwrap();
378
1
    assert_eq!(program.exprs.len(), 1);
379
1
    assert_eq!(program.annotations.len(), 1);
380
1
    assert_eq!(program.annotations[0].name, "test");
381
1
    assert!(matches!(&program.annotations[0].value, Expr::List(_)));
382
1
}
383

            
384
#[test]
385
1
fn test_parse_multiple_annotations() {
386
1
    let code = "; @test (= (count 'defun) 5)\n; @test (= (count 'defvar) 2)\n42";
387
1
    let program = Reader::parse(code).unwrap();
388
1
    assert_eq!(program.annotations.len(), 2);
389
1
}
390

            
391
#[test]
392
1
fn test_annotation_with_simple_expr() {
393
1
    let code = "; @version 1\n42";
394
1
    let program = Reader::parse(code).unwrap();
395
1
    assert_eq!(program.annotations.len(), 1);
396
1
    assert_eq!(program.annotations[0].name, "version");
397
1
    assert_eq!(
398
1
        program.annotations[0].value,
399
1
        Expr::Number(Fraction::from_integer(1))
400
    );
401
1
}
402

            
403
#[test]
404
1
fn test_regular_comment_no_annotation() {
405
1
    let code = "; just a comment\n42";
406
1
    let program = Reader::parse(code).unwrap();
407
1
    assert!(program.annotations.is_empty());
408
1
}
409

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

            
422
#[test]
423
1
fn test_parse_cons_proper_list() {
424
1
    let program = Reader::parse("(a . (b . nil))").unwrap();
425
1
    assert_eq!(
426
        program.exprs,
427
1
        vec![Expr::cons(
428
1
            Expr::Symbol("A".into()),
429
1
            Expr::cons(Expr::Symbol("B".into()), Expr::Nil)
430
        )]
431
    );
432
1
}
433

            
434
#[test]
435
1
fn test_parse_improper_list() {
436
1
    let program = Reader::parse("(1 2 . 3)").unwrap();
437
1
    assert_eq!(
438
        program.exprs,
439
1
        vec![Expr::cons(
440
1
            Expr::Number(Fraction::from_integer(1)),
441
1
            Expr::cons(
442
1
                Expr::Number(Fraction::from_integer(2)),
443
1
                Expr::Number(Fraction::from_integer(3))
444
            )
445
        )]
446
    );
447
1
}
448

            
449
#[test]
450
1
fn test_invalid_dotted_pair_multiple_exprs_after_dot() {
451
1
    let result = Reader::parse("(1 . (2 . 3) (3 . nil))");
452
1
    assert!(result.is_err());
453
1
    let err = result.unwrap_err();
454
1
    let msg = err.to_string();
455
1
    assert!(
456
1
        msg.contains("closing paren") || msg.contains("one expression after dot"),
457
        "Error should mention closing paren or one expression after dot, got: {msg}"
458
    );
459
1
}
460

            
461
#[test]
462
1
fn test_parse_quasiquote() {
463
1
    let program = Reader::parse("`foo").unwrap();
464
1
    assert_eq!(
465
        program.exprs,
466
1
        vec![Expr::Quasiquote(Box::new(Expr::Symbol("FOO".into())))]
467
    );
468
1
}
469

            
470
#[test]
471
1
fn test_parse_unquote() {
472
1
    let program = Reader::parse(",foo").unwrap();
473
1
    assert_eq!(
474
        program.exprs,
475
1
        vec![Expr::Unquote(Box::new(Expr::Symbol("FOO".into())))]
476
    );
477
1
}
478

            
479
#[test]
480
1
fn test_parse_unquote_splicing() {
481
1
    let program = Reader::parse(",@foo").unwrap();
482
1
    assert_eq!(
483
        program.exprs,
484
1
        vec![Expr::UnquoteSplicing(Box::new(Expr::Symbol("FOO".into())))]
485
    );
486
1
}
487

            
488
#[test]
489
1
fn test_parse_quasiquoted_list() {
490
1
    let program = Reader::parse("`(if ,test ,body)").unwrap();
491
1
    assert_eq!(
492
        program.exprs,
493
1
        vec![Expr::Quasiquote(Box::new(Expr::List(vec![
494
1
            Expr::Symbol("IF".into()),
495
1
            Expr::Unquote(Box::new(Expr::Symbol("TEST".into()))),
496
1
            Expr::Unquote(Box::new(Expr::Symbol("BODY".into()))),
497
1
        ])))]
498
    );
499
1
}
500

            
501
#[test]
502
1
fn test_parse_byte_vector_empty() {
503
1
    let program = Reader::parse("#u8()").unwrap();
504
1
    assert_eq!(program.exprs, vec![Expr::Bytes(Vec::new())]);
505
1
}
506

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

            
513
#[test]
514
1
fn test_parse_byte_vector_with_whitespace() {
515
1
    let program = Reader::parse("#u8(  10  20  30  )").unwrap();
516
1
    assert_eq!(program.exprs, vec![Expr::Bytes(vec![10, 20, 30])]);
517
1
}
518

            
519
#[test]
520
1
fn test_parse_byte_vector_full_byte_range() {
521
1
    let program = Reader::parse("#u8(0 127 128 255)").unwrap();
522
1
    assert_eq!(program.exprs, vec![Expr::Bytes(vec![0, 127, 128, 255])]);
523
1
}
524

            
525
#[test]
526
1
fn test_parse_byte_vector_overflow_rejected() {
527
1
    let result = Reader::parse("#u8(256)");
528
1
    assert!(result.is_err());
529
1
}
530

            
531
#[test]
532
1
fn test_parse_byte_vector_unclosed_rejected() {
533
1
    let result = Reader::parse("#u8(1 2 3");
534
1
    assert!(result.is_err());
535
1
}
536

            
537
#[test]
538
1
fn test_parse_base64_empty() {
539
1
    let program = Reader::parse("#\"\"").unwrap();
540
1
    assert_eq!(program.exprs, vec![Expr::Bytes(Vec::new())]);
541
1
}
542

            
543
#[test]
544
1
fn test_parse_base64_simple() {
545
1
    let program = Reader::parse("#\"aGVsbG8=\"").unwrap();
546
1
    assert_eq!(program.exprs, vec![Expr::Bytes(b"hello".to_vec())]);
547
1
}
548

            
549
#[test]
550
1
fn test_parse_base64_full_byte_range() {
551
1
    let mut bytes: Vec<u8> = (0u8..=255).collect();
552
1
    let encoded = BASE64_STANDARD.encode(&bytes);
553
1
    let source = format!("#\"{encoded}\"");
554
1
    let program = Reader::parse(&source).unwrap();
555
1
    let parsed = match program.exprs.as_slice() {
556
1
        [Expr::Bytes(b)] => b.clone(),
557
        other => panic!("expected single bytes expression, got {other:?}"),
558
    };
559
1
    bytes.sort();
560
1
    let mut parsed_sorted = parsed.clone();
561
1
    parsed_sorted.sort();
562
1
    assert_eq!(parsed_sorted, bytes);
563
1
}
564

            
565
#[test]
566
1
fn test_parse_base64_invalid_rejected() {
567
1
    let result = Reader::parse("#\"not!base64!\"");
568
1
    assert!(result.is_err());
569
1
}
570

            
571
#[test]
572
1
fn test_parse_base64_unclosed_rejected() {
573
1
    let result = Reader::parse("#\"aGVsbG8=");
574
1
    assert!(result.is_err());
575
1
}
576

            
577
#[test]
578
1
fn test_byte_vector_in_list() {
579
1
    let program = Reader::parse("(blob #u8(1 2 3) :tag)").unwrap();
580
1
    assert_eq!(
581
        program.exprs,
582
1
        vec![Expr::List(vec![
583
1
            Expr::Symbol("BLOB".into()),
584
1
            Expr::Bytes(vec![1, 2, 3]),
585
1
            Expr::Keyword("TAG".into()),
586
1
        ])]
587
    );
588
1
}
589

            
590
#[test]
591
1
fn test_bool_still_parses_after_hash_dispatch() {
592
1
    let program = Reader::parse("#t #f #T #F").unwrap();
593
1
    assert_eq!(
594
        program.exprs,
595
1
        vec![Expr::Bool(true), Expr::Nil, Expr::Bool(true), Expr::Nil,]
596
    );
597
1
}