Skip to main content

sema_reader/
reader.rs

1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use sema_core::{resolve, SemaError, Span, SpanMap, Value, ValueView};
5
6use crate::lexer::{tokenize, FStringPart, SpannedToken, Token};
7
8struct Parser {
9    tokens: Vec<SpannedToken>,
10    pos: usize,
11    span_map: SpanMap,
12}
13
14impl Parser {
15    fn new(tokens: Vec<SpannedToken>) -> Self {
16        Parser {
17            tokens,
18            pos: 0,
19            span_map: SpanMap::new(),
20        }
21    }
22
23    fn peek(&self) -> Option<&Token> {
24        let mut pos = self.pos;
25        while let Some(t) = self.tokens.get(pos) {
26            match &t.token {
27                Token::Comment(_) | Token::Newline => pos += 1,
28                _ => return Some(&t.token),
29            }
30        }
31        None
32    }
33
34    fn span(&self) -> Span {
35        let mut pos = self.pos;
36        while let Some(t) = self.tokens.get(pos) {
37            match &t.token {
38                Token::Comment(_) | Token::Newline => pos += 1,
39                _ => return t.span,
40            }
41        }
42        Span::point(0, 0)
43    }
44
45    fn skip_trivia(&mut self) {
46        while let Some(t) = self.tokens.get(self.pos) {
47            match &t.token {
48                Token::Comment(_) | Token::Newline => self.pos += 1,
49                _ => break,
50            }
51        }
52    }
53
54    fn advance(&mut self) -> Option<&SpannedToken> {
55        self.skip_trivia();
56        let tok = self.tokens.get(self.pos);
57        if tok.is_some() {
58            self.pos += 1;
59        }
60        tok
61    }
62
63    fn expect(&mut self, expected: &Token) -> Result<(), SemaError> {
64        let span = self.span();
65        match self.advance() {
66            Some(t) if &t.token == expected => Ok(()),
67            Some(t) => Err(SemaError::Reader {
68                message: format!(
69                    "expected `{}`, got `{}`",
70                    token_display(expected),
71                    token_display(&t.token)
72                ),
73                span,
74            }),
75            None => Err(SemaError::Reader {
76                message: format!("expected `{}`, got end of input", token_display(expected)),
77                span,
78            }),
79        }
80    }
81
82    fn parse_expr(&mut self) -> Result<Value, SemaError> {
83        let span = self.span();
84        match self.peek() {
85            None => Err(SemaError::Reader {
86                message: "unexpected end of input".to_string(),
87                span,
88            }),
89            Some(Token::LParen) => self.parse_list(),
90            Some(Token::LBracket) => self.parse_vector(),
91            Some(Token::LBrace) => self.parse_map(),
92            Some(Token::Quote) => {
93                self.advance();
94                let inner = self.parse_expr().map_err(|_| {
95                    SemaError::Reader {
96                        message: "quote (') requires an expression after it".to_string(),
97                        span,
98                    }
99                    .with_hint("e.g. '(1 2 3) or 'foo")
100                })?;
101                self.make_list_with_span(vec![Value::symbol("quote"), inner], span)
102            }
103            Some(Token::Quasiquote) => {
104                self.advance();
105                let inner = self.parse_expr().map_err(|_| {
106                    SemaError::Reader {
107                        message: "quasiquote (`) requires an expression after it".to_string(),
108                        span,
109                    }
110                    .with_hint("e.g. `(list ,x)")
111                })?;
112                self.make_list_with_span(vec![Value::symbol("quasiquote"), inner], span)
113            }
114            Some(Token::Unquote) => {
115                self.advance();
116                let inner = self.parse_expr().map_err(|_| {
117                    SemaError::Reader {
118                        message: "unquote (,) requires an expression after it".to_string(),
119                        span,
120                    }
121                    .with_hint("use inside quasiquote, e.g. `(list ,x)")
122                })?;
123                self.make_list_with_span(vec![Value::symbol("unquote"), inner], span)
124            }
125            Some(Token::UnquoteSplice) => {
126                self.advance();
127                let inner = self.parse_expr().map_err(|_| {
128                    SemaError::Reader {
129                        message: "unquote-splicing (,@) requires an expression after it"
130                            .to_string(),
131                        span,
132                    }
133                    .with_hint("use inside quasiquote, e.g. `(list ,@xs)")
134                })?;
135                self.make_list_with_span(vec![Value::symbol("unquote-splicing"), inner], span)
136            }
137            Some(Token::BytevectorStart) => self.parse_bytevector(),
138            Some(Token::ShortLambdaStart) => self.parse_short_lambda(),
139            Some(_) => self.parse_atom(),
140        }
141    }
142
143    fn make_list_with_span(&mut self, items: Vec<Value>, span: Span) -> Result<Value, SemaError> {
144        let rc = Rc::new(items);
145        let ptr = Rc::as_ptr(&rc) as usize;
146        self.span_map.insert(ptr, span);
147        Ok(Value::list_from_rc(rc))
148    }
149
150    /// Get the span of the previously consumed token (the one at pos-1).
151    fn prev_span(&self) -> Span {
152        if self.pos > 0 {
153            self.tokens[self.pos - 1].span
154        } else {
155            Span::point(0, 0)
156        }
157    }
158
159    fn parse_list(&mut self) -> Result<Value, SemaError> {
160        let open_span = self.span();
161        self.expect(&Token::LParen)?;
162        let mut items = Vec::new();
163        while self.peek() != Some(&Token::RParen) {
164            if self.peek().is_none() {
165                return Err(SemaError::Reader {
166                    message: "unterminated list".to_string(),
167                    span: open_span,
168                }
169                .with_hint("add a closing `)`"));
170            }
171            if self.peek() == Some(&Token::RBracket) {
172                return Err(SemaError::Reader {
173                    message: "mismatched bracket: expected `)` to close `(`, found `]`".to_string(),
174                    span: self.span(),
175                }
176                .with_hint("this list was opened with `(` — close it with `)`"));
177            }
178            if self.peek() == Some(&Token::RBrace) {
179                return Err(SemaError::Reader {
180                    message: "mismatched bracket: expected `)` to close `(`, found `}`".to_string(),
181                    span: self.span(),
182                }
183                .with_hint("this list was opened with `(` — close it with `)`"));
184            }
185            // Handle dotted pairs: (a . b)
186            if self.peek() == Some(&Token::Dot) {
187                self.advance(); // skip dot
188                let cdr = self.parse_expr()?;
189                self.expect(&Token::RParen)?;
190                let close = self.prev_span();
191                items.push(Value::symbol("."));
192                items.push(cdr);
193                return self.make_list_with_span(items, open_span.to(&close));
194            }
195            items.push(self.parse_expr()?);
196        }
197        self.expect(&Token::RParen)?;
198        let close = self.prev_span();
199        self.make_list_with_span(items, open_span.to(&close))
200    }
201
202    fn parse_vector(&mut self) -> Result<Value, SemaError> {
203        let open_span = self.span();
204        self.expect(&Token::LBracket)?;
205        let mut items = Vec::new();
206        while self.peek() != Some(&Token::RBracket) {
207            if self.peek().is_none() {
208                return Err(SemaError::Reader {
209                    message: "unterminated vector".to_string(),
210                    span: open_span,
211                }
212                .with_hint("add a closing `]`"));
213            }
214            if self.peek() == Some(&Token::RParen) {
215                return Err(SemaError::Reader {
216                    message: "mismatched bracket: expected `]` to close `[`, found `)`".to_string(),
217                    span: self.span(),
218                }
219                .with_hint("this vector was opened with `[` — close it with `]`"));
220            }
221            if self.peek() == Some(&Token::RBrace) {
222                return Err(SemaError::Reader {
223                    message: "mismatched bracket: expected `]` to close `[`, found `}`".to_string(),
224                    span: self.span(),
225                }
226                .with_hint("this vector was opened with `[` — close it with `]`"));
227            }
228            items.push(self.parse_expr()?);
229        }
230        self.expect(&Token::RBracket)?;
231        let close = self.prev_span();
232        let rc = Rc::new(items);
233        let ptr = Rc::as_ptr(&rc) as usize;
234        self.span_map.insert(ptr, open_span.to(&close));
235        Ok(Value::vector_from_rc(rc))
236    }
237
238    fn parse_map(&mut self) -> Result<Value, SemaError> {
239        let open_span = self.span();
240        self.expect(&Token::LBrace)?;
241        let mut map = BTreeMap::new();
242        while self.peek() != Some(&Token::RBrace) {
243            if self.peek().is_none() {
244                return Err(SemaError::Reader {
245                    message: "unterminated map".to_string(),
246                    span: open_span,
247                }
248                .with_hint("add a closing `}`"));
249            }
250            if self.peek() == Some(&Token::RParen) {
251                return Err(SemaError::Reader {
252                    message: "mismatched bracket: expected `}` to close `{`, found `)`".to_string(),
253                    span: self.span(),
254                }
255                .with_hint("this map was opened with `{` — close it with `}`"));
256            }
257            if self.peek() == Some(&Token::RBracket) {
258                return Err(SemaError::Reader {
259                    message: "mismatched bracket: expected `}` to close `{`, found `]`".to_string(),
260                    span: self.span(),
261                }
262                .with_hint("this map was opened with `{` — close it with `}`"));
263            }
264            let key = self.parse_expr()?;
265            if self.peek() == Some(&Token::RBrace) || self.peek().is_none() {
266                return Err(SemaError::Reader {
267                    message: "map literal must have even number of forms".to_string(),
268                    span: self.span(),
269                });
270            }
271            let val = self.parse_expr()?;
272            map.insert(key, val);
273        }
274        self.expect(&Token::RBrace)?;
275        Ok(Value::map(map))
276    }
277
278    fn parse_bytevector(&mut self) -> Result<Value, SemaError> {
279        let open_span = self.span();
280        self.advance(); // consume BytevectorStart token
281        let mut bytes = Vec::new();
282        while self.peek() != Some(&Token::RParen) {
283            if self.peek().is_none() {
284                return Err(SemaError::Reader {
285                    message: "unterminated bytevector".to_string(),
286                    span: open_span,
287                }
288                .with_hint("add a closing `)`"));
289            }
290            let span = self.span();
291            match self.peek() {
292                Some(Token::Int(n)) => {
293                    let n = *n;
294                    self.advance();
295                    if !(0..=255).contains(&n) {
296                        return Err(SemaError::Reader {
297                            message: format!("#u8(...): byte value {n} out of range 0..255"),
298                            span,
299                        });
300                    }
301                    bytes.push(n as u8);
302                }
303                _ => {
304                    return Err(SemaError::Reader {
305                        message: "#u8(...): expected integer byte value".to_string(),
306                        span,
307                    });
308                }
309            }
310        }
311        self.expect(&Token::RParen)?;
312        Ok(Value::bytevector(bytes))
313    }
314
315    fn parse_short_lambda(&mut self) -> Result<Value, SemaError> {
316        let open_span = self.span();
317        self.advance(); // consume ShortLambdaStart
318        let mut body_items = Vec::new();
319        while self.peek() != Some(&Token::RParen) {
320            if self.peek().is_none() {
321                return Err(SemaError::Reader {
322                    message: "unterminated short lambda #(...)".to_string(),
323                    span: open_span,
324                }
325                .with_hint("add a closing `)`"));
326            }
327            body_items.push(self.parse_expr()?);
328        }
329        self.expect(&Token::RParen)?;
330
331        // Build the body as a single list form: (fn-name arg1 arg2 ...)
332        let body = Value::list(body_items);
333
334        // Scan body for % / %1 / %2 etc., rewrite % → %1
335        let mut max_arg: usize = 0;
336        let body = rewrite_percent_args(&body, &mut max_arg);
337
338        // Build parameter list
339        let params: Vec<Value> = if max_arg == 0 {
340            vec![]
341        } else {
342            (1..=max_arg)
343                .map(|n| Value::symbol(&format!("%{}", n)))
344                .collect()
345        };
346
347        Ok(Value::list(vec![
348            Value::symbol("lambda"),
349            Value::list(params),
350            body,
351        ]))
352    }
353
354    fn parse_atom(&mut self) -> Result<Value, SemaError> {
355        let span = self.span();
356        match self.advance() {
357            Some(SpannedToken {
358                token: Token::Int(n),
359                ..
360            }) => Ok(Value::int(*n)),
361            Some(SpannedToken {
362                token: Token::Float(f),
363                ..
364            }) => Ok(Value::float(*f)),
365            Some(SpannedToken {
366                token: Token::String(s),
367                ..
368            }) => Ok(Value::string(s)),
369            Some(SpannedToken {
370                token: Token::Regex(s),
371                ..
372            }) => Ok(Value::string(s)),
373            Some(SpannedToken {
374                token: Token::Symbol(s),
375                ..
376            }) => {
377                if s == "nil" {
378                    Ok(Value::nil())
379                } else {
380                    Ok(Value::symbol(s))
381                }
382            }
383            Some(SpannedToken {
384                token: Token::Keyword(s),
385                ..
386            }) => Ok(Value::keyword(s)),
387            Some(SpannedToken {
388                token: Token::Bool(b),
389                ..
390            }) => Ok(Value::bool(*b)),
391            Some(SpannedToken {
392                token: Token::Char(c),
393                ..
394            }) => Ok(Value::char(*c)),
395            Some(SpannedToken {
396                token: Token::FString(parts),
397                ..
398            }) => {
399                let parts = parts.clone();
400                let mut items = vec![Value::symbol("str")];
401                for part in &parts {
402                    match part {
403                        FStringPart::Literal(s) => {
404                            if !s.is_empty() {
405                                items.push(Value::string(s));
406                            }
407                        }
408                        FStringPart::Expr(src) => {
409                            let val = read(src)?;
410                            items.push(val);
411                        }
412                    }
413                }
414                Ok(Value::list(items))
415            }
416            Some(t) => {
417                let (name, hint) = match &t.token {
418                    Token::RParen => (
419                        "unexpected closing `)`",
420                        Some("no matching opening parenthesis"),
421                    ),
422                    Token::RBracket => (
423                        "unexpected closing `]`",
424                        Some("no matching opening bracket"),
425                    ),
426                    Token::RBrace => ("unexpected closing `}`", Some("no matching opening brace")),
427                    Token::Dot => (
428                        "unexpected `.`",
429                        Some("dots are used in pair notation, e.g. (a . b)"),
430                    ),
431                    _ => ("unexpected token", None),
432                };
433                let err = SemaError::Reader {
434                    message: name.to_string(),
435                    span,
436                };
437                Err(if let Some(h) = hint {
438                    err.with_hint(h)
439                } else {
440                    err
441                })
442            }
443            None => Err(SemaError::Reader {
444                message: "unexpected end of input".to_string(),
445                span,
446            }),
447        }
448    }
449}
450
451fn token_display(tok: &Token) -> &'static str {
452    match tok {
453        Token::LParen => "(",
454        Token::RParen => ")",
455        Token::LBracket => "[",
456        Token::RBracket => "]",
457        Token::LBrace => "{",
458        Token::RBrace => "}",
459        Token::Quote => "'",
460        Token::Quasiquote => "`",
461        Token::Unquote => ",",
462        Token::UnquoteSplice => ",@",
463        Token::Dot => ".",
464        Token::BytevectorStart => "#u8(",
465        Token::Int(_) => "integer",
466        Token::Float(_) => "float",
467        Token::String(_) => "string",
468        Token::Symbol(_) => "symbol",
469        Token::Keyword(_) => "keyword",
470        Token::Bool(_) => "boolean",
471        Token::Char(_) => "character",
472        Token::FString(_) => "f-string",
473        Token::ShortLambdaStart => "#(",
474        Token::Comment(_) => "comment",
475        Token::Newline => "newline",
476        Token::Regex(_) => "regex",
477    }
478}
479
480/// Recursively scan a Value AST for `%`, `%1`, `%2`, etc. symbols.
481/// Rewrites bare `%` to `%1`. Tracks the highest numbered arg in `max_arg`.
482/// Skips recursion into nested `(lambda ...)` / `(fn ...)` forms.
483fn rewrite_percent_args(expr: &Value, max_arg: &mut usize) -> Value {
484    match expr.view() {
485        ValueView::Symbol(spur) => {
486            let name = resolve(spur);
487            if name == "%" {
488                *max_arg = (*max_arg).max(1);
489                Value::symbol("%1")
490            } else if let Some(rest) = name.strip_prefix('%') {
491                if let Ok(n) = rest.parse::<usize>() {
492                    if n > 0 {
493                        *max_arg = (*max_arg).max(n);
494                    }
495                }
496                expr.clone()
497            } else {
498                expr.clone()
499            }
500        }
501        ValueView::List(items) => {
502            // Skip nested (lambda ...) / (fn ...) forms — their % args are their own
503            if let Some(first) = items.first() {
504                if let ValueView::Symbol(s) = first.view() {
505                    let name = resolve(s);
506                    if name == "lambda" || name == "fn" {
507                        return expr.clone();
508                    }
509                }
510            }
511            let new_items: Vec<Value> = items
512                .iter()
513                .map(|item| rewrite_percent_args(item, max_arg))
514                .collect();
515            Value::list(new_items)
516        }
517        ValueView::Vector(items) => {
518            let new_items: Vec<Value> = items
519                .iter()
520                .map(|item| rewrite_percent_args(item, max_arg))
521                .collect();
522            Value::vector(new_items)
523        }
524        _ => expr.clone(),
525    }
526}
527
528/// Read a single s-expression from a string.
529pub fn read(input: &str) -> Result<Value, SemaError> {
530    let tokens = tokenize(input)?;
531    let mut parser = Parser::new(tokens);
532    if parser.peek().is_none() {
533        return Ok(Value::nil());
534    }
535    parser.parse_expr()
536}
537
538/// Read all s-expressions from a string.
539pub fn read_many(input: &str) -> Result<Vec<Value>, SemaError> {
540    let tokens = tokenize(input)?;
541    let mut parser = Parser::new(tokens);
542    let mut exprs = Vec::new();
543    while parser.peek().is_some() {
544        exprs.push(parser.parse_expr()?);
545    }
546    Ok(exprs)
547}
548
549/// Read all s-expressions and return the accumulated span map.
550pub fn read_many_with_spans(input: &str) -> Result<(Vec<Value>, SpanMap), SemaError> {
551    let tokens = tokenize(input)?;
552    let mut parser = Parser::new(tokens);
553    let mut exprs = Vec::new();
554    while parser.peek().is_some() {
555        exprs.push(parser.parse_expr()?);
556    }
557    Ok((exprs, parser.span_map))
558}
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    #[test]
565    fn test_read_int() {
566        assert_eq!(read("42").unwrap(), Value::int(42));
567    }
568
569    #[test]
570    fn test_read_negative_int() {
571        assert_eq!(read("-7").unwrap(), Value::int(-7));
572    }
573
574    #[test]
575    fn test_read_float() {
576        assert_eq!(read("3.14").unwrap(), Value::float(3.14));
577    }
578
579    #[test]
580    fn test_read_string() {
581        assert_eq!(read("\"hello\"").unwrap(), Value::string("hello"));
582    }
583
584    #[test]
585    fn test_read_symbol() {
586        assert_eq!(read("foo").unwrap(), Value::symbol("foo"));
587    }
588
589    #[test]
590    fn test_read_keyword() {
591        assert_eq!(read(":bar").unwrap(), Value::keyword("bar"));
592    }
593
594    #[test]
595    fn test_read_bool() {
596        assert_eq!(read("#t").unwrap(), Value::bool(true));
597        assert_eq!(read("#f").unwrap(), Value::bool(false));
598    }
599
600    #[test]
601    fn test_read_list() {
602        let result = read("(+ 1 2)").unwrap();
603        assert_eq!(
604            result,
605            Value::list(vec![Value::symbol("+"), Value::int(1), Value::int(2)])
606        );
607    }
608
609    #[test]
610    fn test_read_nested_list() {
611        let result = read("(* (+ 1 2) 3)").unwrap();
612        assert_eq!(
613            result,
614            Value::list(vec![
615                Value::symbol("*"),
616                Value::list(vec![Value::symbol("+"), Value::int(1), Value::int(2)]),
617                Value::int(3)
618            ])
619        );
620    }
621
622    #[test]
623    fn test_read_vector() {
624        let result = read("[1 2 3]").unwrap();
625        assert_eq!(
626            result,
627            Value::vector(vec![Value::int(1), Value::int(2), Value::int(3)])
628        );
629    }
630
631    #[test]
632    fn test_read_map() {
633        let result = read("{:a 1 :b 2}").unwrap();
634        let mut expected = BTreeMap::new();
635        expected.insert(Value::keyword("a"), Value::int(1));
636        expected.insert(Value::keyword("b"), Value::int(2));
637        assert_eq!(result, Value::map(expected));
638    }
639
640    #[test]
641    fn test_read_quote() {
642        let result = read("'foo").unwrap();
643        assert_eq!(
644            result,
645            Value::list(vec![Value::symbol("quote"), Value::symbol("foo")])
646        );
647    }
648
649    #[test]
650    fn test_read_quasiquote() {
651        let result = read("`(a ,b ,@c)").unwrap();
652        assert_eq!(
653            result,
654            Value::list(vec![
655                Value::symbol("quasiquote"),
656                Value::list(vec![
657                    Value::symbol("a"),
658                    Value::list(vec![Value::symbol("unquote"), Value::symbol("b")]),
659                    Value::list(vec![Value::symbol("unquote-splicing"), Value::symbol("c")]),
660                ])
661            ])
662        );
663    }
664
665    #[test]
666    fn test_read_nil() {
667        assert_eq!(read("nil").unwrap(), Value::nil());
668    }
669
670    #[test]
671    fn test_read_many_exprs() {
672        let results = read_many("1 2 3").unwrap();
673        assert_eq!(results, vec![Value::int(1), Value::int(2), Value::int(3)]);
674    }
675
676    #[test]
677    fn test_comments() {
678        let result = read_many("; comment\n(+ 1 2)").unwrap();
679        assert_eq!(result.len(), 1);
680    }
681
682    #[test]
683    fn test_read_zero() {
684        assert_eq!(read("0").unwrap(), Value::int(0));
685    }
686
687    #[test]
688    fn test_read_negative_zero() {
689        assert_eq!(read("-0").unwrap(), Value::int(0));
690    }
691
692    #[test]
693    fn test_read_leading_zeros() {
694        assert_eq!(read("007").unwrap(), Value::int(7));
695    }
696
697    #[test]
698    fn test_read_large_int() {
699        assert_eq!(read("9999999999999").unwrap(), Value::int(9999999999999));
700    }
701
702    #[test]
703    fn test_read_int_overflow() {
704        // i64::MAX + 1 should error, not silently wrap
705        assert!(read("9999999999999999999999").is_err());
706    }
707
708    #[test]
709    fn test_read_negative_float() {
710        assert_eq!(read("-2.5").unwrap(), Value::float(-2.5));
711    }
712
713    #[test]
714    fn test_read_float_leading_zero() {
715        assert_eq!(read("0.5").unwrap(), Value::float(0.5));
716    }
717
718    #[test]
719    fn test_read_minus_is_symbol() {
720        // Bare `-` should be a symbol (subtraction operator), not a number
721        assert_eq!(read("-").unwrap(), Value::symbol("-"));
722    }
723
724    #[test]
725    fn test_read_minus_in_list() {
726        // `(- 3)` should parse as call to `-` with arg 3
727        let result = read("(- 3)").unwrap();
728        assert_eq!(result, Value::list(vec![Value::symbol("-"), Value::int(3)]));
729    }
730
731    #[test]
732    fn test_read_negative_in_list() {
733        // `(-3)` should parse as list containing -3
734        let result = read("(-3)").unwrap();
735        assert_eq!(result, Value::list(vec![Value::int(-3)]));
736    }
737
738    #[test]
739    fn test_read_empty_string() {
740        assert_eq!(read(r#""""#).unwrap(), Value::string(""));
741    }
742
743    #[test]
744    fn test_read_string_with_escapes() {
745        assert_eq!(
746            read(r#""\n\t\r\\\"" "#).unwrap(),
747            Value::string("\n\t\r\\\"")
748        );
749    }
750
751    #[test]
752    fn test_read_string_unknown_escape() {
753        // Unknown escape sequences are preserved literally
754        assert_eq!(read(r#""\z""#).unwrap(), Value::string("\\z"));
755    }
756
757    #[test]
758    fn test_read_string_with_newline() {
759        assert_eq!(
760            read("\"line1\nline2\"").unwrap(),
761            Value::string("line1\nline2")
762        );
763    }
764
765    #[test]
766    fn test_read_unterminated_string() {
767        assert!(read("\"hello").is_err());
768    }
769
770    #[test]
771    fn test_read_string_escaped_quote_at_end() {
772        // `"test\"` — the backslash escapes the quote, string is unterminated
773        assert!(read(r#""test\""#).is_err());
774    }
775
776    #[test]
777    fn test_read_string_with_unicode() {
778        assert_eq!(read("\"héllo\"").unwrap(), Value::string("héllo"));
779        assert_eq!(read("\"日本語\"").unwrap(), Value::string("日本語"));
780        assert_eq!(read("\"🎉\"").unwrap(), Value::string("🎉"));
781    }
782
783    #[test]
784    fn test_read_string_with_parens() {
785        assert_eq!(read("\"(+ 1 2)\"").unwrap(), Value::string("(+ 1 2)"));
786    }
787
788    #[test]
789    fn test_read_operator_symbols() {
790        assert_eq!(read("+").unwrap(), Value::symbol("+"));
791        assert_eq!(read("*").unwrap(), Value::symbol("*"));
792        assert_eq!(read("/").unwrap(), Value::symbol("/"));
793        assert_eq!(read("<=").unwrap(), Value::symbol("<="));
794        assert_eq!(read(">=").unwrap(), Value::symbol(">="));
795    }
796
797    #[test]
798    fn test_read_predicate_symbols() {
799        assert_eq!(read("null?").unwrap(), Value::symbol("null?"));
800        assert_eq!(read("list?").unwrap(), Value::symbol("list?"));
801    }
802
803    #[test]
804    fn test_read_arrow_symbols() {
805        assert_eq!(
806            read("string->symbol").unwrap(),
807            Value::symbol("string->symbol")
808        );
809    }
810
811    #[test]
812    fn test_read_namespaced_symbols() {
813        assert_eq!(read("file/read").unwrap(), Value::symbol("file/read"));
814        assert_eq!(read("http/get").unwrap(), Value::symbol("http/get"));
815    }
816
817    #[test]
818    fn test_read_true_false_as_bool() {
819        assert_eq!(read("true").unwrap(), Value::bool(true));
820        assert_eq!(read("false").unwrap(), Value::bool(false));
821    }
822
823    #[test]
824    fn test_read_bare_colon_error() {
825        // `:` alone without a name should error
826        assert!(read(":").is_err());
827    }
828
829    #[test]
830    fn test_read_keyword_with_numbers() {
831        assert_eq!(read(":foo123").unwrap(), Value::keyword("foo123"));
832    }
833
834    #[test]
835    fn test_read_keyword_with_hyphens() {
836        assert_eq!(read(":max-turns").unwrap(), Value::keyword("max-turns"));
837    }
838
839    #[test]
840    fn test_read_hash_invalid() {
841        assert!(read("#x").is_err());
842        assert!(read("#").is_err());
843    }
844
845    #[test]
846    fn test_read_empty() {
847        assert_eq!(read("").unwrap(), Value::nil());
848    }
849
850    #[test]
851    fn test_read_whitespace_only() {
852        assert_eq!(read("   \n\t  ").unwrap(), Value::nil());
853    }
854
855    #[test]
856    fn test_read_many_empty() {
857        assert_eq!(read_many("").unwrap(), vec![]);
858    }
859
860    #[test]
861    fn test_read_many_whitespace_only() {
862        assert_eq!(read_many("  \n  ").unwrap(), vec![]);
863    }
864
865    #[test]
866    fn test_read_comment_only() {
867        assert_eq!(read_many("; just a comment").unwrap(), vec![]);
868    }
869
870    #[test]
871    fn test_read_empty_list() {
872        assert_eq!(read("()").unwrap(), Value::list(vec![]));
873    }
874
875    #[test]
876    fn test_read_deeply_nested() {
877        let result = read("((((42))))").unwrap();
878        assert_eq!(
879            result,
880            Value::list(vec![Value::list(vec![Value::list(vec![Value::list(
881                vec![Value::int(42)]
882            )])])])
883        );
884    }
885
886    #[test]
887    fn test_read_unterminated_list() {
888        assert!(read("(1 2").is_err());
889    }
890
891    #[test]
892    fn test_read_extra_rparen() {
893        // `read` only reads one expr, so extra `)` is just ignored (not consumed)
894        // But `read_many` should fail since `)` is not a valid expr start
895        let result = read("42").unwrap();
896        assert_eq!(result, Value::int(42));
897    }
898
899    #[test]
900    fn test_read_dotted_pair() {
901        let result = read("(a . b)").unwrap();
902        assert_eq!(
903            result,
904            Value::list(vec![
905                Value::symbol("a"),
906                Value::symbol("."),
907                Value::symbol("b")
908            ])
909        );
910    }
911
912    #[test]
913    fn test_read_empty_vector() {
914        assert_eq!(read("[]").unwrap(), Value::vector(vec![]));
915    }
916
917    #[test]
918    fn test_read_unterminated_vector() {
919        assert!(read("[1 2").is_err());
920    }
921
922    #[test]
923    fn test_read_empty_map() {
924        assert_eq!(read("{}").unwrap(), Value::map(BTreeMap::new()));
925    }
926
927    #[test]
928    fn test_read_unterminated_map() {
929        assert!(read("{:a 1").is_err());
930    }
931
932    #[test]
933    fn test_read_map_odd_elements() {
934        assert!(read("{:a 1 :b}").is_err());
935    }
936
937    #[test]
938    fn test_read_map_duplicate_keys() {
939        // Later key wins (BTreeMap insert replaces)
940        let result = read("{:a 1 :a 2}").unwrap();
941        let mut expected = BTreeMap::new();
942        expected.insert(Value::keyword("a"), Value::int(2));
943        assert_eq!(result, Value::map(expected));
944    }
945
946    #[test]
947    fn test_read_nested_quote() {
948        let result = read("''foo").unwrap();
949        assert_eq!(
950            result,
951            Value::list(vec![
952                Value::symbol("quote"),
953                Value::list(vec![Value::symbol("quote"), Value::symbol("foo")])
954            ])
955        );
956    }
957
958    #[test]
959    fn test_read_quote_list() {
960        let result = read("'(1 2 3)").unwrap();
961        assert_eq!(
962            result,
963            Value::list(vec![
964                Value::symbol("quote"),
965                Value::list(vec![Value::int(1), Value::int(2), Value::int(3)])
966            ])
967        );
968    }
969
970    #[test]
971    fn test_read_quote_at_eof() {
972        assert!(read("'").is_err());
973    }
974
975    #[test]
976    fn test_read_unquote_at_eof() {
977        assert!(read(",").is_err());
978    }
979
980    #[test]
981    fn test_read_unquote_splice_at_eof() {
982        assert!(read(",@").is_err());
983    }
984
985    #[test]
986    fn test_read_quasiquote_at_eof() {
987        assert!(read("`").is_err());
988    }
989
990    #[test]
991    fn test_read_comment_after_expr() {
992        assert_eq!(read_many("42 ; comment").unwrap(), vec![Value::int(42)]);
993    }
994
995    #[test]
996    fn test_read_multiple_comments() {
997        let result = read_many("; first\n; second\n42").unwrap();
998        assert_eq!(result, vec![Value::int(42)]);
999    }
1000
1001    #[test]
1002    fn test_read_comment_no_newline() {
1003        // Comment at end of input without trailing newline
1004        assert_eq!(read_many("; comment").unwrap(), vec![]);
1005    }
1006
1007    #[test]
1008    fn test_read_crlf_line_endings() {
1009        let result = read_many("1\r\n2\r\n3").unwrap();
1010        assert_eq!(result, vec![Value::int(1), Value::int(2), Value::int(3)]);
1011    }
1012
1013    #[test]
1014    fn test_read_tabs_as_whitespace() {
1015        assert_eq!(
1016            read("(\t+\t1\t2\t)").unwrap(),
1017            Value::list(vec![Value::symbol("+"), Value::int(1), Value::int(2)])
1018        );
1019    }
1020
1021    #[test]
1022    fn test_read_mixed_collections() {
1023        // List containing vector and map
1024        let result = read("([1 2] {:a 3})").unwrap();
1025        let mut map = BTreeMap::new();
1026        map.insert(Value::keyword("a"), Value::int(3));
1027        assert_eq!(
1028            result,
1029            Value::list(vec![
1030                Value::vector(vec![Value::int(1), Value::int(2)]),
1031                Value::map(map)
1032            ])
1033        );
1034    }
1035
1036    #[test]
1037    fn test_read_many_mixed_types() {
1038        let result = read_many(r#"42 3.14 "hello" foo :bar #t nil"#).unwrap();
1039        assert_eq!(result.len(), 7);
1040        assert_eq!(result[0], Value::int(42));
1041        assert_eq!(result[1], Value::float(3.14));
1042        assert_eq!(result[2], Value::string("hello"));
1043        assert_eq!(result[3], Value::symbol("foo"));
1044        assert_eq!(result[4], Value::keyword("bar"));
1045        assert_eq!(result[5], Value::bool(true));
1046        assert_eq!(result[6], Value::nil());
1047    }
1048
1049    #[test]
1050    fn test_span_map_tracks_lists() {
1051        let (exprs, spans) = read_many_with_spans("(+ 1 2)").unwrap();
1052        assert_eq!(exprs.len(), 1);
1053        // The list should have a span entry
1054        let rc = exprs[0].as_list_rc().expect("expected list");
1055        let ptr = Rc::as_ptr(&rc) as usize;
1056        let span = spans.get(&ptr).expect("list should have span");
1057        assert_eq!(span.line, 1);
1058        assert_eq!(span.col, 1);
1059    }
1060
1061    #[test]
1062    fn test_span_map_multiline() {
1063        let (exprs, spans) = read_many_with_spans("(foo)\n(bar)").unwrap();
1064        assert_eq!(exprs.len(), 2);
1065        let rc = exprs[1].as_list_rc().expect("expected list");
1066        let ptr = Rc::as_ptr(&rc) as usize;
1067        let span = spans.get(&ptr).expect("second list should have span");
1068        assert_eq!(span.line, 2);
1069        assert_eq!(span.col, 1);
1070    }
1071
1072    #[test]
1073    fn test_read_unexpected_char() {
1074        assert!(read("@").is_err());
1075        assert!(read("$").is_err());
1076    }
1077
1078    #[test]
1079    fn test_read_char_literal() {
1080        assert_eq!(read("#\\a").unwrap(), Value::char('a'));
1081        assert_eq!(read("#\\Z").unwrap(), Value::char('Z'));
1082        assert_eq!(read("#\\0").unwrap(), Value::char('0'));
1083    }
1084
1085    #[test]
1086    fn test_read_char_named() {
1087        assert_eq!(read("#\\space").unwrap(), Value::char(' '));
1088        assert_eq!(read("#\\newline").unwrap(), Value::char('\n'));
1089        assert_eq!(read("#\\tab").unwrap(), Value::char('\t'));
1090        assert_eq!(read("#\\return").unwrap(), Value::char('\r'));
1091        assert_eq!(read("#\\nul").unwrap(), Value::char('\0'));
1092    }
1093
1094    #[test]
1095    fn test_read_char_special() {
1096        assert_eq!(read("#\\(").unwrap(), Value::char('('));
1097        assert_eq!(read("#\\)").unwrap(), Value::char(')'));
1098    }
1099
1100    #[test]
1101    fn test_read_char_in_list() {
1102        let result = read("(#\\a #\\b)").unwrap();
1103        assert_eq!(
1104            result,
1105            Value::list(vec![Value::char('a'), Value::char('b')])
1106        );
1107    }
1108
1109    #[test]
1110    fn test_read_char_unknown_name() {
1111        assert!(read("#\\foobar").is_err());
1112    }
1113
1114    #[test]
1115    fn test_read_char_eof() {
1116        assert!(read("#\\").is_err());
1117    }
1118
1119    #[test]
1120    fn test_read_bytevector_literal() {
1121        assert_eq!(
1122            read("#u8(1 2 3)").unwrap(),
1123            Value::bytevector(vec![1, 2, 3])
1124        );
1125    }
1126
1127    #[test]
1128    fn test_read_bytevector_empty() {
1129        assert_eq!(read("#u8()").unwrap(), Value::bytevector(vec![]));
1130    }
1131
1132    #[test]
1133    fn test_read_bytevector_single() {
1134        assert_eq!(read("#u8(255)").unwrap(), Value::bytevector(vec![255]));
1135    }
1136
1137    #[test]
1138    fn test_read_bytevector_out_of_range() {
1139        assert!(read("#u8(256)").is_err());
1140    }
1141
1142    #[test]
1143    fn test_read_bytevector_negative() {
1144        assert!(read("#u8(-1)").is_err());
1145    }
1146
1147    #[test]
1148    fn test_read_bytevector_non_integer() {
1149        assert!(read("#u8(1.5)").is_err());
1150    }
1151
1152    #[test]
1153    fn test_read_bytevector_unterminated() {
1154        assert!(read("#u8(1 2").is_err());
1155    }
1156
1157    #[test]
1158    fn test_read_bytevector_in_list() {
1159        let result = read("(#u8(1 2) #u8(3))").unwrap();
1160        assert_eq!(
1161            result,
1162            Value::list(vec![
1163                Value::bytevector(vec![1, 2]),
1164                Value::bytevector(vec![3]),
1165            ])
1166        );
1167    }
1168
1169    #[test]
1170    fn test_read_string_hex_escape_basic() {
1171        // \x41; is 'A'
1172        let result = read(r#""\x41;""#).unwrap();
1173        assert_eq!(result, Value::string("A"));
1174    }
1175
1176    #[test]
1177    fn test_read_string_hex_escape_lowercase() {
1178        let result = read(r#""\x6c;""#).unwrap();
1179        assert_eq!(result, Value::string("l"));
1180    }
1181
1182    #[test]
1183    fn test_read_string_hex_escape_mixed_case() {
1184        let result = read(r#""\x4F;""#).unwrap();
1185        assert_eq!(result, Value::string("O"));
1186    }
1187
1188    #[test]
1189    fn test_read_string_hex_escape_esc_char() {
1190        // \x1B; is ESC (0x1b) — the main motivating use case
1191        let result = read(r#""\x1B;""#).unwrap();
1192        assert_eq!(result, Value::string("\x1B"));
1193    }
1194
1195    #[test]
1196    fn test_read_string_hex_escape_null() {
1197        let result = read(r#""\x0;""#).unwrap();
1198        assert_eq!(result, Value::string("\0"));
1199    }
1200
1201    #[test]
1202    fn test_read_string_hex_escape_unicode() {
1203        // \x3BB; is λ (Greek small letter lambda)
1204        let result = read(r#""\x3BB;""#).unwrap();
1205        assert_eq!(result, Value::string("λ"));
1206    }
1207
1208    #[test]
1209    fn test_read_string_hex_escape_emoji() {
1210        // \x1F600; is 😀
1211        let result = read(r#""\x1F600;""#).unwrap();
1212        assert_eq!(result, Value::string("😀"));
1213    }
1214
1215    #[test]
1216    fn test_read_string_hex_escape_in_context() {
1217        // Mix hex escapes with regular text and other escapes
1218        let result = read(r#""hello\x20;world""#).unwrap();
1219        assert_eq!(result, Value::string("hello world"));
1220    }
1221
1222    #[test]
1223    fn test_read_string_hex_escape_multiple() {
1224        let result = read(r#""\x48;\x69;""#).unwrap();
1225        assert_eq!(result, Value::string("Hi"));
1226    }
1227
1228    #[test]
1229    fn test_read_string_hex_escape_missing_semicolon() {
1230        assert!(read(r#""\x41""#).is_err());
1231    }
1232
1233    #[test]
1234    fn test_read_string_hex_escape_no_digits() {
1235        assert!(read(r#""\x;""#).is_err());
1236    }
1237
1238    #[test]
1239    fn test_read_string_hex_escape_invalid_hex() {
1240        assert!(read(r#""\xGG;""#).is_err());
1241    }
1242
1243    #[test]
1244    fn test_read_string_hex_escape_invalid_codepoint() {
1245        // 0xD800 is a surrogate — invalid Unicode scalar
1246        assert!(read(r#""\xD800;""#).is_err());
1247    }
1248
1249    #[test]
1250    fn test_read_string_hex_escape_too_large() {
1251        // 0x110000 is above Unicode max
1252        assert!(read(r#""\x110000;""#).is_err());
1253    }
1254
1255    #[test]
1256    fn test_read_string_u_escape_basic() {
1257        // \u0041 is 'A'
1258        let result = read(r#""\u0041""#).unwrap();
1259        assert_eq!(result, Value::string("A"));
1260    }
1261
1262    #[test]
1263    fn test_read_string_u_escape_lambda() {
1264        let result = read(r#""\u03BB""#).unwrap();
1265        assert_eq!(result, Value::string("λ"));
1266    }
1267
1268    #[test]
1269    fn test_read_string_u_escape_esc() {
1270        let result = read(r#""\u001B""#).unwrap();
1271        assert_eq!(result, Value::string("\x1B"));
1272    }
1273
1274    #[test]
1275    fn test_read_string_u_escape_too_few_digits() {
1276        assert!(read(r#""\u041""#).is_err());
1277    }
1278
1279    #[test]
1280    fn test_read_string_u_escape_surrogate() {
1281        assert!(read(r#""\uD800""#).is_err());
1282    }
1283
1284    #[test]
1285    fn test_read_string_big_u_escape_basic() {
1286        let result = read(r#""\U00000041""#).unwrap();
1287        assert_eq!(result, Value::string("A"));
1288    }
1289
1290    #[test]
1291    fn test_read_string_big_u_escape_emoji() {
1292        let result = read(r#""\U0001F600""#).unwrap();
1293        assert_eq!(result, Value::string("😀"));
1294    }
1295
1296    #[test]
1297    fn test_read_string_big_u_escape_too_few_digits() {
1298        assert!(read(r#""\U0041""#).is_err());
1299    }
1300
1301    #[test]
1302    fn test_read_string_big_u_escape_invalid() {
1303        assert!(read(r#""\U00110000""#).is_err());
1304    }
1305
1306    #[test]
1307    fn test_read_string_null_escape() {
1308        let result = read(r#""\0""#).unwrap();
1309        assert_eq!(result, Value::string("\0"));
1310    }
1311
1312    #[test]
1313    fn test_read_string_mixed_escapes() {
1314        // Mix all escape types in one string
1315        let result = read(r#""\x48;\u0069\n\t""#).unwrap();
1316        assert_eq!(result, Value::string("Hi\n\t"));
1317    }
1318
1319    #[test]
1320    fn test_read_string_ansi_escape_sequence() {
1321        // Real-world: ANSI color code ESC[31m (red)
1322        let result = read(r#""\x1B;[31mRed\x1B;[0m""#).unwrap();
1323        assert_eq!(result, Value::string("\x1B[31mRed\x1B[0m"));
1324    }
1325
1326    // ── f-string tests ──
1327
1328    #[test]
1329    fn test_read_fstring_no_interpolation() {
1330        let result = read(r#"f"hello""#).unwrap();
1331        assert_eq!(
1332            result,
1333            Value::list(vec![Value::symbol("str"), Value::string("hello")])
1334        );
1335    }
1336
1337    #[test]
1338    fn test_read_fstring_single_var() {
1339        let result = read(r#"f"hello ${name}""#).unwrap();
1340        assert_eq!(
1341            result,
1342            Value::list(vec![
1343                Value::symbol("str"),
1344                Value::string("hello "),
1345                Value::symbol("name"),
1346            ])
1347        );
1348    }
1349
1350    #[test]
1351    fn test_read_fstring_multiple_vars() {
1352        let result = read(r#"f"${a} and ${b}""#).unwrap();
1353        assert_eq!(
1354            result,
1355            Value::list(vec![
1356                Value::symbol("str"),
1357                Value::symbol("a"),
1358                Value::string(" and "),
1359                Value::symbol("b"),
1360            ])
1361        );
1362    }
1363
1364    #[test]
1365    fn test_read_fstring_expression() {
1366        let result = read(r#"f"result: ${(+ 1 2)}""#).unwrap();
1367        assert_eq!(
1368            result,
1369            Value::list(vec![
1370                Value::symbol("str"),
1371                Value::string("result: "),
1372                Value::list(vec![Value::symbol("+"), Value::int(1), Value::int(2),]),
1373            ])
1374        );
1375    }
1376
1377    #[test]
1378    fn test_read_fstring_escaped_dollar() {
1379        let result = read(r#"f"costs \$5""#).unwrap();
1380        assert_eq!(
1381            result,
1382            Value::list(vec![Value::symbol("str"), Value::string("costs $5")])
1383        );
1384    }
1385
1386    #[test]
1387    fn test_read_fstring_dollar_without_brace() {
1388        let result = read(r#"f"costs $5""#).unwrap();
1389        assert_eq!(
1390            result,
1391            Value::list(vec![Value::symbol("str"), Value::string("costs $5")])
1392        );
1393    }
1394
1395    #[test]
1396    fn test_read_fstring_escape_sequences() {
1397        let result = read(r#"f"line1\nline2""#).unwrap();
1398        assert_eq!(
1399            result,
1400            Value::list(vec![Value::symbol("str"), Value::string("line1\nline2"),])
1401        );
1402    }
1403
1404    #[test]
1405    fn test_read_fstring_empty_interpolation_error() {
1406        assert!(read(r#"f"hello ${}""#).is_err());
1407    }
1408
1409    #[test]
1410    fn test_read_fstring_unterminated_interpolation_error() {
1411        assert!(read(r#"f"hello ${name""#).is_err());
1412    }
1413
1414    #[test]
1415    fn test_read_fstring_unterminated_string_error() {
1416        assert!(read(r#"f"hello"#).is_err());
1417    }
1418
1419    #[test]
1420    fn test_read_fstring_keyword_access() {
1421        let result = read(r#"f"name: ${(:name user)}""#).unwrap();
1422        assert_eq!(
1423            result,
1424            Value::list(vec![
1425                Value::symbol("str"),
1426                Value::string("name: "),
1427                Value::list(vec![Value::keyword("name"), Value::symbol("user")]),
1428            ])
1429        );
1430    }
1431
1432    #[test]
1433    fn test_read_fstring_in_list() {
1434        let result = read(r#"(println f"hello ${name}")"#).unwrap();
1435        assert_eq!(
1436            result,
1437            Value::list(vec![
1438                Value::symbol("println"),
1439                Value::list(vec![
1440                    Value::symbol("str"),
1441                    Value::string("hello "),
1442                    Value::symbol("name"),
1443                ]),
1444            ])
1445        );
1446    }
1447
1448    #[test]
1449    fn test_read_fstring_empty() {
1450        let result = read(r#"f"""#).unwrap();
1451        assert_eq!(result, Value::list(vec![Value::symbol("str")]));
1452    }
1453
1454    #[test]
1455    fn test_read_fstring_only_expr() {
1456        let result = read(r#"f"${x}""#).unwrap();
1457        assert_eq!(
1458            result,
1459            Value::list(vec![Value::symbol("str"), Value::symbol("x")])
1460        );
1461    }
1462
1463    #[test]
1464    fn test_read_f_symbol_still_works() {
1465        // Plain 'f' symbol (not followed by '"') should still parse as symbol
1466        let result = read("f").unwrap();
1467        assert_eq!(result, Value::symbol("f"));
1468    }
1469
1470    #[test]
1471    fn test_read_f_prefixed_symbol_still_works() {
1472        // 'foo' should still parse as a normal symbol
1473        let result = read("foo").unwrap();
1474        assert_eq!(result, Value::symbol("foo"));
1475    }
1476
1477    // ── short lambda tests ──
1478
1479    #[test]
1480    fn test_read_short_lambda_single_arg() {
1481        // #(+ % 1) → (lambda (%1) (+ %1 1))
1482        let result = read("#(+ % 1)").unwrap();
1483        assert_eq!(
1484            result,
1485            Value::list(vec![
1486                Value::symbol("lambda"),
1487                Value::list(vec![Value::symbol("%1")]),
1488                Value::list(vec![Value::symbol("+"), Value::symbol("%1"), Value::int(1),]),
1489            ])
1490        );
1491    }
1492
1493    #[test]
1494    fn test_read_short_lambda_two_args() {
1495        // #(+ %1 %2) → (lambda (%1 %2) (+ %1 %2))
1496        let result = read("#(+ %1 %2)").unwrap();
1497        assert_eq!(
1498            result,
1499            Value::list(vec![
1500                Value::symbol("lambda"),
1501                Value::list(vec![Value::symbol("%1"), Value::symbol("%2")]),
1502                Value::list(vec![
1503                    Value::symbol("+"),
1504                    Value::symbol("%1"),
1505                    Value::symbol("%2"),
1506                ]),
1507            ])
1508        );
1509    }
1510
1511    #[test]
1512    fn test_read_short_lambda_bare_percent_is_percent1() {
1513        // #(* % %) → (lambda (%1) (* %1 %1))
1514        let result = read("#(* % %)").unwrap();
1515        assert_eq!(
1516            result,
1517            Value::list(vec![
1518                Value::symbol("lambda"),
1519                Value::list(vec![Value::symbol("%1")]),
1520                Value::list(vec![
1521                    Value::symbol("*"),
1522                    Value::symbol("%1"),
1523                    Value::symbol("%1"),
1524                ]),
1525            ])
1526        );
1527    }
1528
1529    #[test]
1530    fn test_read_short_lambda_no_args() {
1531        // #(println "hello") → (lambda () (println "hello"))
1532        let result = read(r#"#(println "hello")"#).unwrap();
1533        assert_eq!(
1534            result,
1535            Value::list(vec![
1536                Value::symbol("lambda"),
1537                Value::list(vec![]),
1538                Value::list(vec![Value::symbol("println"), Value::string("hello"),]),
1539            ])
1540        );
1541    }
1542
1543    #[test]
1544    fn test_read_short_lambda_in_list() {
1545        // (map #(+ % 1) numbers)
1546        let result = read("(map #(+ % 1) numbers)").unwrap();
1547        assert_eq!(
1548            result,
1549            Value::list(vec![
1550                Value::symbol("map"),
1551                Value::list(vec![
1552                    Value::symbol("lambda"),
1553                    Value::list(vec![Value::symbol("%1")]),
1554                    Value::list(vec![Value::symbol("+"), Value::symbol("%1"), Value::int(1),]),
1555                ]),
1556                Value::symbol("numbers"),
1557            ])
1558        );
1559    }
1560
1561    #[test]
1562    fn test_read_short_lambda_unterminated() {
1563        assert!(read("#(+ % 1").is_err());
1564    }
1565
1566    #[test]
1567    fn test_read_short_lambda_nested_expr() {
1568        // #(> (string-length %) 3) → (lambda (%1) (> (string-length %1) 3))
1569        let result = read("#(> (string-length %) 3)").unwrap();
1570        assert_eq!(
1571            result,
1572            Value::list(vec![
1573                Value::symbol("lambda"),
1574                Value::list(vec![Value::symbol("%1")]),
1575                Value::list(vec![
1576                    Value::symbol(">"),
1577                    Value::list(vec![Value::symbol("string-length"), Value::symbol("%1"),]),
1578                    Value::int(3),
1579                ]),
1580            ])
1581        );
1582    }
1583
1584    #[test]
1585    fn test_read_regex_literal_digits() {
1586        let result = read(r#"#"\d+""#).unwrap();
1587        assert_eq!(result, Value::string(r"\d+"));
1588    }
1589
1590    #[test]
1591    fn test_read_regex_literal_char_class() {
1592        let result = read(r#"#"[a-z]+""#).unwrap();
1593        assert_eq!(result, Value::string("[a-z]+"));
1594    }
1595
1596    #[test]
1597    fn test_read_regex_literal_backslashes_literal() {
1598        let result = read(r#"#"hello\.world""#).unwrap();
1599        assert_eq!(result, Value::string(r"hello\.world"));
1600    }
1601
1602    #[test]
1603    fn test_read_regex_literal_escaped_quote() {
1604        let result = read(r#"#"foo\"bar""#).unwrap();
1605        assert_eq!(result, Value::string(r#"foo"bar"#));
1606    }
1607
1608    #[test]
1609    fn test_read_regex_literal_unterminated() {
1610        assert!(read(r#"#"abc"#).is_err());
1611    }
1612
1613    #[test]
1614    fn test_mismatched_paren_bracket() {
1615        let err = read("(list [1 2 3)").unwrap_err();
1616        let msg = err.to_string();
1617        assert!(
1618            msg.contains("mismatched"),
1619            "expected mismatched error, got: {msg}"
1620        );
1621    }
1622
1623    #[test]
1624    fn test_mismatched_bracket_paren() {
1625        let err = read("[1 2 3)").unwrap_err();
1626        let msg = err.to_string();
1627        assert!(
1628            msg.contains("mismatched"),
1629            "expected mismatched error, got: {msg}"
1630        );
1631    }
1632
1633    #[test]
1634    fn test_mismatched_paren_brace() {
1635        let err = read("(+ 1 2}").unwrap_err();
1636        let msg = err.to_string();
1637        assert!(
1638            msg.contains("mismatched"),
1639            "expected mismatched error, got: {msg}"
1640        );
1641    }
1642
1643    #[test]
1644    fn test_mismatched_brace_paren() {
1645        let err = read("{:a 1)").unwrap_err();
1646        let msg = err.to_string();
1647        assert!(
1648            msg.contains("mismatched"),
1649            "expected mismatched error, got: {msg}"
1650        );
1651    }
1652
1653    #[test]
1654    fn test_mismatched_brace_bracket() {
1655        let err = read("{:a 1]").unwrap_err();
1656        let msg = err.to_string();
1657        assert!(
1658            msg.contains("mismatched"),
1659            "expected mismatched error, got: {msg}"
1660        );
1661    }
1662
1663    #[test]
1664    fn test_mismatched_bracket_brace() {
1665        let err = read("[1 2}").unwrap_err();
1666        let msg = err.to_string();
1667        assert!(
1668            msg.contains("mismatched"),
1669            "expected mismatched error, got: {msg}"
1670        );
1671    }
1672
1673    #[test]
1674    fn test_correct_brackets_still_work() {
1675        assert!(read("(list [1 2 3])").is_ok());
1676        assert!(read("{:a 1}").is_ok());
1677        assert!(read("[1 [2 3] 4]").is_ok());
1678    }
1679
1680    #[test]
1681    fn test_auto_gensym_symbol_parsing() {
1682        let val = read("v#").unwrap();
1683        assert_eq!(val.as_symbol().unwrap(), "v#");
1684
1685        let val = read("tmp#").unwrap();
1686        assert_eq!(val.as_symbol().unwrap(), "tmp#");
1687
1688        let val = read("`(let ((v# 1)) v#)").unwrap();
1689        let items = val.as_list().unwrap();
1690        assert_eq!(items[0].as_symbol().unwrap(), "quasiquote");
1691    }
1692
1693    #[test]
1694    fn test_hash_reader_dispatch_still_works() {
1695        let val = read("#t").unwrap();
1696        assert_eq!(val.as_bool(), Some(true));
1697
1698        let val = read("#f").unwrap();
1699        assert_eq!(val.as_bool(), Some(false));
1700
1701        let val = read("#\\space").unwrap();
1702        assert_eq!(val.as_char(), Some(' '));
1703
1704        let val = read("#(+ % 1)").unwrap();
1705        assert!(val.as_list().is_some());
1706    }
1707
1708    #[test]
1709    fn test_auto_gensym_edge_cases() {
1710        let val = read("x##").unwrap();
1711        assert_eq!(val.as_symbol().unwrap(), "x##");
1712
1713        let val = read(":foo").unwrap();
1714        assert!(val.as_keyword().is_some());
1715    }
1716}