///|
/// A visitor over the node model: one method per group of node types (see
/// `accept` for which method sees which node). Every method has a default:
/// `visit_*` returns `Continue` and `leave` does nothing, so a visitor
/// implements only what it needs. The visitor's state belongs to the caller.
/// See `accept` for an example.
pub(open) trait Visitor {
/// Declarations other than functions: type aliases, custom types, ports,
/// infix declarations and destructurings (top-level or in a `let`).
fn visit_declaration(Self, NodeRef) -> Control = _
/// Function declarations, top-level or in a `let`.
fn visit_function(Self, NodeRef) -> Control = _
/// Expressions, the `case`, `let` and `if` expressions included.
fn visit_expression(Self, NodeRef) -> Control = _
/// Patterns, in arguments, case branches, lambdas and destructurings.
fn visit_pattern(Self, NodeRef) -> Control = _
/// Type annotations, in signatures, ports, aliases and constructors.
fn visit_type(Self, NodeRef) -> Control = _
/// Case branches (the `case` expression itself goes to `visit_expression`).
fn visit_case(Self, NodeRef) -> Control = _
/// Import declarations.
fn visit_import(Self, NodeRef) -> Control = _
/// Regular comments, `--` and `{- -}` (doc comments go to `visit_other`).
fn visit_comment(Self, NodeRef) -> Control = _
/// Doc attributes, `@name …` and `@docs`.
fn visit_attribute(Self, NodeRef) -> Control = _
/// Every other node: file, module, names, signatures, implementations, doc
/// comments (`documentation`), …
fn visit_other(Self, NodeRef) -> Control = _
/// Runs after a node's children, as in `walk`.
fn leave(Self, NodeRef) -> Unit = _
}
///|
impl Visitor with fn visit_declaration(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_function(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_expression(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_pattern(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_type(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_case(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_import(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_comment(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_attribute(_self, _node) {
Continue
}
///|
impl Visitor with fn visit_other(_self, _node) {
Continue
}
///|
impl Visitor with fn leave(_self, _node) {
()
}
///|
fn[V : Visitor] dispatch(visitor : V, n : NodeRef) -> Control {
match n {
Declaration(_, _) | LetDeclaration(_) =>
if n.kind() == "function" {
visitor.visit_function(n)
} else {
visitor.visit_declaration(n)
}
Expression(_) => visitor.visit_expression(n)
Pattern(_) => visitor.visit_pattern(n)
TypeAnnotation(_) => visitor.visit_type(n)
Case(_) => visitor.visit_case(n)
Import(_) => visitor.visit_import(n)
Comment(_) => visitor.visit_comment(n)
Attribute(_) => visitor.visit_attribute(n)
_ => visitor.visit_other(n)
}
}
///|
/// Walk `root` (see `walk`) and call one `visitor` method per node:
/// functions (top-level or `let`) → `visit_function`; other declarations →
/// `visit_declaration`; expressions, patterns, types → `visit_expression`,
/// `visit_pattern`, `visit_type`; case branches → `visit_case`; imports,
/// comments, doc attributes → `visit_import`, `visit_comment`,
/// `visit_attribute`; every other node (doc comments included) →
/// `visit_other`. The method's `Control` steers the walk; `leave` runs after
/// each node's children.
///
/// A visitor needs its own type, so this example is not a doc test (a doc
/// test holds only `test` blocks). The cookbook article "Choose a traversal"
/// has a tested visitor:
/// https://github.com/moonrockz/krueger/blob/main/docs/cookbook/traversal.mbt.md
///
/// ```mbt nocheck
/// struct Names {
/// functions : Array[String]
/// mut patterns : Int
/// }
///
/// impl @syntax.Visitor for Names with fn visit_function(self, n) {
/// match n {
/// Declaration({ value: FunctionDeclaration(f), .. }, _) =>
/// self.functions.push(f.declaration.value.name.value)
/// _ => ()
/// }
/// Continue
/// }
///
/// impl @syntax.Visitor for Names with fn visit_pattern(self, _) {
/// self.patterns += 1
/// Continue
/// }
///
/// test {
/// let src = "module Main exposing (..)\n\nadd x y =\n x + y\n\nz = 0\n"
/// let result = @parser.parse_module(
/// @scanner.SourceText::new(src),
/// @scanner.DefaultScanner::new(),
/// )
/// let root = @syntax.NodeRef::of_result(result).unwrap()
/// let names = { functions: [], patterns: 0, }
/// @syntax.accept(root, names)
/// debug_inspect(names.functions, content="[\"add\", \"z\"]")
/// inspect(names.patterns, content="2")
/// }
/// ```
pub fn[V : Visitor] accept(root : NodeRef, visitor : V) -> Unit {
walk(root, n => dispatch(visitor, n), leave=n => visitor.leave(n))
}