meta_language/grammar/import/
pest.rs1use 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
12pub 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}