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
32209
    pub fn parse(input: &str) -> Result<Program> {
16
32209
        debug!(input_len = input.len(), "parse start");
17
32209
        let mut remaining = input;
18
32209
        let (exprs, annotations) = parse_program(&mut remaining).map_err(|e| {
19
133
            let offset = input.len() - remaining.len();
20
133
            Error::parse(format_parse_error(e), input, offset..offset + 1)
21
133
        })?;
22
32076
        debug!(
23
            expr_count = exprs.len(),
24
            annotation_count = annotations.len(),
25
            "parse complete"
26
        );
27
32076
        Ok(Program::with_annotations(exprs, annotations))
28
32209
    }
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
440
    pub fn parse_expr(input: &str) -> Result<Expr> {
37
440
        let mut remaining = input;
38
440
        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
440
    }
43
}
44

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

            
52
133
fn format_context_error(ctx: &ContextError) -> String {
53
133
    let mut parts = Vec::new();
54
178
    for c in ctx.context() {
55
89
        match c {
56
89
            StrContext::Label(label) => parts.push(format!("in {label}")),
57
89
            StrContext::Expected(StrContextValue::Description(desc)) => {
58
89
                parts.push(format!("expected {desc}"));
59
89
            }
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
133
    if parts.is_empty() {
70
44
        "parse error".to_string()
71
    } else {
72
89
        parts.join(", ")
73
    }
74
133
}
75

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

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

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

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

            
113
32209
    let mut exprs = Vec::new();
114
    loop {
115
102757
        if input.is_empty() {
116
32076
            break;
117
70681
        }
118
70681
        let expr = parse_expr(input)?;
119
70548
        exprs.push(expr);
120
70548
        skip_ws_and_comments(input, &mut annotations)?;
121
    }
122

            
123
32076
    Ok((exprs, annotations))
124
32209
}
125

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

            
148
862856
fn parse_nil(input: &mut &str) -> ModalResult<Expr> {
149
862856
    alt(("nil", "NIL", "Nil"))
150
862856
        .value(Expr::Nil)
151
862856
        .parse_next(input)
152
862856
}
153

            
154
859547
fn parse_bool(input: &mut &str) -> ModalResult<Expr> {
155
859547
    alt(("#t", "#T", "#f", "#F"))
156
859547
        .map(|s: &str| match s {
157
2952
            "#t" | "#T" => Expr::Bool(true),
158
310
            _ => Expr::Nil,
159
2952
        })
160
859547
        .parse_next(input)
161
859547
}
162

            
163
96523
fn is_delimiter(c: char) -> bool {
164
96523
    c.is_whitespace() || "()\"'`,;".contains(c)
165
96523
}
166

            
167
856595
fn parse_number(input: &mut &str) -> ModalResult<Expr> {
168
856595
    let sign = opt(alt(("-".value(-1i64), "+".value(1i64))))
169
856595
        .map(|s| s.unwrap_or(1))
170
856595
        .parse_next(input)?;
171

            
172
856595
    let int_part: &str = digit1.parse_next(input)?;
173
97895
    let int_val: i64 = int_part
174
97895
        .parse()
175
97895
        .map_err(|_| ErrMode::Cut(ContextError::new()))?;
176

            
177
97895
    let frac_part = opt(preceded('.', digit1)).parse_next(input)?;
178

            
179
97895
    if input.starts_with(|c: char| !is_delimiter(c)) {
180
17600
        return Err(ErrMode::Backtrack(ContextError::new()));
181
80295
    }
182

            
183
80295
    let fraction = if let Some(decimals) = frac_part {
184
1849
        let decimal_places = decimals.len() as u32;
185
1849
        let decimal_val: i64 = decimals
186
1849
            .parse()
187
1849
            .map_err(|_| ErrMode::Cut(ContextError::new()))?;
188
1849
        let denom = 10i64.pow(decimal_places);
189
1849
        Fraction::new(sign * (int_val * denom + decimal_val), denom)
190
    } else {
191
78446
        Fraction::from_integer(sign * int_val)
192
    };
193

            
194
80295
    Ok(Expr::Number(fraction))
195
856595
}
196

            
197
776300
fn parse_string(input: &mut &str) -> ModalResult<Expr> {
198
776300
    alt((parse_triple_quoted_string, parse_double_quoted_string)).parse_next(input)
199
776300
}
200

            
201
775904
fn parse_double_quoted_string(input: &mut &str) -> ModalResult<Expr> {
202
775904
    let _ = '"'.parse_next(input)?;
203
8892
    let mut result = String::new();
204

            
205
    loop {
206
96344
        let chunk: &str = take_till(0.., |c| c == '"' || c == '\\').parse_next(input)?;
207
10081
        result.push_str(chunk);
208

            
209
10081
        match cut_err(any)
210
10081
            .context(StrContext::Label("string"))
211
10081
            .context(StrContext::Expected(StrContextValue::Description(
212
10081
                "closing quote",
213
10081
            )))
214
10081
            .parse_next(input)?
215
        {
216
8848
            '"' => return Ok(Expr::String(result)),
217
            '\\' => {
218
1189
                let escaped = any.parse_next(input)?;
219
1189
                match escaped {
220
309
                    'n' => result.push('\n'),
221
308
                    't' => result.push('\t'),
222
44
                    'r' => result.push('\r'),
223
264
                    '\\' => result.push('\\'),
224
264
                    '"' => result.push('"'),
225
                    c => {
226
                        result.push('\\');
227
                        result.push(c);
228
                    }
229
                }
230
            }
231
            _ => unreachable!(),
232
        }
233
    }
234
775904
}
235

            
236
776300
fn parse_triple_quoted_string(input: &mut &str) -> ModalResult<Expr> {
237
776300
    let _ = "\"\"\"".parse_next(input)?;
238
396
    let mut content = String::new();
239

            
240
    loop {
241
25256
        if input.starts_with("\"\"\"") {
242
396
            let _ = "\"\"\"".parse_next(input)?;
243
396
            return Ok(Expr::String(content));
244
24860
        }
245
24860
        if input.is_empty() {
246
            return Err(ErrMode::Cut(
247
                ContextError::new()
248
                    .add_context(input, &input.checkpoint(), StrContext::Label("string"))
249
                    .add_context(
250
                        input,
251
                        &input.checkpoint(),
252
                        StrContext::Expected(StrContextValue::Description("closing \"\"\"")),
253
                    ),
254
            ));
255
24860
        }
256
24860
        content.push(any.parse_next(input)?);
257
    }
258
776300
}
259

            
260
767012
fn parse_quote(input: &mut &str) -> ModalResult<Expr> {
261
767012
    let _ = '\''.parse_next(input)?;
262
19497
    let expr = parse_expr.parse_next(input)?;
263
19497
    Ok(Expr::Quote(Box::new(expr)))
264
767012
}
265

            
266
747515
fn parse_quasiquote(input: &mut &str) -> ModalResult<Expr> {
267
747515
    let _ = '`'.parse_next(input)?;
268
222
    let expr = parse_expr.parse_next(input)?;
269
222
    Ok(Expr::Quasiquote(Box::new(expr)))
270
747515
}
271

            
272
747293
fn parse_unquote(input: &mut &str) -> ModalResult<Expr> {
273
747293
    let _ = ','.parse_next(input)?;
274
268
    if input.starts_with('@') {
275
45
        let _ = '@'.parse_next(input)?;
276
45
        let expr = parse_expr.parse_next(input)?;
277
45
        return Ok(Expr::UnquoteSplicing(Box::new(expr)));
278
223
    }
279
223
    let expr = parse_expr.parse_next(input)?;
280
223
    Ok(Expr::Unquote(Box::new(expr)))
281
747293
}
282

            
283
747025
fn parse_list(input: &mut &str) -> ModalResult<Expr> {
284
747025
    let _ = '('.parse_next(input)?;
285
283071
    skip_ws_and_comments_no_annotations(input)?;
286

            
287
283071
    let mut items = Vec::new();
288
283071
    let mut dotted_cdr: Option<Expr> = None;
289

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

            
327
282982
    if dotted_cdr.is_none() {
328
282977
        cut_err(')')
329
282977
            .context(StrContext::Label("list"))
330
282977
            .context(StrContext::Expected(StrContextValue::Description(
331
282977
                "closing paren",
332
282977
            )))
333
282977
            .parse_next(input)?;
334
5
    }
335

            
336
282982
    if let Some(cdr) = dotted_cdr {
337
5
        let mut result = cdr;
338
6
        for item in items.into_iter().rev() {
339
6
            result = Expr::cons(item, result);
340
6
        }
341
5
        Ok(result)
342
    } else {
343
282977
        Ok(Expr::List(items))
344
    }
345
747025
}
346

            
347
463954
fn parse_keyword(input: &mut &str) -> ModalResult<Expr> {
348
463954
    let _ = ':'.parse_next(input)?;
349
533
    let first: char =
350
533
        none_of(|c: char| c.is_whitespace() || "()\"'`,".contains(c)).parse_next(input)?;
351

            
352
3796
    let rest: &str = take_while(0.., |c: char| !c.is_whitespace() && !"()\"'`,".contains(c))
353
533
        .parse_next(input)?;
354

            
355
533
    let mut name = String::with_capacity(1 + rest.len());
356
533
    name.push(first);
357
533
    name.push_str(rest);
358
533
    name.make_ascii_uppercase();
359
533
    Ok(Expr::Keyword(name))
360
463954
}
361

            
362
463421
fn parse_symbol(input: &mut &str) -> ModalResult<Expr> {
363
463377
    let first: char =
364
463421
        none_of(|c: char| c.is_whitespace() || "()\"'`,".contains(c)).parse_next(input)?;
365

            
366
1915674
    let rest: &str = take_while(0.., |c: char| !c.is_whitespace() && !"()\"'`,".contains(c))
367
463377
        .parse_next(input)?;
368

            
369
463377
    let mut name = String::with_capacity(1 + rest.len());
370
463377
    name.push(first);
371
463377
    name.push_str(rest);
372
463377
    name.make_ascii_uppercase();
373
463377
    if name == "NIL" {
374
88
        return Ok(Expr::Nil);
375
463289
    }
376
463289
    if name == "T" {
377
4710
        return Ok(Expr::Bool(true));
378
458579
    }
379
458579
    Ok(Expr::Symbol(name))
380
463421
}
381

            
382
#[cfg(test)]
383
mod tests {
384
    use super::*;
385

            
386
    #[test]
387
1
    fn test_parse_nil() {
388
1
        let program = Reader::parse("nil").unwrap();
389
1
        assert_eq!(program.exprs, vec![Expr::Nil]);
390
1
    }
391

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

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

            
412
    #[test]
413
1
    fn test_parse_nil_case_insensitive() {
414
1
        assert_eq!(Reader::parse("nil").unwrap().exprs, vec![Expr::Nil]);
415
1
        assert_eq!(Reader::parse("NIL").unwrap().exprs, vec![Expr::Nil]);
416
1
        assert_eq!(Reader::parse("Nil").unwrap().exprs, vec![Expr::Nil]);
417
1
    }
418

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
596
    #[test]
597
1
    fn test_parse_only_comments() {
598
1
        let program = Reader::parse("; just a comment").unwrap();
599
1
        assert!(program.exprs.is_empty());
600
1
    }
601

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

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

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

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

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

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

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

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

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

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

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

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