Skip to main content

rantlr_core/
parser.rs

1//! Recursive-descent parser: Token stream → [`Grammar`] AST.
2
3use crate::ast::{
4    ExampleDecl, Expr, Grammar, GrammarItem, ImportDecl, RuleDecl, TokenBody, TokenDecl,
5};
6use crate::diagnostic::{Diagnostic, DiagnosticKind};
7use crate::error::RantlrError;
8use crate::lexer::{lex, LexerError};
9use crate::span::Span;
10use crate::token::{BuiltinType, Keyword, Token, TokenKind};
11
12/// Parse a `.gr` source string into a [`Grammar`] AST.
13pub fn parse(source: &str) -> Result<Grammar, RantlrError> {
14    let tokens = lex(source).map_err(|e| match e {
15        LexerError::Diagnostic(diag) => RantlrError::from_diagnostic(source, diag),
16    })?;
17    Parser::new(source, tokens).parse_grammar()
18}
19
20struct Parser<'src> {
21    source: &'src str,
22    tokens: Vec<Token>,
23    pos: usize,
24}
25
26impl<'src> Parser<'src> {
27    fn new(source: &'src str, tokens: Vec<Token>) -> Self {
28        Self {
29            source,
30            tokens,
31            pos: 0,
32        }
33    }
34
35    fn parse_grammar(mut self) -> Result<Grammar, RantlrError> {
36        let start = self.peek().span;
37        self.expect_keyword(Keyword::Grammar)?;
38        let (name, name_span) = self.expect_ident("grammar name")?;
39        self.expect_semi()?;
40
41        let mut items = Vec::new();
42        while !self.at_eof() {
43            items.push(self.parse_item()?);
44        }
45
46        let end = self.prev_span();
47        Ok(Grammar {
48            name,
49            name_span,
50            items,
51            span: start.merge(end),
52        })
53    }
54
55    fn parse_item(&mut self) -> Result<GrammarItem, RantlrError> {
56        match &self.peek().kind {
57            TokenKind::Keyword(Keyword::Token) => Ok(GrammarItem::Token(self.parse_token_decl()?)),
58            TokenKind::Keyword(Keyword::Rule) => Ok(GrammarItem::Rule(self.parse_rule_decl()?)),
59            TokenKind::Keyword(Keyword::Example) => {
60                Ok(GrammarItem::Example(self.parse_example_decl()?))
61            }
62            TokenKind::Keyword(Keyword::Import) => {
63                Ok(GrammarItem::Import(self.parse_import_decl()?))
64            }
65            _ => Err(self.unexpected(
66                "expected `token`, `rule`, `example`, or `import`",
67            )),
68        }
69    }
70
71    fn parse_token_decl(&mut self) -> Result<TokenDecl, RantlrError> {
72        let start = self.peek().span;
73        self.expect_keyword(Keyword::Token)?;
74        let (name, name_span) = self.expect_ident("token name")?;
75        self.expect_kind(TokenKind::Eq, "`=`")?;
76
77        let body = match &self.peek().kind {
78            TokenKind::Builtin(b) => {
79                let b = *b;
80                self.bump();
81                TokenBody::Builtin(b)
82            }
83            TokenKind::String(s) => {
84                let s = s.clone();
85                self.bump();
86                TokenBody::Literal(s)
87            }
88            TokenKind::Ident(name) => {
89                // Defensive: accept PascalCase builtin names even if the lexer
90                // emitted Ident (e.g. older front-ends / partial rebuilds).
91                let span = self.peek().span;
92                let name = name.clone();
93                if let Some(b) = BuiltinType::from_ident(&name) {
94                    self.bump();
95                    TokenBody::Builtin(b)
96                } else {
97                    return Err(self.err_at(
98                        span,
99                        format!("expected a built-in type or string literal, found `{name}`"),
100                        Some(
101                            "built-ins: Number, Identifier, QuotedString, Email, Url, DateTime",
102                        ),
103                    ));
104                }
105            }
106            _ => {
107                return Err(self.unexpected(
108                    "expected a built-in type (e.g. Number or Identifier) or a string literal",
109                ));
110            }
111        };
112
113        let skip = if self.eat_kind(&TokenKind::Arrow) {
114            self.expect_keyword(Keyword::Skip)?;
115            true
116        } else {
117            false
118        };
119
120        self.expect_semi()?;
121        let end = self.prev_span();
122        Ok(TokenDecl {
123            name,
124            name_span,
125            body,
126            skip,
127            span: start.merge(end),
128        })
129    }
130
131    fn parse_rule_decl(&mut self) -> Result<RuleDecl, RantlrError> {
132        let start = self.peek().span;
133        self.expect_keyword(Keyword::Rule)?;
134        let (name, name_span) = self.expect_ident("rule name")?;
135        self.expect_kind(TokenKind::LBrace, "`{`")?;
136        let body = self.parse_expr()?;
137        self.expect_kind(TokenKind::RBrace, "`}`")?;
138        let end = self.prev_span();
139        Ok(RuleDecl {
140            name,
141            name_span,
142            body,
143            span: start.merge(end),
144        })
145    }
146
147    fn parse_example_decl(&mut self) -> Result<ExampleDecl, RantlrError> {
148        let start = self.peek().span;
149        self.expect_keyword(Keyword::Example)?;
150
151        let label = if let TokenKind::String(s) = &self.peek().kind {
152            let s = s.clone();
153            self.bump();
154            Some(s)
155        } else {
156            None
157        };
158
159        self.expect_kind(TokenKind::LBrace, "`{`")?;
160        self.expect_keyword(Keyword::Input)?;
161        self.expect_kind(TokenKind::Colon, "`:`")?;
162
163        let input = match &self.peek().kind {
164            TokenKind::RawString(s) | TokenKind::String(s) => {
165                let s = s.clone();
166                self.bump();
167                s
168            }
169            _ => {
170                return Err(self.unexpected(
171                    "expected example input as a string or raw string (`...`)",
172                ));
173            }
174        };
175
176        let expect = if self.eat_keyword(Keyword::Expect) {
177            self.expect_kind(TokenKind::Colon, "`:`")?;
178            let (name, _) = self.expect_ident("expected rule name")?;
179            Some(name)
180        } else {
181            None
182        };
183
184        self.expect_kind(TokenKind::RBrace, "`}`")?;
185        let end = self.prev_span();
186        Ok(ExampleDecl {
187            label,
188            input,
189            expect,
190            span: start.merge(end),
191        })
192    }
193
194    fn parse_import_decl(&mut self) -> Result<ImportDecl, RantlrError> {
195        let start = self.peek().span;
196        self.expect_keyword(Keyword::Import)?;
197        let path = match &self.peek().kind {
198            TokenKind::String(s) => {
199                let s = s.clone();
200                self.bump();
201                s
202            }
203            _ => return Err(self.unexpected("expected a string path after `import`")),
204        };
205        self.expect_semi()?;
206        let end = self.prev_span();
207        Ok(ImportDecl {
208            path,
209            span: start.merge(end),
210        })
211    }
212
213    // ── Expressions ──────────────────────────────────────────────
214    // expr  = alt
215    // alt   = seq ("|" seq)*
216    // seq   = atom+
217    // atom  = match | repeat | optional | primary
218    // primary = Ident | Builtin | String | "(" expr ")"
219
220    fn parse_expr(&mut self) -> Result<Expr, RantlrError> {
221        self.parse_alt()
222    }
223
224    fn parse_alt(&mut self) -> Result<Expr, RantlrError> {
225        let mut alts = vec![self.parse_seq()?];
226        while self.eat_kind(&TokenKind::Pipe) {
227            alts.push(self.parse_seq()?);
228        }
229        Ok(Expr::alt(alts))
230    }
231
232    fn parse_seq(&mut self) -> Result<Expr, RantlrError> {
233        let mut items = Vec::new();
234        while self.at_atom_start() {
235            items.push(self.parse_atom()?);
236        }
237        if items.is_empty() {
238            return Err(self.unexpected(
239                "expected an expression (match, repeat, optional, name, string, or group)",
240            ));
241        }
242        Ok(Expr::seq(items))
243    }
244
245    fn parse_atom(&mut self) -> Result<Expr, RantlrError> {
246        match &self.peek().kind {
247            TokenKind::Keyword(Keyword::Match) => self.parse_match(),
248            TokenKind::Keyword(Keyword::Repeat) => self.parse_repeat(),
249            TokenKind::Keyword(Keyword::Optional) => self.parse_optional(),
250            _ => self.parse_primary(),
251        }
252    }
253
254    fn parse_match(&mut self) -> Result<Expr, RantlrError> {
255        self.expect_keyword(Keyword::Match)?;
256        // Each arm is a single primary so `match "+" | "-" term` parses as
257        // Match(["+","-"]) followed by Ref(term), not one giant match arm.
258        // Use a group for compound arms: `match Num | ("(" expr ")")`.
259        let mut arms = vec![self.parse_primary()?];
260        while self.eat_kind(&TokenKind::Pipe) {
261            if !self.at_primary_start() {
262                return Err(self.unexpected("expected a match arm after `|`"));
263            }
264            arms.push(self.parse_primary()?);
265        }
266        Ok(Expr::Match { arms })
267    }
268
269    fn parse_repeat(&mut self) -> Result<Expr, RantlrError> {
270        self.expect_keyword(Keyword::Repeat)?;
271
272        let (min, max) = if self.eat_kind(&TokenKind::LParen) {
273            let min = match &self.peek().kind {
274                TokenKind::Integer(n) => {
275                    let n = *n;
276                    self.bump();
277                    Some(n)
278                }
279                TokenKind::DotDot => None,
280                _ => {
281                    return Err(self.unexpected(
282                        "expected a lower bound integer or `..` in repeat(...)",
283                    ));
284                }
285            };
286            self.expect_kind(TokenKind::DotDot, "`..`")?;
287            let max = if let TokenKind::Integer(n) = &self.peek().kind {
288                let n = *n;
289                self.bump();
290                Some(n)
291            } else {
292                None
293            };
294            self.expect_kind(TokenKind::RParen, "`)`")?;
295            (min.or(Some(0)), max)
296        } else {
297            (None, None)
298        };
299
300        self.expect_kind(TokenKind::LBrace, "`{`")?;
301        let body = self.parse_expr()?;
302        self.expect_kind(TokenKind::RBrace, "`}`")?;
303
304        Ok(Expr::Repeat {
305            min,
306            max,
307            body: Box::new(body),
308        })
309    }
310
311    fn parse_optional(&mut self) -> Result<Expr, RantlrError> {
312        self.expect_keyword(Keyword::Optional)?;
313        self.expect_kind(TokenKind::LBrace, "`{`")?;
314        let body = self.parse_expr()?;
315        self.expect_kind(TokenKind::RBrace, "`}`")?;
316        Ok(Expr::Optional {
317            body: Box::new(body),
318        })
319    }
320
321    fn parse_primary(&mut self) -> Result<Expr, RantlrError> {
322        let tok = self.peek().clone();
323        match tok.kind {
324            TokenKind::Ident(name) => {
325                self.bump();
326                Ok(Expr::Ref {
327                    name,
328                    span: tok.span,
329                })
330            }
331            TokenKind::Builtin(b) => {
332                // Builtins may appear as bare refs only in token decls;
333                // in rules, treat as a named ref to the built-in type.
334                self.bump();
335                Ok(Expr::Ref {
336                    name: b.as_str().to_string(),
337                    span: tok.span,
338                })
339            }
340            TokenKind::String(value) => {
341                self.bump();
342                Ok(Expr::Literal {
343                    value,
344                    span: tok.span,
345                })
346            }
347            TokenKind::LParen => {
348                self.bump();
349                let body = self.parse_expr()?;
350                self.expect_kind(TokenKind::RParen, "`)`")?;
351                Ok(Expr::Group {
352                    body: Box::new(body),
353                })
354            }
355            _ => Err(self.unexpected("expected a name, string, or `(...)` group")),
356        }
357    }
358
359    // ── Token helpers ────────────────────────────────────────────
360
361    fn at_atom_start(&self) -> bool {
362        matches!(
363            &self.peek().kind,
364            TokenKind::Keyword(Keyword::Match)
365                | TokenKind::Keyword(Keyword::Repeat)
366                | TokenKind::Keyword(Keyword::Optional)
367                | TokenKind::Ident(_)
368                | TokenKind::Builtin(_)
369                | TokenKind::String(_)
370                | TokenKind::LParen
371        )
372    }
373
374    fn at_primary_start(&self) -> bool {
375        matches!(
376            &self.peek().kind,
377            TokenKind::Ident(_)
378                | TokenKind::Builtin(_)
379                | TokenKind::String(_)
380                | TokenKind::LParen
381        )
382    }
383
384    fn at_eof(&self) -> bool {
385        self.peek().kind.is_eof()
386    }
387
388    fn peek(&self) -> &Token {
389        &self.tokens[self.pos.min(self.tokens.len().saturating_sub(1))]
390    }
391
392    fn prev_span(&self) -> Span {
393        if self.pos == 0 {
394            Span::default()
395        } else {
396            self.tokens[self.pos - 1].span
397        }
398    }
399
400    fn bump(&mut self) {
401        if !self.peek().kind.is_eof() {
402            self.pos += 1;
403        }
404    }
405
406    fn eat_kind(&mut self, kind: &TokenKind) -> bool {
407        let matched = match (kind, &self.peek().kind) {
408            (TokenKind::LBrace, TokenKind::LBrace)
409            | (TokenKind::RBrace, TokenKind::RBrace)
410            | (TokenKind::LParen, TokenKind::LParen)
411            | (TokenKind::RParen, TokenKind::RParen)
412            | (TokenKind::LBracket, TokenKind::LBracket)
413            | (TokenKind::RBracket, TokenKind::RBracket)
414            | (TokenKind::Pipe, TokenKind::Pipe)
415            | (TokenKind::Comma, TokenKind::Comma)
416            | (TokenKind::Semi, TokenKind::Semi)
417            | (TokenKind::Colon, TokenKind::Colon)
418            | (TokenKind::Eq, TokenKind::Eq)
419            | (TokenKind::DotDot, TokenKind::DotDot)
420            | (TokenKind::Arrow, TokenKind::Arrow)
421            | (TokenKind::Eof, TokenKind::Eof) => true,
422            _ => false,
423        };
424        if matched {
425            self.bump();
426        }
427        matched
428    }
429
430    fn eat_keyword(&mut self, kw: Keyword) -> bool {
431        if matches!(&self.peek().kind, TokenKind::Keyword(k) if *k == kw) {
432            self.bump();
433            true
434        } else {
435            false
436        }
437    }
438
439    fn expect_keyword(&mut self, kw: Keyword) -> Result<(), RantlrError> {
440        if self.eat_keyword(kw) {
441            Ok(())
442        } else {
443            Err(self.unexpected(&format!("expected keyword `{}`", kw.as_str())))
444        }
445    }
446
447    fn expect_kind(&mut self, kind: TokenKind, label: &str) -> Result<(), RantlrError> {
448        if self.eat_kind(&kind) {
449            Ok(())
450        } else {
451            Err(self.unexpected(&format!("expected {label}")))
452        }
453    }
454
455    fn expect_semi(&mut self) -> Result<(), RantlrError> {
456        self.expect_kind(TokenKind::Semi, "`;`")
457    }
458
459    fn expect_ident(&mut self, what: &str) -> Result<(String, Span), RantlrError> {
460        match &self.peek().kind {
461            TokenKind::Ident(name) => {
462                let name = name.clone();
463                let span = self.peek().span;
464                self.bump();
465                Ok((name, span))
466            }
467            // Allow builtins as names? No — grammar/rule/token names are Idents.
468            TokenKind::Builtin(b) => {
469                // Token names like `Number` as rule names are rare; reject clearly.
470                let span = self.peek().span;
471                Err(self.err_at(
472                    span,
473                    format!(
474                        "expected {what}, found built-in type `{}`",
475                        b.as_str()
476                    ),
477                    Some("built-in types are used on the right-hand side of `token Name = ...`"),
478                ))
479            }
480            _ => Err(self.unexpected(&format!("expected {what}"))),
481        }
482    }
483
484    fn unexpected(&self, message: &str) -> RantlrError {
485        let tok = self.peek();
486        let found = describe_token(&tok.kind);
487        self.err_at(
488            tok.span,
489            format!("{message}, found {found}"),
490            None,
491        )
492    }
493
494    fn err_at(
495        &self,
496        span: Span,
497        message: impl Into<String>,
498        help: Option<&str>,
499    ) -> RantlrError {
500        let mut diag = Diagnostic::error(DiagnosticKind::ParseError, message, span);
501        if let Some(help) = help {
502            diag = diag.with_help(help);
503        }
504        RantlrError::from_diagnostic(self.source, diag)
505    }
506}
507
508fn describe_token(kind: &TokenKind) -> String {
509    match kind {
510        TokenKind::Eof => "end of file".into(),
511        TokenKind::Keyword(k) => format!("`{}`", k.as_str()),
512        TokenKind::Builtin(b) => format!("built-in `{}`", b.as_str()),
513        TokenKind::Ident(s) => format!("identifier `{s}`"),
514        TokenKind::String(s) => format!("string {s:?}"),
515        TokenKind::RawString(s) => format!("raw string {s:?}"),
516        TokenKind::Integer(n) => format!("integer `{n}`"),
517        TokenKind::LBrace => "`{`".into(),
518        TokenKind::RBrace => "`}`".into(),
519        TokenKind::LParen => "`(`".into(),
520        TokenKind::RParen => "`)`".into(),
521        TokenKind::LBracket => "`[`".into(),
522        TokenKind::RBracket => "`]`".into(),
523        TokenKind::Pipe => "`|`".into(),
524        TokenKind::Comma => "`,`".into(),
525        TokenKind::Semi => "`;`".into(),
526        TokenKind::Colon => "`:`".into(),
527        TokenKind::Eq => "`=`".into(),
528        TokenKind::DotDot => "`..`".into(),
529        TokenKind::Arrow => "`->`".into(),
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536    use crate::ast::{GrammarItem, TokenBody};
537    use crate::token::BuiltinType;
538
539    #[test]
540    fn parses_calculator_example() {
541        let src = include_str!("../testdata/calculator.gr");
542        let g = parse(src).expect("parse calculator.gr");
543        assert_eq!(g.name, "Calculator");
544
545        let rules: Vec<_> = g
546            .items
547            .iter()
548            .filter_map(|i| match i {
549                GrammarItem::Rule(r) => Some(r.name.as_str()),
550                _ => None,
551            })
552            .collect();
553        assert_eq!(rules, ["prog", "expr", "term", "factor"]);
554
555        let tokens: Vec<_> = g
556            .items
557            .iter()
558            .filter_map(|i| match i {
559                GrammarItem::Token(t) => Some((t.name.as_str(), t.skip, &t.body)),
560                _ => None,
561            })
562            .collect();
563        assert!(matches!(
564            tokens[0],
565            ("Num", false, TokenBody::Builtin(BuiltinType::Number))
566        ));
567        assert!(tokens.iter().any(|(n, skip, _)| *n == "Ws" && *skip));
568
569        let examples = g
570            .items
571            .iter()
572            .filter(|i| matches!(i, GrammarItem::Example(_)))
573            .count();
574        assert_eq!(examples, 2);
575    }
576
577    #[test]
578    fn parses_match_repeat_optional() {
579        let src = r#"
580            grammar Mini;
581            token N = Number;
582            rule items {
583                optional { item }
584                repeat(0..) { "," item }
585            }
586            rule item {
587                match N | "x" | ("(" N ")")
588            }
589        "#;
590        let g = parse(src).expect("parse");
591        let rule = g
592            .items
593            .iter()
594            .find_map(|i| match i {
595                GrammarItem::Rule(r) if r.name == "items" => Some(r),
596                _ => None,
597            })
598            .unwrap();
599
600        match &rule.body {
601            Expr::Seq { items } => {
602                assert!(matches!(items[0], Expr::Optional { .. }));
603                assert!(matches!(
604                    items[1],
605                    Expr::Repeat {
606                        min: Some(0),
607                        max: None,
608                        ..
609                    }
610                ));
611            }
612            other => panic!("expected Seq, got {other:?}"),
613        }
614    }
615
616    #[test]
617    fn parse_error_includes_snippet() {
618        let src = "grammar X;\nrule bad {\n";
619        let err = parse(src).unwrap_err();
620        assert_eq!(err.kind, crate::error::ErrorKind::Parse);
621        assert!(err.snippet.contains("|"));
622        assert!(err.snippet.contains("^"));
623        assert!(err.line >= 2);
624    }
625}