use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::fmt;
use super::super::emit::{EmitReport, GrammarEmitError};
use super::super::import::GrammarImportError;
use super::super::round_trip::{GrammarEmitFn, GrammarImportFn, GrammarRoundTripError};
use super::super::{Grammar, GrammarExpr, GrammarFormat, GrammarRule, RuleAttributes, RuleKind};
use super::links::render_links_expression;
use super::{grammar_emitter, grammar_importer, render_rule_fields};
mod encoding;
mod metadata;
use encoding::{Construct, constructs_of, encode, kept_kinds, unsupported};
use metadata::render_declarations_text;
pub use encoding::MAX_LOWERED_CHARACTERS;
pub use metadata::{
GrammarLoweringFailure, GrammarLoweringFailureKind, GrammarLoweringReport,
check_grammar_lowering, dropped_grammar_features, parse_lowering_metadata, reconstruct_grammar,
render_lowering_metadata,
};
pub const GRAMMAR_LOWERING_FORMATS: &[&str] = &[
"abnf",
"antlr",
"bnf",
"ebnf",
"gbnf",
"lark",
"pest",
"tree-sitter-json",
];
const HELPER_PREFIX: &str = "lowered";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GrammarLoweringStatus {
Exact,
Approximate,
Broken,
}
impl GrammarLoweringStatus {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Exact => "exact",
Self::Approximate => "approximate",
Self::Broken => "broken",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GrammarLoweringEncoding {
Exact,
Approximate,
}
impl GrammarLoweringEncoding {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Exact => "exact",
Self::Approximate => "approximate",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GrammarLoweringStep {
Rename {
rule: String,
value: String,
},
Helper {
helper: String,
owner: String,
construct: String,
encoding: GrammarLoweringEncoding,
note: String,
original: GrammarExpr,
},
Kind {
rule: String,
kind: RuleKind,
},
Doc {
rule: String,
doc: String,
},
Declarations {
declarations: String,
},
Attributes {
rule: String,
attributes: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GrammarLoweringMetadata {
pub format: String,
pub status: GrammarLoweringStatus,
pub source: Option<GrammarFormat>,
pub start: String,
pub order: Vec<String>,
pub steps: Vec<GrammarLoweringStep>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GrammarLowering {
pub format: String,
pub status: GrammarLoweringStatus,
pub grammar: Grammar,
pub executable: String,
pub report: EmitReport,
pub imported: Grammar,
pub steps: Vec<GrammarLoweringStep>,
pub metadata: String,
}
#[derive(Clone, Copy, Default)]
pub struct GrammarLoweringOptions<'a> {
pub emit: Option<GrammarEmitFn<'a>>,
pub import: Option<GrammarImportFn<'a>>,
pub accepts: &'a [String],
pub rejects: &'a [String],
pub edit_metadata: Option<&'a dyn Fn(&str) -> String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GrammarLoweringError {
UnsupportedFormat(String),
NoStartRule,
Unencodable(String),
MissingRule(String),
Import(GrammarImportError),
Emit(GrammarEmitError),
Definition(GrammarRoundTripError),
}
impl fmt::Display for GrammarLoweringError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedFormat(format) => write!(
formatter,
"the lowering does not support the format {format}"
),
Self::NoStartRule => formatter.write_str("the grammar has no start rule"),
Self::Unencodable(format) => write!(
formatter,
"a character set without printable ASCII characters cannot be written in {format}"
),
Self::MissingRule(name) => {
write!(formatter, "the executable grammar has no rule {name}")
}
Self::Import(error) => write!(formatter, "{error}"),
Self::Emit(error) => write!(formatter, "{error}"),
Self::Definition(error) => write!(formatter, "{error}"),
}
}
}
impl Error for GrammarLoweringError {}
impl From<GrammarImportError> for GrammarLoweringError {
fn from(error: GrammarImportError) -> Self {
Self::Import(error)
}
}
impl From<GrammarEmitError> for GrammarLoweringError {
fn from(error: GrammarEmitError) -> Self {
Self::Emit(error)
}
}
impl From<GrammarRoundTripError> for GrammarLoweringError {
fn from(error: GrammarRoundTripError) -> Self {
Self::Definition(error)
}
}
fn check_format(format: &str) -> Result<(), GrammarLoweringError> {
if GRAMMAR_LOWERING_FORMATS.contains(&format) {
Ok(())
} else {
Err(GrammarLoweringError::UnsupportedFormat(format.to_owned()))
}
}
pub(super) fn map_children<E>(
expr: &GrammarExpr,
map: &mut impl FnMut(&GrammarExpr) -> Result<GrammarExpr, E>,
) -> Result<GrammarExpr, E> {
let mut boxed = |item: &GrammarExpr| map(item).map(Box::new);
Ok(match expr {
GrammarExpr::Choice {
ordered,
alternatives,
} => GrammarExpr::Choice {
ordered: *ordered,
alternatives: alternatives
.iter()
.map(&mut *map)
.collect::<Result<_, _>>()?,
},
GrammarExpr::Sequence(items) => {
GrammarExpr::Sequence(items.iter().map(&mut *map).collect::<Result<_, _>>()?)
}
GrammarExpr::Optional(item) => GrammarExpr::Optional(boxed(item)?),
GrammarExpr::ZeroOrMore(item) => GrammarExpr::ZeroOrMore(boxed(item)?),
GrammarExpr::OneOrMore(item) => GrammarExpr::OneOrMore(boxed(item)?),
GrammarExpr::And(item) => GrammarExpr::And(boxed(item)?),
GrammarExpr::Not(item) => GrammarExpr::Not(boxed(item)?),
GrammarExpr::Repeat { expr, min, max } => GrammarExpr::Repeat {
expr: boxed(expr)?,
min: *min,
max: *max,
},
GrammarExpr::Capture { label, expr } => GrammarExpr::Capture {
label: label.clone(),
expr: boxed(expr)?,
},
_ => expr.clone(),
})
}
pub(super) fn assemble(
start: &str,
rules: Vec<GrammarRule>,
source: Option<GrammarFormat>,
) -> Grammar {
let mut grammar = Grammar::new().with_start(start);
for rule in rules {
grammar.add_rule(rule);
}
if let Some(source) = source {
grammar.set_source_format(source);
}
grammar
}
fn rule(name: &str, kind: RuleKind, expr: GrammarExpr, doc: Option<String>) -> GrammarRule {
GrammarRule {
name: name.to_owned(),
expr,
kind,
concept: None,
source_names: Vec::new(),
doc,
attributes: RuleAttributes::default(),
}
}
struct Lowerer<'a> {
format: &'a str,
unsupported: &'static [Construct],
taken: HashSet<String>,
counter: usize,
helpers: Vec<GrammarRule>,
memo: HashMap<String, String>,
steps: Vec<GrammarLoweringStep>,
}
impl Lowerer<'_> {
fn helper_name(&mut self, lexical: bool) -> String {
let name = loop {
self.counter += 1;
let name = format!("{HELPER_PREFIX}{}", self.counter);
if !self.taken.contains(&name) {
break name;
}
};
self.taken.insert(name.clone());
if lexical { name.to_uppercase() } else { name }
}
fn lower(
&mut self,
expr: &GrammarExpr,
owner: &str,
inner: bool,
lexical: bool,
) -> Result<GrammarExpr, GrammarLoweringError> {
let Some(construct) = constructs_of(expr, inner, lexical)
.into_iter()
.find(|construct| self.unsupported.contains(construct))
else {
return map_children(expr, &mut |item| self.lower(item, owner, true, lexical));
};
let helper_lexical = lexical || construct == Construct::ParserCharacters;
let key = format!(
"{} {helper_lexical} {}",
construct.as_str(),
render_links_expression(expr)
);
if let Some(helper) = self.memo.get(&key) {
return Ok(GrammarExpr::NonTerminal(helper.clone()));
}
let helper = self.helper_name(helper_lexical);
self.memo.insert(key, helper.clone());
let (body, encoding, note) = encode(self.format, construct, expr, &helper)?;
self.steps.push(GrammarLoweringStep::Helper {
helper: helper.clone(),
owner: owner.to_owned(),
construct: construct.as_str().to_owned(),
encoding,
note: note.to_owned(),
original: expr.clone(),
});
let index = self.helpers.len();
let kind = if helper_lexical {
RuleKind::Token
} else {
RuleKind::Normal
};
self.helpers
.push(rule(&helper, kind, GrammarExpr::Empty, None));
self.helpers[index].expr = self.lower(&body, &helper, false, helper_lexical)?;
Ok(GrammarExpr::NonTerminal(helper))
}
}
pub fn lower_grammar(
grammar: &Grammar,
format: &str,
options: &GrammarLoweringOptions<'_>,
) -> Result<GrammarLowering, GrammarLoweringError> {
check_format(format)?;
let start = grammar
.start_rule()
.ok_or(GrammarLoweringError::NoStartRule)?;
let own_emit = grammar_emitter(format)
.ok_or_else(|| GrammarLoweringError::UnsupportedFormat(format.to_owned()))?;
let own_import = grammar_importer(format)
.ok_or_else(|| GrammarLoweringError::UnsupportedFormat(format.to_owned()))?;
let emit: GrammarEmitFn<'_> = options.emit.unwrap_or(&own_emit);
let import: GrammarImportFn<'_> = options.import.unwrap_or(&own_import);
let mut state = Lowerer {
format,
unsupported: unsupported(format),
taken: grammar
.rule_names()
.iter()
.map(|name| name.to_lowercase())
.collect(),
counter: 0,
helpers: Vec::new(),
memo: HashMap::new(),
steps: Vec::new(),
};
let kept = kept_kinds(format);
let mut rules = Vec::new();
for original in grammar.rules() {
let expr = state.lower(&original.expr, &original.name, false, false)?;
let refers_to_helper = format == "tree-sitter-json"
&& render_links_expression(&expr) != render_links_expression(&original.expr);
let kind = if kept.contains(&original.kind) && !refers_to_helper {
original.kind
} else {
RuleKind::Normal
};
rules.push(rule(&original.name, kind, expr, original.doc.clone()));
}
rules.append(&mut state.helpers);
let lowered = assemble(&start.name, rules, grammar.source_format());
let first = import(&emit(&lowered)?.0)?;
let first = first.rule_names();
let renames: Vec<(String, String)> = lowered
.rule_names()
.iter()
.zip(&first)
.filter(|(name, executable)| name != executable)
.map(|(name, executable)| ((*name).to_owned(), (*executable).to_owned()))
.collect();
let named = |name: &str| {
renames
.iter()
.find(|(rule, _)| rule == name)
.map_or_else(|| name.to_owned(), |(_, value)| value.clone())
};
let mut executable_rules: Vec<GrammarRule> = lowered
.rules()
.iter()
.map(|lowered_rule| {
let expr = rename_references(&lowered_rule.expr, &named);
rule(
&named(&lowered_rule.name),
lowered_rule.kind,
expr,
lowered_rule.doc.clone(),
)
})
.collect();
let executable_start = named(&start.name);
let mut executable = assemble(
&executable_start,
executable_rules.clone(),
grammar.source_format(),
);
let (mut text, mut report) = emit(&executable)?;
let mut imported = import(&text)?;
let mut unkept = false;
for executable_rule in &mut executable_rules {
let kept_doc = imported
.rule(&executable_rule.name)
.is_some_and(|back| back.doc == executable_rule.doc);
if executable_rule.doc.is_some() && !kept_doc {
executable_rule.doc = None;
unkept = true;
}
}
if unkept {
executable = assemble(&executable_start, executable_rules, grammar.source_format());
(text, report) = emit(&executable)?;
imported = import(&text)?;
}
let mut steps: Vec<GrammarLoweringStep> = renames
.iter()
.map(|(rule, value)| GrammarLoweringStep::Rename {
rule: rule.clone(),
value: value.clone(),
})
.collect();
steps.append(&mut state.steps);
for original in grammar.rules() {
let Some(back) = imported.rule(&named(&original.name)) else {
continue;
};
if back.kind != original.kind {
steps.push(GrammarLoweringStep::Kind {
rule: original.name.clone(),
kind: original.kind,
});
}
if let Some(doc) = &original.doc
&& back.doc.as_ref() != Some(doc)
{
steps.push(GrammarLoweringStep::Doc {
rule: original.name.clone(),
doc: doc.clone(),
});
}
}
if let Some(declarations) = render_declarations_text(grammar.declarations()) {
steps.push(GrammarLoweringStep::Declarations { declarations });
}
for original in grammar.rules() {
let fields = render_rule_fields(&original.attributes);
if !fields.is_empty() {
steps.push(GrammarLoweringStep::Attributes {
rule: original.name.clone(),
attributes: fields.join(" "),
});
}
}
let approximate = steps.iter().any(|step| {
matches!(
step,
GrammarLoweringStep::Helper {
encoding: GrammarLoweringEncoding::Approximate,
..
} | GrammarLoweringStep::Declarations { .. }
| GrammarLoweringStep::Attributes { .. }
)
});
let status = if approximate {
GrammarLoweringStatus::Approximate
} else {
GrammarLoweringStatus::Exact
};
let metadata = GrammarLoweringMetadata {
format: format.to_owned(),
status,
source: grammar.source_format(),
start: start.name.clone(),
order: grammar
.rule_names()
.into_iter()
.map(str::to_owned)
.collect(),
steps,
};
Ok(GrammarLowering {
format: format.to_owned(),
status,
grammar: executable,
executable: text,
report,
imported,
metadata: render_lowering_metadata(&metadata),
steps: metadata.steps,
})
}
fn rename_references(expr: &GrammarExpr, named: &impl Fn(&str) -> String) -> GrammarExpr {
match expr {
GrammarExpr::NonTerminal(name) => GrammarExpr::NonTerminal(named(name)),
_ => map_children::<std::convert::Infallible>(expr, &mut |item| {
Ok(rename_references(item, named))
})
.unwrap_or_else(|never| match never {}),
}
}