(** Reusable context/ancestor tracking for checkers. Many checkers need to track element ancestors, depth, or maintain context stacks during DOM traversal. This module provides common utilities to reduce duplication. *) (** Generic stack-based context tracker. *) module Stack : sig type 'a t (** Create an empty context stack. *) val create : unit -> 'a t (** Reset the stack to empty. *) val reset : 'a t -> unit (** Push a context onto the stack. *) val push : 'a t -> 'a -> unit (** Pop a context from the stack. Returns None if empty. *) val pop : 'a t -> 'a option (** Get the current (top) context without removing it. *) val current : 'a t -> 'a option (** Get current depth (number of items on stack). *) val depth : 'a t -> int (** Check if stack is empty. *) val is_empty : 'a t -> bool (** Get all ancestors (bottom to top). *) val to_list : 'a t -> 'a list (** Check if any ancestor satisfies predicate. *) val exists : 'a t -> ('a -> bool) -> bool (** Find first ancestor satisfying predicate (top to bottom). *) val find : 'a t -> ('a -> bool) -> 'a option (** Iterate over all contexts (top to bottom). *) val iter : 'a t -> ('a -> unit) -> unit end = struct type 'a t = { mutable stack : 'a list; mutable len : int } let create () = { stack = []; len = 0 } let reset t = t.stack <- []; t.len <- 0 let push t x = t.stack <- x :: t.stack; t.len <- t.len + 1 let pop t = match t.stack with | [] -> None | x :: rest -> t.stack <- rest; t.len <- t.len - 1; Some x let current t = match t.stack with | [] -> None | x :: _ -> Some x let depth t = t.len (* O(1) instead of O(n) *) let is_empty t = t.len = 0 let to_list t = List.rev t.stack let exists t f = List.exists f t.stack let find t f = List.find_opt f t.stack let iter t f = List.iter f t.stack end (** Simple depth counter for tracking nesting level. *) module Depth : sig type t (** Create a depth counter starting at 0. *) val create : unit -> t (** Reset depth to 0. *) val reset : t -> unit (** Increment depth (entering element). *) val enter : t -> unit (** Decrement depth (leaving element). Returns false if was already 0. *) val leave : t -> bool (** Get current depth. *) val get : t -> int (** Check if inside (depth > 0). *) val is_inside : t -> bool end = struct type t = { mutable depth : int } let create () = { depth = 0 } let reset t = t.depth <- 0 let enter t = t.depth <- t.depth + 1 let leave t = if t.depth > 0 then begin t.depth <- t.depth - 1; true end else false let get t = t.depth let is_inside t = t.depth > 0 end (** Element name stack for tracking ancestors by name. *) module Ancestors : sig type t (** Create an empty ancestor tracker. *) val create : unit -> t (** Reset to empty. *) val reset : t -> unit (** Push an element name onto the ancestor stack. *) val push : t -> string -> unit (** Pop an element from the ancestor stack. *) val pop : t -> unit (** Get the immediate parent element name. *) val parent : t -> string option (** Check if an element name is an ancestor. *) val has_ancestor : t -> string -> bool (** Get depth (number of ancestors). *) val depth : t -> int (** Get all ancestor names (outermost first). *) val to_list : t -> string list end = struct type t = { mutable stack : string list; mutable len : int } let create () = { stack = []; len = 0 } let reset t = t.stack <- []; t.len <- 0 let push t name = t.stack <- name :: t.stack; t.len <- t.len + 1 let pop t = match t.stack with | _ :: rest -> t.stack <- rest; t.len <- t.len - 1 | [] -> () let parent t = match t.stack with | x :: _ -> Some x | [] -> None let has_ancestor t name = List.mem name t.stack let depth t = t.len (* O(1) instead of O(n) *) let to_list t = List.rev t.stack end