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