Skip to main content

meta_language/grammar/import/
antlr.rs

1mod char_set;
2mod commands;
3mod lexer;
4
5use char_set::{has_case, lower_char_set, negate_expr, with_case_variants};
6use commands::{apply_retypes, command_channel, command_type};
7use lexer::{Lexer, Token, TokenKind};
8
9use super::{GrammarImportError, parse_error, unsupported_error};
10use crate::grammar::feature::{FeatureExpr, FeatureForm, FieldValue, class_expression};
11use crate::grammar::{
12    Grammar, GrammarExpr, GrammarFormat, GrammarRule, RuleAttributes, RuleKind, UnicodeClassItem,
13};
14
15const FORMAT: GrammarFormat = GrammarFormat::Antlr;
16
17/// Parses ANTLR v4 `.g4` grammar text into the grammar IR.
18///
19/// The importer is a clean-room parser for the structural ANTLR grammar subset
20/// needed by the grammar IR. ANTLR alternatives are lowered as unordered CFG
21/// choices, while lexer commands and dropped target-language actions are
22/// preserved in rule documentation; `-> skip` and `-> channel(NAME)` also put
23/// the rule on that channel, which makes it trivia. `EOF` is the end of the
24/// input, and character sets read `\uXXXX`, `\u{X...}` and `\p{NAME}` for a
25/// general category or script.
26///
27/// # Errors
28///
29/// Returns [`GrammarImportError`] when the source cannot be parsed as the
30/// supported ANTLR subset or when a parsed prelude construct is recognized but
31/// not yet representable.
32pub fn import_antlr(text: &str) -> Result<Grammar, GrammarImportError> {
33    Parser::new(Lexer::new(text).tokenize()?).parse_grammar()
34}
35
36#[derive(Clone, Debug)]
37struct Parser {
38    tokens: Vec<Token>,
39    cursor: usize,
40    pending_comments: Vec<String>,
41    // `options { caseInsensitive = true; }` of the current grammar, and of
42    // the current rule, which may override it.
43    case_insensitive: bool,
44    rule_case_insensitive: bool,
45}
46
47impl Parser {
48    const fn new(tokens: Vec<Token>) -> Self {
49        Self {
50            tokens,
51            cursor: 0,
52            pending_comments: Vec::new(),
53            case_insensitive: false,
54            rule_case_insensitive: false,
55        }
56    }
57
58    fn parse_grammar(&mut self) -> Result<Grammar, GrammarImportError> {
59        let mut grammar = Grammar::new().with_source_format(FORMAT);
60        let mut rules = Vec::new();
61        while !self.is_end() {
62            self.collect_comments();
63            if self.is_end() {
64                break;
65            }
66            if self.parse_header()? || self.parse_skipped_directive()? {
67                continue;
68            }
69            rules.push(self.parse_rule()?);
70        }
71        for rule in apply_retypes(rules) {
72            grammar.add_rule(rule);
73        }
74
75        let Some(start) = grammar
76            .rules()
77            .iter()
78            .find(|rule| rule.kind() == RuleKind::Normal)
79            .or_else(|| grammar.rules().first())
80            .map(|rule| rule.name.clone())
81        else {
82            return Err(parse_error(FORMAT, "ANTLR grammar does not contain rules"));
83        };
84        grammar.set_start(start);
85        Ok(grammar)
86    }
87
88    fn parse_header(&mut self) -> Result<bool, GrammarImportError> {
89        let matched = if self.check_keyword("grammar") {
90            self.advance();
91            true
92        } else if (self.check_keyword("lexer") || self.check_keyword("parser"))
93            && self.check_next_keyword("grammar")
94        {
95            self.advance();
96            self.advance();
97            true
98        } else {
99            false
100        };
101
102        if !matched {
103            return Ok(false);
104        }
105        self.expect_ident("grammar name")?;
106        self.expect_semicolon()?;
107        self.pending_comments.clear();
108        self.case_insensitive = false;
109        Ok(true)
110    }
111
112    fn parse_skipped_directive(&mut self) -> Result<bool, GrammarImportError> {
113        if self.check_any_keyword(&["options", "tokens", "channels"]) {
114            let is_options = self.check_keyword("options");
115            self.advance();
116            if let Some(TokenKind::Action(options)) = self.peek_kind() {
117                if is_options {
118                    self.case_insensitive = parse_options(options)
119                        .iter()
120                        .rev()
121                        .find(|(key, _)| key == "caseInsensitive")
122                        .is_some_and(|(_, value)| value == "true");
123                }
124                self.advance();
125                self.try_consume_semicolon();
126                self.pending_comments.clear();
127                return Ok(true);
128            }
129            self.skip_until_semicolon()?;
130            self.pending_comments.clear();
131            return Ok(true);
132        }
133
134        if self.check_any_keyword(&["import", "mode"]) {
135            self.advance();
136            self.skip_until_semicolon()?;
137            self.pending_comments.clear();
138            return Ok(true);
139        }
140
141        Ok(false)
142    }
143
144    /// A rule and the token type its `-> type(NAME)` command gives it.
145    fn parse_rule(&mut self) -> Result<(GrammarRule, Option<String>), GrammarImportError> {
146        let comments = std::mem::take(&mut self.pending_comments);
147        let fragment = self.try_consume_keyword("fragment");
148        let name = self.expect_ident("rule name")?;
149        // A comment between the name and its ':' documents the rule too.
150        self.collect_comments();
151        let mut comments = comments;
152        comments.append(&mut self.pending_comments);
153        let mut notes = Vec::new();
154        self.parse_rule_prelude(&mut notes)?;
155        self.expect_colon()?;
156
157        let alternatives = self.parse_alternatives(&mut notes)?;
158        let expr = precedence_climbing(&name, alternatives);
159        let command = if self.try_consume_arrow() {
160            Some(self.parse_lexer_command()?)
161        } else {
162            None
163        };
164        self.expect_semicolon()?;
165
166        let kind = if fragment {
167            RuleKind::Silent
168        } else if name.chars().next().is_some_and(char::is_uppercase) {
169            RuleKind::Token
170        } else {
171            RuleKind::Normal
172        };
173
174        let channel = command.as_deref().and_then(command_channel);
175        let retype = if channel.is_none() {
176            command.as_deref().and_then(command_type)
177        } else {
178            None
179        };
180        let mut rule = GrammarRule::new(name, expr).with_kind(kind);
181        if channel.is_some() {
182            rule = rule.with_attributes(RuleAttributes {
183                channel,
184                ..RuleAttributes::default()
185            });
186        }
187        if let Some(doc) = rule_doc(comments, notes, command) {
188            rule = rule.with_doc(doc);
189        }
190        Ok((rule, retype))
191    }
192
193    // `returns [...]`, `throws ...` and `locals [...]` declare target-language
194    // values, which join the rule's doc like actions. `options {...}` may set
195    // `caseInsensitive` for the rule; other options join the doc.
196    fn parse_rule_prelude(&mut self, notes: &mut Vec<String>) -> Result<(), GrammarImportError> {
197        self.rule_case_insensitive = self.case_insensitive;
198        if matches!(self.peek_kind(), Some(TokenKind::CharSet(_))) {
199            return Err(unsupported_error(FORMAT, "rule arguments"));
200        }
201        while !self.check_colon() {
202            if (self.check_keyword("returns") || self.check_keyword("locals"))
203                && let Some(TokenKind::CharSet(text)) = self.peek_next_kind()
204            {
205                let note = format!(
206                    "dropped {} [{text}]",
207                    self.peek().map(Token::text).unwrap_or_default()
208                );
209                self.advance();
210                self.advance();
211                notes.push(note);
212            } else if self.check_keyword("throws") {
213                self.advance();
214                let mut names = vec![self.expect_ident("exception name")?];
215                while matches!(self.peek_kind(), Some(TokenKind::Comma)) {
216                    self.advance();
217                    names.push(self.expect_ident("exception name")?);
218                }
219                notes.push(format!("dropped throws {}", names.join(", ")));
220            } else if self.check_keyword("options")
221                && let Some(TokenKind::Action(text)) = self.peek_next_kind()
222            {
223                let options = parse_options(text);
224                self.advance();
225                self.advance();
226                for (key, value) in options {
227                    if key == "caseInsensitive" {
228                        self.rule_case_insensitive = value == "true";
229                    } else {
230                        notes.push(format!("rule option {key}={value}"));
231                    }
232                }
233            } else {
234                return Err(self.expected("':' before rule body"));
235            }
236            self.skip_inline_comments();
237        }
238        Ok(())
239    }
240
241    fn parse_choice(&mut self, notes: &mut Vec<String>) -> Result<GrammarExpr, GrammarImportError> {
242        let mut alternatives = Vec::new();
243        for (expr, _) in self.parse_alternatives(notes)? {
244            push_choice_alternative(&mut alternatives, expr);
245        }
246        Ok(finish_choice(alternatives))
247    }
248
249    fn parse_alternatives(
250        &mut self,
251        notes: &mut Vec<String>,
252    ) -> Result<Vec<(GrammarExpr, &'static str)>, GrammarImportError> {
253        let mut alternatives = vec![self.parse_alternative(notes)?];
254        while self.try_consume_pipe() {
255            alternatives.push(self.parse_alternative(notes)?);
256        }
257        Ok(alternatives)
258    }
259
260    // An alternative with its `<key = value, ...>` options and its `# Label`,
261    // which names the context class ANTLR generates for it, not syntax: the
262    // label joins the rule's doc. `assoc` is kept for the precedence of a
263    // left-recursive rule; any other option joins the doc.
264    fn parse_alternative(
265        &mut self,
266        notes: &mut Vec<String>,
267    ) -> Result<(GrammarExpr, &'static str), GrammarImportError> {
268        self.skip_inline_comments();
269        let options = if matches!(self.peek_kind(), Some(TokenKind::LAngle)) {
270            self.advance();
271            self.parse_element_options()?
272        } else {
273            Vec::new()
274        };
275        let mut assoc = None;
276        for (key, value) in options {
277            if key == "assoc" {
278                assoc = Some(value);
279            } else {
280                notes.push(format!("alternative option {key}={value}"));
281            }
282        }
283        let sequence = self.parse_sequence(notes)?;
284        if matches!(self.peek_kind(), Some(TokenKind::Hash)) {
285            self.advance();
286            let label = self.expect_ident("alternative label")?;
287            notes.push(format!("alternative {label}"));
288            self.skip_inline_comments();
289        }
290        let associativity = match assoc.as_deref() {
291            None | Some("left") => "left",
292            Some("right") => "right",
293            Some(other) => {
294                return Err(unsupported_error(FORMAT, format!("associativity {other}")));
295            }
296        };
297        Ok((sequence, associativity))
298    }
299
300    fn parse_element_options(&mut self) -> Result<Vec<(String, String)>, GrammarImportError> {
301        let mut options: Vec<(String, String)> = Vec::new();
302        loop {
303            let key = self.expect_ident("element option")?;
304            let mut value = "true".to_owned();
305            if matches!(self.peek_kind(), Some(TokenKind::Equal)) {
306                self.advance();
307                value = match self.peek_kind() {
308                    Some(TokenKind::Ident(text) | TokenKind::String(text)) => text.clone(),
309                    _ => return Err(self.expected("element option value")),
310                };
311                self.advance();
312            }
313            options.retain(|(existing, _)| *existing != key);
314            options.push((key, value));
315            if matches!(self.peek_kind(), Some(TokenKind::Comma)) {
316                self.advance();
317            } else {
318                break;
319            }
320        }
321        if !matches!(self.peek_kind(), Some(TokenKind::RAngle)) {
322            return Err(self.expected("'>'"));
323        }
324        self.advance();
325        Ok(options)
326    }
327
328    fn parse_sequence(
329        &mut self,
330        notes: &mut Vec<String>,
331    ) -> Result<GrammarExpr, GrammarImportError> {
332        let mut items = Vec::new();
333        loop {
334            self.skip_inline_comments();
335            if self.is_sequence_end() {
336                break;
337            }
338            push_sequence_item(&mut items, self.parse_element(notes)?);
339        }
340        Ok(finish_sequence(items))
341    }
342
343    fn parse_element(
344        &mut self,
345        notes: &mut Vec<String>,
346    ) -> Result<GrammarExpr, GrammarImportError> {
347        self.skip_inline_comments();
348        if matches!(self.peek_kind(), Some(TokenKind::Action(_))) {
349            return Ok(self.parse_action(notes));
350        }
351
352        if let Some(label) = self.label_ahead() {
353            self.advance();
354            self.advance();
355            let expr = self.parse_prefixed(notes)?;
356            return Ok(GrammarExpr::capture(label, expr));
357        }
358
359        self.parse_prefixed(notes)
360    }
361
362    fn parse_prefixed(
363        &mut self,
364        notes: &mut Vec<String>,
365    ) -> Result<GrammarExpr, GrammarImportError> {
366        self.skip_inline_comments();
367        let expr = if self.try_consume_tilde() {
368            negate_expr(self.parse_atom(notes)?)
369        } else {
370            self.parse_atom(notes)?
371        };
372        self.parse_suffixes(expr)
373    }
374
375    fn parse_atom(&mut self, notes: &mut Vec<String>) -> Result<GrammarExpr, GrammarImportError> {
376        self.skip_inline_comments();
377        let Some(token) = self.peek().cloned() else {
378            return Err(self.expected("expression element"));
379        };
380
381        match token.kind {
382            TokenKind::Ident(name) => {
383                self.advance();
384                // ANTLR reserves EOF for the end of the input.
385                if name == "EOF" {
386                    return Ok(GrammarExpr::Not(Box::new(GrammarExpr::AnyChar)));
387                }
388                Ok(GrammarExpr::NonTerminal(name))
389            }
390            TokenKind::String(value) => {
391                self.advance();
392                if self.try_consume_range() {
393                    let end = self.expect_string("range end")?;
394                    let start = single_char(&value, "range start", token.offset)?;
395                    let end = single_char(&end, "range end", token.offset)?;
396                    if start > end {
397                        return Err(error_at(token.offset, "literal range start exceeds end"));
398                    }
399                    if self.rule_case_insensitive {
400                        let items = with_case_variants(&[UnicodeClassItem::Range(start, end)]);
401                        if items.len() > 1 {
402                            return Ok(class_expression(false, items));
403                        }
404                    }
405                    Ok(GrammarExpr::CharRange(start, end))
406                } else if self.rule_case_insensitive && has_case(&value) {
407                    Ok(GrammarExpr::TerminalInsensitive(value))
408                } else {
409                    Ok(GrammarExpr::Terminal(value))
410                }
411            }
412            TokenKind::CharSet(content) => {
413                self.advance();
414                lower_char_set(&content, token.offset, self.rule_case_insensitive)
415            }
416            TokenKind::Dot => {
417                self.advance();
418                Ok(GrammarExpr::AnyChar)
419            }
420            TokenKind::LParen => {
421                self.advance();
422                let expr = self.parse_choice(notes)?;
423                self.expect_rparen()?;
424                Ok(expr)
425            }
426            TokenKind::Action(_) => Ok(self.parse_action(notes)),
427            _ => Err(self.expected("expression element")),
428        }
429    }
430
431    fn parse_action(&mut self, notes: &mut Vec<String>) -> GrammarExpr {
432        self.advance();
433        if self.try_consume_question() {
434            notes.push("dropped predicate".to_string());
435        } else {
436            notes.push("dropped action".to_string());
437        }
438        GrammarExpr::Empty
439    }
440
441    fn parse_suffixes(&mut self, mut expr: GrammarExpr) -> Result<GrammarExpr, GrammarImportError> {
442        loop {
443            let Some(suffix) = self.consume_suffix() else {
444                return Ok(expr);
445            };
446            expr = match suffix {
447                Suffix::Optional => GrammarExpr::optional(expr),
448                Suffix::ZeroOrMore => GrammarExpr::zero_or_more(expr),
449                Suffix::OneOrMore => GrammarExpr::one_or_more(expr),
450            };
451            if self.try_consume_question() {
452                expr = GrammarExpr::capture("non_greedy", expr);
453            }
454        }
455    }
456
457    fn parse_lexer_command(&mut self) -> Result<String, GrammarImportError> {
458        let mut tokens = Vec::new();
459        while !self.is_end() && !self.check_semicolon() {
460            if matches!(self.peek_kind(), Some(TokenKind::Comment(_))) {
461                self.advance();
462            } else {
463                tokens.push(self.advance().clone());
464            }
465        }
466        if tokens.is_empty() {
467            return Err(self.expected("lexer command"));
468        }
469        Ok(format_command(&tokens))
470    }
471
472    fn skip_until_semicolon(&mut self) -> Result<(), GrammarImportError> {
473        while !self.is_end() {
474            if self.try_consume_semicolon() {
475                return Ok(());
476            }
477            self.advance();
478        }
479        Err(self.expected("';' after directive"))
480    }
481
482    fn collect_comments(&mut self) {
483        while let Some(TokenKind::Comment(comment)) = self.peek_kind() {
484            self.pending_comments.push(comment.clone());
485            self.advance();
486        }
487    }
488
489    fn skip_inline_comments(&mut self) {
490        while matches!(self.peek_kind(), Some(TokenKind::Comment(_))) {
491            self.advance();
492        }
493    }
494
495    fn label_ahead(&self) -> Option<String> {
496        let label = self.peek()?.ident()?;
497        if matches!(
498            self.tokens.get(self.cursor + 1).map(|token| &token.kind),
499            Some(TokenKind::Equal | TokenKind::PlusEqual)
500        ) {
501            Some(label.to_string())
502        } else {
503            None
504        }
505    }
506
507    fn consume_suffix(&mut self) -> Option<Suffix> {
508        match self.peek_kind() {
509            Some(TokenKind::Question) => {
510                self.advance();
511                Some(Suffix::Optional)
512            }
513            Some(TokenKind::Star) => {
514                self.advance();
515                Some(Suffix::ZeroOrMore)
516            }
517            Some(TokenKind::Plus) => {
518                self.advance();
519                Some(Suffix::OneOrMore)
520            }
521            _ => None,
522        }
523    }
524
525    fn is_sequence_end(&self) -> bool {
526        self.is_end()
527            || matches!(
528                self.peek_kind(),
529                Some(
530                    TokenKind::Pipe
531                        | TokenKind::Semicolon
532                        | TokenKind::Arrow
533                        | TokenKind::RParen
534                        | TokenKind::Hash
535                )
536            )
537    }
538
539    fn expect_ident(&mut self, role: &str) -> Result<String, GrammarImportError> {
540        let Some(token) = self.peek().cloned() else {
541            return Err(self.expected(role));
542        };
543        match token.kind {
544            TokenKind::Ident(value) => {
545                self.advance();
546                Ok(value)
547            }
548            _ => Err(self.expected(role)),
549        }
550    }
551
552    fn expect_string(&mut self, role: &str) -> Result<String, GrammarImportError> {
553        let Some(token) = self.peek().cloned() else {
554            return Err(self.expected(role));
555        };
556        match token.kind {
557            TokenKind::String(value) => {
558                self.advance();
559                Ok(value)
560            }
561            _ => Err(self.expected(role)),
562        }
563    }
564
565    fn expect_colon(&mut self) -> Result<(), GrammarImportError> {
566        if self.check_colon() {
567            self.advance();
568            Ok(())
569        } else {
570            Err(self.expected("':'"))
571        }
572    }
573
574    fn expect_semicolon(&mut self) -> Result<(), GrammarImportError> {
575        if self.check_semicolon() {
576            self.advance();
577            Ok(())
578        } else {
579            Err(self.expected("';'"))
580        }
581    }
582
583    fn expect_rparen(&mut self) -> Result<(), GrammarImportError> {
584        if matches!(self.peek_kind(), Some(TokenKind::RParen)) {
585            self.advance();
586            Ok(())
587        } else {
588            Err(self.expected("')'"))
589        }
590    }
591
592    fn expected(&self, expected: &str) -> GrammarImportError {
593        let offset = self.peek().map_or(0, |token| token.offset);
594        error_at(offset, format!("expected {expected}"))
595    }
596
597    fn try_consume_keyword(&mut self, keyword: &str) -> bool {
598        if self.check_keyword(keyword) {
599            self.advance();
600            true
601        } else {
602            false
603        }
604    }
605
606    fn try_consume_arrow(&mut self) -> bool {
607        if matches!(self.peek_kind(), Some(TokenKind::Arrow)) {
608            self.advance();
609            true
610        } else {
611            false
612        }
613    }
614
615    fn try_consume_pipe(&mut self) -> bool {
616        if matches!(self.peek_kind(), Some(TokenKind::Pipe)) {
617            self.advance();
618            true
619        } else {
620            false
621        }
622    }
623
624    fn try_consume_question(&mut self) -> bool {
625        if matches!(self.peek_kind(), Some(TokenKind::Question)) {
626            self.advance();
627            true
628        } else {
629            false
630        }
631    }
632
633    fn try_consume_tilde(&mut self) -> bool {
634        if matches!(self.peek_kind(), Some(TokenKind::Tilde)) {
635            self.advance();
636            true
637        } else {
638            false
639        }
640    }
641
642    fn try_consume_range(&mut self) -> bool {
643        if matches!(self.peek_kind(), Some(TokenKind::Range)) {
644            self.advance();
645            true
646        } else {
647            false
648        }
649    }
650
651    fn try_consume_semicolon(&mut self) -> bool {
652        if self.check_semicolon() {
653            self.advance();
654            true
655        } else {
656            false
657        }
658    }
659
660    fn check_keyword(&self, keyword: &str) -> bool {
661        self.peek().is_some_and(|token| token.is_keyword(keyword))
662    }
663
664    fn check_next_keyword(&self, keyword: &str) -> bool {
665        self.tokens
666            .get(self.cursor + 1)
667            .is_some_and(|token| token.is_keyword(keyword))
668    }
669
670    fn check_any_keyword(&self, keywords: &[&str]) -> bool {
671        self.peek()
672            .and_then(Token::ident)
673            .is_some_and(|value| keywords.contains(&value))
674    }
675
676    fn check_colon(&self) -> bool {
677        matches!(self.peek_kind(), Some(TokenKind::Colon))
678    }
679
680    fn check_semicolon(&self) -> bool {
681        matches!(self.peek_kind(), Some(TokenKind::Semicolon))
682    }
683
684    const fn is_end(&self) -> bool {
685        self.cursor >= self.tokens.len()
686    }
687
688    fn peek(&self) -> Option<&Token> {
689        self.tokens.get(self.cursor)
690    }
691
692    fn peek_kind(&self) -> Option<&TokenKind> {
693        self.peek().map(|token| &token.kind)
694    }
695
696    fn peek_next_kind(&self) -> Option<&TokenKind> {
697        self.tokens.get(self.cursor + 1).map(|token| &token.kind)
698    }
699
700    fn advance(&mut self) -> &Token {
701        let token = &self.tokens[self.cursor];
702        self.cursor += 1;
703        token
704    }
705}
706
707#[derive(Clone, Copy, Debug, PartialEq, Eq)]
708enum Suffix {
709    Optional,
710    ZeroOrMore,
711    OneOrMore,
712}
713
714/// The `key = value;` pairs of an `options {...}` block.
715fn parse_options(text: &str) -> Vec<(String, String)> {
716    // Like the JavaScript importer, a pair counts only when `;` ends it.
717    let pairs = text.rsplit_once(';').map_or("", |(pairs, _)| pairs);
718    pairs
719        .split(';')
720        .filter_map(|part| {
721            let (key, value) = part.split_once('=')?;
722            let key = key.trim_end();
723            let start = key
724                .rfind(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
725                .map_or(0, |index| index + 1);
726            let key = &key[start..];
727            is_identifier(key).then(|| (key.to_owned(), value.trim().to_owned()))
728        })
729        .collect()
730}
731
732fn is_identifier(text: &str) -> bool {
733    let mut chars = text.chars();
734    chars
735        .next()
736        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
737        && chars.all(|next| next.is_ascii_alphanumeric() || next == '_')
738}
739
740// A numbered channel such as `channel(2)`; channel 0 is the default one.
741fn is_channel_number(text: &str) -> bool {
742    !text.starts_with('0') && !text.is_empty() && text.chars().all(|digit| digit.is_ascii_digit())
743}
744
745// In a left-recursive rule, ANTLR gives each alternative that starts or ends
746// with the rule a precedence: earlier alternatives bind tighter, and
747// `<assoc=right>` makes one right-associative.
748fn precedence_climbing(name: &str, alternatives: Vec<(GrammarExpr, &'static str)>) -> GrammarExpr {
749    let left_recursive = alternatives
750        .iter()
751        .any(|(expr, _)| edge_ref(expr, Edge::Left) == Some(name));
752    let count = alternatives.len();
753    let mut choice = Vec::new();
754    for (index, (expr, associativity)) in alternatives.into_iter().enumerate() {
755        let climbs = left_recursive
756            && (edge_ref(&expr, Edge::Left) == Some(name)
757                || edge_ref(&expr, Edge::Right) == Some(name));
758        let expr = if climbs {
759            let level = i64::try_from(count - index).unwrap_or(i64::MAX);
760            GrammarExpr::feature(FeatureExpr::Form(FeatureForm::new(
761                "precedence",
762                vec![
763                    FieldValue::Integer(level),
764                    FieldValue::Word(associativity.into()),
765                    FieldValue::Expression(expr),
766                ],
767            )))
768        } else {
769            expr
770        };
771        push_choice_alternative(&mut choice, expr);
772    }
773    finish_choice(choice)
774}
775
776#[derive(Clone, Copy)]
777enum Edge {
778    Left,
779    Right,
780}
781
782fn edge_ref(expr: &GrammarExpr, edge: Edge) -> Option<&str> {
783    match expr {
784        GrammarExpr::NonTerminal(name) => Some(name),
785        GrammarExpr::Capture { expr, .. } => edge_ref(expr, edge),
786        GrammarExpr::Sequence(items) => match edge {
787            Edge::Left => items.first(),
788            Edge::Right => items.last(),
789        }
790        .and_then(|item| edge_ref(item, edge)),
791        _ => None,
792    }
793}
794
795fn finish_sequence(items: Vec<GrammarExpr>) -> GrammarExpr {
796    match items.len() {
797        0 => GrammarExpr::Empty,
798        1 => items.into_iter().next().expect("one sequence item exists"),
799        _ => GrammarExpr::Sequence(items),
800    }
801}
802
803fn push_sequence_item(items: &mut Vec<GrammarExpr>, item: GrammarExpr) {
804    match item {
805        GrammarExpr::Empty => {}
806        GrammarExpr::Sequence(nested) => {
807            for item in nested {
808                push_sequence_item(items, item);
809            }
810        }
811        item => items.push(item),
812    }
813}
814
815fn finish_choice(alternatives: Vec<GrammarExpr>) -> GrammarExpr {
816    if alternatives.iter().all(|expr| expr == &GrammarExpr::Empty) {
817        return GrammarExpr::Empty;
818    }
819    match alternatives.len() {
820        0 => GrammarExpr::Empty,
821        1 => alternatives
822            .into_iter()
823            .next()
824            .expect("one alternative exists"),
825        _ => GrammarExpr::Choice {
826            ordered: false,
827            alternatives,
828        },
829    }
830}
831
832fn push_choice_alternative(alternatives: &mut Vec<GrammarExpr>, alternative: GrammarExpr) {
833    match alternative {
834        GrammarExpr::Choice {
835            ordered: false,
836            alternatives: nested,
837        } => alternatives.extend(nested),
838        alternative => alternatives.push(alternative),
839    }
840}
841
842fn single_char(value: &str, role: &str, offset: usize) -> Result<char, GrammarImportError> {
843    let mut chars = value.chars();
844    let Some(character) = chars.next() else {
845        return Err(error_at(
846            offset,
847            format!("{role} must contain one character"),
848        ));
849    };
850    if chars.next().is_some() {
851        return Err(error_at(
852            offset,
853            format!("{role} {value:?} must contain one character"),
854        ));
855    }
856    Ok(character)
857}
858
859fn rule_doc(comments: Vec<String>, notes: Vec<String>, command: Option<String>) -> Option<String> {
860    let mut parts = Vec::new();
861    parts.extend(comments.into_iter().filter(|comment| !comment.is_empty()));
862    parts.extend(notes);
863    parts.extend(command);
864    if parts.is_empty() {
865        None
866    } else {
867        Some(parts.join("; "))
868    }
869}
870
871fn format_command(tokens: &[Token]) -> String {
872    let mut output = "->".to_string();
873    for token in tokens {
874        match token.kind {
875            TokenKind::LParen => output.push('('),
876            TokenKind::RParen => output.push(')'),
877            TokenKind::Comma => output.push_str(", "),
878            _ => {
879                if output == "->" || (!output.ends_with('(') && !output.ends_with(", ")) {
880                    output.push(' ');
881                }
882                output.push_str(&token.text());
883            }
884        }
885    }
886    output
887}
888
889fn error_at(offset: usize, message: impl Into<String>) -> GrammarImportError {
890    parse_error(FORMAT, format!("{} at byte {offset}", message.into()))
891}