use std::ops::ControlFlow::{self, Break, Continue};
use super::{tree::Child, Cst};
use crate::cst::{Node, NodeId, TokenId};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Visit {
Abort,
Stop,
Skip,
Continue,
}
pub struct VisitCtx<'a> {
pub cst: &'a Cst,
}
impl<'a> VisitCtx<'a> {
pub fn node(&self, id: NodeId) -> Option<&Node> {
self.cst.node(id)
}
}
#[allow(unused_variables)]
pub trait Visitor {
#[must_use]
fn enter_tree(&mut self, ctx: &VisitCtx<'_>, tree: NodeId) -> Visit {
Visit::Continue
}
#[must_use]
fn exit_tree(&mut self, ctx: &VisitCtx<'_>, tree: NodeId) -> Visit {
Visit::Continue
}
#[must_use]
fn visit_token(&mut self, ctx: &VisitCtx<'_>, token: TokenId, parent: NodeId) -> Visit {
Visit::Continue
}
}
pub trait VisitorExt: Sized + Visitor {
fn walk(mut self, tree: &Cst) -> Self {
tree.walk(&mut self);
self
}
}
impl<T: Sized + Visitor> VisitorExt for T {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WalkOutcome {
Completed,
Stopped,
Aborted,
}
enum Interrupt {
Unwind,
Abort,
}
enum Resume {
Siblings,
Parent,
}
type Walk = ControlFlow<Interrupt, Resume>;
impl Cst {
pub fn walk<V: Visitor>(&self, visitor: &mut V) -> WalkOutcome {
if self.nodes.is_empty() {
return WalkOutcome::Completed;
}
match self.walk_inner(visitor, NodeId(0)) {
Continue(_) => WalkOutcome::Completed,
Break(Interrupt::Unwind) => WalkOutcome::Stopped,
Break(Interrupt::Abort) => WalkOutcome::Aborted,
}
}
fn walk_inner<V: Visitor>(&self, visitor: &mut V, node_idx: NodeId) -> Walk {
let ctx = VisitCtx { cst: self };
let walked = match visitor.enter_tree(&ctx, node_idx) {
Visit::Abort => Break(Interrupt::Abort),
Visit::Stop => Break(Interrupt::Unwind),
Visit::Skip => Continue(()),
Visit::Continue => self.walk_children(visitor, &ctx, node_idx),
};
if let Break(Interrupt::Abort) = walked {
return Break(Interrupt::Abort);
}
match (walked, visitor.exit_tree(&ctx, node_idx)) {
(_, Visit::Abort) => Break(Interrupt::Abort),
(Break(Interrupt::Unwind), _) | (_, Visit::Stop) => Break(Interrupt::Unwind),
(_, Visit::Skip) => Continue(Resume::Parent),
(_, Visit::Continue) => Continue(Resume::Siblings),
}
}
fn walk_children<V: Visitor>(
&self,
visitor: &mut V,
ctx: &VisitCtx<'_>,
node_idx: NodeId,
) -> ControlFlow<Interrupt> {
for child in self.node(node_idx).unwrap().children.get(self) {
match child {
Child::Token(token) => match visitor.visit_token(ctx, *token, node_idx) {
Visit::Continue => {}
Visit::Skip => break,
Visit::Stop => return Break(Interrupt::Unwind),
Visit::Abort => return Break(Interrupt::Abort),
},
Child::Tree(child) => match self.walk_inner(visitor, *child)? {
Resume::Siblings => {}
Resume::Parent => break,
},
}
}
Continue(())
}
}