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