1#![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
16fn 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 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)); }
54 }
55 }
56
57 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 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 fn bump(&mut self) -> CljxResult<(Token, Span)> {
79 self.fill()?;
80 Ok(self.peeked.take().unwrap())
81 }
82
83 fn peek_tok(&mut self) -> CljxResult<Token> {
85 self.fill()?;
86 Ok(self.peeked.as_ref().unwrap().0.clone())
87 }
88
89 fn peek_span(&mut self) -> CljxResult<Span> {
91 self.fill()?;
92 Ok(self.peeked.as_ref().unwrap().1.clone())
93 }
94
95 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 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 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 Token::RParen | Token::RBracket | Token::RBrace => {
133 self.bump()?;
134 Err(self.make_error("unexpected closing delimiter", span))
135 }
136
137 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 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 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 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 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()?; Ok(None)
326 }
327
328 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 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 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); }
375 }
376 }
377
378 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); }
391 }
392 }
393
394 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()?; 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
421impl 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#[cfg(test)]
438mod tests {
439 use std::sync::Arc;
440
441 use cljrs_types::{error::CljxError, span::Span};
442
443 use super::*;
444
445 fn dummy_span() -> Span {
448 Span::new(Arc::new("<test>".to_string()), 0, 0, 1, 1)
449 }
450
451 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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 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 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}