use crate::op::Op;
use vize_l0::ensure_sufficient_stack;
use vize_l0::id::NodeId;
mod nodes;
pub use nodes::{NodeEvent, NodeLimit, NodeRef, visit_events, visit_nodes};
#[derive(Debug, Clone, Default)]
pub struct PageWalk {
next: u32,
visits: u32,
exhausted: bool,
}
impl PageWalk {
#[must_use]
#[inline]
pub fn new() -> Self {
Self {
next: 0,
visits: 0,
exhausted: false,
}
}
#[inline]
pub fn mint(&mut self) -> Option<NodeId> {
self.visits = self.visits.saturating_add(1);
self.mint_attached()
}
#[inline]
pub fn mint_attached(&mut self) -> Option<NodeId> {
if self.exhausted {
return None;
}
match NodeId::from_index(self.next) {
Some(id) => {
self.next += 1;
Some(id)
}
None => {
self.exhausted = true;
None
}
}
}
#[must_use]
#[inline]
pub fn minted(&self) -> u32 {
self.next
}
#[must_use]
#[inline]
pub fn visits(&self) -> u32 {
self.visits
}
#[inline]
pub fn skip(&mut self, count: usize) {
if self.exhausted {
return;
}
match u32::try_from(count)
.ok()
.and_then(|count| self.next.checked_add(count))
{
Some(next) => self.next = next,
None => self.exhausted = true,
}
}
#[must_use]
#[inline]
pub fn is_exhausted(&self) -> bool {
self.exhausted
}
}
pub fn visit_ops<'a>(
walk: &mut PageWalk,
ops: &mut [Op<'a>],
visit: &mut impl FnMut(Option<NodeId>, &mut Op<'a>),
) {
ensure_sufficient_stack(|| visit_ops_guarded(walk, ops, visit));
}
fn visit_ops_guarded<'a>(
walk: &mut PageWalk,
ops: &mut [Op<'a>],
visit: &mut impl FnMut(Option<NodeId>, &mut Op<'a>),
) {
for op in ops.iter_mut() {
let id = walk.mint();
visit(id, op);
match op {
Op::Element(element) => {
walk.skip(element.bindings.len());
visit_ops(walk, &mut element.children.ops, visit);
}
Op::Component(component) => {
walk.skip(component.bindings.len());
visit_ops(walk, &mut component.children.ops, visit);
}
Op::Text(_) | Op::Interpolation(_) | Op::Comment(_) => {}
Op::If(if_op) => {
for branch in if_op.branches.iter_mut() {
visit_ops(walk, &mut branch.region.ops, visit);
}
}
Op::For(for_op) => visit_ops(walk, &mut for_op.region.ops, visit),
Op::OriginalFor(for_op) => visit_ops(walk, &mut for_op.region.ops, visit),
Op::Slot(slot) => {
walk.skip(slot.bindings.len());
visit_ops(walk, &mut slot.fallback.ops, visit);
}
}
}
}
#[inline]
pub fn assert_accounting(walk: &PageWalk, op_count: u32, pass: &str) {
assert!(
walk.exhausted || walk.next == op_count,
"{pass} pass renumbering diverged from the minted accounting: \
walked {} ops, the lowering minted {op_count}",
walk.next,
);
}