use boreal_parser::regex::Node;
pub trait Visitor {
type Output;
type Err;
#[allow(clippy::missing_errors_doc)]
fn visit_pre(&mut self, node: &Node) -> Result<VisitAction, Self::Err>;
#[allow(clippy::missing_errors_doc)]
fn visit_post(&mut self, _node: &Node) -> Result<(), Self::Err> {
Ok(())
}
fn visit_alternation_in(&mut self) {}
#[allow(clippy::missing_errors_doc)]
fn finish(self) -> Result<Self::Output, Self::Err>;
}
pub enum VisitAction {
Continue,
Skip,
}
pub fn visit<V: Visitor>(mut node: &Node, mut visitor: V) -> Result<V::Output, V::Err> {
let mut stack = Vec::new();
loop {
if let VisitAction::Continue = visitor.visit_pre(node)? {
if let Some(frame) = build_stack_frame(node) {
let child = frame.node;
stack.push((frame, node));
node = child;
continue;
}
}
visitor.visit_post(node)?;
loop {
match stack.pop() {
Some((frame, parent)) => {
match frame.next() {
Some(new_frame) => {
if new_frame.is_alternation {
visitor.visit_alternation_in();
}
let child = new_frame.node;
stack.push((new_frame, parent));
node = child;
break;
}
None => visitor.visit_post(parent)?,
}
}
None => {
return visitor.finish();
}
}
}
}
}
struct StackFrame<'a> {
node: &'a Node,
rest: &'a [Node],
is_alternation: bool,
}
impl<'a> StackFrame<'a> {
fn next(self) -> Option<StackFrame<'a>> {
if self.rest.is_empty() {
None
} else {
Some(StackFrame {
node: &self.rest[0],
rest: &self.rest[1..],
is_alternation: self.is_alternation,
})
}
}
}
fn build_stack_frame(node: &Node) -> Option<StackFrame> {
match node {
Node::Group(node) | Node::Repetition { node, .. } => Some(StackFrame {
node,
rest: &[],
is_alternation: false,
}),
Node::Concat(nodes) | Node::Alternation(nodes) if nodes.is_empty() => None,
Node::Concat(nodes) => Some(StackFrame {
node: &nodes[0],
rest: &nodes[1..],
is_alternation: false,
}),
Node::Alternation(nodes) => Some(StackFrame {
node: &nodes[0],
rest: &nodes[1..],
is_alternation: true,
}),
_ => None,
}
}