1
//! S-expression reader (winnow-based).
2
//!
3
//! Public surface: [`Reader::parse`] for full programs and
4
//! [`Reader::parse_expr`] for a single expression. Internally split
5
//! into topic-focused submodules to stay under the ~500-line
6
//! CLAUDE.md guideline:
7
//! - [`whitespace`] — whitespace + `;` / `; @annotation` comments.
8
//! - [`literals`] — `nil`, `#t` / `#f`, numbers (integer / decimal /
9
//!   rational), `#u8(...)` byte vectors, `#"..."` base64 bytes.
10
//! - [`strings`] — `"..."` and `"""..."""` string literals.
11
//! - [`forms`] — `'`/`,`/`,@`/`\`` reader macros and parenthesised
12
//!   lists (including dotted-pair tail).
13
//! - [`atoms`] — `:keyword` and bare symbols.
14

            
15
mod atoms;
16
mod forms;
17
mod literals;
18
mod strings;
19
mod whitespace;
20

            
21
#[cfg(test)]
22
mod tests;
23

            
24
use tracing::debug;
25
use winnow::combinator::alt;
26
use winnow::error::{ContextError, ErrMode, ModalResult, StrContext, StrContextValue};
27
use winnow::prelude::*;
28

            
29
use crate::ast::{Annotation, Expr, Program};
30
use crate::error::{Error, Result};
31

            
32
pub struct Reader;
33

            
34
impl Reader {
35
214893
    pub fn parse(input: &str) -> Result<Program> {
36
214893
        debug!(input_len = input.len(), "parse start");
37
214893
        let mut remaining = input;
38
214893
        let (exprs, annotations) = parse_program(&mut remaining).map_err(|e| {
39
965
            let offset = input.len() - remaining.len();
40
965
            Error::parse(format_parse_error(e), input, offset..offset + 1)
41
965
        })?;
42
213928
        debug!(
43
            expr_count = exprs.len(),
44
            annotation_count = annotations.len(),
45
            "parse complete"
46
        );
47
213928
        Ok(Program::with_annotations(exprs, annotations))
48
214893
    }
49

            
50
    #[must_use]
51
70788
    pub fn is_incomplete(input: &str) -> bool {
52
70788
        let mut remaining = input;
53
70788
        parse_program(&mut remaining).is_err() && remaining.is_empty()
54
70788
    }
55

            
56
691
    pub fn parse_expr(input: &str) -> Result<Expr> {
57
691
        let mut remaining = input;
58
691
        parse_expr(&mut remaining).map_err(|e| {
59
            let offset = input.len() - remaining.len();
60
            Error::parse(format_parse_error(e), input, offset..offset + 1)
61
        })
62
691
    }
63
}
64

            
65
965
fn format_parse_error(err: ErrMode<ContextError>) -> String {
66
965
    match err {
67
965
        ErrMode::Backtrack(ctx) | ErrMode::Cut(ctx) => format_context_error(&ctx),
68
        ErrMode::Incomplete(_) => "unexpected end of input".to_string(),
69
    }
70
965
}
71

            
72
965
fn format_context_error(ctx: &ContextError) -> String {
73
965
    let mut parts = Vec::new();
74
1587
    for c in ctx.context() {
75
897
        match c {
76
690
            StrContext::Label(label) => parts.push(format!("in {label}")),
77
897
            StrContext::Expected(StrContextValue::Description(desc)) => {
78
897
                parts.push(format!("expected {desc}"));
79
897
            }
80
            StrContext::Expected(StrContextValue::CharLiteral(c)) => {
81
                parts.push(format!("expected '{c}'"));
82
            }
83
            StrContext::Expected(StrContextValue::StringLiteral(s)) => {
84
                parts.push(format!("expected \"{s}\""));
85
            }
86
            _ => {}
87
        }
88
    }
89
965
    if parts.is_empty() {
90
68
        "parse error".to_string()
91
    } else {
92
897
        parts.join(", ")
93
    }
94
965
}
95

            
96
285681
fn parse_program(input: &mut &str) -> ModalResult<(Vec<Expr>, Vec<Annotation>)> {
97
285681
    let mut annotations = Vec::new();
98
285681
    whitespace::skip_ws_and_comments(input, &mut annotations)?;
99

            
100
285681
    let mut exprs = Vec::new();
101
    loop {
102
884278
        if input.is_empty() {
103
284036
            break;
104
600242
        }
105
600242
        let expr = parse_expr(input)?;
106
598597
        exprs.push(expr);
107
598597
        whitespace::skip_ws_and_comments(input, &mut annotations)?;
108
    }
109

            
110
284036
    Ok((exprs, annotations))
111
285681
}
112

            
113
6059579
pub(super) fn parse_expr(input: &mut &str) -> ModalResult<Expr> {
114
6059579
    let expr = alt((
115
6059579
        alt((
116
6059579
            literals::parse_nil,
117
6059579
            literals::parse_hash_literal,
118
6059579
            literals::parse_number,
119
6059579
            strings::parse_string,
120
6059579
            forms::parse_quote,
121
6059579
        )),
122
6059579
        alt((
123
6059579
            forms::parse_quasiquote,
124
6059579
            forms::parse_unquote,
125
6059579
            forms::parse_list,
126
6059579
            atoms::parse_keyword,
127
6059579
            atoms::parse_symbol,
128
6059579
        )),
129
6059579
    ))
130
6059579
    .parse_next(input)?;
131
6057526
    debug!(expr = ?expr, "parsed expression");
132
6057526
    Ok(expr)
133
6059579
}