use std::{cmp, collections::VecDeque, fmt::Display};
use crate::execution_profile::ExecutionProfile;
use regex_charclass::CharacterClass;
use regex_syntax::hir::{Class, ClassBytes, ClassUnicode, Hir, HirKind, Look};
use self::fast_automaton::FastAutomaton;
use super::*;
mod analyze;
mod builder;
mod operation;
#[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
#[must_use = "regular expressions are immutable; operations return a new expression"]
pub enum RegularExpression {
Character(CharRange),
Repetition(Box<RegularExpression>, u32, Option<u32>),
Concat(VecDeque<RegularExpression>),
Alternation(Vec<RegularExpression>),
}
impl Display for RegularExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
enum Frame<'a> {
Node(&'a RegularExpression),
Literal(&'static str),
Quantifier(u32, Option<u32>),
}
let mut stack = vec![Frame::Node(self)];
while let Some(frame) = stack.pop() {
match frame {
Frame::Literal(literal) => f.write_str(literal)?,
Frame::Quantifier(min, max_opt) => {
if min == 0 && max_opt.is_none() {
write!(f, "*")?;
} else if min == 1 && max_opt.is_none() {
write!(f, "+")?;
} else if min == 0 && max_opt == Some(1) {
write!(f, "?")?;
} else if let Some(max) = max_opt {
if max == min {
write!(f, "{{{max}}}")?;
} else {
write!(f, "{{{min},{max}}}")?;
}
} else {
write!(f, "{{{min},}}")?;
}
}
Frame::Node(RegularExpression::Character(range)) => {
if range.is_empty() {
write!(f, "[]")?;
} else {
write!(f, "{}", range.to_regex())?;
}
}
Frame::Node(RegularExpression::Repetition(regular_expression, min, max_opt)) => {
stack.push(Frame::Quantifier(*min, *max_opt));
if RegularExpression::quantifier_needs_parens(regular_expression) {
stack.push(Frame::Literal(")"));
stack.push(Frame::Node(regular_expression));
stack.push(Frame::Literal("("));
} else {
stack.push(Frame::Node(regular_expression));
}
}
Frame::Node(RegularExpression::Concat(concat)) => {
stack.extend(concat.iter().rev().map(Frame::Node));
}
Frame::Node(RegularExpression::Alternation(alternation)) => {
match alternation.as_slice() {
[] => write!(f, "[]")?,
[single] => stack.push(Frame::Node(single)),
parts => {
stack.push(Frame::Literal(")"));
for (i, regex) in parts.iter().enumerate().rev() {
stack.push(Frame::Node(regex));
if i != 0 {
stack.push(Frame::Literal("|"));
}
}
stack.push(Frame::Literal("("));
}
}
}
}
}
Ok(())
}
}
impl RegularExpression {
fn quantifier_needs_parens(mut r: &RegularExpression) -> bool {
loop {
match r {
RegularExpression::Character(..) => return false,
RegularExpression::Repetition(..) => return true,
RegularExpression::Concat(parts) => match parts.len() {
1 => r = &parts[0],
_ => return true,
},
RegularExpression::Alternation(parts) => match parts.len() {
0 => return false,
1 => r = &parts[0],
_ => return false,
},
}
}
}
pub fn is_empty(&self) -> bool {
match self {
RegularExpression::Alternation(alternation) => alternation.is_empty(),
RegularExpression::Character(range) => range.is_empty(),
_ => false,
}
}
pub fn is_empty_string(&self) -> bool {
match self {
RegularExpression::Concat(concat) => concat.is_empty(),
_ => false,
}
}
pub fn is_total(&self) -> bool {
match self {
RegularExpression::Repetition(regular_expression, min, max_opt) => {
if min != &0 || max_opt.is_some() {
false
} else {
match &**regular_expression {
RegularExpression::Character(range) => range.is_total(),
_ => false,
}
}
}
_ => false,
}
}
pub const MAX_NESTING_DEPTH: usize = 1000;
fn assert_depth_within_limit(&self) -> Result<(), EngineError> {
let mut stack = vec![(self, 1usize)];
while let Some((node, depth)) = stack.pop() {
if depth > Self::MAX_NESTING_DEPTH {
return Err(EngineError::RegexTooDeeplyNested(Self::MAX_NESTING_DEPTH));
}
match node {
RegularExpression::Character(_) => {}
RegularExpression::Repetition(inner, _, _) => stack.push((inner, depth + 1)),
RegularExpression::Concat(parts) => {
stack.extend(parts.iter().map(|p| (p, depth + 1)));
}
RegularExpression::Alternation(parts) => {
stack.extend(parts.iter().map(|p| (p, depth + 1)));
}
}
}
Ok(())
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn to_automaton(&self) -> Result<FastAutomaton, EngineError> {
self.assert_depth_within_limit()?;
self.to_automaton_inner(&ExecutionProfile::get())
}
fn to_automaton_inner(
&self,
execution_profile: &ExecutionProfile,
) -> Result<FastAutomaton, EngineError> {
if execution_profile.limits_number_of_states() {
execution_profile.assert_max_number_of_states(self.get_number_of_states_in_nfa())?;
}
match self {
RegularExpression::Character(range) => Ok(FastAutomaton::new_from_range(range)),
RegularExpression::Repetition(regular_expression, min, max_opt) => {
if let Some(max) = max_opt
&& max < min
{
return Err(EngineError::InvalidRepetitionBounds(*min, *max));
}
let mut automaton = regular_expression.to_automaton_inner(execution_profile)?;
automaton.repeat_mut(*min, *max_opt)?;
Ok(automaton)
}
RegularExpression::Concat(concat) => {
let mut concats = Vec::with_capacity(concat.len());
for c in concat.iter() {
concats.push(c.to_automaton_inner(execution_profile)?);
}
FastAutomaton::concat_all(&concats)
}
RegularExpression::Alternation(alternation) => {
let mut alternates = Vec::with_capacity(alternation.len());
for c in alternation.iter() {
alternates.push(c.to_automaton_inner(execution_profile)?);
}
FastAutomaton::union_all(&alternates)
}
}
}
pub fn evaluate_complexity(&self) -> f64 {
let (score, depth, _) = self.eval_inner();
score + Self::depth_penalty(depth)
}
fn eval_inner(&self) -> (f64, usize, bool) {
match self {
RegularExpression::Character(range) => {
let len = range.to_regex().len() as f64;
let base = 1.0 + 0.05 * len.min(40.0);
(base, 1, false)
}
RegularExpression::Repetition(inner, min, max_opt) => {
let (inner_score, inner_depth, inner_has_rep) = inner.eval_inner();
let mut m = match max_opt {
None => 1.6,
Some(max) if max > min => 1.3,
Some(max) if max == min && *min > 1 => 1.1,
_ => 1.0,
};
if inner_has_rep {
m *= 1.5;
}
(inner_score * m, inner_depth + 1, true)
}
RegularExpression::Concat(items) => {
let mut sum = 0.0;
let mut max_depth = 0usize;
let mut has_rep = false;
for (i, it) in items.iter().enumerate() {
let (s, d, h) = it.eval_inner();
sum += s;
if i > 0 {
sum *= 0.98;
}
if d > max_depth {
max_depth = d;
}
has_rep |= h;
}
(sum, max_depth + 1, has_rep)
}
RegularExpression::Alternation(branches) => {
if branches.is_empty() {
return (0.0, 1, false);
}
let mut sum = 0.0;
let mut max_depth = 0usize;
let mut has_rep = false;
for b in branches {
let (s, d, h) = b.eval_inner();
sum += s;
if d > max_depth {
max_depth = d;
}
has_rep |= h;
}
let k = branches.len() as f64;
let multiplier = 1.0 + 0.15 * (k - 1.0);
(sum * multiplier, max_depth + 1, has_rep)
}
}
}
fn depth_penalty(depth: usize) -> f64 {
if depth <= 2 {
0.0
} else {
((depth - 2) as f64).powi(2) * 0.8
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() -> Result<(), String> {
let automaton = RegularExpression::new_empty();
assert!(automaton.is_empty());
assert!(!automaton.is_empty_string());
assert!(!automaton.is_total());
Ok(())
}
#[test]
fn test_empty_string() -> Result<(), String> {
let automaton = RegularExpression::new_empty_string();
assert!(!automaton.is_empty());
assert!(automaton.is_empty_string());
assert!(!automaton.is_total());
Ok(())
}
#[test]
fn test_total() -> Result<(), String> {
let automaton = RegularExpression::new_total();
assert!(!automaton.is_empty());
assert!(!automaton.is_empty_string());
assert!(automaton.is_total());
Ok(())
}
fn drop_chain_iteratively(mut regex: RegularExpression) {
loop {
regex = match regex {
RegularExpression::Repetition(inner, _, _) => *inner,
RegularExpression::Concat(mut parts) if parts.len() == 1 => {
parts.pop_front().expect("len() == 1")
}
RegularExpression::Alternation(mut parts) if parts.len() == 1 => {
parts.pop().expect("len() == 1")
}
_ => return,
};
}
}
#[test]
fn display_does_not_recurse_on_deep_trees() {
const DEPTH: usize = 100_000;
let mut regex = RegularExpression::new("a").unwrap();
for _ in 0..DEPTH {
regex = RegularExpression::Repetition(Box::new(regex), 0, None);
}
let printed = regex.to_string();
assert_eq!(3 * DEPTH - 1, printed.len());
assert!(printed.starts_with("((("));
assert!(printed.ends_with(")*)*)*"));
drop_chain_iteratively(regex);
let mut regex = RegularExpression::new("ab").unwrap();
for i in 0..DEPTH {
regex = if i % 2 == 0 {
RegularExpression::Concat(VecDeque::from([regex]))
} else {
RegularExpression::Alternation(vec![regex])
};
}
let regex = RegularExpression::Repetition(Box::new(regex), 0, None);
assert_eq!("(ab)*", regex.to_string());
drop_chain_iteratively(regex);
}
}