use std::collections::{BTreeMap, BTreeSet};
use crate::language_profile::LanguageProfile;
use crate::link_network::LinkType;
pub const GRAMMAR_FORMATS: &[&str] = &["bnf"];
pub const GRAMMAR_CONSTRUCTS: &[&str] = &[
"empty-expression",
"sequence",
"ordered-choice",
"unordered-choice",
"optional-expression",
"zero-or-more-repetition",
"one-or-more-repetition",
"counted-repetition",
"character-range",
"character-class",
"any-character",
"terminal",
"case-insensitive-terminal",
"nonterminal",
"positive-predicate",
"negative-predicate",
"capture",
"atomic-rule",
"silent-rule",
"token-rule",
];
pub const FORMER_GRAMMAR_CONSTRUCTS: &[(&str, &str)] = &[
("empty", "empty-expression"),
("optional", "optional-expression"),
("zero-or-more", "zero-or-more-repetition"),
("one-or-more", "one-or-more-repetition"),
("repeat-range", "counted-repetition"),
("char-range", "character-range"),
("char-class", "character-class"),
("any-char", "any-character"),
("non-terminal", "nonterminal"),
("and-predicate", "positive-predicate"),
("not-predicate", "negative-predicate"),
("rule-kind-atomic", "atomic-rule"),
("rule-kind-silent", "silent-rule"),
("rule-kind-token", "token-rule"),
];
#[must_use]
pub fn current_grammar_construct(construct: &str) -> &str {
FORMER_GRAMMAR_CONSTRUCTS
.iter()
.find(|(former, _)| *former == construct)
.map_or(construct, |(_, current)| current)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GrammarFidelityLevel {
Lossless,
Equivalent,
Lossy,
}
impl GrammarFidelityLevel {
#[must_use]
pub const fn symbol(self) -> &'static str {
match self {
Self::Lossless => "✅",
Self::Equivalent => "≈",
Self::Lossy => "⚠️",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GrammarFormatProfile {
format: &'static str,
profile: LanguageProfile,
equivalent_constructs: BTreeSet<String>,
}
impl GrammarFormatProfile {
#[must_use]
pub const fn new(format: &'static str, profile: LanguageProfile) -> Self {
Self {
format,
profile,
equivalent_constructs: BTreeSet::new(),
}
}
#[must_use]
pub fn with_lossless_construct(mut self, construct: impl Into<String>) -> Self {
self.profile = self.profile.with_concept(construct);
self
}
#[must_use]
pub fn with_equivalent_construct(mut self, construct: impl Into<String>) -> Self {
let construct = construct.into();
self.profile = self.profile.with_concept(construct.clone());
self.equivalent_constructs.insert(construct);
self
}
#[must_use]
pub fn with_lossy_fallback(
mut self,
construct: impl Into<String>,
fallback: impl Into<String>,
) -> Self {
self.profile = self.profile.with_concept_fallback(construct, fallback);
self
}
#[must_use]
pub const fn format(&self) -> &'static str {
self.format
}
#[must_use]
pub const fn language_profile(&self) -> &LanguageProfile {
&self.profile
}
#[must_use]
pub const fn fallbacks(&self) -> &BTreeMap<String, String> {
self.profile.fallbacks()
}
#[must_use]
pub const fn equivalent_constructs(&self) -> &BTreeSet<String> {
&self.equivalent_constructs
}
#[must_use]
pub fn supports_construct(&self, construct: &str) -> bool {
self.profile
.supports_concept(current_grammar_construct(construct))
}
#[must_use]
pub fn construct_fallback(&self, construct: &str) -> Option<&str> {
self.profile
.concept_fallback(current_grammar_construct(construct))
}
#[must_use]
pub fn construct_fidelity(&self, construct: &str) -> Option<GrammarFidelityLevel> {
if self.supports_construct(construct) {
if self
.equivalent_constructs
.contains(current_grammar_construct(construct))
{
Some(GrammarFidelityLevel::Equivalent)
} else {
Some(GrammarFidelityLevel::Lossless)
}
} else if self.construct_fallback(construct).is_some() {
Some(GrammarFidelityLevel::Lossy)
} else {
None
}
}
}
#[must_use]
pub fn grammar_format_profile(format: &str) -> Option<GrammarFormatProfile> {
let canonical = canonical_grammar_format(format)?;
Some(match canonical {
"bnf" => bnf_profile(),
_ => unreachable!("canonical_grammar_format only yields known formats"),
})
}
#[must_use]
pub fn canonical_grammar_format(format: &str) -> Option<&'static str> {
match format.to_ascii_lowercase().as_str() {
"bnf" | "classic-bnf" | "classic bnf" | "backus-naur form" | "backus naur form" => {
Some("bnf")
}
_ => None,
}
}
fn base_profile(format: &'static str, name: &'static str) -> GrammarFormatProfile {
GrammarFormatProfile::new(
format,
LanguageProfile::new(name, format)
.with_link_type(LinkType::Grammar)
.with_link_type(LinkType::Concept)
.with_link_type(LinkType::Token),
)
}
fn with_lossless_constructs<'a>(
mut profile: GrammarFormatProfile,
constructs: impl IntoIterator<Item = &'a str>,
) -> GrammarFormatProfile {
for construct in constructs {
profile = profile.with_lossless_construct(construct);
}
profile
}
fn bnf_profile() -> GrammarFormatProfile {
with_lossless_constructs(
base_profile("bnf", "Backus-Naur Form"),
[
"empty-expression",
"sequence",
"unordered-choice",
"terminal",
"nonterminal",
],
)
.with_lossy_fallback(
"ordered-choice",
"emitted as an unordered BNF alternative; priority semantics are not preserved",
)
.with_lossy_fallback(
"optional-expression",
"emitted through a synthetic helper production with an empty alternative",
)
.with_lossy_fallback(
"zero-or-more-repetition",
"emitted through a recursive synthetic helper production with an empty alternative",
)
.with_lossy_fallback(
"one-or-more-repetition",
"emitted through a recursive synthetic helper production plus one required item",
)
.with_lossy_fallback(
"counted-repetition",
"emitted as required occurrences plus optional or recursive synthetic helper productions",
)
.with_lossy_fallback(
"character-range",
"expanded to a synthetic helper production enumerating each character when the range is bounded",
)
.with_lossy_fallback(
"character-class",
"expanded to a synthetic helper production for finite non-negated classes; unsupported classes are rejected",
)
.with_lossy_fallback(
"any-character",
"unsupported by BNF emission and rejected instead of silently broadening the language",
)
.with_lossy_fallback(
"case-insensitive-terminal",
"emitted as a case-sensitive literal and reported as lossy",
)
.with_lossy_fallback(
"positive-predicate",
"unsupported by BNF emission and rejected because lookahead has no BNF equivalent",
)
.with_lossy_fallback(
"negative-predicate",
"unsupported by BNF emission and rejected because lookahead has no BNF equivalent",
)
.with_lossy_fallback(
"capture",
"emitted as the captured expression while dropping the capture label",
)
.with_lossy_fallback(
"atomic-rule",
"emitted as a normal BNF production; rule-kind metadata is dropped",
)
.with_lossy_fallback(
"silent-rule",
"emitted as a normal BNF production; rule-kind metadata is dropped",
)
.with_lossy_fallback(
"token-rule",
"emitted as a normal BNF production; rule-kind metadata is dropped",
)
}