Skip to main content

ebnf_to_bnf/
lib.rs

1//^
2//^ HEAD
3//^
4
5//> HEAD -> FLAGS
6#![feature(default_field_values)]
7
8//> HEAD -> MODULES
9mod prelude;
10#[cfg(test)]
11mod tests;
12
13//> HEAD -> PRELUDE
14use self::prelude::{
15    LazyLock,
16    Regex,
17    Map,
18    AddAssign,
19    Borrow,
20    Set
21};
22
23
24//^
25//^ PATTERNS
26//^
27
28//> PATTERNS -> COLLAPSE
29static COLLAPSE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\(([^()]*)\)").unwrap());
30
31//> PATTERNS -> POSTFIX
32static POSTFIX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?P<atom>[A-Za-z0-9_$]+)(?P<operator>[*+?])").unwrap());
33
34
35//^
36//^ EXTENSOR
37//^
38
39//> EXTENSOR -> LOCALES
40struct Locales {
41    parentheses: Map<String, usize>,
42    more: Map<String, usize>,
43    multiple: Map<String, usize>,
44    optional: Map<String, usize>,
45    names: Map<usize, String>
46} impl Locales {pub fn new() -> Self {return Self {
47    parentheses: Map::new(),
48    more: Map::new(),
49    multiple: Map::new(),
50    optional: Map::new(),
51    names: Map::new()
52}}}
53
54//> EXTENSOR -> DELIMITER
55#[derive(Clone, Copy)]
56pub enum Delimiter {
57    Arrow,
58    Colon,
59    Definition
60} impl Into<&'static str> for Delimiter {fn into(self) -> &'static str {match self {
61    Delimiter::Arrow => "->",
62    Delimiter::Colon => ":",
63    Delimiter::Definition => "::="
64}}}
65
66//> EXTENSOR -> COMMENT
67#[derive(Clone, Copy)]
68pub enum Comment {
69    Hashtag,
70    DoubleSlash
71} impl Into<&'static str> for Comment {fn into(self) -> &'static str {match self {
72    Comment::Hashtag => "#",
73    Comment::DoubleSlash => "//"
74}}}
75
76//> EXTENSOR -> NAMING
77#[derive(Clone, Copy)]
78pub enum Naming {
79    Descriptive,
80    Verbose,
81    Numeral
82}
83
84//> EXTENSOR -> OPTIONS
85#[derive(Clone, Copy)]
86pub struct Options {
87    input_delimiter: Delimiter = Delimiter::Definition,
88    output_delimiter: Delimiter = Delimiter::Definition,
89    comment: Comment = Comment::DoubleSlash,
90    naming: Naming = Naming::Descriptive
91}
92
93//> EXTENSOR -> CONVERT
94pub fn convert(ebnf: &str, options: Options) -> Option<String> {
95    let mut rules = Set::new();
96    let mut locales = Locales::new();
97    let mut counter = 0;
98    for line in ebnf.lines().map(str::trim) {
99        if line.is_empty() || line.starts_with::<&'static str>(options.comment.into()) {rules.insert(line.to_string()); continue}
100        let (rule, productions) = line.split_once::<&'static str>(options.input_delimiter.into()).map(|data| (data.0.trim(), data.1.trim()))?;
101        let body = expand(productions, &mut rules, &mut locales, &mut counter, &options);
102        rules.insert(format!("{rule} {} {body}", Into::<&'static str>::into(options.output_delimiter)));
103    };
104    return Some(Vec::from_iter(rules.into_iter()).join("\n"));
105}
106
107//> EXTENSOR -> EXPAND
108fn expand(productions: &str, rules: &mut Set<String>, locales: &mut Locales, counter: &mut usize, options: &Options) -> String {
109    let mut productions = productions.to_string();
110    while COLLAPSE.is_match(&productions) {productions = collapse(&productions, rules, locales, counter, options)};
111    return postfix(&productions, rules, locales, counter, options);
112}
113
114//> EXTENSOR -> COLLAPSE
115fn collapse(productions: &str, rules: &mut Set<String>, locales: &mut Locales, counter: &mut usize, options: &Options) -> String {
116    let hit = COLLAPSE.find(productions).unwrap();
117    let inside = &productions[hit.start() + 1 .. hit.end() - 1];
118    let symbol = namify(*locales.parentheses.entry(inside.to_string()).or_insert_with(|| {counter.add_assign(1); *counter}), productions, locales, options).to_string();
119    let expanded = expand(inside, rules, locales, counter, options);
120    rules.insert(format!("{symbol} {} {expanded}", Into::<&'static str>::into(options.output_delimiter)));
121    return format!("{}{symbol}{}", &productions[..hit.start()], &productions[hit.end()..]);
122}
123
124//> EXTENSOR -> POSTFIX
125fn postfix(productions: &str, rules: &mut Set<String>, locales: &mut Locales, counter: &mut usize, options: &Options) -> String {
126    let mut result = productions.to_string();
127    while let Some(hit) = POSTFIX.captures(&result) {
128        let atom = hit.name("atom").unwrap().as_str();
129        let operator = hit.name("operator").unwrap().as_str();
130        let symbol = namify(*match operator {
131            "+" => locales.more.entry(atom.to_string()).or_insert_with(|| {counter.add_assign(1); *counter}),
132            "*" => locales.multiple.entry(atom.to_string()).or_insert_with(|| {counter.add_assign(1); *counter}),
133            "?" => locales.optional.entry(atom.to_string()).or_insert_with(|| {counter.add_assign(1); *counter}),
134            _ => unreachable!()
135        }, productions, locales, options);
136        rules.insert(match operator {
137            "+" => format!("{symbol} {} {atom} {symbol} | {atom}", Into::<&'static str>::into(options.output_delimiter)),
138            "*" => format!("{symbol} {} {atom} {symbol} |", Into::<&'static str>::into(options.output_delimiter)),
139            "?" => format!("{symbol} {} {atom} |", Into::<&'static str>::into(options.output_delimiter)),
140            _ => unreachable!()
141        });
142        let group = hit.get(0).unwrap();
143        result = format!("{}{symbol}{}", &result[..group.start()], &result[group.end()..]);
144    };
145    return result;
146}
147
148//> EXTENSOR -> NAMIFY
149fn namify<'source>(symbol: usize, productions: &str, locales: &'source mut Locales, options: &Options) -> &'source str {return locales.names.entry(symbol).or_insert_with(|| match options.naming {
150    Naming::Verbose => productions.replace(" ", "_").replace("(", "open_").replace(")", "_close").replace("+", "_more").replace("*", "_multiple").replace("?", "_optional"),
151    Naming::Descriptive => productions.replace(" ", "_").replace("(", "op_").replace(")", "_cl").replace("+", "_mor").replace("*", "_mul").replace("?", "_opt"),
152    Naming::Numeral => format!("${symbol}")
153})}
154
155
156//^
157//^ TRAIT
158//^
159
160//> TRAIT -> DEFINITION
161pub trait ToBNF {
162    fn to_bnf(&self) -> Option<String>;
163    fn to_bnf_with_options(&self, options: Options) -> Option<String>;
164}
165
166//> TRAIT -> DEREF
167impl<Type: Borrow<str>> ToBNF for Type {
168    fn to_bnf(&self) -> Option<String> {convert(self.borrow(), Options {..})}
169    fn to_bnf_with_options(&self, options: Options) -> Option<String> {convert(self.borrow(), options)}
170}