use std::collections::HashMap;
use super::abnf::{abnf_rules, parse_abnf};
use super::common::Parsed;
use super::ebnf::{ebnf_rules, parse_ebnf};
use super::ir::{ir_rules, parse_ir};
use super::peg::{parse_peg, peg_rules};
use crate::detect::DiagramType;
use crate::errors::{Error, ErrorCode};
use crate::js::chars::{drop_indents, has_prefix_fold, space_run, trim_start};
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum RailroadNode {
Terminal(String),
Nonterminal(String),
Special(String),
Sequence(Vec<usize>),
Choice(Vec<usize>),
Optional(usize),
Repetition {
element: usize,
min: f64,
max: f64,
},
}
impl RailroadNode {
pub(crate) fn parts(&self) -> Vec<usize> {
match self {
RailroadNode::Sequence(kids) | RailroadNode::Choice(kids) => kids.clone(),
RailroadNode::Optional(element) | RailroadNode::Repetition { element, .. } => {
vec![*element]
}
_ => Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct RailroadRule {
pub(crate) name: String,
pub(crate) definition: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Notation {
Railroad,
Ebnf,
Abnf,
Peg,
}
impl Notation {
#[cfg(test)]
pub(crate) fn name(self) -> &'static str {
match self {
Notation::Railroad => "railroad",
Notation::Ebnf => "ebnf",
Notation::Abnf => "abnf",
Notation::Peg => "peg",
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct RailroadDb {
pub(crate) notation: Notation,
pub(crate) title: Option<String>,
pub(crate) acc_title: Option<String>,
pub(crate) acc_descr: Option<String>,
pub(crate) rules: Vec<RailroadRule>,
pub(crate) nodes: Vec<RailroadNode>,
by_name: HashMap<String, usize>,
}
impl RailroadDb {
fn new() -> RailroadDb {
RailroadDb {
notation: Notation::Railroad,
title: None,
acc_title: None,
acc_descr: None,
rules: Vec::new(),
nodes: Vec::new(),
by_name: HashMap::new(),
}
}
fn set_title(&mut self, text: &str) {
self.title = Some(text.to_string());
}
fn set_acc_title(&mut self, text: &str) {
self.acc_title = Some(trim_start(text).to_string());
}
fn set_acc_description(&mut self, text: &str) {
self.acc_descr = Some(drop_indents(text));
}
fn add_rule(&mut self, rule: RailroadRule) {
self.by_name.insert(rule.name.clone(), self.rules.len());
self.rules.push(rule);
}
#[cfg(test)]
pub(crate) fn get_rule(&self, name: &str) -> Option<&RailroadRule> {
self.by_name.get(name).map(|&i| &self.rules[i])
}
}
pub(crate) fn notation_of(source: &str) -> Option<Notation> {
let rest = &source[space_run(source, 0)..];
if !has_prefix_fold(rest, "railroad-") {
return None;
}
let rest = &rest["railroad-".len()..];
for (infix, notation) in [
("ebnf-", Notation::Ebnf),
("abnf-", Notation::Abnf),
("peg-", Notation::Peg),
] {
if has_prefix_fold(rest, infix) && has_prefix_fold(&rest[infix.len()..], "beta") {
return Some(notation);
}
}
has_prefix_fold(rest, "beta").then_some(Notation::Railroad)
}
fn grammar(
source: &str,
notation: Notation,
nodes: &mut Vec<RailroadNode>,
) -> Result<(Parsed, Vec<RailroadRule>), Error> {
Ok(match notation {
Notation::Ebnf => {
let ast = parse_ebnf(source)?;
let rules = ebnf_rules(&ast, nodes);
(ast.parsed(), rules)
}
Notation::Abnf => {
let ast = parse_abnf(source)?;
let rules = abnf_rules(&ast, nodes);
(ast.parsed(), rules)
}
Notation::Peg => {
let ast = parse_peg(source)?;
let rules = peg_rules(&ast, nodes);
(ast.parsed(), rules)
}
Notation::Railroad => {
let ast = parse_ir(source)?;
let rules = ir_rules(&ast, nodes);
(ast.parsed(), rules)
}
})
}
fn populate(db: &mut RailroadDb, source: &str, notation: Notation) -> Result<(), Error> {
let (ast, rules) = grammar(source, notation, &mut db.nodes)?;
db.notation = notation;
if let Some(d) = ast.acc_descr.as_deref().filter(|d| !d.is_empty()) {
db.set_acc_description(d);
}
if let Some(t) = ast.acc_title.as_deref().filter(|t| !t.is_empty()) {
db.set_acc_title(t);
}
if let Some(t) = ast.title.as_deref().filter(|t| !t.is_empty()) {
db.set_title(t);
}
for rule in rules {
db.add_rule(rule);
}
Ok(())
}
pub(crate) fn parse_railroad(source: &str, title: Option<&str>) -> Result<RailroadDb, Error> {
let Some(notation) = notation_of(source) else {
let mut e = Error::new("No railroad diagram keyword found.", ErrorCode::Syntax);
e.diagram = Some(DiagramType::Railroad);
return Err(e);
};
let mut db = RailroadDb::new();
if let Some(t) = title.filter(|t| !t.is_empty()) {
db.title = Some(t.to_string());
}
populate(&mut db, source, notation)?;
Ok(db)
}