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