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",
"sequence",
"ordered-choice",
"unordered-choice",
"optional",
"zero-or-more",
"one-or-more",
"repeat-range",
"char-range",
"char-class",
"any-char",
"terminal",
"case-insensitive-terminal",
"non-terminal",
"and-predicate",
"not-predicate",
"capture",
"rule-kind-atomic",
"rule-kind-silent",
"rule-kind-token",
];
#[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(construct)
}
#[must_use]
pub fn construct_fallback(&self, construct: &str) -> Option<&str> {
self.profile.concept_fallback(construct)
}
#[must_use]
pub fn construct_fidelity(&self, construct: &str) -> Option<GrammarFidelityLevel> {
if self.supports_construct(construct) {
if self.equivalent_constructs.contains(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",
"sequence",
"unordered-choice",
"terminal",
"non-terminal",
],
)
.with_lossy_fallback(
"ordered-choice",
"emitted as an unordered BNF alternative; priority semantics are not preserved",
)
.with_lossy_fallback(
"optional",
"emitted through a synthetic helper production with an empty alternative",
)
.with_lossy_fallback(
"zero-or-more",
"emitted through a recursive synthetic helper production with an empty alternative",
)
.with_lossy_fallback(
"one-or-more",
"emitted through a recursive synthetic helper production plus one required item",
)
.with_lossy_fallback(
"repeat-range",
"emitted as required occurrences plus optional or recursive synthetic helper productions",
)
.with_lossy_fallback(
"char-range",
"expanded to a synthetic helper production enumerating each character when the range is bounded",
)
.with_lossy_fallback(
"char-class",
"expanded to a synthetic helper production for finite non-negated classes; unsupported classes are rejected",
)
.with_lossy_fallback(
"any-char",
"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(
"and-predicate",
"unsupported by BNF emission and rejected because lookahead has no BNF equivalent",
)
.with_lossy_fallback(
"not-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(
"rule-kind-atomic",
"emitted as a normal BNF production; rule-kind metadata is dropped",
)
.with_lossy_fallback(
"rule-kind-silent",
"emitted as a normal BNF production; rule-kind metadata is dropped",
)
.with_lossy_fallback(
"rule-kind-token",
"emitted as a normal BNF production; rule-kind metadata is dropped",
)
}