Skip to main content

meta_language/grammar/import/
pest.rs

1use std::collections::BTreeSet;
2
3use pest_meta::ast::{Expr as PestExpr, Rule as PestRule, RuleType as PestRuleType};
4use pest_meta::{
5    parser::{self, Rule as PestParserRule},
6    validator,
7};
8
9use super::{parse_error, unsupported_error, GrammarImportError};
10use crate::grammar::{Grammar, GrammarExpr, GrammarFormat, GrammarRule, RuleKind};
11
12/// Parses PEG `.pest` grammar text into the grammar IR.
13///
14/// # Errors
15///
16/// Returns [`GrammarImportError`] when the pest grammar cannot be parsed or
17/// validated, when a parsed construct cannot be represented in the grammar IR,
18/// or when a non-terminal reference does not resolve to a local rule or pest
19/// built-in.
20pub fn import_pest(text: &str) -> Result<Grammar, GrammarImportError> {
21    let pairs = parser::parse(PestParserRule::grammar_rules, text)
22        .map_err(|error| parse_error(GrammarFormat::Peg, error.to_string()))?;
23    let used_builtins = validator::validate_pairs(pairs.clone())
24        .map_err(|errors| parse_error(GrammarFormat::Peg, format_errors(errors)))?;
25    let parsed = parser::consume_rules(pairs)
26        .map_err(|errors| parse_error(GrammarFormat::Peg, format_errors(errors)))?;
27    let grammar = lower_grammar(parsed)?;
28    validate_references(&grammar, &used_builtins)?;
29    Ok(grammar)
30}
31
32fn lower_grammar(parsed: Vec<PestRule>) -> Result<Grammar, GrammarImportError> {
33    let mut grammar = Grammar::new().with_source_format(GrammarFormat::Peg);
34    for rule in parsed {
35        grammar.add_rule(
36            GrammarRule::new(rule.name, lower_expr(rule.expr)?).with_kind(lower_rule_type(rule.ty)),
37        );
38    }
39    Ok(grammar)
40}
41
42const fn lower_rule_type(rule_type: PestRuleType) -> RuleKind {
43    match rule_type {
44        PestRuleType::Normal | PestRuleType::NonAtomic => RuleKind::Normal,
45        PestRuleType::Silent => RuleKind::Silent,
46        PestRuleType::Atomic | PestRuleType::CompoundAtomic => RuleKind::Atomic,
47    }
48}
49
50fn lower_expr(expr: PestExpr) -> Result<GrammarExpr, GrammarImportError> {
51    match expr {
52        PestExpr::Str(value) => Ok(GrammarExpr::Terminal(value)),
53        PestExpr::Insens(value) => Ok(GrammarExpr::TerminalInsensitive(value)),
54        PestExpr::Range(start, end) => Ok(GrammarExpr::CharRange(
55            single_char(&start, "range start")?,
56            single_char(&end, "range end")?,
57        )),
58        PestExpr::Ident(name) if name == "ANY" => Ok(GrammarExpr::AnyChar),
59        PestExpr::Ident(name) => Ok(GrammarExpr::NonTerminal(name)),
60        PestExpr::PeekSlice(_, _) => Err(unsupported_error(GrammarFormat::Peg, "PeekSlice")),
61        PestExpr::PosPred(inner) => lower_expr(*inner).map(GrammarExpr::and),
62        PestExpr::NegPred(inner) => lower_expr(*inner).map(GrammarExpr::not),
63        PestExpr::Seq(left, right) => lower_sequence([lower_expr(*left), lower_expr(*right)]),
64        PestExpr::Choice(left, right) => lower_choice([lower_expr(*left), lower_expr(*right)]),
65        PestExpr::Opt(inner) => lower_expr(*inner).map(GrammarExpr::optional),
66        PestExpr::Rep(inner) => lower_expr(*inner).map(GrammarExpr::zero_or_more),
67        PestExpr::RepOnce(inner) => lower_expr(*inner).map(GrammarExpr::one_or_more),
68        PestExpr::RepExact(inner, count) => {
69            let count = repetition_count(count)?;
70            lower_expr(*inner).map(|expr| GrammarExpr::repeat(expr, count, Some(count)))
71        }
72        PestExpr::RepMin(inner, min) => {
73            let min = repetition_count(min)?;
74            lower_expr(*inner).map(|expr| GrammarExpr::repeat(expr, min, None))
75        }
76        PestExpr::RepMax(inner, max) => {
77            let max = repetition_count(max)?;
78            lower_expr(*inner).map(|expr| GrammarExpr::repeat(expr, 0, Some(max)))
79        }
80        PestExpr::RepMinMax(inner, min, max) => {
81            let min = repetition_count(min)?;
82            let max = repetition_count(max)?;
83            lower_expr(*inner).map(|expr| GrammarExpr::repeat(expr, min, Some(max)))
84        }
85        PestExpr::Skip(_) => Err(unsupported_error(GrammarFormat::Peg, "Skip")),
86        PestExpr::Push(_) => Err(unsupported_error(GrammarFormat::Peg, "Push")),
87    }
88}
89
90fn lower_sequence<I>(items: I) -> Result<GrammarExpr, GrammarImportError>
91where
92    I: IntoIterator<Item = Result<GrammarExpr, GrammarImportError>>,
93{
94    let mut lowered = Vec::new();
95    for item in items {
96        push_sequence_item(&mut lowered, item?);
97    }
98
99    Ok(match lowered.len() {
100        0 => GrammarExpr::Empty,
101        1 => lowered.remove(0),
102        _ => GrammarExpr::Sequence(lowered),
103    })
104}
105
106fn push_sequence_item(items: &mut Vec<GrammarExpr>, item: GrammarExpr) {
107    match item {
108        GrammarExpr::Empty => {}
109        GrammarExpr::Sequence(nested) => {
110            for item in nested {
111                push_sequence_item(items, item);
112            }
113        }
114        item => items.push(item),
115    }
116}
117
118fn lower_choice<I>(alternatives: I) -> Result<GrammarExpr, GrammarImportError>
119where
120    I: IntoIterator<Item = Result<GrammarExpr, GrammarImportError>>,
121{
122    let mut lowered = Vec::new();
123    for alternative in alternatives {
124        push_choice_alternative(&mut lowered, alternative?);
125    }
126
127    Ok(match lowered.len() {
128        0 => GrammarExpr::Empty,
129        1 => lowered.remove(0),
130        _ => GrammarExpr::Choice {
131            ordered: true,
132            alternatives: lowered,
133        },
134    })
135}
136
137fn push_choice_alternative(alternatives: &mut Vec<GrammarExpr>, alternative: GrammarExpr) {
138    match alternative {
139        GrammarExpr::Choice {
140            ordered: true,
141            alternatives: nested,
142        } => alternatives.extend(nested),
143        alternative => alternatives.push(alternative),
144    }
145}
146
147fn single_char(value: &str, role: &str) -> Result<char, GrammarImportError> {
148    let mut chars = value.chars();
149    let Some(character) = chars.next() else {
150        return Err(parse_error(
151            GrammarFormat::Peg,
152            format!("{role} must contain exactly one character"),
153        ));
154    };
155    if chars.next().is_some() {
156        return Err(parse_error(
157            GrammarFormat::Peg,
158            format!("{role} {value:?} must contain exactly one character"),
159        ));
160    }
161    Ok(character)
162}
163
164fn repetition_count(count: u32) -> Result<usize, GrammarImportError> {
165    usize::try_from(count).map_err(|_| {
166        unsupported_error(
167            GrammarFormat::Peg,
168            format!("repetition count {count} exceeds usize"),
169        )
170    })
171}
172
173fn validate_references(
174    grammar: &Grammar,
175    used_builtins: &[&str],
176) -> Result<(), GrammarImportError> {
177    let used_builtins = used_builtins.iter().copied().collect::<BTreeSet<_>>();
178    if let Some(name) = grammar
179        .undefined_nonterminals()
180        .into_iter()
181        .find(|name| !used_builtins.contains(name.as_str()))
182    {
183        return Err(parse_error(
184            GrammarFormat::Peg,
185            format!("undefined non-terminal {name}"),
186        ));
187    }
188    Ok(())
189}
190
191fn format_errors<E>(errors: impl IntoIterator<Item = E>) -> String
192where
193    E: ToString,
194{
195    errors
196        .into_iter()
197        .map(|error| error.to_string())
198        .collect::<Vec<_>>()
199        .join("\n\n")
200}