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`] — numbers (integer / decimal / rational), `#u8(...)` byte
9
//!   vectors, `#"..."` base64 bytes, and the `#t` / `#f` rejection.
10
//! - [`strings`] — `"..."` and `"""..."""` string literals.
11
//! - [`forms`] — `'`/`,`/`,@`/`\`` reader macros and parenthesised
12
//!   lists (including dotted-pair tail).
13
//! - [`atoms`] — `:keyword` and bare symbols, including `nil` and `t`.
14

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

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

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

            
31
use crate::ast::{Annotation, Expr, Program};
32
use crate::error::{Error, Result};
33

            
34
pub struct Reader;
35

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

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

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

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

            
74
1845
fn format_context_error(ctx: &ContextError) -> String {
75
1845
    if let Some(cause) = ctx.cause() {
76
172
        return cause.to_string();
77
1673
    }
78
1673
    let mut parts = Vec::new();
79
2762
    for c in ctx.context() {
80
1507
        match c {
81
1255
            StrContext::Label(label) => parts.push(format!("in {label}")),
82
1507
            StrContext::Expected(StrContextValue::Description(desc)) => {
83
1507
                parts.push(format!("expected {desc}"));
84
1507
            }
85
            StrContext::Expected(StrContextValue::CharLiteral(c)) => {
86
                parts.push(format!("expected '{c}'"));
87
            }
88
            StrContext::Expected(StrContextValue::StringLiteral(s)) => {
89
                parts.push(format!("expected \"{s}\""));
90
            }
91
            _ => {}
92
        }
93
    }
94
1673
    if parts.is_empty() {
95
166
        "parse error".to_string()
96
    } else {
97
1507
        parts.join(", ")
98
    }
99
1845
}
100

            
101
438235
fn parse_program(input: &mut &str) -> ModalResult<(Vec<Expr>, Vec<Annotation>)> {
102
438235
    let mut annotations = Vec::new();
103
438235
    whitespace::skip_ws_and_comments(input, &mut annotations)?;
104

            
105
438235
    let mut exprs = Vec::new();
106
    loop {
107
1313554
        if input.is_empty() {
108
435560
            break;
109
877994
        }
110
877994
        let expr = parse_expr(input)?;
111
875319
        exprs.push(expr);
112
875319
        whitespace::skip_ws_and_comments(input, &mut annotations)?;
113
    }
114

            
115
435560
    Ok((exprs, annotations))
116
438235
}
117

            
118
/// Nesting the reader accepts before refusing the input.
119
///
120
/// `parse_expr` and `parse_list` are mutually recursive with a stack frame per
121
/// level, so without a ceiling a few kilobytes of `(((…` aborts the whole
122
/// process — and the reader is reachable from a report query parameter and from
123
/// the SSH eval channel. Same purpose as `MAX_MACRO_EXPANSION_DEPTH`.
124
///
125
/// 64 is measured, not guessed: on a 2 MiB stack (the default for both tokio
126
/// workers and libtest threads) a debug build survives 128 levels and aborts at
127
/// 192, so this keeps a 2x margin — and is still far more nesting than any real
128
/// filter or program.
129
const MAX_READ_DEPTH: usize = 64;
130

            
131
thread_local! {
132
    static READ_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
133
}
134

            
135
/// Restores the reader's depth however the parser returns.
136
struct DepthGuard;
137

            
138
impl Drop for DepthGuard {
139
8947802
    fn drop(&mut self) {
140
8947802
        READ_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
141
8947802
    }
142
}
143

            
144
8947802
pub(super) fn parse_expr(input: &mut &str) -> ModalResult<Expr> {
145
8947802
    let depth = READ_DEPTH.with(|d| {
146
8947802
        let next = d.get() + 1;
147
8947802
        d.set(next);
148
8947802
        next
149
8947802
    });
150
8947802
    let _guard = DepthGuard;
151
8947802
    if depth > MAX_READ_DEPTH {
152
166
        return Err(ErrMode::Cut(
153
166
            ContextError::new()
154
166
                .add_context(input, &input.checkpoint(), StrContext::Label("expression"))
155
166
                .add_context(
156
166
                    input,
157
166
                    &input.checkpoint(),
158
166
                    StrContext::Expected(StrContextValue::Description(
159
166
                        "nesting within the reader depth limit",
160
166
                    )),
161
166
                ),
162
166
        ));
163
8947636
    }
164

            
165
8947636
    let expr = alt((
166
8947636
        alt((
167
8947636
            literals::parse_hash_literal,
168
8947636
            literals::parse_number,
169
8947636
            strings::parse_string,
170
8947636
            forms::parse_quote,
171
8947636
        )),
172
8947636
        alt((
173
8947636
            forms::parse_quasiquote,
174
8947636
            forms::parse_unquote,
175
8947636
            forms::parse_list,
176
8947636
            atoms::parse_keyword,
177
8947636
            atoms::parse_symbol,
178
8947636
        )),
179
8947636
    ))
180
8947636
    .parse_next(input)?;
181
8933339
    debug!(expr = ?expr, "parsed expression");
182
8933339
    Ok(expr)
183
8947802
}