use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
StringEq(String),
StringEqCI(String),
RuleRef(String),
CharOf(String),
Sequence(Vec<Expr>),
Alternation(Vec<Expr>),
Repetition(Box<Expr>),
Optional(Box<Expr>),
NotFollowedBy(Box<Expr>),
SideCondition(Box<Expr>, &'static str),
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Prec {
Alternation,
Sequence,
Atom,
}
impl Expr {
pub fn empty() -> Expr {
Expr::Sequence(vec![])
}
fn is_empty(&self) -> bool {
matches!(self, Expr::Sequence(exprs) if exprs.is_empty())
}
pub fn sequence(mut exprs: Vec<Expr>) -> Expr {
exprs = exprs
.into_iter()
.flat_map(|expr| match expr {
Expr::Sequence(inner) => inner,
_ => vec![expr],
})
.collect();
match exprs.len() {
1 => exprs.into_iter().next().unwrap(),
_ => Expr::Sequence(exprs),
}
}
pub fn alternation(mut exprs: Vec<Expr>) -> Expr {
exprs = exprs
.into_iter()
.flat_map(|expr| match expr {
Expr::Alternation(inner) => inner,
_ => vec![expr],
})
.collect();
match exprs.len() {
1 => exprs.into_iter().next().unwrap(),
_ => Expr::Alternation(exprs),
}
}
pub fn repetition(expr: Expr) -> Expr {
let expr = expr.remove_empties();
if expr.is_empty() {
expr
} else {
Expr::Repetition(Box::new(expr))
}
}
pub fn optional(expr: Expr) -> Expr {
let expr = expr.remove_empties();
if expr.is_empty() {
expr
} else {
Expr::Optional(Box::new(expr))
}
}
pub fn side_condition(expr: Expr, text: &'static str) -> Expr {
Expr::SideCondition(Box::new(expr), text)
}
pub fn side_conditions(&self) -> Vec<&'static str> {
let mut notes = Vec::new();
self.collect_side_conditions(&mut notes);
notes
}
fn collect_side_conditions(&self, notes: &mut Vec<&'static str>) {
match self {
Expr::SideCondition(inner, text) => {
inner.collect_side_conditions(notes);
if !notes.contains(text) {
notes.push(text);
}
}
Expr::Sequence(exprs) | Expr::Alternation(exprs) => {
for expr in exprs {
expr.collect_side_conditions(notes);
}
}
Expr::Repetition(inner) | Expr::Optional(inner) | Expr::NotFollowedBy(inner) => {
inner.collect_side_conditions(notes);
}
Expr::StringEq(_) | Expr::StringEqCI(_) | Expr::RuleRef(_) | Expr::CharOf(_) => {}
}
}
pub fn format(&self, f: &mut dyn fmt::Write) -> fmt::Result {
let notes = self.side_conditions();
self.format_internal(f, Prec::Alternation, ¬es)
}
fn prec(&self) -> Prec {
match self {
_ if self.is_empty() => Prec::Atom,
Expr::Alternation(_) | Expr::SideCondition(..) => Prec::Alternation,
Expr::Sequence(_) => Prec::Sequence,
_ => Prec::Atom,
}
}
fn format_internal(
&self,
f: &mut dyn fmt::Write,
min_prec: Prec,
notes: &[&'static str],
) -> fmt::Result {
let paren = self.prec() < min_prec;
if paren {
write!(f, "( ")?;
}
match self {
Expr::StringEq(s) => write!(f, "\"{}\"", escape_string(s)),
Expr::StringEqCI(s) => write!(f, "\"{}\"i", escape_string(s)),
Expr::RuleRef(name) => write!(f, "{name}"),
Expr::CharOf(name) => write!(f, "'{}'", escape_char_class(name)),
Expr::Sequence(exprs) => Self::format_list(exprs, " ", Prec::Sequence, f, notes),
Expr::Alternation(exprs) => {
Self::format_list(exprs, " | ", Prec::Alternation, f, notes)
}
Expr::Repetition(inner) => {
write!(f, "{{ ")?;
inner.format_internal(f, Prec::Alternation, notes)?;
write!(f, " }}")
}
Expr::Optional(inner) => {
write!(f, "[ ")?;
inner.format_internal(f, Prec::Alternation, notes)?;
write!(f, " ]")
}
Expr::NotFollowedBy(inner) => {
write!(f, "!")?;
inner.format_internal(f, Prec::Atom, notes)
}
Expr::SideCondition(inner, text) => {
inner.format_internal(f, Prec::Atom, notes)?;
let index = notes.iter().position(|note| note == text).unwrap() + 1;
write!(f, " ^{index}")
}
}?;
if paren {
write!(f, " )")?;
}
Ok(())
}
fn format_list(
exprs: &[Expr],
sep: &str,
min_prec: Prec,
f: &mut dyn fmt::Write,
notes: &[&'static str],
) -> fmt::Result {
let mut first = true;
for expr in exprs {
if !expr.is_empty() {
if !first {
write!(f, "{sep}")?;
}
expr.format_internal(f, min_prec, notes)?;
first = false;
}
}
Ok(())
}
fn remove_empties(self) -> Expr {
if let Expr::Sequence(mut exprs) = self {
exprs.retain(|expr| !expr.is_empty());
match exprs.len() {
0 => Expr::empty(),
1 => exprs.pop().unwrap(),
_ => Expr::Sequence(exprs),
}
} else {
self
}
}
}
fn escape_string(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
fn escape_char_class(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}