dazzle_core/scheme/
parser.rs

1//! Scheme parser (lexer + S-expression parser)
2//!
3//! Ported from OpenJade's `SchemeParser.cxx` (~2,500 lines).
4//!
5//! ## Key Features
6//!
7//! - **Whitespace-agnostic**: Unlike Steel, this parser handles all valid R4RS whitespace
8//! - **Line number tracking**: Error messages report line:column, not byte spans
9//! - **Full R4RS syntax**:
10//!   - Numbers: integers, reals, hex (#x), octal (#o), binary (#b)
11//!   - Strings: escapes (\n, \t, \", \\, \xNN)
12//!   - Symbols and keywords
13//!   - Quote/quasiquote/unquote/unquote-splicing
14//!   - Comments: line (;) and block (#| ... |#)
15//!   - Vectors: #(...)
16//!   - Booleans: #t, #f
17//!   - Characters: #\a, #\space, #\newline
18//!
19//! ## Architecture
20//!
21//! 1. **Lexer** (`Tokenizer`): Character stream → Token stream
22//! 2. **Parser** (`Parser`): Token stream → Value (S-expressions)
23//!
24//! ## OpenJade Correspondence
25//!
26//! | Dazzle        | OpenJade             | Purpose                |
27//! |---------------|----------------------|------------------------|
28//! | `Token`       | `SchemeParser::tok_` | Token types            |
29//! | `Tokenizer`   | `SchemeParser`       | Lexical analysis       |
30//! | `Parser`      | `SchemeParser::get*` | Syntax analysis        |
31//!
32//! ## Error Handling
33//!
34//! Parse errors include:
35//! - Line and column numbers (not byte offsets!)
36//! - Descriptive messages
37//! - Context (what was expected)
38
39use crate::scheme::value::Value;
40use std::fmt;
41
42// =============================================================================
43// Token Types
44// =============================================================================
45
46/// Token type (corresponds to OpenJade's token enum)
47#[derive(Debug, Clone, PartialEq)]
48pub enum Token {
49    // Literals
50    Integer(i64),
51    Real(f64),
52    String(String),
53    Char(char),
54    Symbol(String),
55    Keyword(String),
56    Bool(bool),
57
58    // Delimiters
59    LeftParen,   // (
60    RightParen,  // )
61    LeftBracket, // [  (optional R5RS)
62    RightBracket, // ] (optional R5RS)
63    Dot,         // .
64
65    // Quotation
66    Quote,            // '
67    Quasiquote,       // `
68    Unquote,          // ,
69    UnquoteSplicing,  // ,@
70
71    // Vector
72    VectorStart, // #(
73
74    // End of input
75    Eof,
76}
77
78impl fmt::Display for Token {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Token::Integer(n) => write!(f, "{}", n),
82            Token::Real(n) => write!(f, "{}", n),
83            Token::String(s) => write!(f, "\"{}\"", s),
84            Token::Char(ch) => write!(f, "#\\{}", ch),
85            Token::Symbol(s) => write!(f, "{}", s),
86            Token::Keyword(s) => write!(f, "#:{}", s),
87            Token::Bool(b) => write!(f, "{}", if *b { "#t" } else { "#f" }),
88            Token::LeftParen => write!(f, "("),
89            Token::RightParen => write!(f, ")"),
90            Token::LeftBracket => write!(f, "["),
91            Token::RightBracket => write!(f, "]"),
92            Token::Dot => write!(f, "."),
93            Token::Quote => write!(f, "'"),
94            Token::Quasiquote => write!(f, "`"),
95            Token::Unquote => write!(f, ","),
96            Token::UnquoteSplicing => write!(f, ",@"),
97            Token::VectorStart => write!(f, "#("),
98            Token::Eof => write!(f, "<EOF>"),
99        }
100    }
101}
102
103// =============================================================================
104// Source Position (for error reporting)
105// =============================================================================
106
107/// Source code position (line and column)
108///
109/// **Important**: Line and column numbers start at 1 (human-readable).
110///
111/// Derives Trace and Finalize for GC compatibility (used in PairData).
112/// Note: Can't derive Copy with Finalize due to destructor conflict.
113#[derive(Debug, Clone, PartialEq, Eq, gc::Trace, gc::Finalize)]
114pub struct Position {
115    pub line: usize,
116    pub column: usize,
117}
118
119impl Position {
120    pub fn new() -> Self {
121        Position { line: 1, column: 1 }
122    }
123}
124
125impl Default for Position {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl fmt::Display for Position {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{}:{}", self.line, self.column)
134    }
135}
136
137// =============================================================================
138// Parse Error
139// =============================================================================
140
141/// Parse error with line:column position and optional filename
142#[derive(Debug, Clone)]
143pub struct ParseError {
144    pub message: String,
145    pub position: Position,
146    pub filename: Option<String>,
147}
148
149impl ParseError {
150    pub fn new(message: String, position: Position) -> Self {
151        ParseError { message, position, filename: None }
152    }
153
154    pub fn with_filename(message: String, position: Position, filename: String) -> Self {
155        ParseError { message, position, filename: Some(filename) }
156    }
157}
158
159impl fmt::Display for ParseError {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        if let Some(ref filename) = self.filename {
162            write!(f, "{}:{}:E: {}", filename, self.position, self.message)
163        } else {
164            write!(f, "Parse error at {}: {}", self.position, self.message)
165        }
166    }
167}
168
169impl std::error::Error for ParseError {}
170
171pub type ParseResult<T> = Result<T, ParseError>;
172
173// =============================================================================
174// Tokenizer (Lexer)
175// =============================================================================
176
177/// Tokenizer for Scheme source code
178///
179/// Corresponds to OpenJade's `SchemeParser` lexical analysis methods.
180///
181/// ## Whitespace Handling
182///
183/// **Critical difference from Steel**: This tokenizer is fully whitespace-agnostic.
184/// Whitespace can appear anywhere between tokens without affecting parsing.
185///
186/// Example that breaks Steel but works here:
187/// ```scheme
188/// (let ((x 1)
189///       (y 2))  ; Multi-line let bindings
190///   (+ x y))
191/// ```
192pub struct Tokenizer {
193    /// Input source code
194    input: Vec<char>,
195
196    /// Current position in input
197    pos: usize,
198
199    /// Current line (1-based)
200    line: usize,
201
202    /// Current column (1-based)
203    column: usize,
204
205    /// Peeked token (for lookahead)
206    peeked: Option<Token>,
207
208    /// Optional filename for error reporting
209    filename: Option<String>,
210}
211
212impl Tokenizer {
213    /// Create a new tokenizer from source code
214    pub fn new(input: &str) -> Self {
215        Tokenizer {
216            input: input.chars().collect(),
217            pos: 0,
218            line: 1,
219            column: 1,
220            peeked: None,
221            filename: None,
222        }
223    }
224
225    /// Create a new tokenizer from source code with a filename for error reporting
226    pub fn new_with_filename(input: &str, filename: String) -> Self {
227        Tokenizer {
228            input: input.chars().collect(),
229            pos: 0,
230            line: 1,
231            column: 1,
232            peeked: None,
233            filename: Some(filename),
234        }
235    }
236
237    /// Helper to create a ParseError with the current filename
238    fn error(&self, message: String, position: Position) -> ParseError {
239        if let Some(ref filename) = self.filename {
240            ParseError::with_filename(message, position, filename.clone())
241        } else {
242            ParseError::new(message, position)
243        }
244    }
245
246    /// Get current position (for error reporting)
247    pub fn position(&self) -> Position {
248        Position {
249            line: self.line,
250            column: self.column,
251        }
252    }
253
254    /// Peek at current character without consuming
255    fn peek_char(&self) -> Option<char> {
256        if self.pos < self.input.len() {
257            Some(self.input[self.pos])
258        } else {
259            None
260        }
261    }
262
263    /// Peek at character at offset from current position
264    fn peek_char_at(&self, offset: usize) -> Option<char> {
265        let index = self.pos + offset;
266        if index < self.input.len() {
267            Some(self.input[index])
268        } else {
269            None
270        }
271    }
272
273    /// Consume and return current character
274    fn next_char(&mut self) -> Option<char> {
275        if self.pos < self.input.len() {
276            let ch = self.input[self.pos];
277            self.pos += 1;
278
279            // Update line/column tracking
280            if ch == '\n' {
281                self.line += 1;
282                self.column = 1;
283            } else {
284                self.column += 1;
285            }
286
287            Some(ch)
288        } else {
289            None
290        }
291    }
292
293    /// Skip whitespace and comments
294    ///
295    /// **Key feature**: Handles both line comments (;) and block comments (#| ... |#)
296    fn skip_whitespace(&mut self) {
297        loop {
298            match self.peek_char() {
299                // Whitespace
300                Some(ch) if ch.is_whitespace() => {
301                    self.next_char();
302                }
303
304                // Line comment: ; to end of line
305                Some(';') => {
306                    self.next_char();
307                    while let Some(ch) = self.peek_char() {
308                        self.next_char();
309                        if ch == '\n' {
310                            break;
311                        }
312                    }
313                }
314
315                // Block comment: #| ... |#
316                Some('#') if self.peek_char_at(1) == Some('|') => {
317                    self.next_char(); // #
318                    self.next_char(); // |
319
320                    // Find closing |#
321                    let mut depth = 1;
322                    while depth > 0 {
323                        match self.next_char() {
324                            Some('|') if self.peek_char() == Some('#') => {
325                                self.next_char(); // #
326                                depth -= 1;
327                            }
328                            Some('#') if self.peek_char() == Some('|') => {
329                                self.next_char(); // |
330                                depth += 1; // Nested block comment
331                            }
332                            Some(_) => {} // Continue
333                            None => break, // EOF in comment (error, but tolerate)
334                        }
335                    }
336                }
337
338                // Not whitespace or comment
339                _ => break,
340            }
341        }
342    }
343
344    /// Check if character is a delimiter (ends a token)
345    fn is_delimiter(ch: char) -> bool {
346        ch.is_whitespace()
347            || matches!(
348                ch,
349                '(' | ')' | '[' | ']' | '"' | ';' | ',' | '`' | '\''
350            )
351    }
352
353    /// Parse an integer or real number
354    fn parse_number(&mut self, start_pos: Position) -> ParseResult<Token> {
355        let mut num_str = String::new();
356
357        // Collect digits and special characters
358        while let Some(ch) = self.peek_char() {
359            if ch.is_ascii_digit() || matches!(ch, '.' | 'e' | 'E' | '+' | '-') {
360                num_str.push(ch);
361                self.next_char();
362            } else if Self::is_delimiter(ch) {
363                break;
364            } else {
365                // Invalid character in number
366                return Err(self.error(
367                    format!("Invalid character in number: {}", ch),
368                    start_pos,
369                ));
370            }
371        }
372
373        // Try parsing as integer first
374        if let Ok(n) = num_str.parse::<i64>() {
375            return Ok(Token::Integer(n));
376        }
377
378        // Try parsing as float
379        if let Ok(n) = num_str.parse::<f64>() {
380            return Ok(Token::Real(n));
381        }
382
383        Err(self.error(
384            format!("Invalid number: {}", num_str),
385            start_pos,
386        ))
387    }
388
389    /// Parse a hexadecimal number (#x prefix)
390    fn parse_hex_number(&mut self, start_pos: Position) -> ParseResult<Token> {
391        let mut num_str = String::new();
392
393        while let Some(ch) = self.peek_char() {
394            if ch.is_ascii_hexdigit() {
395                num_str.push(ch);
396                self.next_char();
397            } else if Self::is_delimiter(ch) {
398                break;
399            } else {
400                return Err(self.error(
401                    format!("Invalid character in hex number: {}", ch),
402                    start_pos,
403                ));
404            }
405        }
406
407        if num_str.is_empty() {
408            return Err(self.error("Empty hex number".to_string(), start_pos));
409        }
410
411        i64::from_str_radix(&num_str, 16)
412            .map(Token::Integer)
413            .map_err(|_| self.error(format!("Invalid hex number: {}", num_str), start_pos))
414    }
415
416    /// Parse an octal number (#o prefix)
417    fn parse_octal_number(&mut self, start_pos: Position) -> ParseResult<Token> {
418        let mut num_str = String::new();
419
420        while let Some(ch) = self.peek_char() {
421            if ch.is_digit(8) {
422                num_str.push(ch);
423                self.next_char();
424            } else if Self::is_delimiter(ch) {
425                break;
426            } else {
427                return Err(self.error(
428                    format!("Invalid character in octal number: {}", ch),
429                    start_pos,
430                ));
431            }
432        }
433
434        if num_str.is_empty() {
435            return Err(self.error("Empty octal number".to_string(), start_pos));
436        }
437
438        i64::from_str_radix(&num_str, 8)
439            .map(Token::Integer)
440            .map_err(|_| self.error(format!("Invalid octal number: {}", num_str), start_pos))
441    }
442
443    /// Parse a binary number (#b prefix)
444    fn parse_binary_number(&mut self, start_pos: Position) -> ParseResult<Token> {
445        let mut num_str = String::new();
446
447        while let Some(ch) = self.peek_char() {
448            if matches!(ch, '0' | '1') {
449                num_str.push(ch);
450                self.next_char();
451            } else if Self::is_delimiter(ch) {
452                break;
453            } else {
454                return Err(self.error(
455                    format!("Invalid character in binary number: {}", ch),
456                    start_pos,
457                ));
458            }
459        }
460
461        if num_str.is_empty() {
462            return Err(self.error("Empty binary number".to_string(), start_pos));
463        }
464
465        i64::from_str_radix(&num_str, 2)
466            .map(Token::Integer)
467            .map_err(|_| self.error(format!("Invalid binary number: {}", num_str), start_pos))
468    }
469
470    /// Parse a symbol or keyword
471    fn parse_symbol(&mut self) -> String {
472        let mut sym = String::new();
473
474        while let Some(ch) = self.peek_char() {
475            if Self::is_delimiter(ch) {
476                break;
477            }
478            sym.push(ch);
479            self.next_char();
480        }
481
482        sym
483    }
484
485    /// Parse a string literal
486    fn parse_string(&mut self, start_pos: Position) -> ParseResult<String> {
487        self.next_char(); // Consume opening "
488
489        let mut result = String::new();
490
491        loop {
492            match self.next_char() {
493                Some('"') => {
494                    // Closing quote
495                    // Normalize CRLF to LF (to match OpenJade behavior)
496                    // OpenJade always outputs Unix line endings regardless of template line endings
497                    let normalized = result.replace("\r\n", "\n");
498                    return Ok(normalized);
499                }
500                Some('\\') => {
501                    // Escape sequence
502                    match self.next_char() {
503                        Some('n') => result.push('\n'),
504                        Some('t') => result.push('\t'),
505                        Some('r') => result.push('\r'),
506                        Some('\\') => result.push('\\'),
507                        Some('"') => result.push('"'),
508                        Some(ch) => result.push(ch), // Unknown escape, keep literal
509                        None => {
510                            return Err(self.error(
511                                "Unexpected EOF in string escape".to_string(),
512                                start_pos,
513                            ))
514                        }
515                    }
516                }
517                Some(ch) => {
518                    result.push(ch);
519                }
520                None => {
521                    return Err(self.error(
522                        "Unexpected EOF in string".to_string(),
523                        start_pos,
524                    ))
525                }
526            }
527        }
528    }
529
530    /// Parse a character literal (#\a, #\space, #\newline)
531    /// Called after # has been consumed
532    fn parse_char(&mut self, start_pos: Position) -> ParseResult<char> {
533        // Expect backslash
534        if self.next_char() != Some('\\') {
535            return Err(self.error(
536                "Expected \\ after # in character literal".to_string(),
537                start_pos,
538            ));
539        }
540
541        // Read character name
542        let mut name = String::new();
543        while let Some(ch) = self.peek_char() {
544            if Self::is_delimiter(ch) {
545                break;
546            }
547            name.push(ch);
548            self.next_char();
549        }
550
551        if name.is_empty() {
552            return Err(self.error(
553                "Empty character literal".to_string(),
554                start_pos,
555            ));
556        }
557
558        // Named characters
559        match name.as_str() {
560            "space" => Ok(' '),
561            "newline" => Ok('\n'),
562            "tab" => Ok('\t'),
563            "return" => Ok('\r'),
564            // OpenJade Unicode character literal: #\U-XXXX (e.g., #\U-00E4 for ä)
565            // Format: U-XXXX where XXXX is hexadecimal Unicode code point
566            s if s.starts_with("U-") => {
567                let hex_str = &s[2..]; // Skip "U-"
568                u32::from_str_radix(hex_str, 16)
569                    .ok()
570                    .and_then(std::char::from_u32)
571                    .ok_or_else(|| self.error(
572                        format!("Invalid Unicode character literal: #\\{}", name),
573                        start_pos,
574                    ))
575            }
576            // Accept any single Unicode character (handles UTF-8 multi-byte sequences)
577            // OpenJade accepts UTF-8 characters in character literals for define-language
578            s if s.chars().count() == 1 => Ok(s.chars().next().unwrap()),
579            _ => Err(self.error(
580                format!("Invalid character literal: #\\{}", name),
581                start_pos,
582            )),
583        }
584    }
585
586    /// Parse a CDATA string literal: <![CDATA[...]]>
587    /// This is an OpenJade extension for multi-line string literals
588    /// The content between <![CDATA[ and ]]> is returned as a string token
589    fn parse_cdata_string(&mut self, start_pos: Position) -> ParseResult<Token> {
590        // Skip "<![CDATA["
591        for _ in 0..9 {
592            self.next_char();
593        }
594
595        let mut content = String::new();
596
597        // Read until we find "]]>"
598        loop {
599            match self.peek_char() {
600                None => {
601                    return Err(self.error(
602                        "Unclosed CDATA section: missing ]]>".to_string(),
603                        start_pos,
604                    ));
605                }
606                Some(']') => {
607                    // Check if this is the closing ]]>
608                    if self.pos + 2 < self.input.len()
609                        && self.input[self.pos] == ']'
610                        && self.input[self.pos + 1] == ']'
611                        && self.input[self.pos + 2] == '>'
612                    {
613                        // Skip ]]>
614                        self.next_char(); // ]
615                        self.next_char(); // ]
616                        self.next_char(); // >
617                        break;
618                    } else {
619                        content.push(']');
620                        self.next_char();
621                    }
622                }
623                Some(ch) => {
624                    content.push(ch);
625                    self.next_char();
626                }
627            }
628        }
629
630        Ok(Token::String(content))
631    }
632
633    /// Get the next token
634    pub fn next_token(&mut self) -> ParseResult<Token> {
635        // Check if we have a peeked token
636        if let Some(tok) = self.peeked.take() {
637            return Ok(tok);
638        }
639
640        // Skip whitespace and comments
641        self.skip_whitespace();
642
643        let start_pos = self.position();
644
645        match self.peek_char() {
646            None => Ok(Token::Eof),
647
648            Some('(') => {
649                self.next_char();
650                Ok(Token::LeftParen)
651            }
652
653            Some(')') => {
654                self.next_char();
655                Ok(Token::RightParen)
656            }
657
658            Some('[') => {
659                self.next_char();
660                Ok(Token::LeftBracket)
661            }
662
663            Some(']') => {
664                self.next_char();
665                Ok(Token::RightBracket)
666            }
667
668            Some('\'') => {
669                self.next_char();
670                Ok(Token::Quote)
671            }
672
673            Some('`') => {
674                self.next_char();
675                Ok(Token::Quasiquote)
676            }
677
678            Some(',') => {
679                self.next_char();
680                // Check for ,@
681                if self.peek_char() == Some('@') {
682                    self.next_char();
683                    Ok(Token::UnquoteSplicing)
684                } else {
685                    Ok(Token::Unquote)
686                }
687            }
688
689            Some('"') => {
690                let s = self.parse_string(start_pos)?;
691                Ok(Token::String(s))
692            }
693
694            Some('#') => {
695                self.next_char(); // Consume #
696                match self.peek_char() {
697                    Some('t') => {
698                        self.next_char();
699                        Ok(Token::Bool(true))
700                    }
701                    Some('f') => {
702                        self.next_char();
703                        Ok(Token::Bool(false))
704                    }
705                    Some('(') => {
706                        self.next_char();
707                        Ok(Token::VectorStart)
708                    }
709                    Some('\\') => {
710                        let ch = self.parse_char(start_pos)?;
711                        Ok(Token::Char(ch))
712                    }
713                    Some(':') => {
714                        self.next_char(); // Consume :
715                        let name = self.parse_symbol();
716                        Ok(Token::Keyword(name))
717                    }
718                    Some('x') | Some('X') => {
719                        self.next_char(); // Consume x
720                        self.parse_hex_number(start_pos)
721                    }
722                    Some('o') | Some('O') => {
723                        self.next_char(); // Consume o
724                        self.parse_octal_number(start_pos)
725                    }
726                    Some('b') | Some('B') => {
727                        self.next_char(); // Consume b
728                        self.parse_binary_number(start_pos)
729                    }
730                    _ => Err(self.error(
731                        format!("Invalid # syntax: #{:?}", self.peek_char()),
732                        start_pos,
733                    )),
734                }
735            }
736
737            Some(ch) if ch.is_ascii_digit() => self.parse_number(start_pos),
738
739            Some('+') | Some('-') => {
740                // Could be number or symbol
741                if let Some(next) = self.peek_char_at(1) {
742                    if next.is_ascii_digit() {
743                        self.parse_number(start_pos)
744                    } else {
745                        let sym = self.parse_symbol();
746                        Ok(Token::Symbol(sym))
747                    }
748                } else {
749                    let sym = self.parse_symbol();
750                    Ok(Token::Symbol(sym))
751                }
752            }
753
754            Some('.') => {
755                // Could be dot or number starting with .
756                if let Some(next) = self.peek_char_at(1) {
757                    if next.is_ascii_digit() {
758                        self.parse_number(start_pos)
759                    } else {
760                        self.next_char();
761                        Ok(Token::Dot)
762                    }
763                } else {
764                    self.next_char();
765                    Ok(Token::Dot)
766                }
767            }
768
769            Some('<') => {
770                // Check for CDATA section: <![CDATA[...]]>
771                // This is an OpenJade extension for multi-line string literals
772                let cdata_prefix = ['<', '!', '[', 'C', 'D', 'A', 'T', 'A', '['];
773                let is_cdata = self.pos + cdata_prefix.len() <= self.input.len()
774                    && self.input[self.pos..self.pos + cdata_prefix.len()] == cdata_prefix;
775
776                if is_cdata {
777                    self.parse_cdata_string(start_pos)
778                } else {
779                    // Regular < symbol
780                    let sym = self.parse_symbol();
781                    Ok(Token::Symbol(sym))
782                }
783            }
784
785            Some(_) => {
786                // Symbol or DSSSL keyword (trailing colon)
787                let sym = self.parse_symbol();
788
789                // DSSSL uses trailing colon for keywords: name:
790                if sym.ends_with(':') {
791                    let keyword_name = sym[..sym.len()-1].to_string();
792                    Ok(Token::Keyword(keyword_name))
793                } else {
794                    Ok(Token::Symbol(sym))
795                }
796            }
797        }
798    }
799
800    /// Peek at the next token without consuming it
801    pub fn peek_token(&mut self) -> ParseResult<&Token> {
802        if self.peeked.is_none() {
803            let tok = self.next_token()?;
804            self.peeked = Some(tok);
805        }
806        Ok(self.peeked.as_ref().unwrap())
807    }
808}
809
810// =============================================================================
811// Parser (S-expression builder)
812// =============================================================================
813
814/// Parser for building Scheme values from tokens
815///
816/// Corresponds to OpenJade's `SchemeParser::get*` methods.
817///
818/// ## Usage
819///
820/// ```ignore
821/// let parser = Parser::new("(+ 1 2)");
822/// let expr = parser.parse().unwrap();
823/// ```
824pub struct Parser {
825    tokenizer: Tokenizer,
826    filename: Option<String>,
827}
828
829impl Parser {
830    /// Create a new parser from source code
831    pub fn new(input: &str) -> Self {
832        Parser {
833            tokenizer: Tokenizer::new(input),
834            filename: None,
835        }
836    }
837
838    /// Create a new parser from source code with a filename for error reporting
839    pub fn new_with_filename(input: &str, filename: String) -> Self {
840        Parser {
841            tokenizer: Tokenizer::new_with_filename(input, filename.clone()),
842            filename: Some(filename),
843        }
844    }
845
846    /// Helper to create a ParseError with the current filename
847    fn error(&self, message: String, position: Position) -> ParseError {
848        if let Some(ref filename) = self.filename {
849            ParseError::with_filename(message, position, filename.clone())
850        } else {
851            ParseError::new(message, position)
852        }
853    }
854
855    /// Parse a single S-expression
856    ///
857    /// Returns `Ok(Value)` on success, `Err(ParseError)` on failure.
858    pub fn parse(&mut self) -> ParseResult<Value> {
859        self.parse_expr()
860    }
861
862    /// Peek at the next token without consuming it
863    ///
864    /// Returns `Ok(&Token)` if successful, `Err(ParseError)` on tokenizer error.
865    /// This is useful for checking if we're at EOF before attempting to parse.
866    pub fn peek_token(&mut self) -> ParseResult<&Token> {
867        self.tokenizer.peek_token()
868    }
869
870    /// Get the current position in the source code
871    ///
872    /// Returns the position of the last token consumed by the parser.
873    /// Useful for error reporting.
874    pub fn current_position(&self) -> Position {
875        self.tokenizer.position()
876    }
877
878    /// Parse all S-expressions in input
879    ///
880    /// Returns a list of all top-level expressions.
881    pub fn parse_all(&mut self) -> ParseResult<Vec<Value>> {
882        let mut exprs = Vec::new();
883
884        loop {
885            let tok = self.tokenizer.peek_token()?;
886            if *tok == Token::Eof {
887                break;
888            }
889            exprs.push(self.parse_expr()?);
890        }
891
892        Ok(exprs)
893    }
894
895    /// Parse a single expression
896    fn parse_expr(&mut self) -> ParseResult<Value> {
897        let start_pos = self.tokenizer.position();
898        let tok = self.tokenizer.next_token()?;
899
900        match tok {
901            // Literals
902            Token::Integer(n) => Ok(Value::integer(n)),
903            Token::Real(n) => Ok(Value::real(n)),
904            Token::String(s) => Ok(Value::string(s)),
905            Token::Char(ch) => Ok(Value::char(ch)),
906            Token::Bool(b) => Ok(Value::bool(b)),
907            Token::Symbol(s) => Ok(Value::symbol(&s)),
908            Token::Keyword(s) => Ok(Value::keyword(&s)),
909
910            // Lists
911            Token::LeftParen | Token::LeftBracket => self.parse_list(start_pos),
912
913            // Vectors
914            Token::VectorStart => self.parse_vector(start_pos),
915
916            // Quote
917            Token::Quote => {
918                let quoted = self.parse_expr()?;
919                Ok(Value::cons_with_pos(Value::symbol("quote"), Value::cons(quoted, Value::Nil), start_pos))
920            }
921
922            // Quasiquote
923            Token::Quasiquote => {
924                let quoted = self.parse_expr()?;
925                Ok(Value::cons_with_pos(
926                    Value::symbol("quasiquote"),
927                    Value::cons(quoted, Value::Nil),
928                    start_pos
929                ))
930            }
931
932            // Unquote
933            Token::Unquote => {
934                let quoted = self.parse_expr()?;
935                Ok(Value::cons_with_pos(
936                    Value::symbol("unquote"),
937                    Value::cons(quoted, Value::Nil),
938                    start_pos
939                ))
940            }
941
942            // Unquote-splicing
943            Token::UnquoteSplicing => {
944                let quoted = self.parse_expr()?;
945                Ok(Value::cons_with_pos(
946                    Value::symbol("unquote-splicing"),
947                    Value::cons(quoted, Value::Nil),
948                    start_pos
949                ))
950            }
951
952            // Unexpected tokens
953            Token::RightParen | Token::RightBracket => Err(self.error(
954                format!("Unexpected closing delimiter: {}", tok),
955                start_pos,
956            )),
957
958            Token::Dot => Err(self.error(
959                "Unexpected dot outside of list".to_string(),
960                start_pos,
961            )),
962
963            Token::Eof => Err(self.error(
964                "Unexpected end of input".to_string(),
965                start_pos,
966            )),
967        }
968    }
969
970    /// Parse a list (after opening paren consumed)
971    fn parse_list(&mut self, start_pos: Position) -> ParseResult<Value> {
972        let mut elements = Vec::new();
973        let mut element_positions = Vec::new();
974        let mut dotted_tail = None;
975
976        loop {
977            let tok = self.tokenizer.peek_token()?;
978
979            match tok {
980                Token::RightParen | Token::RightBracket => {
981                    self.tokenizer.next_token()?; // Consume closing paren
982                    break;
983                }
984
985                Token::Dot => {
986                    // Check if we have any elements before the dot
987                    // A dot immediately after ( is invalid: (.foo) is not valid Scheme
988                    // Valid dotted pairs require at least one element: (a . b)
989                    if elements.is_empty() {
990                        let dot_pos = self.tokenizer.position();
991                        return Err(self.error(
992                            "Invalid syntax: dot cannot appear immediately after opening parenthesis\n\
993                            Note: Identifiers cannot start with '.' (dot character is reserved for dotted pairs)\n\
994                            Example of valid dotted pair: (a . b)\n\
995                            Example of invalid syntax: (.gitignore)".to_string(),
996                            dot_pos,
997                        ));
998                    }
999
1000                    self.tokenizer.next_token()?; // Consume dot
1001
1002                    // Parse the tail
1003                    dotted_tail = Some(self.parse_expr()?);
1004
1005                    // Expect closing paren
1006                    let tok = self.tokenizer.next_token()?;
1007                    if !matches!(tok, Token::RightParen | Token::RightBracket) {
1008                        return Err(self.error(
1009                            format!("Expected ) after dotted tail, got {}", tok),
1010                            start_pos,
1011                        ));
1012                    }
1013                    break;
1014                }
1015
1016                Token::Eof => {
1017                    return Err(self.error(
1018                        "Unexpected EOF in list".to_string(),
1019                        start_pos,
1020                    ))
1021                }
1022
1023                _ => {
1024                    // Capture the position BEFORE parsing each element
1025                    // This gives us the position where this element appears in source
1026                    let elem_pos = self.tokenizer.position();
1027                    elements.push(self.parse_expr()?);
1028                    element_positions.push(elem_pos);
1029                }
1030            }
1031        }
1032
1033        // Build the list from right to left, using each element's individual position
1034        let mut result = dotted_tail.unwrap_or(Value::Nil);
1035        for (elem, elem_pos) in elements.into_iter().zip(element_positions.into_iter()).rev() {
1036            result = Value::cons_with_pos(elem, result, elem_pos);
1037        }
1038
1039        Ok(result)
1040    }
1041
1042    /// Parse a vector (after #( consumed)
1043    fn parse_vector(&mut self, start_pos: Position) -> ParseResult<Value> {
1044        let mut elements = Vec::new();
1045
1046        loop {
1047            let tok = self.tokenizer.peek_token()?;
1048
1049            match tok {
1050                Token::RightParen => {
1051                    self.tokenizer.next_token()?; // Consume )
1052                    break;
1053                }
1054
1055                Token::Eof => {
1056                    return Err(self.error(
1057                        "Unexpected EOF in vector".to_string(),
1058                        start_pos,
1059                    ))
1060                }
1061
1062                _ => {
1063                    elements.push(self.parse_expr()?);
1064                }
1065            }
1066        }
1067
1068        Ok(Value::vector(elements))
1069    }
1070}
1071
1072// =============================================================================
1073// Tests
1074// =============================================================================
1075
1076#[cfg(test)]
1077mod tests {
1078    use super::*;
1079
1080    #[test]
1081    fn test_tokenize_simple() {
1082        let mut tok = Tokenizer::new("(+ 1 2)");
1083        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1084        assert_eq!(tok.next_token().unwrap(), Token::Symbol("+".to_string()));
1085        assert_eq!(tok.next_token().unwrap(), Token::Integer(1));
1086        assert_eq!(tok.next_token().unwrap(), Token::Integer(2));
1087        assert_eq!(tok.next_token().unwrap(), Token::RightParen);
1088        assert_eq!(tok.next_token().unwrap(), Token::Eof);
1089    }
1090
1091    #[test]
1092    fn test_tokenize_whitespace_agnostic() {
1093        // This is the critical test that Steel fails!
1094        let input = r#"(let ((x 1)
1095                            (y 2))
1096                         (+ x y))"#;
1097        let mut tok = Tokenizer::new(input);
1098
1099        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1100        assert_eq!(tok.next_token().unwrap(), Token::Symbol("let".to_string()));
1101        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1102        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1103        assert_eq!(tok.next_token().unwrap(), Token::Symbol("x".to_string()));
1104        assert_eq!(tok.next_token().unwrap(), Token::Integer(1));
1105        assert_eq!(tok.next_token().unwrap(), Token::RightParen);
1106        // Multi-line whitespace handled correctly!
1107        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1108        assert_eq!(tok.next_token().unwrap(), Token::Symbol("y".to_string()));
1109        assert_eq!(tok.next_token().unwrap(), Token::Integer(2));
1110    }
1111
1112    #[test]
1113    fn test_tokenize_strings() {
1114        let mut tok = Tokenizer::new(r#""hello world""#);
1115        assert_eq!(
1116            tok.next_token().unwrap(),
1117            Token::String("hello world".to_string())
1118        );
1119
1120        let mut tok = Tokenizer::new(r#""with\nnewline""#);
1121        assert_eq!(
1122            tok.next_token().unwrap(),
1123            Token::String("with\nnewline".to_string())
1124        );
1125    }
1126
1127    #[test]
1128    fn test_tokenize_cdata() {
1129        // Test CDATA section parsing (OpenJade extension)
1130        let mut tok = Tokenizer::new(r#"<![CDATA[<!DOCTYPE HTML>]]>"#);
1131        assert_eq!(
1132            tok.next_token().unwrap(),
1133            Token::String("<!DOCTYPE HTML>".to_string())
1134        );
1135
1136        // Test CDATA with newlines
1137        let mut tok = Tokenizer::new("<![CDATA[\nLine 1\nLine 2\n]]>");
1138        assert_eq!(
1139            tok.next_token().unwrap(),
1140            Token::String("\nLine 1\nLine 2\n".to_string())
1141        );
1142
1143        // Test CDATA in expression context
1144        let mut tok = Tokenizer::new("(define x <![CDATA[test]]>)");
1145        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1146        assert_eq!(tok.next_token().unwrap(), Token::Symbol("define".to_string()));
1147        assert_eq!(tok.next_token().unwrap(), Token::Symbol("x".to_string()));
1148        assert_eq!(tok.next_token().unwrap(), Token::String("test".to_string()));
1149        assert_eq!(tok.next_token().unwrap(), Token::RightParen);
1150    }
1151
1152    #[test]
1153    fn test_tokenize_comments() {
1154        let mut tok = Tokenizer::new("(+ 1 ; comment\n 2)");
1155        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1156        assert_eq!(tok.next_token().unwrap(), Token::Symbol("+".to_string()));
1157        assert_eq!(tok.next_token().unwrap(), Token::Integer(1));
1158        // Comment skipped!
1159        assert_eq!(tok.next_token().unwrap(), Token::Integer(2));
1160        assert_eq!(tok.next_token().unwrap(), Token::RightParen);
1161    }
1162
1163    #[test]
1164    fn test_tokenize_block_comments() {
1165        let mut tok = Tokenizer::new("(+ 1 #| block comment |# 2)");
1166        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1167        assert_eq!(tok.next_token().unwrap(), Token::Symbol("+".to_string()));
1168        assert_eq!(tok.next_token().unwrap(), Token::Integer(1));
1169        // Block comment skipped!
1170        assert_eq!(tok.next_token().unwrap(), Token::Integer(2));
1171        assert_eq!(tok.next_token().unwrap(), Token::RightParen);
1172    }
1173
1174    #[test]
1175    fn test_tokenize_booleans() {
1176        let mut tok = Tokenizer::new("#t #f");
1177        assert_eq!(tok.next_token().unwrap(), Token::Bool(true));
1178        assert_eq!(tok.next_token().unwrap(), Token::Bool(false));
1179    }
1180
1181    #[test]
1182    fn test_tokenize_characters() {
1183        let mut tok = Tokenizer::new(r#"#\a #\space #\newline"#);
1184        assert_eq!(tok.next_token().unwrap(), Token::Char('a'));
1185        assert_eq!(tok.next_token().unwrap(), Token::Char(' '));
1186        assert_eq!(tok.next_token().unwrap(), Token::Char('\n'));
1187    }
1188
1189    #[test]
1190    fn test_tokenize_hex_numbers() {
1191        // Lowercase #x
1192        let mut tok = Tokenizer::new("#xff");
1193        assert_eq!(tok.next_token().unwrap(), Token::Integer(255));
1194
1195        // Uppercase #X
1196        let mut tok = Tokenizer::new("#X10");
1197        assert_eq!(tok.next_token().unwrap(), Token::Integer(16));
1198
1199        // Mixed case
1200        let mut tok = Tokenizer::new("#xDEADBEEF");
1201        assert_eq!(tok.next_token().unwrap(), Token::Integer(0xDEADBEEF));
1202
1203        // Zero
1204        let mut tok = Tokenizer::new("#x0");
1205        assert_eq!(tok.next_token().unwrap(), Token::Integer(0));
1206    }
1207
1208    #[test]
1209    fn test_tokenize_octal_numbers() {
1210        // Lowercase #o
1211        let mut tok = Tokenizer::new("#o77");
1212        assert_eq!(tok.next_token().unwrap(), Token::Integer(63));
1213
1214        // Uppercase #O
1215        let mut tok = Tokenizer::new("#O10");
1216        assert_eq!(tok.next_token().unwrap(), Token::Integer(8));
1217
1218        // Zero
1219        let mut tok = Tokenizer::new("#o0");
1220        assert_eq!(tok.next_token().unwrap(), Token::Integer(0));
1221
1222        // Max valid octal digits
1223        let mut tok = Tokenizer::new("#o777");
1224        assert_eq!(tok.next_token().unwrap(), Token::Integer(511));
1225    }
1226
1227    #[test]
1228    fn test_tokenize_binary_numbers() {
1229        // Lowercase #b
1230        let mut tok = Tokenizer::new("#b1010");
1231        assert_eq!(tok.next_token().unwrap(), Token::Integer(10));
1232
1233        // Uppercase #B
1234        let mut tok = Tokenizer::new("#B1111");
1235        assert_eq!(tok.next_token().unwrap(), Token::Integer(15));
1236
1237        // Zero
1238        let mut tok = Tokenizer::new("#b0");
1239        assert_eq!(tok.next_token().unwrap(), Token::Integer(0));
1240
1241        // All ones
1242        let mut tok = Tokenizer::new("#b11111111");
1243        assert_eq!(tok.next_token().unwrap(), Token::Integer(255));
1244    }
1245
1246    #[test]
1247    fn test_tokenize_quote() {
1248        let mut tok = Tokenizer::new("'(1 2)");
1249        assert_eq!(tok.next_token().unwrap(), Token::Quote);
1250        assert_eq!(tok.next_token().unwrap(), Token::LeftParen);
1251        assert_eq!(tok.next_token().unwrap(), Token::Integer(1));
1252        assert_eq!(tok.next_token().unwrap(), Token::Integer(2));
1253        assert_eq!(tok.next_token().unwrap(), Token::RightParen);
1254    }
1255
1256    #[test]
1257    fn test_error_positions() {
1258        let mut tok = Tokenizer::new("(+ 1\n  \"unclosed string");
1259        tok.next_token().unwrap(); // (
1260        tok.next_token().unwrap(); // +
1261        tok.next_token().unwrap(); // 1
1262
1263        let err = tok.next_token().unwrap_err();
1264        assert_eq!(err.position.line, 2); // Error on line 2
1265        assert!(err.message.contains("EOF in string"));
1266    }
1267
1268    // =================================================================
1269    // Parser Tests
1270    // =================================================================
1271
1272    #[test]
1273    fn test_parse_integer() {
1274        let mut parser = Parser::new("42");
1275        let val = parser.parse().unwrap();
1276        assert!(val.is_integer());
1277        if let Value::Integer(n) = val {
1278            assert_eq!(n, 42);
1279        }
1280    }
1281
1282    #[test]
1283    fn test_parse_simple_list() {
1284        let mut parser = Parser::new("(+ 1 2)");
1285        let val = parser.parse().unwrap();
1286        assert!(val.is_list());
1287
1288        // Check structure: (+ 1 2)
1289        if let Value::Pair(ref p) = val {
1290            let pair = p.borrow();
1291            assert!(pair.car.is_symbol());
1292        }
1293    }
1294
1295    #[test]
1296    fn test_parse_nested_list() {
1297        let mut parser = Parser::new("(+ (* 2 3) 4)");
1298        let val = parser.parse().unwrap();
1299        assert!(val.is_list());
1300    }
1301
1302    #[test]
1303    fn test_parse_quoted() {
1304        let mut parser = Parser::new("'(1 2 3)");
1305        let val = parser.parse().unwrap();
1306
1307        // Should be (quote (1 2 3))
1308        if let Value::Pair(ref p) = val {
1309            let pair = p.borrow();
1310            if let Value::Symbol(s) = &pair.car {
1311                assert_eq!(&**s, "quote");
1312            } else {
1313                panic!("Expected symbol 'quote'");
1314            }
1315        } else {
1316            panic!("Expected pair");
1317        }
1318    }
1319
1320    #[test]
1321    fn test_parse_vector() {
1322        let mut parser = Parser::new("#(1 2 3)");
1323        let val = parser.parse().unwrap();
1324        assert!(val.is_vector());
1325
1326        if let Value::Vector(ref v) = val {
1327            let vec = v.borrow();
1328            assert_eq!(vec.len(), 3);
1329        }
1330    }
1331
1332    #[test]
1333    fn test_parse_dotted_list() {
1334        let mut parser = Parser::new("(1 . 2)");
1335        let val = parser.parse().unwrap();
1336
1337        if let Value::Pair(ref p) = val {
1338            let pair = p.borrow();
1339            assert!(matches!(pair.car, Value::Integer(1)));
1340            assert!(matches!(pair.cdr, Value::Integer(2)));
1341        } else {
1342            panic!("Expected pair");
1343        }
1344    }
1345
1346    #[test]
1347    fn test_parse_string() {
1348        let mut parser = Parser::new(r#""hello world""#);
1349        let val = parser.parse().unwrap();
1350        assert!(val.is_string());
1351    }
1352
1353    #[test]
1354    fn test_parse_bool() {
1355        let mut parser = Parser::new("#t");
1356        let val = parser.parse().unwrap();
1357        assert!(val.is_bool());
1358        assert!(val.is_true());
1359
1360        let mut parser = Parser::new("#f");
1361        let val = parser.parse().unwrap();
1362        assert!(val.is_bool());
1363        assert!(!val.is_true());
1364    }
1365
1366    #[test]
1367    fn test_parse_multiline_let() {
1368        // THE CRITICAL TEST: Multi-line let bindings (breaks Steel!)
1369        let input = r#"
1370            (let ((x 1)
1371                  (y 2))
1372              (+ x y))
1373        "#;
1374
1375        let mut parser = Parser::new(input);
1376        let val = parser.parse().unwrap();
1377        assert!(val.is_list());
1378
1379        // Should parse successfully despite multi-line formatting
1380        // This is what Steel cannot handle!
1381    }
1382
1383    #[test]
1384    fn test_parse_all() {
1385        let input = "(define x 1) (define y 2) (+ x y)";
1386        let mut parser = Parser::new(input);
1387        let exprs = parser.parse_all().unwrap();
1388        assert_eq!(exprs.len(), 3);
1389    }
1390
1391    #[test]
1392    fn test_parse_empty_list() {
1393        let mut parser = Parser::new("()");
1394        let val = parser.parse().unwrap();
1395        assert!(val.is_nil());
1396    }
1397
1398    #[test]
1399    fn test_parse_keyword() {
1400        let mut parser = Parser::new("#:foo");
1401        let val = parser.parse().unwrap();
1402        if let Value::Keyword(ref k) = val {
1403            assert_eq!(&**k, "foo");
1404        } else {
1405            panic!("Expected keyword");
1406        }
1407    }
1408
1409    #[test]
1410    fn test_parse_error_with_filename() {
1411        // Test that parser errors include the filename when provided
1412        let mut parser = Parser::new_with_filename("(define x", "test.scm".to_string());
1413        let err = parser.parse().unwrap_err();
1414        let err_string = err.to_string();
1415
1416        // Error should include filename
1417        assert!(err_string.contains("test.scm"), "Error should contain filename: {}", err_string);
1418        // Error should include line and column (error is at the opening paren position)
1419        assert!(err_string.contains("1:1"), "Error should contain position: {}", err_string);
1420        // Error should use format: filename:line:column:E: message
1421        assert!(err_string.contains("test.scm:1:1:E:"), "Error should use OpenJade format: {}", err_string);
1422    }
1423
1424    #[test]
1425    fn test_parse_error_without_filename() {
1426        // Test that parser errors still work without filename
1427        let mut parser = Parser::new("(define x");
1428        let err = parser.parse().unwrap_err();
1429        let err_string = err.to_string();
1430
1431        // Error should NOT include filename
1432        assert!(!err_string.contains("test.scm"), "Error should not contain filename when not provided");
1433        // But should still include position (error is at the opening paren position)
1434        assert!(err_string.contains("1:1"), "Error should still contain position: {}", err_string);
1435    }
1436
1437    #[test]
1438    fn test_parse_error_dot_after_open_paren() {
1439        // Test that (.gitignore) gives a clear error
1440        // This is invalid Scheme syntax - identifiers cannot start with dot
1441        let mut parser = Parser::new("(.gitignore)");
1442        let err = parser.parse().unwrap_err();
1443        let err_string = err.to_string();
1444
1445        // Error should mention that dot cannot appear after opening paren
1446        assert!(err_string.contains("dot cannot appear immediately after opening parenthesis"),
1447                "Error should mention invalid dot position: {}", err_string);
1448        // Error should mention that identifiers cannot start with dot
1449        assert!(err_string.contains("Identifiers cannot start with '.'"),
1450                "Error should explain why: {}", err_string);
1451    }
1452
1453    #[test]
1454    fn test_parse_error_dot_function_definition() {
1455        // Test that (define (.gitignore) ...) gives a clear error
1456        let mut parser = Parser::new("(define (.gitignore) (list))");
1457        let err = parser.parse().unwrap_err();
1458        let err_string = err.to_string();
1459
1460        // Should get same error about invalid dot syntax
1461        assert!(err_string.contains("dot cannot appear immediately after opening parenthesis"),
1462                "Error should mention invalid dot position: {}", err_string);
1463    }
1464
1465    #[test]
1466    fn test_parse_valid_dotted_pair_still_works() {
1467        // Verify that valid dotted pairs like (a . b) still work
1468        let mut parser = Parser::new("(a . b)");
1469        let val = parser.parse().unwrap();
1470
1471        // Should successfully parse as a dotted pair
1472        if let Value::Pair(ref p) = val {
1473            let pair = p.borrow();
1474            if let Value::Symbol(s) = &pair.car {
1475                assert_eq!(&**s, "a");
1476            } else {
1477                panic!("Expected symbol 'a'");
1478            }
1479            if let Value::Symbol(s) = &pair.cdr {
1480                assert_eq!(&**s, "b");
1481            } else {
1482                panic!("Expected symbol 'b'");
1483            }
1484        } else {
1485            panic!("Expected pair");
1486        }
1487    }
1488}
1489