1use super::common::{
5 Cursor, canonical_repeat, choice, collect_references, decimal, default_escapes,
6 grammar_from_rules, is_line_terminator, is_space, json_quote, sequence, split_lines,
7 strip_line_comment, trim,
8};
9use super::{GrammarImportError, parse_error, unsupported_error};
10use crate::grammar::{CharClassItem, Grammar, GrammarExpr, GrammarFormat, GrammarRule};
11
12const FORMAT: GrammarFormat = GrammarFormat::Abnf;
13
14pub fn import_abnf(text: &str) -> Result<Grammar, GrammarImportError> {
26 let mut rules: Vec<GrammarRule> = Vec::new();
27 for line in logical_lines(text)? {
28 let Some((name, incremental, rhs)) = split_rule(&line) else {
29 return Err(parse_error(
30 FORMAT,
31 format!("invalid rule {}", json_quote(&line)),
32 ));
33 };
34 let expr = AbnfParser::new(rhs).parse()?;
35 let existing = rules
36 .iter_mut()
37 .find(|rule| rule.name.eq_ignore_ascii_case(name));
38 match (incremental, existing) {
39 (true, Some(existing)) => {
40 let previous = std::mem::replace(&mut existing.expr, GrammarExpr::Empty);
41 existing.expr = choice(vec![previous, expr], false);
42 }
43 (true, None) => {
44 return Err(parse_error(
45 FORMAT,
46 format!("incremental alternative for undefined rule {name}"),
47 ));
48 }
49 (false, Some(_)) => {
50 return Err(parse_error(FORMAT, format!("duplicate rule {name}")));
51 }
52 (false, None) => rules.push(GrammarRule::new(name, expr)),
53 }
54 }
55
56 canonicalize_references(&mut rules);
57 inject_core_rules(&mut rules);
58 grammar_from_rules(FORMAT, rules, &[])
59}
60
61fn logical_lines(source: &str) -> Result<Vec<String>, GrammarImportError> {
64 let mut lines: Vec<String> = Vec::new();
65 for physical in split_lines(source) {
66 let line = strip_line_comment(physical, ';', true).trim_end_matches(is_space);
67 if line.is_empty() {
68 continue;
69 }
70 if line.starts_with([' ', '\t']) {
71 let Some(previous) = lines.last_mut() else {
72 return Err(parse_error(FORMAT, "continuation without a rule"));
73 };
74 previous.push(' ');
75 previous.push_str(trim(line));
76 } else {
77 lines.push(line.to_string());
78 }
79 }
80 Ok(lines)
81}
82
83fn split_rule(line: &str) -> Option<(&str, bool, &str)> {
86 if !line.starts_with(|character: char| character.is_ascii_alphabetic()) {
87 return None;
88 }
89 let name_end = line
90 .find(|character: char| !(character.is_ascii_alphanumeric() || character == '-'))
91 .unwrap_or(line.len());
92 let (name, rest) = line.split_at(name_end);
93 let rest = rest.trim_start_matches(is_space).strip_prefix('=')?;
94 let (incremental, rest) = rest
95 .strip_prefix('/')
96 .map_or((false, rest), |rest| (true, rest));
97 let rhs = rest.trim_start_matches(is_space);
98 (!rhs.contains(is_line_terminator)).then_some((name, incremental, rhs))
99}
100
101struct AbnfParser<'source> {
102 cursor: Cursor<'source>,
103}
104
105impl<'source> AbnfParser<'source> {
106 const fn new(source: &'source str) -> Self {
107 Self {
108 cursor: Cursor::new(source, FORMAT),
109 }
110 }
111
112 fn parse(mut self) -> Result<GrammarExpr, GrammarImportError> {
113 let expr = self.alternation(None)?;
114 self.cursor.skip_space()?;
115 if !self.cursor.eof() {
116 return Err(self.cursor.error("unexpected ABNF input"));
117 }
118 Ok(expr)
119 }
120
121 fn alternation(&mut self, close: Option<char>) -> Result<GrammarExpr, GrammarImportError> {
122 let mut alternatives = vec![self.concatenation(close)?];
123 while self.cursor.try_consume("/")? {
124 alternatives.push(self.concatenation(close)?);
125 }
126 Ok(choice(alternatives, false))
127 }
128
129 fn concatenation(&mut self, close: Option<char>) -> Result<GrammarExpr, GrammarImportError> {
130 let mut items = Vec::new();
131 loop {
132 self.cursor.skip_space()?;
133 let next = self.cursor.peek();
134 if next.is_none() || next == Some('/') || (close.is_some() && next == close) {
135 break;
136 }
137 items.push(self.repetition()?);
138 }
139 Ok(sequence(items))
140 }
141
142 fn repetition(&mut self) -> Result<GrammarExpr, GrammarImportError> {
145 self.cursor.skip_space()?;
146 let rest = self.cursor.rest();
147 let low = leading(rest, |character| character.is_ascii_digit());
148 let bounds = if let Some(after) = rest[low.len()..].strip_prefix('*') {
149 let high = leading(after, |character| character.is_ascii_digit());
150 self.cursor.advance(low.len() + 1 + high.len());
151 Some((decimal(low), (!high.is_empty()).then(|| decimal(high))))
152 } else if low.is_empty() {
153 None
154 } else {
155 self.cursor.advance(low.len());
156 Some((decimal(low), Some(decimal(low))))
157 };
158 let atom = self.atom()?;
159 match bounds {
160 Some((min, max)) => canonical_repeat(FORMAT, atom, min, max),
161 None => Ok(atom),
162 }
163 }
164
165 fn atom(&mut self) -> Result<GrammarExpr, GrammarImportError> {
166 self.cursor.skip_space()?;
167 if self.cursor.eof() {
168 return Err(self.cursor.error("expected ABNF element"));
169 }
170 if self.cursor.try_consume("(")? {
171 let expr = self.alternation(Some(')'))?;
172 self.cursor.consume(")")?;
173 return Ok(expr);
174 }
175 if self.cursor.try_consume("[")? {
176 let expr = self.alternation(Some(']'))?;
177 self.cursor.consume("]")?;
178 return Ok(GrammarExpr::optional(expr));
179 }
180 match self.cursor.peek() {
181 Some('"') => Ok(GrammarExpr::TerminalInsensitive(
182 self.cursor.quoted(Some(default_escapes))?,
183 )),
184 Some('<') => Err(unsupported_error(FORMAT, "prose-val")),
185 Some('%') => self.percent_value(),
186 _ => Ok(GrammarExpr::NonTerminal(self.cursor.identifier_with(
187 |character| character.is_ascii_alphabetic(),
188 |character| character.is_ascii_alphanumeric() || character == '-',
189 )?)),
190 }
191 }
192
193 fn percent_value(&mut self) -> Result<GrammarExpr, GrammarImportError> {
196 self.cursor.consume("%")?;
197 let kind = self.cursor.take().map(|kind| kind.to_ascii_lowercase());
198 if matches!(kind, Some('s' | 'i')) && self.cursor.peek() == Some('"') {
199 let value = self.cursor.quoted(Some(default_escapes))?;
200 return Ok(if kind == Some('s') {
201 GrammarExpr::Terminal(value)
202 } else {
203 GrammarExpr::TerminalInsensitive(value)
204 });
205 }
206 let radix = match kind {
207 Some('b') => 2,
208 Some('d') => 10,
209 Some('x') => 16,
210 _ => return Err(self.cursor.error("invalid percent value")),
211 };
212 let is_digit = |character: char| character.is_digit(radix);
213 let rest = self.cursor.rest();
214 let first = leading(rest, is_digit);
215 if first.is_empty() {
216 return Err(self.cursor.error("invalid numeric terminal"));
217 }
218 let after = &rest[first.len()..];
219 if let Some(end) = after
220 .strip_prefix('-')
221 .map(|after| leading(after, is_digit))
222 .filter(|end| !end.is_empty())
223 {
224 self.cursor.advance(first.len() + 1 + end.len());
225 return Ok(GrammarExpr::CharRange(
226 decode_code_point(first, radix)?,
227 decode_code_point(end, radix)?,
228 ));
229 }
230 let mut values = vec![first];
231 let mut length = first.len();
232 while let Some(value) = rest[length..]
233 .strip_prefix('.')
234 .map(|after| leading(after, is_digit))
235 .filter(|value| !value.is_empty())
236 {
237 values.push(value);
238 length += 1 + value.len();
239 }
240 self.cursor.advance(length);
241 let mut literal = String::new();
242 for value in values {
243 literal.push(decode_code_point(value, radix)?);
244 }
245 Ok(GrammarExpr::Terminal(literal))
246 }
247}
248
249fn leading(text: &str, predicate: impl Fn(char) -> bool) -> &str {
251 let end = text
252 .find(|character: char| !predicate(character))
253 .unwrap_or(text.len());
254 &text[..end]
255}
256
257fn decode_code_point(value: &str, radix: u32) -> Result<char, GrammarImportError> {
258 u32::from_str_radix(value, radix)
259 .ok()
260 .and_then(char::from_u32)
261 .ok_or_else(|| unsupported_error(FORMAT, format!("numeric terminal value {value}")))
262}
263
264fn canonicalize_references(rules: &mut [GrammarRule]) {
267 let names = rules
268 .iter()
269 .map(|rule| rule.name.clone())
270 .collect::<Vec<_>>();
271 for rule in rules {
272 for_each_reference(&mut rule.expr, &mut |name| {
273 if let Some(canonical) = names
274 .iter()
275 .find(|candidate| candidate.eq_ignore_ascii_case(name))
276 {
277 name.clone_from(canonical);
278 }
279 });
280 }
281}
282
283fn for_each_reference(expr: &mut GrammarExpr, visit: &mut impl FnMut(&mut String)) {
284 match expr {
285 GrammarExpr::NonTerminal(name) => visit(name),
286 GrammarExpr::Choice {
287 alternatives: items,
288 ..
289 }
290 | GrammarExpr::Sequence(items) => {
291 for item in items {
292 for_each_reference(item, visit);
293 }
294 }
295 GrammarExpr::Optional(expr)
296 | GrammarExpr::ZeroOrMore(expr)
297 | GrammarExpr::OneOrMore(expr)
298 | GrammarExpr::And(expr)
299 | GrammarExpr::Not(expr)
300 | GrammarExpr::Capture { expr, .. }
301 | GrammarExpr::Repeat { expr, .. } => for_each_reference(expr, visit),
302 GrammarExpr::Feature(feature) => {
303 feature.map_expressions(&mut |inner| for_each_reference(inner, visit));
304 }
305 GrammarExpr::Empty
306 | GrammarExpr::Terminal(_)
307 | GrammarExpr::TerminalInsensitive(_)
308 | GrammarExpr::CharRange(_, _)
309 | GrammarExpr::CharClass { .. }
310 | GrammarExpr::AnyChar => {}
311 }
312}
313
314fn inject_core_rules(rules: &mut Vec<GrammarRule>) {
317 loop {
318 let mut referenced = Vec::new();
319 for rule in rules.iter() {
320 collect_references(&rule.expr, &mut referenced);
321 }
322 let mut added = false;
323 for name in referenced {
324 if rules.iter().any(|rule| rule.name == name) {
325 continue;
326 }
327 if let Some(expr) = core_expression(&name) {
328 rules.push(GrammarRule::new(name, expr));
329 added = true;
330 }
331 }
332 if !added {
333 return;
334 }
335 }
336}
337
338fn core_expression(name: &str) -> Option<GrammarExpr> {
339 let wsp =
340 || GrammarExpr::char_class(false, [CharClassItem::Char(' '), CharClassItem::Char('\t')]);
341 let terminal = |value: &str| GrammarExpr::Terminal(value.to_string());
342 Some(match name.to_ascii_uppercase().as_str() {
343 "ALPHA" => GrammarExpr::char_class(
344 false,
345 [
346 CharClassItem::Range('A', 'Z'),
347 CharClassItem::Range('a', 'z'),
348 ],
349 ),
350 "BIT" => GrammarExpr::CharRange('0', '1'),
351 "CHAR" => GrammarExpr::CharRange('\u{01}', '\u{7f}'),
352 "CR" => terminal("\r"),
353 "CRLF" => sequence(vec![terminal("\r"), terminal("\n")]),
354 "CTL" => choice(
355 vec![
356 GrammarExpr::CharRange('\u{00}', '\u{1f}'),
357 GrammarExpr::CharRange('\u{7f}', '\u{7f}'),
358 ],
359 false,
360 ),
361 "DIGIT" => GrammarExpr::CharRange('0', '9'),
362 "DQUOTE" => terminal("\""),
363 "HEXDIG" => GrammarExpr::char_class(
364 false,
365 [
366 CharClassItem::Range('0', '9'),
367 CharClassItem::Range('A', 'F'),
368 CharClassItem::Range('a', 'f'),
369 ],
370 ),
371 "HTAB" => terminal("\t"),
372 "LF" => terminal("\n"),
373 "OCTET" => GrammarExpr::CharRange('\u{00}', '\u{ff}'),
374 "SP" => terminal(" "),
375 "VCHAR" => GrammarExpr::CharRange('!', '~'),
376 "WSP" => wsp(),
377 "LWSP" => GrammarExpr::zero_or_more(choice(
378 vec![wsp(), sequence(vec![terminal("\r\n"), wsp()])],
379 false,
380 )),
381 _ => return None,
382 })
383}