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