1
//! Reader macros for quote / quasiquote / unquote / unquote-splicing
2
//! plus the parenthesised list parser (including the dotted-pair tail
3
//! `(a . b)` form).
4

            
5
use winnow::combinator::cut_err;
6
use winnow::error::{AddContext, ContextError, ErrMode, ModalResult, StrContext, StrContextValue};
7
use winnow::prelude::*;
8

            
9
use crate::ast::Expr;
10

            
11
use super::parse_expr;
12
use super::whitespace::skip_ws_and_comments_no_annotations;
13

            
14
/// `'nil` and `'t` are the constants themselves, so `(eql nil '())` holds.
15
8401341
pub(super) fn parse_quote(input: &mut &str) -> ModalResult<Expr> {
16
8401341
    let _ = '\''.parse_next(input)?;
17
57695
    match parse_expr.parse_next(input)? {
18
6891
        constant @ (Expr::Nil | Expr::T) => Ok(constant),
19
50803
        expr => Ok(Expr::Quote(Box::new(expr))),
20
    }
21
8401341
}
22

            
23
8343646
pub(super) fn parse_quasiquote(input: &mut &str) -> ModalResult<Expr> {
24
8343646
    let _ = '`'.parse_next(input)?;
25
1746
    let expr = parse_expr.parse_next(input)?;
26
1746
    Ok(Expr::Quasiquote(Box::new(expr)))
27
8343646
}
28

            
29
8341900
pub(super) fn parse_unquote(input: &mut &str) -> ModalResult<Expr> {
30
8341900
    let _ = ','.parse_next(input)?;
31
1830
    if input.starts_with('@') {
32
250
        let _ = '@'.parse_next(input)?;
33
250
        let expr = parse_expr.parse_next(input)?;
34
250
        return Ok(Expr::UnquoteSplicing(Box::new(expr)));
35
1580
    }
36
1580
    let expr = parse_expr.parse_next(input)?;
37
1580
    Ok(Expr::Unquote(Box::new(expr)))
38
8341900
}
39

            
40
8340070
pub(super) fn parse_list(input: &mut &str) -> ModalResult<Expr> {
41
8340070
    let _ = '('.parse_next(input)?;
42
3204117
    skip_ws_and_comments_no_annotations(input)?;
43

            
44
3204117
    let mut items = Vec::new();
45
3204117
    let mut dotted_cdr: Option<Expr> = None;
46

            
47
    loop {
48
11186570
        if input.starts_with(')') {
49
3190332
            break;
50
7996238
        }
51
7996238
        if input.is_empty() {
52
1245
            return Err(ErrMode::Cut(
53
1245
                ContextError::new()
54
1245
                    .add_context(input, &input.checkpoint(), StrContext::Label("list"))
55
1245
                    .add_context(
56
1245
                        input,
57
1245
                        &input.checkpoint(),
58
1245
                        StrContext::Expected(StrContextValue::Description("closing paren")),
59
1245
                    ),
60
1245
            ));
61
7994993
        }
62
7994993
        if input.starts_with('.') && input.chars().nth(1).is_some_and(char::is_whitespace) {
63
753
            let _ = '.'.parse_next(input)?;
64
753
            skip_ws_and_comments_no_annotations(input)?;
65
753
            dotted_cdr = Some(
66
753
                cut_err(parse_expr)
67
753
                    .context(StrContext::Label("cdr expression"))
68
753
                    .parse_next(input)?,
69
            );
70
753
            skip_ws_and_comments_no_annotations(input)?;
71
753
            cut_err(')')
72
753
                .context(StrContext::Label("closing paren"))
73
753
                .context(StrContext::Expected(StrContextValue::Description(
74
753
                    "only one expression after dot in dotted pair",
75
753
                )))
76
753
                .parse_next(input)?;
77
752
            break;
78
7994240
        }
79
7994240
        let expr = parse_expr(input)?;
80
7982453
        items.push(expr);
81
7982453
        skip_ws_and_comments_no_annotations(input)?;
82
    }
83

            
84
3191084
    if dotted_cdr.is_none() {
85
3190332
        cut_err(')')
86
3190332
            .context(StrContext::Label("list"))
87
3190332
            .context(StrContext::Expected(StrContextValue::Description(
88
3190332
                "closing paren",
89
3190332
            )))
90
3190332
            .parse_next(input)?;
91
752
    }
92

            
93
3190332
    match dotted_cdr {
94
752
        Some(cdr) => Ok(items
95
752
            .into_iter()
96
752
            .rev()
97
753
            .fold(cdr, |tail, item| Expr::cons(item, tail))),
98
3190332
        None if items.is_empty() => Ok(Expr::Nil),
99
3175721
        None => Ok(Expr::List(items)),
100
    }
101
8340070
}