use crate::SymbolDomain;
use core::fmt;
use core::marker::PhantomData;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CaptureId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AssertionId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Anchor {
SubjectStart,
SubjectEnd,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RepeatBounds {
min: usize,
max: Option<usize>,
}
impl RepeatBounds {
pub fn new(min: usize, max: Option<usize>) -> Result<Self, IrError> {
if let Some(max) = max
&& min > max
{
return Err(IrError::InvalidRepeatBounds { min, max });
}
Ok(Self { min, max })
}
pub const fn min(self) -> usize {
self.min
}
pub const fn max(self) -> Option<usize> {
self.max
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IrNode<S, E> {
Symbol(S),
Any,
Concat(Vec<Self>),
Alternation(Vec<Self>),
Repeat {
node: Box<Self>,
bounds: RepeatBounds,
greedy: bool,
},
Group(Box<Self>),
Capture {
id: CaptureId,
node: Box<Self>,
},
Anchor(Anchor),
Assertion(AssertionId),
Extension(E),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnginePolicy<E> {
admitted_extensions: BTreeSet<E>,
}
impl<E: Ord> EnginePolicy<E> {
pub fn new(admitted_extensions: impl IntoIterator<Item = E>) -> Self {
Self {
admitted_extensions: admitted_extensions.into_iter().collect(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PatternIr<D: SymbolDomain, E> {
root: IrNode<D::Symbol, E>,
assertions: BTreeMap<AssertionId, IrNode<D::Symbol, E>>,
domain: PhantomData<fn() -> D>,
}
impl<D, E> PatternIr<D, E>
where
D: SymbolDomain,
E: Clone + fmt::Debug + Ord,
{
pub fn new(
root: IrNode<D::Symbol, E>,
assertions: BTreeMap<AssertionId, IrNode<D::Symbol, E>>,
policy: &EnginePolicy<E>,
) -> Result<Self, IrError> {
let mut captures = BTreeSet::new();
validate_node(&root, &assertions, policy, &mut captures)?;
for definition in assertions.values() {
validate_node(definition, &assertions, policy, &mut captures)?;
}
validate_assertion_cycles(&root, &assertions, &mut Vec::new())?;
for (id, definition) in &assertions {
validate_assertion_cycles(definition, &assertions, &mut vec![*id])?;
}
Ok(Self {
root,
assertions,
domain: PhantomData,
})
}
pub fn root(&self) -> &IrNode<D::Symbol, E> {
&self.root
}
pub fn assertions(&self) -> &BTreeMap<AssertionId, IrNode<D::Symbol, E>> {
&self.assertions
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IrError {
InvalidRepeatBounds {
min: usize,
max: usize,
},
DuplicateCapture(CaptureId),
MissingAssertion(AssertionId),
AssertionCycle(Vec<AssertionId>),
UnsupportedExtension(String),
}
impl fmt::Display for IrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidRepeatBounds { min, max } => {
write!(
f,
"invalid repeat bounds: minimum {min} exceeds maximum {max}"
)
}
Self::DuplicateCapture(id) => write!(f, "duplicate capture id {}", id.0),
Self::MissingAssertion(id) => write!(f, "missing assertion id {}", id.0),
Self::AssertionCycle(path) => write!(f, "assertion cycle: {path:?}"),
Self::UnsupportedExtension(extension) => {
write!(f, "target engine does not admit extension {extension}")
}
}
}
}
impl std::error::Error for IrError {}
fn validate_node<S, E>(
node: &IrNode<S, E>,
assertions: &BTreeMap<AssertionId, IrNode<S, E>>,
policy: &EnginePolicy<E>,
captures: &mut BTreeSet<CaptureId>,
) -> Result<(), IrError>
where
E: fmt::Debug + Ord,
{
match node {
IrNode::Concat(nodes) | IrNode::Alternation(nodes) => {
for node in nodes {
validate_node(node, assertions, policy, captures)?;
}
}
IrNode::Repeat { node, .. } | IrNode::Group(node) => {
validate_node(node, assertions, policy, captures)?;
}
IrNode::Capture { id, node } => {
if !captures.insert(*id) {
return Err(IrError::DuplicateCapture(*id));
}
validate_node(node, assertions, policy, captures)?;
}
IrNode::Assertion(id) => {
assertions.get(id).ok_or(IrError::MissingAssertion(*id))?;
}
IrNode::Extension(extension) if !policy.admitted_extensions.contains(extension) => {
return Err(IrError::UnsupportedExtension(format!("{extension:?}")));
}
IrNode::Symbol(_) | IrNode::Any | IrNode::Anchor(_) | IrNode::Extension(_) => {}
}
Ok(())
}
fn validate_assertion_cycles<S, E>(
node: &IrNode<S, E>,
assertions: &BTreeMap<AssertionId, IrNode<S, E>>,
path: &mut Vec<AssertionId>,
) -> Result<(), IrError> {
match node {
IrNode::Concat(nodes) | IrNode::Alternation(nodes) => {
for node in nodes {
validate_assertion_cycles(node, assertions, path)?;
}
}
IrNode::Repeat { node, .. } | IrNode::Group(node) | IrNode::Capture { node, .. } => {
validate_assertion_cycles(node, assertions, path)?;
}
IrNode::Assertion(id) => {
if let Some(cycle_start) = path.iter().position(|seen| seen == id) {
let mut cycle = path[cycle_start..].to_vec();
cycle.push(*id);
return Err(IrError::AssertionCycle(cycle));
}
let definition = assertions.get(id).ok_or(IrError::MissingAssertion(*id))?;
path.push(*id);
let result = validate_assertion_cycles(definition, assertions, path);
path.pop();
result?;
}
IrNode::Symbol(_) | IrNode::Any | IrNode::Anchor(_) | IrNode::Extension(_) => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ByteDomain;
#[test]
fn invalid_repeat_names_both_bounds() {
let error = RepeatBounds::new(4, Some(3)).unwrap_err();
assert_eq!(
error.to_string(),
"invalid repeat bounds: minimum 4 exceeds maximum 3"
);
}
#[test]
fn rejects_duplicate_capture_ids() {
let capture = |symbol| IrNode::Capture {
id: CaptureId(7),
node: Box::new(IrNode::Symbol(symbol)),
};
let root = IrNode::Concat(vec![capture(b'a'), capture(b'b')]);
let error =
PatternIr::<ByteDomain, &str>::new(root, BTreeMap::new(), &EnginePolicy::new([]))
.unwrap_err();
assert_eq!(error, IrError::DuplicateCapture(CaptureId(7)));
}
#[test]
fn rejects_assertion_cycles() {
let assertions = BTreeMap::from([
(AssertionId(1), IrNode::Assertion(AssertionId(2))),
(AssertionId(2), IrNode::Assertion(AssertionId(1))),
]);
let error = PatternIr::<ByteDomain, &str>::new(
IrNode::Assertion(AssertionId(1)),
assertions,
&EnginePolicy::new([]),
)
.unwrap_err();
assert_eq!(
error,
IrError::AssertionCycle(vec![AssertionId(1), AssertionId(2), AssertionId(1)])
);
}
#[test]
fn target_controls_dialect_extensions() {
let denied = PatternIr::<ByteDomain, &str>::new(
IrNode::Extension("backreference"),
BTreeMap::new(),
&EnginePolicy::new([]),
);
assert!(matches!(denied, Err(IrError::UnsupportedExtension(_))));
let admitted = PatternIr::<ByteDomain, &str>::new(
IrNode::Extension("backreference"),
BTreeMap::new(),
&EnginePolicy::new(["backreference"]),
);
assert!(admitted.is_ok());
}
}