1
use winnow::ascii::{digit1, line_ending, space0, till_line_ending};
2
use winnow::combinator::{alt, cut_err, opt, preceded};
3
use winnow::error::{AddContext, ContextError, ErrMode, ModalResult, StrContext, StrContextValue};
4
use winnow::prelude::*;
5
use winnow::token::{any, none_of, take_till, take_while};
6

            
7
use tracing::debug;
8

            
9
use crate::ast::{Annotation, Expr, Fraction, Program};
10
use crate::error::{Error, Result};
11

            
12
pub struct Reader;
13

            
14
impl Reader {
15
21089
    pub fn parse(input: &str) -> Result<Program> {
16
21089
        debug!(input_len = input.len(), "parse start");
17
21089
        let mut remaining = input;
18
21089
        let (exprs, annotations) = parse_program(&mut remaining).map_err(|e| {
19
103
            let offset = input.len() - remaining.len();
20
103
            Error::parse(format_parse_error(e), input, offset..offset + 1)
21
103
        })?;
22
20986
        debug!(
23
            expr_count = exprs.len(),
24
            annotation_count = annotations.len(),
25
            "parse complete"
26
        );
27
20986
        Ok(Program::with_annotations(exprs, annotations))
28
21089
    }
29

            
30
    #[must_use]
31
    pub fn is_incomplete(input: &str) -> bool {
32
        let mut remaining = input;
33
        parse_program(&mut remaining).is_err() && remaining.is_empty()
34
    }
35

            
36
340
    pub fn parse_expr(input: &str) -> Result<Expr> {
37
340
        let mut remaining = input;
38
340
        parse_expr(&mut remaining).map_err(|e| {
39
            let offset = input.len() - remaining.len();
40
            Error::parse(format_parse_error(e), input, offset..offset + 1)
41
        })
42
340
    }
43
}
44

            
45
103
fn format_parse_error(err: ErrMode<ContextError>) -> String {
46
103
    match err {
47
103
        ErrMode::Backtrack(ctx) | ErrMode::Cut(ctx) => format_context_error(&ctx),
48
        ErrMode::Incomplete(_) => "unexpected end of input".to_string(),
49
    }
50
103
}
51

            
52
103
fn format_context_error(ctx: &ContextError) -> String {
53
103
    let mut parts = Vec::new();
54
138
    for c in ctx.context() {
55
69
        match c {
56
69
            StrContext::Label(label) => parts.push(format!("in {label}")),
57
69
            StrContext::Expected(StrContextValue::Description(desc)) => {
58
69
                parts.push(format!("expected {desc}"));
59
69
            }
60
            StrContext::Expected(StrContextValue::CharLiteral(c)) => {
61
                parts.push(format!("expected '{c}'"));
62
            }
63
            StrContext::Expected(StrContextValue::StringLiteral(s)) => {
64
                parts.push(format!("expected \"{s}\""));
65
            }
66
            _ => {}
67
        }
68
    }
69
103
    if parts.is_empty() {
70
34
        "parse error".to_string()
71
    } else {
72
69
        parts.join(", ")
73
    }
74
103
}
75

            
76
784142
fn skip_ws_and_comments(input: &mut &str, annotations: &mut Vec<Annotation>) -> ModalResult<()> {
77
    loop {
78
909445
        let _ = space0.parse_next(input)?;
79
909445
        if input.starts_with("; @") {
80
5138
            let _ = "; @".parse_next(input)?;
81
25693
            let name: &str = take_while(1.., |c: char| c.is_alphanumeric() || c == '-' || c == '_')
82
5138
                .parse_next(input)?;
83
5138
            let _ = space0.parse_next(input)?;
84

            
85
5138
            let value = parse_expr(input).unwrap_or(Expr::Nil);
86
5138
            annotations.push(Annotation {
87
5138
                name: name.to_string(),
88
5138
                value,
89
5138
            });
90
5138
            let _ = till_line_ending.parse_next(input)?;
91
5138
            let _ = opt(line_ending).parse_next(input)?;
92
904307
        } else if input.starts_with(';') {
93
12758
            let _ = till_line_ending.parse_next(input)?;
94
12758
            let _ = opt(line_ending).parse_next(input)?;
95
891549
        } else if input.starts_with('\n') || input.starts_with('\r') {
96
107407
            let _ = line_ending.parse_next(input)?;
97
        } else {
98
784142
            break;
99
        }
100
    }
101
784142
    Ok(())
102
784142
}
103

            
104
714115
fn skip_ws_and_comments_no_annotations(input: &mut &str) -> ModalResult<()> {
105
714115
    let mut dummy = Vec::new();
106
714115
    skip_ws_and_comments(input, &mut dummy)
107
714115
}
108

            
109
21089
fn parse_program(input: &mut &str) -> ModalResult<(Vec<Expr>, Vec<Annotation>)> {
110
21089
    let mut annotations = Vec::new();
111
21089
    skip_ws_and_comments(input, &mut annotations)?;
112

            
113
21089
    let mut exprs = Vec::new();
114
    loop {
115
70027
        if input.is_empty() {
116
20986
            break;
117
49041
        }
118
49041
        let expr = parse_expr(input)?;
119
48938
        exprs.push(expr);
120
48938
        skip_ws_and_comments(input, &mut annotations)?;
121
    }
122

            
123
20986
    Ok((exprs, annotations))
124
21089
}
125

            
126
592362
fn parse_expr(input: &mut &str) -> ModalResult<Expr> {
127
592362
    let expr = alt((
128
592362
        parse_nil,
129
592362
        parse_bool,
130
592362
        parse_number,
131
592362
        parse_string,
132
592362
        parse_quote,
133
592362
        parse_quasiquote,
134
592362
        parse_unquote,
135
592362
        parse_list,
136
592362
        parse_keyword,
137
592362
        parse_symbol,
138
592362
    ))
139
592362
    .parse_next(input)?;
140
592225
    debug!(expr = ?expr, "parsed expression");
141
592225
    Ok(expr)
142
592362
}
143

            
144
592362
fn parse_nil(input: &mut &str) -> ModalResult<Expr> {
145
592362
    alt(("nil", "NIL", "Nil"))
146
592362
        .value(Expr::Nil)
147
592362
        .parse_next(input)
148
592362
}
149

            
150
590147
fn parse_bool(input: &mut &str) -> ModalResult<Expr> {
151
590147
    alt(("#t", "#T", "#f", "#F"))
152
590147
        .map(|s: &str| match s {
153
2282
            "#t" | "#T" => Expr::Bool(true),
154
240
            _ => Expr::Nil,
155
2282
        })
156
590147
        .parse_next(input)
157
590147
}
158

            
159
64791
fn is_delimiter(c: char) -> bool {
160
64791
    c.is_whitespace() || "()\"'`,;".contains(c)
161
64791
}
162

            
163
587865
fn parse_number(input: &mut &str) -> ModalResult<Expr> {
164
587865
    let sign = opt(alt(("-".value(-1i64), "+".value(1i64))))
165
587865
        .map(|s| s.unwrap_or(1))
166
587865
        .parse_next(input)?;
167

            
168
587865
    let int_part: &str = digit1.parse_next(input)?;
169
65785
    let int_val: i64 = int_part
170
65785
        .parse()
171
65785
        .map_err(|_| ErrMode::Cut(ContextError::new()))?;
172

            
173
65785
    let frac_part = opt(preceded('.', digit1)).parse_next(input)?;
174

            
175
65785
    if input.starts_with(|c: char| !is_delimiter(c)) {
176
11356
        return Err(ErrMode::Backtrack(ContextError::new()));
177
54429
    }
178

            
179
54429
    let fraction = if let Some(decimals) = frac_part {
180
1429
        let decimal_places = decimals.len() as u32;
181
1429
        let decimal_val: i64 = decimals
182
1429
            .parse()
183
1429
            .map_err(|_| ErrMode::Cut(ContextError::new()))?;
184
1429
        let denom = 10i64.pow(decimal_places);
185
1429
        Fraction::new(sign * (int_val * denom + decimal_val), denom)
186
    } else {
187
53000
        Fraction::from_integer(sign * int_val)
188
    };
189

            
190
54429
    Ok(Expr::Number(fraction))
191
587865
}
192

            
193
533436
fn parse_string(input: &mut &str) -> ModalResult<Expr> {
194
533436
    alt((parse_triple_quoted_string, parse_double_quoted_string)).parse_next(input)
195
533436
}
196

            
197
533130
fn parse_double_quoted_string(input: &mut &str) -> ModalResult<Expr> {
198
533130
    let _ = '"'.parse_next(input)?;
199
6666
    let mut result = String::new();
200

            
201
    loop {
202
77980
        let chunk: &str = take_till(0.., |c| c == '"' || c == '\\').parse_next(input)?;
203
7585
        result.push_str(chunk);
204

            
205
7585
        match cut_err(any)
206
7585
            .context(StrContext::Label("string"))
207
7585
            .context(StrContext::Expected(StrContextValue::Description(
208
7585
                "closing quote",
209
7585
            )))
210
7585
            .parse_next(input)?
211
        {
212
6632
            '"' => return Ok(Expr::String(result)),
213
            '\\' => {
214
919
                let escaped = any.parse_next(input)?;
215
919
                match escaped {
216
239
                    'n' => result.push('\n'),
217
238
                    't' => result.push('\t'),
218
34
                    'r' => result.push('\r'),
219
204
                    '\\' => result.push('\\'),
220
204
                    '"' => result.push('"'),
221
                    c => {
222
                        result.push('\\');
223
                        result.push(c);
224
                    }
225
                }
226
            }
227
            _ => unreachable!(),
228
        }
229
    }
230
533130
}
231

            
232
533436
fn parse_triple_quoted_string(input: &mut &str) -> ModalResult<Expr> {
233
533436
    let _ = "\"\"\"".parse_next(input)?;
234
306
    let mut content = String::new();
235

            
236
    loop {
237
19516
        if input.starts_with("\"\"\"") {
238
306
            let _ = "\"\"\"".parse_next(input)?;
239
306
            return Ok(Expr::String(content));
240
19210
        }
241
19210
        if input.is_empty() {
242
            return Err(ErrMode::Cut(
243
                ContextError::new()
244
                    .add_context(input, &input.checkpoint(), StrContext::Label("string"))
245
                    .add_context(
246
                        input,
247
                        &input.checkpoint(),
248
                        StrContext::Expected(StrContextValue::Description("closing \"\"\"")),
249
                    ),
250
            ));
251
19210
        }
252
19210
        content.push(any.parse_next(input)?);
253
    }
254
533436
}
255

            
256
526464
fn parse_quote(input: &mut &str) -> ModalResult<Expr> {
257
526464
    let _ = '\''.parse_next(input)?;
258
15169
    let expr = parse_expr.parse_next(input)?;
259
15169
    Ok(Expr::Quote(Box::new(expr)))
260
526464
}
261

            
262
511295
fn parse_quasiquote(input: &mut &str) -> ModalResult<Expr> {
263
511295
    let _ = '`'.parse_next(input)?;
264
172
    let expr = parse_expr.parse_next(input)?;
265
172
    Ok(Expr::Quasiquote(Box::new(expr)))
266
511295
}
267

            
268
511123
fn parse_unquote(input: &mut &str) -> ModalResult<Expr> {
269
511123
    let _ = ','.parse_next(input)?;
270
208
    if input.starts_with('@') {
271
35
        let _ = '@'.parse_next(input)?;
272
35
        let expr = parse_expr.parse_next(input)?;
273
35
        return Ok(Expr::UnquoteSplicing(Box::new(expr)));
274
173
    }
275
173
    let expr = parse_expr.parse_next(input)?;
276
173
    Ok(Expr::Unquote(Box::new(expr)))
277
511123
}
278

            
279
510915
fn parse_list(input: &mut &str) -> ModalResult<Expr> {
280
510915
    let _ = '('.parse_next(input)?;
281
191849
    skip_ws_and_comments_no_annotations(input)?;
282

            
283
191849
    let mut items = Vec::new();
284
191849
    let mut dotted_cdr: Option<Expr> = None;
285

            
286
    loop {
287
714103
        if input.starts_with(')') {
288
191775
            break;
289
522328
        }
290
522328
        if input.is_empty() {
291
34
            return Err(ErrMode::Cut(
292
34
                ContextError::new()
293
34
                    .add_context(input, &input.checkpoint(), StrContext::Label("list"))
294
34
                    .add_context(
295
34
                        input,
296
34
                        &input.checkpoint(),
297
34
                        StrContext::Expected(StrContextValue::Description("closing paren")),
298
34
                    ),
299
34
            ));
300
522294
        }
301
522294
        if input.starts_with('.') && input.chars().nth(1).is_some_and(char::is_whitespace) {
302
6
            let _ = '.'.parse_next(input)?;
303
6
            skip_ws_and_comments_no_annotations(input)?;
304
6
            dotted_cdr = Some(
305
6
                cut_err(parse_expr)
306
6
                    .context(StrContext::Label("cdr expression"))
307
6
                    .parse_next(input)?,
308
            );
309
6
            skip_ws_and_comments_no_annotations(input)?;
310
6
            cut_err(')')
311
6
                .context(StrContext::Label("closing paren"))
312
6
                .context(StrContext::Expected(StrContextValue::Description(
313
6
                    "only one expression after dot in dotted pair",
314
6
                )))
315
6
                .parse_next(input)?;
316
5
            break;
317
522288
        }
318
522288
        let expr = parse_expr(input)?;
319
522254
        items.push(expr);
320
522254
        skip_ws_and_comments_no_annotations(input)?;
321
    }
322

            
323
191780
    if dotted_cdr.is_none() {
324
191775
        cut_err(')')
325
191775
            .context(StrContext::Label("list"))
326
191775
            .context(StrContext::Expected(StrContextValue::Description(
327
191775
                "closing paren",
328
191775
            )))
329
191775
            .parse_next(input)?;
330
5
    }
331

            
332
191780
    if let Some(cdr) = dotted_cdr {
333
5
        let mut result = cdr;
334
6
        for item in items.into_iter().rev() {
335
6
            result = Expr::cons(item, result);
336
6
        }
337
5
        Ok(result)
338
    } else {
339
191775
        Ok(Expr::List(items))
340
    }
341
510915
}
342

            
343
319066
fn parse_keyword(input: &mut &str) -> ModalResult<Expr> {
344
319066
    let _ = ':'.parse_next(input)?;
345
481
    let first: char =
346
481
        none_of(|c: char| c.is_whitespace() || "()\"'`,".contains(c)).parse_next(input)?;
347

            
348
3242
    let rest: &str = take_while(0.., |c: char| !c.is_whitespace() && !"()\"'`,".contains(c))
349
481
        .parse_next(input)?;
350

            
351
481
    let mut name = String::with_capacity(1 + rest.len());
352
481
    name.push(first);
353
481
    name.push_str(rest);
354
481
    name.make_ascii_uppercase();
355
481
    Ok(Expr::Keyword(name))
356
319066
}
357

            
358
318585
fn parse_symbol(input: &mut &str) -> ModalResult<Expr> {
359
318551
    let first: char =
360
318585
        none_of(|c: char| c.is_whitespace() || "()\"'`,".contains(c)).parse_next(input)?;
361

            
362
1311440
    let rest: &str = take_while(0.., |c: char| !c.is_whitespace() && !"()\"'`,".contains(c))
363
318551
        .parse_next(input)?;
364

            
365
318551
    let mut name = String::with_capacity(1 + rest.len());
366
318551
    name.push(first);
367
318551
    name.push_str(rest);
368
318551
    name.make_ascii_uppercase();
369
318551
    if name == "NIL" {
370
68
        return Ok(Expr::Nil);
371
318483
    }
372
318483
    if name == "T" {
373
3674
        return Ok(Expr::Bool(true));
374
314809
    }
375
314809
    Ok(Expr::Symbol(name))
376
318585
}
377

            
378
#[cfg(test)]
379
mod tests {
380
    use super::*;
381

            
382
    #[test]
383
1
    fn test_parse_nil() {
384
1
        let program = Reader::parse("nil").unwrap();
385
1
        assert_eq!(program.exprs, vec![Expr::Nil]);
386
1
    }
387

            
388
    #[test]
389
1
    fn test_parse_bool() {
390
1
        let program = Reader::parse("#t #T t T").unwrap();
391
1
        assert_eq!(
392
            program.exprs,
393
1
            vec![
394
1
                Expr::Bool(true),
395
1
                Expr::Bool(true),
396
1
                Expr::Bool(true),
397
1
                Expr::Bool(true),
398
            ]
399
        );
400
1
    }
401

            
402
    #[test]
403
1
    fn test_parse_false_is_nil() {
404
1
        let program = Reader::parse("#f #F").unwrap();
405
1
        assert_eq!(program.exprs, vec![Expr::Nil, Expr::Nil]);
406
1
    }
407

            
408
    #[test]
409
1
    fn test_parse_nil_case_insensitive() {
410
1
        assert_eq!(Reader::parse("nil").unwrap().exprs, vec![Expr::Nil]);
411
1
        assert_eq!(Reader::parse("NIL").unwrap().exprs, vec![Expr::Nil]);
412
1
        assert_eq!(Reader::parse("Nil").unwrap().exprs, vec![Expr::Nil]);
413
1
    }
414

            
415
    #[test]
416
1
    fn test_parse_integer() {
417
1
        let program = Reader::parse("42").unwrap();
418
1
        assert_eq!(
419
            program.exprs,
420
1
            vec![Expr::Number(Fraction::from_integer(42))]
421
        );
422
1
    }
423

            
424
    #[test]
425
1
    fn test_parse_negative_number() {
426
1
        let program = Reader::parse("-17").unwrap();
427
1
        assert_eq!(
428
            program.exprs,
429
1
            vec![Expr::Number(Fraction::from_integer(-17))]
430
        );
431
1
    }
432

            
433
    #[test]
434
1
    fn test_parse_decimal() {
435
1
        let program = Reader::parse("0.1").unwrap();
436
1
        assert_eq!(program.exprs, vec![Expr::Number(Fraction::new(1, 10))]);
437
1
    }
438

            
439
    #[test]
440
1
    fn test_parse_symbol() {
441
1
        let program = Reader::parse("foo").unwrap();
442
1
        assert_eq!(program.exprs, vec![Expr::Symbol("FOO".into())]);
443
1
    }
444

            
445
    #[test]
446
1
    fn test_parse_symbol_case_insensitive() {
447
1
        assert_eq!(
448
1
            Reader::parse("foo").unwrap().exprs,
449
1
            Reader::parse("FOO").unwrap().exprs
450
        );
451
1
        assert_eq!(
452
1
            Reader::parse("Sum").unwrap().exprs,
453
1
            Reader::parse("SUM").unwrap().exprs
454
        );
455
1
    }
456

            
457
    #[test]
458
1
    fn test_parse_keyword() {
459
1
        let program = Reader::parse(":foo").unwrap();
460
1
        assert_eq!(program.exprs, vec![Expr::Keyword("FOO".into())]);
461
1
    }
462

            
463
    #[test]
464
1
    fn test_parse_keyword_case_insensitive() {
465
1
        assert_eq!(
466
1
            Reader::parse(":foo").unwrap().exprs,
467
1
            Reader::parse(":FOO").unwrap().exprs
468
        );
469
1
        assert_eq!(
470
1
            Reader::parse(":Name").unwrap().exprs,
471
1
            Reader::parse(":NAME").unwrap().exprs
472
        );
473
1
    }
474

            
475
    #[test]
476
1
    fn test_parse_string() {
477
1
        let program = Reader::parse(r#""hello""#).unwrap();
478
1
        assert_eq!(program.exprs, vec![Expr::String("hello".into())]);
479
1
    }
480

            
481
    #[test]
482
1
    fn test_parse_string_with_escapes() {
483
1
        let program = Reader::parse(r#""hello\nworld""#).unwrap();
484
1
        assert_eq!(program.exprs, vec![Expr::String("hello\nworld".into())]);
485
1
    }
486

            
487
    #[test]
488
1
    fn test_parse_list() {
489
1
        let program = Reader::parse("(+ 1 2)").unwrap();
490
1
        assert_eq!(
491
            program.exprs,
492
1
            vec![Expr::List(vec![
493
1
                Expr::Symbol("+".into()),
494
1
                Expr::Number(Fraction::from_integer(1)),
495
1
                Expr::Number(Fraction::from_integer(2)),
496
1
            ])]
497
        );
498
1
    }
499

            
500
    #[test]
501
1
    fn test_parse_nested_list() {
502
1
        let program = Reader::parse("(define (square x) (* x x))").unwrap();
503
1
        assert_eq!(
504
            program.exprs,
505
1
            vec![Expr::List(vec![
506
1
                Expr::Symbol("DEFINE".into()),
507
1
                Expr::List(vec![
508
1
                    Expr::Symbol("SQUARE".into()),
509
1
                    Expr::Symbol("X".into()),
510
1
                ]),
511
1
                Expr::List(vec![
512
1
                    Expr::Symbol("*".into()),
513
1
                    Expr::Symbol("X".into()),
514
1
                    Expr::Symbol("X".into()),
515
1
                ]),
516
1
            ])]
517
        );
518
1
    }
519

            
520
    #[test]
521
1
    fn test_parse_quote() {
522
1
        let program = Reader::parse("'foo").unwrap();
523
1
        assert_eq!(
524
            program.exprs,
525
1
            vec![Expr::Quote(Box::new(Expr::Symbol("FOO".into())))]
526
        );
527
1
    }
528

            
529
    #[test]
530
1
    fn test_parse_quoted_list() {
531
1
        let program = Reader::parse("'(1 2 3)").unwrap();
532
1
        assert_eq!(
533
            program.exprs,
534
1
            vec![Expr::Quote(Box::new(Expr::List(vec![
535
1
                Expr::Number(Fraction::from_integer(1)),
536
1
                Expr::Number(Fraction::from_integer(2)),
537
1
                Expr::Number(Fraction::from_integer(3)),
538
1
            ])))]
539
        );
540
1
    }
541

            
542
    #[test]
543
1
    fn test_parse_multiple_exprs() {
544
1
        let program = Reader::parse("(DEFINE x 10) (+ x 5)").unwrap();
545
1
        assert_eq!(program.exprs.len(), 2);
546
1
    }
547

            
548
    #[test]
549
1
    fn test_parse_line_comment() {
550
1
        let program = Reader::parse("; this is a comment\n42").unwrap();
551
1
        assert_eq!(
552
            program.exprs,
553
1
            vec![Expr::Number(Fraction::from_integer(42))]
554
        );
555
1
    }
556

            
557
    #[test]
558
1
    fn test_parse_inline_comment() {
559
1
        let program = Reader::parse("42 ; inline comment").unwrap();
560
1
        assert_eq!(
561
            program.exprs,
562
1
            vec![Expr::Number(Fraction::from_integer(42))]
563
        );
564
1
    }
565

            
566
    #[test]
567
1
    fn test_parse_comment_in_list() {
568
1
        let program = Reader::parse("(+ 1 ; add one\n   2)").unwrap();
569
1
        assert_eq!(
570
            program.exprs,
571
1
            vec![Expr::List(vec![
572
1
                Expr::Symbol("+".into()),
573
1
                Expr::Number(Fraction::from_integer(1)),
574
1
                Expr::Number(Fraction::from_integer(2)),
575
1
            ])]
576
        );
577
1
    }
578

            
579
    #[test]
580
1
    fn test_parse_multiple_comments() {
581
1
        let code = "; comment 1
582
1
; comment 2
583
1
42
584
1
; comment 3";
585
1
        let program = Reader::parse(code).unwrap();
586
1
        assert_eq!(
587
            program.exprs,
588
1
            vec![Expr::Number(Fraction::from_integer(42))]
589
        );
590
1
    }
591

            
592
    #[test]
593
1
    fn test_parse_only_comments() {
594
1
        let program = Reader::parse("; just a comment").unwrap();
595
1
        assert!(program.exprs.is_empty());
596
1
    }
597

            
598
    #[test]
599
1
    fn test_parse_annotation() {
600
1
        let code = "; @test (= (count 'defun) 5)\n42";
601
1
        let program = Reader::parse(code).unwrap();
602
1
        assert_eq!(program.exprs.len(), 1);
603
1
        assert_eq!(program.annotations.len(), 1);
604
1
        assert_eq!(program.annotations[0].name, "test");
605
1
        assert!(matches!(&program.annotations[0].value, Expr::List(_)));
606
1
    }
607

            
608
    #[test]
609
1
    fn test_parse_multiple_annotations() {
610
1
        let code = "; @test (= (count 'defun) 5)\n; @test (= (count 'defvar) 2)\n42";
611
1
        let program = Reader::parse(code).unwrap();
612
1
        assert_eq!(program.annotations.len(), 2);
613
1
    }
614

            
615
    #[test]
616
1
    fn test_annotation_with_simple_expr() {
617
1
        let code = "; @version 1\n42";
618
1
        let program = Reader::parse(code).unwrap();
619
1
        assert_eq!(program.annotations.len(), 1);
620
1
        assert_eq!(program.annotations[0].name, "version");
621
1
        assert_eq!(
622
1
            program.annotations[0].value,
623
1
            Expr::Number(Fraction::from_integer(1))
624
        );
625
1
    }
626

            
627
    #[test]
628
1
    fn test_regular_comment_no_annotation() {
629
1
        let code = "; just a comment\n42";
630
1
        let program = Reader::parse(code).unwrap();
631
1
        assert!(program.annotations.is_empty());
632
1
    }
633

            
634
    #[test]
635
1
    fn test_parse_cons() {
636
1
        let program = Reader::parse("(a . b)").unwrap();
637
1
        assert_eq!(
638
            program.exprs,
639
1
            vec![Expr::cons(
640
1
                Expr::Symbol("A".into()),
641
1
                Expr::Symbol("B".into())
642
            )]
643
        );
644
1
    }
645

            
646
    #[test]
647
1
    fn test_parse_cons_proper_list() {
648
1
        let program = Reader::parse("(a . (b . nil))").unwrap();
649
1
        assert_eq!(
650
            program.exprs,
651
1
            vec![Expr::cons(
652
1
                Expr::Symbol("A".into()),
653
1
                Expr::cons(Expr::Symbol("B".into()), Expr::Nil)
654
            )]
655
        );
656
1
    }
657

            
658
    #[test]
659
1
    fn test_parse_improper_list() {
660
1
        let program = Reader::parse("(1 2 . 3)").unwrap();
661
1
        assert_eq!(
662
            program.exprs,
663
1
            vec![Expr::cons(
664
1
                Expr::Number(Fraction::from_integer(1)),
665
1
                Expr::cons(
666
1
                    Expr::Number(Fraction::from_integer(2)),
667
1
                    Expr::Number(Fraction::from_integer(3))
668
                )
669
            )]
670
        );
671
1
    }
672

            
673
    #[test]
674
1
    fn test_invalid_dotted_pair_multiple_exprs_after_dot() {
675
1
        let result = Reader::parse("(1 . (2 . 3) (3 . nil))");
676
1
        assert!(result.is_err());
677
1
        let err = result.unwrap_err();
678
1
        let msg = err.to_string();
679
1
        assert!(
680
1
            msg.contains("closing paren") || msg.contains("one expression after dot"),
681
            "Error should mention closing paren or one expression after dot, got: {msg}"
682
        );
683
1
    }
684

            
685
    #[test]
686
1
    fn test_parse_quasiquote() {
687
1
        let program = Reader::parse("`foo").unwrap();
688
1
        assert_eq!(
689
            program.exprs,
690
1
            vec![Expr::Quasiquote(Box::new(Expr::Symbol("FOO".into())))]
691
        );
692
1
    }
693

            
694
    #[test]
695
1
    fn test_parse_unquote() {
696
1
        let program = Reader::parse(",foo").unwrap();
697
1
        assert_eq!(
698
            program.exprs,
699
1
            vec![Expr::Unquote(Box::new(Expr::Symbol("FOO".into())))]
700
        );
701
1
    }
702

            
703
    #[test]
704
1
    fn test_parse_unquote_splicing() {
705
1
        let program = Reader::parse(",@foo").unwrap();
706
1
        assert_eq!(
707
            program.exprs,
708
1
            vec![Expr::UnquoteSplicing(Box::new(Expr::Symbol("FOO".into())))]
709
        );
710
1
    }
711

            
712
    #[test]
713
1
    fn test_parse_quasiquoted_list() {
714
1
        let program = Reader::parse("`(if ,test ,body)").unwrap();
715
1
        assert_eq!(
716
            program.exprs,
717
1
            vec![Expr::Quasiquote(Box::new(Expr::List(vec![
718
1
                Expr::Symbol("IF".into()),
719
1
                Expr::Unquote(Box::new(Expr::Symbol("TEST".into()))),
720
1
                Expr::Unquote(Box::new(Expr::Symbol("BODY".into()))),
721
1
            ])))]
722
        );
723
1
    }
724
}