use super::hir::Hir;
pub trait Visitor {
type Output;
fn visit_pre(&mut self, hir: &Hir) -> VisitAction;
fn visit_post(&mut self, _hir: &Hir) {}
fn visit_alternation_in(&mut self) {}
fn finish(self) -> Self::Output;
}
pub enum VisitAction {
Continue,
Skip,
}
pub fn visit<V: Visitor>(mut hir: &Hir, mut visitor: V) -> V::Output {
let mut stack = Vec::new();
loop {
if let VisitAction::Continue = visitor.visit_pre(hir) {
if let Some(frame) = build_stack_frame(hir) {
let child = frame.hir;
stack.push((frame, hir));
hir = child;
continue;
}
}
visitor.visit_post(hir);
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.hir;
stack.push((new_frame, parent));
hir = child;
break;
}
None => visitor.visit_post(parent),
}
}
None => {
return visitor.finish();
}
}
}
}
}
struct StackFrame<'a> {
hir: &'a Hir,
rest: &'a [Hir],
is_alternation: bool,
}
impl<'a> StackFrame<'a> {
fn next(self) -> Option<StackFrame<'a>> {
if self.rest.is_empty() {
None
} else {
Some(StackFrame {
hir: &self.rest[0],
rest: &self.rest[1..],
is_alternation: self.is_alternation,
})
}
}
}
fn build_stack_frame(hir: &Hir) -> Option<StackFrame<'_>> {
match hir {
Hir::Group(hir) | Hir::Repetition { hir, .. } => Some(StackFrame {
hir,
rest: &[],
is_alternation: false,
}),
Hir::Concat(hirs) | Hir::Alternation(hirs) if hirs.is_empty() => None,
Hir::Concat(hirs) => Some(StackFrame {
hir: &hirs[0],
rest: &hirs[1..],
is_alternation: false,
}),
Hir::Alternation(hirs) => Some(StackFrame {
hir: &hirs[0],
rest: &hirs[1..],
is_alternation: true,
}),
_ => None,
}
}