Skip to main content

cljrs_reader/
parser.rs

1// CljxError embeds NamedSource<String> for miette diagnostics, which is
2// unavoidably large. Suppress the false-positive for every returning function.
3#![allow(clippy::result_large_err)]
4
5use std::sync::Arc;
6
7use miette::NamedSource;
8
9use cljrs_types::error::{CljxError, CljxResult};
10use cljrs_types::span::Span;
11
12use crate::form::{Form, FormKind};
13use crate::lexer::Lexer;
14use crate::namespaced_map;
15use crate::token::Token;
16
17// ─── Parser ───────────────────────────────────────────────────────────────────
18
19/// True when a map's key/value parity is decidable at read time and violated.
20///
21/// Any reader conditional makes it undecidable: `#?@` contributes a
22/// branch-dependent number of forms and `#?` contributes one or none, so the
23/// check moves to the evaluator, which knows the platform.
24fn map_arity_is_statically_odd(forms: &[Form]) -> bool {
25    let has_conditional = forms
26        .iter()
27        .any(|f| matches!(f.kind, FormKind::ReaderCond { .. }));
28    !has_conditional && !forms.len().is_multiple_of(2)
29}
30
31pub struct Parser {
32    lexer: Lexer,
33    peeked: Option<(Token, Span)>,
34    discarded: Vec<Form>,
35}
36
37impl Parser {
38    pub fn new(source: String, file: String) -> Self {
39        Self {
40            lexer: Lexer::new(source, file),
41            peeked: None,
42            discarded: Vec::new(),
43        }
44    }
45
46    // ── Public API ────────────────────────────────────────────────────────
47
48    /// Return the next form, skipping `#_` discards.  Returns `None` at EOF.
49    pub fn parse_one(&mut self) -> CljxResult<Option<Form>> {
50        loop {
51            if matches!(self.peek_tok()?, Token::Eof) {
52                return Ok(None);
53            }
54            if let Some(form) = self.parse_raw()? {
55                return Ok(Some(form)); // #_ discard — loop for next form
56            }
57        }
58    }
59
60    /// Parse all forms until EOF, returning them as a `Vec`.
61    pub fn parse_all(&mut self) -> CljxResult<Vec<Form>> {
62        let mut forms = Vec::new();
63        while let Some(form) = self.parse_one()? {
64            forms.push(form);
65        }
66        Ok(forms)
67    }
68
69    /// Remove and return the concrete forms consumed by `#_` while parsing.
70    ///
71    /// Most callers can ignore discarded forms. Strict data readers use them
72    /// to validate tagged literals and invoke configured tag handlers even
73    /// when the resulting value is discarded, matching Clojure's EDN reader.
74    pub fn take_discarded_forms(&mut self) -> Vec<Form> {
75        std::mem::take(&mut self.discarded)
76    }
77
78    // ── Lookahead helpers ─────────────────────────────────────────────────
79
80    /// Ensure the one-token lookahead is populated.
81    fn fill(&mut self) -> CljxResult<()> {
82        if self.peeked.is_none() {
83            let pair = self.lexer.next_token()?;
84            self.peeked = Some(pair);
85        }
86        Ok(())
87    }
88
89    /// Consume and return the next token.
90    fn bump(&mut self) -> CljxResult<(Token, Span)> {
91        self.fill()?;
92        Ok(self.peeked.take().unwrap())
93    }
94
95    /// Clone the next token without consuming it.
96    fn peek_tok(&mut self) -> CljxResult<Token> {
97        self.fill()?;
98        Ok(self.peeked.as_ref().unwrap().0.clone())
99    }
100
101    /// Clone the span of the next token without consuming it.
102    fn peek_span(&mut self) -> CljxResult<Span> {
103        self.fill()?;
104        Ok(self.peeked.as_ref().unwrap().1.clone())
105    }
106
107    // ── Error construction ────────────────────────────────────────────────
108
109    fn make_error(&self, msg: impl Into<String>, span: Span) -> CljxError {
110        CljxError::ReadError {
111            message: msg.into(),
112            span: Some(miette::SourceSpan::from(span)),
113            src: NamedSource::new(
114                (**self.lexer.file()).clone(),
115                (**self.lexer.source()).clone(),
116            ),
117        }
118    }
119
120    // ── Span utilities ────────────────────────────────────────────────────
121
122    fn merged_span(&self, start: &Span, end: &Span) -> Span {
123        Span::new(
124            Arc::clone(&start.file),
125            start.start,
126            end.end,
127            start.line,
128            start.col,
129        )
130    }
131
132    // ── Core parsing ──────────────────────────────────────────────────────
133
134    /// Read the next "thing", returning `None` for `#_` discards (and EOF).
135    /// Callers that need a real form should loop past `None`.
136    fn parse_raw(&mut self) -> CljxResult<Option<Form>> {
137        let tok = self.peek_tok()?;
138        let span = self.peek_span()?;
139
140        match tok {
141            Token::Eof => Ok(None),
142
143            // Unexpected closing delimiters
144            Token::RParen | Token::RBracket | Token::RBrace => {
145                self.bump()?;
146                Err(self.make_error("unexpected closing delimiter", span))
147            }
148
149            // ── Atoms ──────────────────────────────────────────────────────────
150            Token::Nil => {
151                self.bump()?;
152                Ok(Some(Form::new(FormKind::Nil, span)))
153            }
154            Token::Bool(b) => {
155                self.bump()?;
156                Ok(Some(Form::new(FormKind::Bool(b), span)))
157            }
158            Token::Int(n) => {
159                self.bump()?;
160                Ok(Some(Form::new(FormKind::Int(n), span)))
161            }
162            Token::BigInt(s) => {
163                self.bump()?;
164                Ok(Some(Form::new(FormKind::BigInt(s), span)))
165            }
166            Token::Float(f) => {
167                self.bump()?;
168                Ok(Some(Form::new(FormKind::Float(f), span)))
169            }
170            Token::BigDecimal(s) => {
171                self.bump()?;
172                Ok(Some(Form::new(FormKind::BigDecimal(s), span)))
173            }
174            Token::Ratio(s) => {
175                self.bump()?;
176                Ok(Some(Form::new(FormKind::Ratio(s), span)))
177            }
178            Token::Char(c) => {
179                self.bump()?;
180                Ok(Some(Form::new(FormKind::Char(c), span)))
181            }
182            Token::Str(s) => {
183                self.bump()?;
184                Ok(Some(Form::new(FormKind::Str(s), span)))
185            }
186            Token::Regex(s) => {
187                self.bump()?;
188                Ok(Some(Form::new(FormKind::Regex(s), span)))
189            }
190            Token::Symbolic(s) => {
191                self.bump()?;
192                let val = match s.as_str() {
193                    "Inf" => f64::INFINITY,
194                    "-Inf" => f64::NEG_INFINITY,
195                    "NaN" => f64::NAN,
196                    _ => unreachable!("lexer guarantees only Inf/-Inf/NaN"),
197                };
198                Ok(Some(Form::new(FormKind::Symbolic(val), span)))
199            }
200
201            // ── Identifiers ────────────────────────────────────────────────────
202            Token::Symbol(s) => {
203                self.bump()?;
204                Ok(Some(Form::new(FormKind::Symbol(s), span)))
205            }
206            Token::Keyword(s) => {
207                self.bump()?;
208                Ok(Some(Form::new(FormKind::Keyword(s), span)))
209            }
210            Token::AutoKeyword(s) => {
211                self.bump()?;
212                Ok(Some(Form::new(FormKind::AutoKeyword(s), span)))
213            }
214
215            // ── Collections ────────────────────────────────────────────────────
216            Token::LParen => {
217                self.bump()?;
218                let (forms, close) = self.parse_seq_forms(Token::RParen, span.clone(), "list")?;
219                Ok(Some(Form::new(
220                    FormKind::List(forms),
221                    self.merged_span(&span, &close),
222                )))
223            }
224            Token::LBracket => {
225                self.bump()?;
226                let (forms, close) =
227                    self.parse_seq_forms(Token::RBracket, span.clone(), "vector")?;
228                Ok(Some(Form::new(
229                    FormKind::Vector(forms),
230                    self.merged_span(&span, &close),
231                )))
232            }
233            Token::LBrace => {
234                self.bump()?;
235                let (forms, close) = self.parse_seq_forms(Token::RBrace, span.clone(), "map")?;
236                if map_arity_is_statically_odd(&forms) {
237                    return Err(
238                        self.make_error("map literal must have an even number of forms", span)
239                    );
240                }
241                Ok(Some(Form::new(
242                    FormKind::Map(forms),
243                    self.merged_span(&span, &close),
244                )))
245            }
246            Token::NamespacedMap(ns) => {
247                self.bump()?;
248                // The lexer guarantees a `{` follows, so this reads the body
249                // through the ordinary map path and then rewrites the keys.
250                let open = self.peek_span()?;
251                self.bump()?;
252                let (forms, close) =
253                    self.parse_seq_forms(Token::RBrace, open.clone(), "namespaced map")?;
254                if !forms.len().is_multiple_of(2) {
255                    return Err(
256                        self.make_error("map literal must have an even number of forms", span)
257                    );
258                }
259                Ok(Some(Form::new(
260                    FormKind::Map(namespaced_map::qualify_keys(&ns, forms)),
261                    self.merged_span(&span, &close),
262                )))
263            }
264            Token::HashSet => {
265                self.bump()?;
266                let (forms, close) = self.parse_seq_forms(Token::RBrace, span.clone(), "set")?;
267                Ok(Some(Form::new(
268                    FormKind::Set(forms),
269                    self.merged_span(&span, &close),
270                )))
271            }
272            Token::HashFn => {
273                self.bump()?;
274                let (forms, close) =
275                    self.parse_seq_forms(Token::RParen, span.clone(), "anonymous function")?;
276                Ok(Some(Form::new(
277                    FormKind::AnonFn(forms),
278                    self.merged_span(&span, &close),
279                )))
280            }
281
282            // ── Wrapping reader macros ─────────────────────────────────────────
283            Token::Quote => {
284                self.bump()?;
285                let inner = self.require_form(span.clone(), "quoted form")?;
286                let end = inner.span.clone();
287                Ok(Some(Form::new(
288                    FormKind::Quote(Box::new(inner)),
289                    self.merged_span(&span, &end),
290                )))
291            }
292            Token::SyntaxQuote => {
293                self.bump()?;
294                let inner = self.require_form(span.clone(), "syntax-quoted form")?;
295                let end = inner.span.clone();
296                Ok(Some(Form::new(
297                    FormKind::SyntaxQuote(Box::new(inner)),
298                    self.merged_span(&span, &end),
299                )))
300            }
301            Token::Unquote => {
302                self.bump()?;
303                let inner = self.require_form(span.clone(), "unquoted form")?;
304                let end = inner.span.clone();
305                Ok(Some(Form::new(
306                    FormKind::Unquote(Box::new(inner)),
307                    self.merged_span(&span, &end),
308                )))
309            }
310            Token::UnquoteSplice => {
311                self.bump()?;
312                let inner = self.require_form(span.clone(), "unquote-spliced form")?;
313                let end = inner.span.clone();
314                Ok(Some(Form::new(
315                    FormKind::UnquoteSplice(Box::new(inner)),
316                    self.merged_span(&span, &end),
317                )))
318            }
319            Token::Deref => {
320                self.bump()?;
321                let inner = self.require_form(span.clone(), "deref form")?;
322                let end = inner.span.clone();
323                Ok(Some(Form::new(
324                    FormKind::Deref(Box::new(inner)),
325                    self.merged_span(&span, &end),
326                )))
327            }
328            Token::HashVar => {
329                self.bump()?;
330                let inner = self.require_form(span.clone(), "var form")?;
331                let end = inner.span.clone();
332                Ok(Some(Form::new(
333                    FormKind::Var(Box::new(inner)),
334                    self.merged_span(&span, &end),
335                )))
336            }
337            Token::Meta => {
338                self.bump()?;
339                let meta = self.require_form(span.clone(), "meta form")?;
340                let target = self.require_form(span.clone(), "annotated form")?;
341                let end = target.span.clone();
342                Ok(Some(Form::new(
343                    FormKind::Meta(Box::new(meta), Box::new(target)),
344                    self.merged_span(&span, &end),
345                )))
346            }
347
348            // ── `#_` discard ───────────────────────────────────────────────────
349            Token::HashDiscard => {
350                self.bump()?;
351                if matches!(self.peek_tok()?, Token::Eof) {
352                    return Err(self.make_error("unexpected end of file after #_", span));
353                }
354                // A nested discard is not itself the form consumed by the
355                // outer discard. Keep reading until one concrete form has
356                // been consumed: `#_ #_ 2 3` discards both 2 and 3.
357                let discarded = self.require_form(span.clone(), "discarded form")?;
358                self.discarded.push(discarded);
359                Ok(None)
360            }
361
362            // ── Reader conditionals ────────────────────────────────────────────
363            Token::ReaderCond => {
364                self.bump()?;
365                let form = self.parse_reader_cond(false, span)?;
366                Ok(Some(form))
367            }
368            Token::ReaderCondSplice => {
369                self.bump()?;
370                let form = self.parse_reader_cond(true, span)?;
371                Ok(Some(form))
372            }
373
374            // ── Tagged literal ─────────────────────────────────────────────────
375            Token::TaggedLiteral(tag) => {
376                self.bump()?;
377                let inner = self.require_form(span.clone(), "tagged literal value")?;
378                let end = inner.span.clone();
379                Ok(Some(Form::new(
380                    FormKind::TaggedLiteral(tag, Box::new(inner)),
381                    self.merged_span(&span, &end),
382                )))
383            }
384        }
385    }
386
387    /// Read forms until `closing` is encountered, returning the forms and the
388    /// closing delimiter's span.  `open_span` is used in the unclosed-delimiter
389    /// error message.
390    fn parse_seq_forms(
391        &mut self,
392        closing: Token,
393        open_span: Span,
394        name: &str,
395    ) -> CljxResult<(Vec<Form>, Span)> {
396        let mut forms = Vec::new();
397        loop {
398            let tok = self.peek_tok()?;
399            if tok == Token::Eof {
400                return Err(self.make_error(format!("unclosed {name}"), open_span));
401            }
402            if tok == closing {
403                let (_, close_span) = self.bump()?;
404                return Ok((forms, close_span));
405            }
406            if let Some(form) = self.parse_raw()? {
407                forms.push(form); // #_ discard — skip
408            }
409        }
410    }
411
412    /// Like `parse_one` but errors (pointing at `macro_span`) when EOF is
413    /// reached before a form is found.
414    fn require_form(&mut self, macro_span: Span, what: &str) -> CljxResult<Form> {
415        loop {
416            if matches!(self.peek_tok()?, Token::Eof) {
417                return Err(self.make_error(
418                    format!("unexpected end of file; expected {what}"),
419                    macro_span,
420                ));
421            }
422            if let Some(form) = self.parse_raw()? {
423                return Ok(form); // #_ discard — keep looking
424            }
425        }
426    }
427
428    /// Parse the body of a reader conditional (`#?` or `#?@`).
429    /// Expects `(` to be the next token; errors otherwise.
430    fn parse_reader_cond(&mut self, splicing: bool, start: Span) -> CljxResult<Form> {
431        let next = self.peek_tok()?;
432        if next != Token::LParen {
433            let span = self.peek_span()?;
434            return Err(self.make_error(
435                "reader conditional requires `(` immediately after `#?`",
436                span,
437            ));
438        }
439        let (_, open_span) = self.bump()?; // consume `(`
440        let (clauses, close_span) =
441            self.parse_seq_forms(Token::RParen, open_span.clone(), "reader conditional")?;
442        if clauses.len() % 2 != 0 {
443            return Err(self.make_error(
444                "reader conditional must have an even number of clauses",
445                open_span,
446            ));
447        }
448        Ok(Form::new(
449            FormKind::ReaderCond { splicing, clauses },
450            self.merged_span(&start, &close_span),
451        ))
452    }
453}
454
455// ─── Iterator ─────────────────────────────────────────────────────────────────
456
457impl Iterator for Parser {
458    type Item = CljxResult<Form>;
459
460    fn next(&mut self) -> Option<Self::Item> {
461        match self.parse_one() {
462            Ok(Some(form)) => Some(Ok(form)),
463            Ok(None) => None,
464            Err(e) => Some(Err(e)),
465        }
466    }
467}
468
469// ─── Tests ────────────────────────────────────────────────────────────────────
470
471#[cfg(test)]
472mod tests {
473    use std::sync::Arc;
474
475    use cljrs_types::{error::CljxError, span::Span};
476
477    use super::*;
478
479    // ── Helpers ───────────────────────────────────────────────────────────────
480
481    fn dummy_span() -> Span {
482        Span::new(Arc::new("<test>".to_string()), 0, 0, 1, 1)
483    }
484
485    /// Construct a Form with a dummy span (for use in assertions only).
486    fn f(kind: FormKind) -> Form {
487        Form::new(kind, dummy_span())
488    }
489
490    fn parse_all(src: &str) -> Vec<Form> {
491        Parser::new(src.to_string(), "<test>".to_string())
492            .parse_all()
493            .unwrap_or_else(|e| panic!("parse error: {e}"))
494    }
495
496    fn parse1(src: &str) -> Form {
497        Parser::new(src.to_string(), "<test>".to_string())
498            .parse_one()
499            .unwrap_or_else(|e| panic!("parse error: {e}"))
500            .expect("expected a form but got EOF")
501    }
502
503    fn parse_err(src: &str) -> String {
504        let mut p = Parser::new(src.to_string(), "<test>".to_string());
505        match p.parse_all() {
506            Err(CljxError::ReadError { message, .. }) => message,
507            Err(e) => panic!("unexpected error type: {e:?}"),
508            Ok(forms) => panic!("expected a parse error but got: {forms:?}"),
509        }
510    }
511
512    // ── Atoms ─────────────────────────────────────────────────────────────────
513
514    #[test]
515    fn test_nil() {
516        assert_eq!(parse1("nil").kind, FormKind::Nil);
517    }
518
519    #[test]
520    fn test_bool() {
521        assert_eq!(parse1("true").kind, FormKind::Bool(true));
522        assert_eq!(parse1("false").kind, FormKind::Bool(false));
523    }
524
525    #[test]
526    fn test_int() {
527        assert_eq!(parse1("42").kind, FormKind::Int(42));
528        assert_eq!(parse1("-7").kind, FormKind::Int(-7));
529    }
530
531    #[test]
532    fn test_bigint() {
533        assert_eq!(parse1("42N").kind, FormKind::BigInt("42".to_string()));
534    }
535
536    #[test]
537    #[allow(clippy::approx_constant)]
538    fn test_float() {
539        assert_eq!(parse1("3.14").kind, FormKind::Float(3.14));
540        assert_eq!(parse1("1e10").kind, FormKind::Float(1e10));
541    }
542
543    #[test]
544    fn test_bigdecimal() {
545        assert_eq!(
546            parse1("3.14M").kind,
547            FormKind::BigDecimal("3.14".to_string())
548        );
549    }
550
551    #[test]
552    fn test_ratio() {
553        assert_eq!(parse1("3/4").kind, FormKind::Ratio("3/4".to_string()));
554        assert_eq!(parse1("-1/2").kind, FormKind::Ratio("-1/2".to_string()));
555    }
556
557    #[test]
558    fn test_char() {
559        assert_eq!(parse1("\\a").kind, FormKind::Char('a'));
560        assert_eq!(parse1("\\newline").kind, FormKind::Char('\n'));
561    }
562
563    #[test]
564    fn test_str() {
565        assert_eq!(parse1("\"hello\"").kind, FormKind::Str("hello".to_string()));
566    }
567
568    #[test]
569    fn test_regex() {
570        assert_eq!(
571            parse1("#\"[a-z]+\"").kind,
572            FormKind::Regex("[a-z]+".to_string())
573        );
574    }
575
576    #[test]
577    fn test_symbolic() {
578        assert!(matches!(
579            parse1("##Inf").kind,
580            FormKind::Symbolic(f) if f == f64::INFINITY
581        ));
582        assert!(matches!(
583            parse1("##-Inf").kind,
584            FormKind::Symbolic(f) if f == f64::NEG_INFINITY
585        ));
586        // NaN != NaN per IEEE 754
587        assert!(matches!(
588            parse1("##NaN").kind,
589            FormKind::Symbolic(f) if f.is_nan()
590        ));
591    }
592
593    #[test]
594    fn test_symbol() {
595        assert_eq!(parse1("foo").kind, FormKind::Symbol("foo".to_string()));
596    }
597
598    #[test]
599    fn test_keyword() {
600        assert_eq!(parse1(":foo").kind, FormKind::Keyword("foo".to_string()));
601    }
602
603    #[test]
604    fn test_auto_keyword() {
605        assert_eq!(
606            parse1("::foo").kind,
607            FormKind::AutoKeyword("foo".to_string())
608        );
609    }
610
611    // ── Collections ───────────────────────────────────────────────────────────
612
613    #[test]
614    fn test_empty_list() {
615        assert_eq!(parse1("()").kind, FormKind::List(vec![]));
616    }
617
618    #[test]
619    fn test_list() {
620        assert_eq!(
621            parse1("(1 2 3)").kind,
622            FormKind::List(vec![
623                f(FormKind::Int(1)),
624                f(FormKind::Int(2)),
625                f(FormKind::Int(3)),
626            ])
627        );
628    }
629
630    #[test]
631    fn test_vector() {
632        assert_eq!(
633            parse1("[1 2]").kind,
634            FormKind::Vector(vec![f(FormKind::Int(1)), f(FormKind::Int(2))])
635        );
636    }
637
638    #[test]
639    fn test_map() {
640        assert_eq!(
641            parse1("{:a 1}").kind,
642            FormKind::Map(vec![
643                f(FormKind::Keyword("a".to_string())),
644                f(FormKind::Int(1)),
645            ])
646        );
647    }
648
649    #[test]
650    fn test_namespaced_map_literal_ns() {
651        assert_eq!(
652            parse1("#:adt{:a 1 :b 2}").kind,
653            FormKind::Map(vec![
654                f(FormKind::Keyword("adt/a".to_string())),
655                f(FormKind::Int(1)),
656                f(FormKind::Keyword("adt/b".to_string())),
657                f(FormKind::Int(2)),
658            ])
659        );
660    }
661
662    #[test]
663    fn test_namespaced_map_leaves_values_and_explicit_keys_alone() {
664        assert_eq!(
665            parse1("#:adt{:a :b other/c 1 :_/d 2}").kind,
666            FormKind::Map(vec![
667                f(FormKind::Keyword("adt/a".to_string())),
668                // A value that looks like a bare key stays bare.
669                f(FormKind::Keyword("b".to_string())),
670                // An explicitly-namespaced key wins over the map's namespace.
671                f(FormKind::Symbol("other/c".to_string())),
672                f(FormKind::Int(1)),
673                // `_` opts a key out of the map's namespace.
674                f(FormKind::Keyword("d".to_string())),
675                f(FormKind::Int(2)),
676            ])
677        );
678    }
679
680    #[test]
681    fn test_namespaced_map_auto_resolved_lowers_to_auto_keyword() {
682        assert_eq!(
683            parse1("#::{:a 1}").kind,
684            FormKind::Map(vec![
685                f(FormKind::AutoKeyword("a".to_string())),
686                f(FormKind::Int(1)),
687            ])
688        );
689        assert_eq!(
690            parse1("#::al{:a 1}").kind,
691            FormKind::Map(vec![
692                f(FormKind::AutoKeyword("al/a".to_string())),
693                f(FormKind::Int(1)),
694            ])
695        );
696    }
697
698    #[test]
699    fn test_namespaced_map_nests() {
700        assert_eq!(
701            parse1("#:a{:x #:b{:y 1}}").kind,
702            FormKind::Map(vec![
703                f(FormKind::Keyword("a/x".to_string())),
704                f(FormKind::Map(vec![
705                    f(FormKind::Keyword("b/y".to_string())),
706                    f(FormKind::Int(1)),
707                ])),
708            ])
709        );
710    }
711
712    #[test]
713    fn test_namespaced_map_odd_form_count_is_an_error() {
714        let err = parse_err("#:a{:x}");
715        assert!(err.contains("even number of forms"), "{err}");
716    }
717
718    #[test]
719    fn test_set() {
720        assert_eq!(
721            parse1("#{1 2}").kind,
722            FormKind::Set(vec![f(FormKind::Int(1)), f(FormKind::Int(2))])
723        );
724    }
725
726    // ── Nested ────────────────────────────────────────────────────────────────
727
728    #[test]
729    fn test_nested() {
730        assert_eq!(
731            parse1("(+ [1 2] {:a 3})").kind,
732            FormKind::List(vec![
733                f(FormKind::Symbol("+".to_string())),
734                f(FormKind::Vector(vec![
735                    f(FormKind::Int(1)),
736                    f(FormKind::Int(2)),
737                ])),
738                f(FormKind::Map(vec![
739                    f(FormKind::Keyword("a".to_string())),
740                    f(FormKind::Int(3)),
741                ])),
742            ])
743        );
744    }
745
746    // ── Reader macros ─────────────────────────────────────────────────────────
747
748    #[test]
749    fn test_quote() {
750        assert_eq!(
751            parse1("'foo").kind,
752            FormKind::Quote(Box::new(f(FormKind::Symbol("foo".to_string()))))
753        );
754    }
755
756    #[test]
757    fn test_syntax_quote() {
758        assert_eq!(
759            parse1("`foo").kind,
760            FormKind::SyntaxQuote(Box::new(f(FormKind::Symbol("foo".to_string()))))
761        );
762    }
763
764    #[test]
765    fn test_unquote() {
766        assert_eq!(
767            parse1("~foo").kind,
768            FormKind::Unquote(Box::new(f(FormKind::Symbol("foo".to_string()))))
769        );
770    }
771
772    #[test]
773    fn test_unquote_splice() {
774        assert_eq!(
775            parse1("~@foo").kind,
776            FormKind::UnquoteSplice(Box::new(f(FormKind::Symbol("foo".to_string()))))
777        );
778    }
779
780    #[test]
781    fn test_deref() {
782        assert_eq!(
783            parse1("@foo").kind,
784            FormKind::Deref(Box::new(f(FormKind::Symbol("foo".to_string()))))
785        );
786    }
787
788    #[test]
789    fn test_var() {
790        assert_eq!(
791            parse1("#'foo").kind,
792            FormKind::Var(Box::new(f(FormKind::Symbol("foo".to_string()))))
793        );
794    }
795
796    // ── Meta ──────────────────────────────────────────────────────────────────
797
798    #[test]
799    fn test_meta_map() {
800        assert_eq!(
801            parse1("^{:a 1} foo").kind,
802            FormKind::Meta(
803                Box::new(f(FormKind::Map(vec![
804                    f(FormKind::Keyword("a".to_string())),
805                    f(FormKind::Int(1)),
806                ]))),
807                Box::new(f(FormKind::Symbol("foo".to_string()))),
808            )
809        );
810    }
811
812    #[test]
813    fn test_meta_keyword() {
814        assert_eq!(
815            parse1("^:kw foo").kind,
816            FormKind::Meta(
817                Box::new(f(FormKind::Keyword("kw".to_string()))),
818                Box::new(f(FormKind::Symbol("foo".to_string()))),
819            )
820        );
821    }
822
823    #[test]
824    fn test_meta_symbol() {
825        assert_eq!(
826            parse1("^Sym foo").kind,
827            FormKind::Meta(
828                Box::new(f(FormKind::Symbol("Sym".to_string()))),
829                Box::new(f(FormKind::Symbol("foo".to_string()))),
830            )
831        );
832    }
833
834    // ── Anonymous function ────────────────────────────────────────────────────
835
836    #[test]
837    fn test_anon_fn() {
838        assert_eq!(
839            parse1("#(+ % 1)").kind,
840            FormKind::AnonFn(vec![
841                f(FormKind::Symbol("+".to_string())),
842                f(FormKind::Symbol("%".to_string())),
843                f(FormKind::Int(1)),
844            ])
845        );
846    }
847
848    // ── #_ discard ────────────────────────────────────────────────────────────
849
850    #[test]
851    fn test_discard_simple() {
852        let forms = parse_all("#_foo bar");
853        assert_eq!(forms.len(), 1);
854        assert_eq!(forms[0].kind, FormKind::Symbol("bar".to_string()));
855    }
856
857    #[test]
858    fn test_discard_in_vector() {
859        assert_eq!(
860            parse1("[1 #_2 3]").kind,
861            FormKind::Vector(vec![f(FormKind::Int(1)), f(FormKind::Int(3))])
862        );
863    }
864
865    #[test]
866    fn test_discard_chained() {
867        // The inner #_ consumes 1; the outer #_ then consumes 2.
868        let forms = parse_all("#_ #_ 1 2 3");
869        assert_eq!(forms.len(), 1);
870        assert_eq!(forms[0].kind, FormKind::Int(3));
871    }
872
873    // ── Reader conditionals ───────────────────────────────────────────────────
874
875    #[test]
876    fn test_reader_cond() {
877        assert_eq!(
878            parse1("#?(:rust 1 :clj 2)").kind,
879            FormKind::ReaderCond {
880                splicing: false,
881                clauses: vec![
882                    f(FormKind::Keyword("rust".to_string())),
883                    f(FormKind::Int(1)),
884                    f(FormKind::Keyword("clj".to_string())),
885                    f(FormKind::Int(2)),
886                ],
887            }
888        );
889    }
890
891    #[test]
892    fn test_reader_cond_splice() {
893        assert_eq!(
894            parse1("#?@(:rust [1 2])").kind,
895            FormKind::ReaderCond {
896                splicing: true,
897                clauses: vec![
898                    f(FormKind::Keyword("rust".to_string())),
899                    f(FormKind::Vector(vec![
900                        f(FormKind::Int(1)),
901                        f(FormKind::Int(2)),
902                    ])),
903                ],
904            }
905        );
906    }
907
908    // ── Tagged literal ────────────────────────────────────────────────────────
909
910    #[test]
911    fn test_tagged_literal() {
912        assert_eq!(
913            parse1("#inst \"2024-01-01\"").kind,
914            FormKind::TaggedLiteral(
915                "inst".to_string(),
916                Box::new(f(FormKind::Str("2024-01-01".to_string()))),
917            )
918        );
919    }
920
921    // ── Span tracking ─────────────────────────────────────────────────────────
922
923    #[test]
924    fn test_span_col_offset() {
925        let form = parse1("  42");
926        assert_eq!(form.span.start, 2);
927        assert_eq!(form.span.col, 3);
928    }
929
930    #[test]
931    fn test_span_multiline() {
932        let forms = parse_all("a\nb");
933        assert_eq!(forms[0].span.line, 1);
934        assert_eq!(forms[1].span.line, 2);
935    }
936
937    // ── parse_all ─────────────────────────────────────────────────────────────
938
939    #[test]
940    fn test_parse_all_multiple() {
941        let forms = parse_all("1 2 3");
942        assert_eq!(forms.len(), 3);
943        assert_eq!(forms[0].kind, FormKind::Int(1));
944        assert_eq!(forms[1].kind, FormKind::Int(2));
945        assert_eq!(forms[2].kind, FormKind::Int(3));
946    }
947
948    // ── Errors ────────────────────────────────────────────────────────────────
949
950    #[test]
951    fn test_err_unclosed_list() {
952        let msg = parse_err("(1 2");
953        assert!(msg.contains("unclosed") || msg.contains("list"), "{msg}");
954    }
955
956    #[test]
957    fn test_err_unexpected_close() {
958        let msg = parse_err(")");
959        assert!(msg.contains("unexpected"), "{msg}");
960    }
961
962    #[test]
963    fn test_err_odd_map() {
964        let msg = parse_err("{:a}");
965        assert!(msg.contains("even") || msg.contains("map"), "{msg}");
966    }
967
968    #[test]
969    fn test_map_with_splice_defers_parity_check() {
970        let mut p = Parser::new(
971            "{:a 1 #?@(:rust [:b 2]) :c 3}".to_string(),
972            "<test>".to_string(),
973        );
974        let form = p.parse_one().unwrap().unwrap();
975        assert!(matches!(form.kind, FormKind::Map(_)));
976    }
977
978    #[test]
979    fn test_map_with_non_splicing_conditional_defers_parity_check() {
980        // A non-splicing `#?` contributes one form or none, so the written
981        // parity is not the expanded parity either: `{#?(:clj :a)}` is an
982        // empty map under `:rust` and must reach the evaluator to find out.
983        for src in ["{#?(:clj :a)}", "{:a #?(:rust 1 :clj 2) :b 2}"] {
984            let mut p = Parser::new(src.to_string(), "<test>".to_string());
985            let form = p
986                .parse_one()
987                .unwrap_or_else(|e| panic!("{src} rejected at read time: {e}"))
988                .unwrap();
989            assert!(matches!(form.kind, FormKind::Map(_)), "{src}");
990        }
991    }
992
993    #[test]
994    fn test_map_odd_without_splice_still_errors() {
995        let msg = parse_err("{:a 1 :b}");
996        assert!(msg.contains("even") || msg.contains("map"), "{msg}");
997    }
998
999    #[test]
1000    fn test_err_reader_cond_non_list() {
1001        // #?[ is invalid; reader cond must be followed by (
1002        let msg = parse_err("#?[1 2]");
1003        assert!(
1004            msg.contains('(') || msg.contains("reader conditional"),
1005            "{msg}"
1006        );
1007    }
1008
1009    #[test]
1010    fn test_err_odd_reader_cond_clauses() {
1011        let msg = parse_err("#?(:cljx)");
1012        assert!(
1013            msg.contains("even") || msg.contains("reader conditional"),
1014            "{msg}"
1015        );
1016    }
1017}