1use std::collections::{HashMap, HashSet};
26use std::error::Error;
27use std::fmt;
28
29use super::super::emit::{EmitReport, GrammarEmitError};
30use super::super::import::GrammarImportError;
31use super::super::round_trip::{GrammarEmitFn, GrammarImportFn, GrammarRoundTripError};
32use super::super::{Grammar, GrammarExpr, GrammarFormat, GrammarRule, RuleAttributes, RuleKind};
33use super::links::render_links_expression;
34use super::{grammar_emitter, grammar_importer, render_rule_fields};
35
36mod encoding;
37mod metadata;
38
39use encoding::{Construct, constructs_of, encode, kept_kinds, unsupported};
40use metadata::render_declarations_text;
41
42pub use encoding::MAX_LOWERED_CHARACTERS;
43
44pub use metadata::{
45 GrammarLoweringFailure, GrammarLoweringFailureKind, GrammarLoweringReport,
46 check_grammar_lowering, dropped_grammar_features, parse_lowering_metadata, reconstruct_grammar,
47 render_lowering_metadata,
48};
49
50pub const GRAMMAR_LOWERING_FORMATS: &[&str] = &[
52 "abnf",
53 "antlr",
54 "bnf",
55 "ebnf",
56 "gbnf",
57 "lark",
58 "pest",
59 "tree-sitter-json",
60];
61
62const HELPER_PREFIX: &str = "lowered";
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum GrammarLoweringStatus {
67 Exact,
69 Approximate,
71 Broken,
73}
74
75impl GrammarLoweringStatus {
76 #[must_use]
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::Exact => "exact",
81 Self::Approximate => "approximate",
82 Self::Broken => "broken",
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum GrammarLoweringEncoding {
90 Exact,
92 Approximate,
94}
95
96impl GrammarLoweringEncoding {
97 #[must_use]
99 pub const fn as_str(self) -> &'static str {
100 match self {
101 Self::Exact => "exact",
102 Self::Approximate => "approximate",
103 }
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
109pub enum GrammarLoweringStep {
110 Rename {
112 rule: String,
114 value: String,
116 },
117 Helper {
119 helper: String,
121 owner: String,
123 construct: String,
125 encoding: GrammarLoweringEncoding,
127 note: String,
129 original: GrammarExpr,
131 },
132 Kind {
134 rule: String,
136 kind: RuleKind,
138 },
139 Doc {
141 rule: String,
143 doc: String,
145 },
146 Declarations {
149 declarations: String,
152 },
153 Attributes {
156 rule: String,
158 attributes: String,
160 },
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
165pub struct GrammarLoweringMetadata {
166 pub format: String,
168 pub status: GrammarLoweringStatus,
170 pub source: Option<GrammarFormat>,
172 pub start: String,
174 pub order: Vec<String>,
176 pub steps: Vec<GrammarLoweringStep>,
178}
179
180#[derive(Clone, Debug, PartialEq, Eq)]
182pub struct GrammarLowering {
183 pub format: String,
185 pub status: GrammarLoweringStatus,
187 pub grammar: Grammar,
189 pub executable: String,
191 pub report: EmitReport,
193 pub imported: Grammar,
195 pub steps: Vec<GrammarLoweringStep>,
197 pub metadata: String,
199}
200
201#[derive(Clone, Copy, Default)]
203pub struct GrammarLoweringOptions<'a> {
204 pub emit: Option<GrammarEmitFn<'a>>,
206 pub import: Option<GrammarImportFn<'a>>,
208 pub accepts: &'a [String],
210 pub rejects: &'a [String],
212 pub edit_metadata: Option<&'a dyn Fn(&str) -> String>,
214}
215
216#[derive(Clone, Debug, PartialEq, Eq)]
218pub enum GrammarLoweringError {
219 UnsupportedFormat(String),
221 NoStartRule,
223 Unencodable(String),
225 MissingRule(String),
227 Import(GrammarImportError),
229 Emit(GrammarEmitError),
231 Definition(GrammarRoundTripError),
233}
234
235impl fmt::Display for GrammarLoweringError {
236 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
237 match self {
238 Self::UnsupportedFormat(format) => write!(
239 formatter,
240 "the lowering does not support the format {format}"
241 ),
242 Self::NoStartRule => formatter.write_str("the grammar has no start rule"),
243 Self::Unencodable(format) => write!(
244 formatter,
245 "a character set without printable ASCII characters cannot be written in {format}"
246 ),
247 Self::MissingRule(name) => {
248 write!(formatter, "the executable grammar has no rule {name}")
249 }
250 Self::Import(error) => write!(formatter, "{error}"),
251 Self::Emit(error) => write!(formatter, "{error}"),
252 Self::Definition(error) => write!(formatter, "{error}"),
253 }
254 }
255}
256
257impl Error for GrammarLoweringError {}
258
259impl From<GrammarImportError> for GrammarLoweringError {
260 fn from(error: GrammarImportError) -> Self {
261 Self::Import(error)
262 }
263}
264
265impl From<GrammarEmitError> for GrammarLoweringError {
266 fn from(error: GrammarEmitError) -> Self {
267 Self::Emit(error)
268 }
269}
270
271impl From<GrammarRoundTripError> for GrammarLoweringError {
272 fn from(error: GrammarRoundTripError) -> Self {
273 Self::Definition(error)
274 }
275}
276
277fn check_format(format: &str) -> Result<(), GrammarLoweringError> {
278 if GRAMMAR_LOWERING_FORMATS.contains(&format) {
279 Ok(())
280 } else {
281 Err(GrammarLoweringError::UnsupportedFormat(format.to_owned()))
282 }
283}
284
285pub(super) fn map_children<E>(
287 expr: &GrammarExpr,
288 map: &mut impl FnMut(&GrammarExpr) -> Result<GrammarExpr, E>,
289) -> Result<GrammarExpr, E> {
290 let mut boxed = |item: &GrammarExpr| map(item).map(Box::new);
291 Ok(match expr {
292 GrammarExpr::Choice {
293 ordered,
294 alternatives,
295 } => GrammarExpr::Choice {
296 ordered: *ordered,
297 alternatives: alternatives
298 .iter()
299 .map(&mut *map)
300 .collect::<Result<_, _>>()?,
301 },
302 GrammarExpr::Sequence(items) => {
303 GrammarExpr::Sequence(items.iter().map(&mut *map).collect::<Result<_, _>>()?)
304 }
305 GrammarExpr::Optional(item) => GrammarExpr::Optional(boxed(item)?),
306 GrammarExpr::ZeroOrMore(item) => GrammarExpr::ZeroOrMore(boxed(item)?),
307 GrammarExpr::OneOrMore(item) => GrammarExpr::OneOrMore(boxed(item)?),
308 GrammarExpr::And(item) => GrammarExpr::And(boxed(item)?),
309 GrammarExpr::Not(item) => GrammarExpr::Not(boxed(item)?),
310 GrammarExpr::Repeat { expr, min, max } => GrammarExpr::Repeat {
311 expr: boxed(expr)?,
312 min: *min,
313 max: *max,
314 },
315 GrammarExpr::Capture { label, expr } => GrammarExpr::Capture {
316 label: label.clone(),
317 expr: boxed(expr)?,
318 },
319 _ => expr.clone(),
320 })
321}
322
323pub(super) fn assemble(
325 start: &str,
326 rules: Vec<GrammarRule>,
327 source: Option<GrammarFormat>,
328) -> Grammar {
329 let mut grammar = Grammar::new().with_start(start);
330 for rule in rules {
331 grammar.add_rule(rule);
332 }
333 if let Some(source) = source {
334 grammar.set_source_format(source);
335 }
336 grammar
337}
338
339fn rule(name: &str, kind: RuleKind, expr: GrammarExpr, doc: Option<String>) -> GrammarRule {
340 GrammarRule {
341 name: name.to_owned(),
342 expr,
343 kind,
344 concept: None,
345 source_names: Vec::new(),
346 doc,
347 attributes: RuleAttributes::default(),
348 }
349}
350
351struct Lowerer<'a> {
353 format: &'a str,
354 unsupported: &'static [Construct],
355 taken: HashSet<String>,
356 counter: usize,
357 helpers: Vec<GrammarRule>,
358 memo: HashMap<String, String>,
359 steps: Vec<GrammarLoweringStep>,
360}
361
362impl Lowerer<'_> {
363 fn helper_name(&mut self, lexical: bool) -> String {
366 let name = loop {
367 self.counter += 1;
368 let name = format!("{HELPER_PREFIX}{}", self.counter);
369 if !self.taken.contains(&name) {
370 break name;
371 }
372 };
373 self.taken.insert(name.clone());
374 if lexical { name.to_uppercase() } else { name }
375 }
376
377 fn lower(
381 &mut self,
382 expr: &GrammarExpr,
383 owner: &str,
384 inner: bool,
385 lexical: bool,
386 ) -> Result<GrammarExpr, GrammarLoweringError> {
387 let Some(construct) = constructs_of(expr, inner, lexical)
388 .into_iter()
389 .find(|construct| self.unsupported.contains(construct))
390 else {
391 return map_children(expr, &mut |item| self.lower(item, owner, true, lexical));
392 };
393 let helper_lexical = lexical || construct == Construct::ParserCharacters;
394 let key = format!(
395 "{} {helper_lexical} {}",
396 construct.as_str(),
397 render_links_expression(expr)
398 );
399 if let Some(helper) = self.memo.get(&key) {
400 return Ok(GrammarExpr::NonTerminal(helper.clone()));
401 }
402 let helper = self.helper_name(helper_lexical);
403 self.memo.insert(key, helper.clone());
404 let (body, encoding, note) = encode(self.format, construct, expr, &helper)?;
405 self.steps.push(GrammarLoweringStep::Helper {
406 helper: helper.clone(),
407 owner: owner.to_owned(),
408 construct: construct.as_str().to_owned(),
409 encoding,
410 note: note.to_owned(),
411 original: expr.clone(),
412 });
413 let index = self.helpers.len();
414 let kind = if helper_lexical {
415 RuleKind::Token
416 } else {
417 RuleKind::Normal
418 };
419 self.helpers
420 .push(rule(&helper, kind, GrammarExpr::Empty, None));
421 self.helpers[index].expr = self.lower(&body, &helper, false, helper_lexical)?;
422 Ok(GrammarExpr::NonTerminal(helper))
423 }
424}
425
426pub fn lower_grammar(
438 grammar: &Grammar,
439 format: &str,
440 options: &GrammarLoweringOptions<'_>,
441) -> Result<GrammarLowering, GrammarLoweringError> {
442 check_format(format)?;
443 let start = grammar
444 .start_rule()
445 .ok_or(GrammarLoweringError::NoStartRule)?;
446 let own_emit = grammar_emitter(format)
447 .ok_or_else(|| GrammarLoweringError::UnsupportedFormat(format.to_owned()))?;
448 let own_import = grammar_importer(format)
449 .ok_or_else(|| GrammarLoweringError::UnsupportedFormat(format.to_owned()))?;
450 let emit: GrammarEmitFn<'_> = options.emit.unwrap_or(&own_emit);
451 let import: GrammarImportFn<'_> = options.import.unwrap_or(&own_import);
452
453 let mut state = Lowerer {
454 format,
455 unsupported: unsupported(format),
456 taken: grammar
457 .rule_names()
458 .iter()
459 .map(|name| name.to_lowercase())
460 .collect(),
461 counter: 0,
462 helpers: Vec::new(),
463 memo: HashMap::new(),
464 steps: Vec::new(),
465 };
466 let kept = kept_kinds(format);
467 let mut rules = Vec::new();
468 for original in grammar.rules() {
469 let expr = state.lower(&original.expr, &original.name, false, false)?;
470 let refers_to_helper = format == "tree-sitter-json"
473 && render_links_expression(&expr) != render_links_expression(&original.expr);
474 let kind = if kept.contains(&original.kind) && !refers_to_helper {
475 original.kind
476 } else {
477 RuleKind::Normal
478 };
479 rules.push(rule(&original.name, kind, expr, original.doc.clone()));
480 }
481 rules.append(&mut state.helpers);
482 let lowered = assemble(&start.name, rules, grammar.source_format());
485
486 let first = import(&emit(&lowered)?.0)?;
491 let first = first.rule_names();
492 let renames: Vec<(String, String)> = lowered
493 .rule_names()
494 .iter()
495 .zip(&first)
496 .filter(|(name, executable)| name != executable)
497 .map(|(name, executable)| ((*name).to_owned(), (*executable).to_owned()))
498 .collect();
499 let named = |name: &str| {
500 renames
501 .iter()
502 .find(|(rule, _)| rule == name)
503 .map_or_else(|| name.to_owned(), |(_, value)| value.clone())
504 };
505 let mut executable_rules: Vec<GrammarRule> = lowered
506 .rules()
507 .iter()
508 .map(|lowered_rule| {
509 let expr = rename_references(&lowered_rule.expr, &named);
510 rule(
511 &named(&lowered_rule.name),
512 lowered_rule.kind,
513 expr,
514 lowered_rule.doc.clone(),
515 )
516 })
517 .collect();
518 let executable_start = named(&start.name);
519 let mut executable = assemble(
520 &executable_start,
521 executable_rules.clone(),
522 grammar.source_format(),
523 );
524 let (mut text, mut report) = emit(&executable)?;
525 let mut imported = import(&text)?;
526 let mut unkept = false;
530 for executable_rule in &mut executable_rules {
531 let kept_doc = imported
532 .rule(&executable_rule.name)
533 .is_some_and(|back| back.doc == executable_rule.doc);
534 if executable_rule.doc.is_some() && !kept_doc {
535 executable_rule.doc = None;
536 unkept = true;
537 }
538 }
539 if unkept {
540 executable = assemble(&executable_start, executable_rules, grammar.source_format());
541 (text, report) = emit(&executable)?;
542 imported = import(&text)?;
543 }
544
545 let mut steps: Vec<GrammarLoweringStep> = renames
546 .iter()
547 .map(|(rule, value)| GrammarLoweringStep::Rename {
548 rule: rule.clone(),
549 value: value.clone(),
550 })
551 .collect();
552 steps.append(&mut state.steps);
553 for original in grammar.rules() {
554 let Some(back) = imported.rule(&named(&original.name)) else {
555 continue;
556 };
557 if back.kind != original.kind {
558 steps.push(GrammarLoweringStep::Kind {
559 rule: original.name.clone(),
560 kind: original.kind,
561 });
562 }
563 if let Some(doc) = &original.doc
564 && back.doc.as_ref() != Some(doc)
565 {
566 steps.push(GrammarLoweringStep::Doc {
567 rule: original.name.clone(),
568 doc: doc.clone(),
569 });
570 }
571 }
572
573 if let Some(declarations) = render_declarations_text(grammar.declarations()) {
577 steps.push(GrammarLoweringStep::Declarations { declarations });
578 }
579 for original in grammar.rules() {
580 let fields = render_rule_fields(&original.attributes);
581 if !fields.is_empty() {
582 steps.push(GrammarLoweringStep::Attributes {
583 rule: original.name.clone(),
584 attributes: fields.join(" "),
585 });
586 }
587 }
588
589 let approximate = steps.iter().any(|step| {
590 matches!(
591 step,
592 GrammarLoweringStep::Helper {
593 encoding: GrammarLoweringEncoding::Approximate,
594 ..
595 } | GrammarLoweringStep::Declarations { .. }
596 | GrammarLoweringStep::Attributes { .. }
597 )
598 });
599 let status = if approximate {
600 GrammarLoweringStatus::Approximate
601 } else {
602 GrammarLoweringStatus::Exact
603 };
604 let metadata = GrammarLoweringMetadata {
605 format: format.to_owned(),
606 status,
607 source: grammar.source_format(),
608 start: start.name.clone(),
609 order: grammar
610 .rule_names()
611 .into_iter()
612 .map(str::to_owned)
613 .collect(),
614 steps,
615 };
616 Ok(GrammarLowering {
617 format: format.to_owned(),
618 status,
619 grammar: executable,
620 executable: text,
621 report,
622 imported,
623 metadata: render_lowering_metadata(&metadata),
624 steps: metadata.steps,
625 })
626}
627
628fn rename_references(expr: &GrammarExpr, named: &impl Fn(&str) -> String) -> GrammarExpr {
630 match expr {
631 GrammarExpr::NonTerminal(name) => GrammarExpr::NonTerminal(named(name)),
632 _ => map_children::<std::convert::Infallible>(expr, &mut |item| {
633 Ok(rename_references(item, named))
634 })
635 .unwrap_or_else(|never| match never {}),
636 }
637}