Adding Tree Navigators
=begin rakudoc
Overview
Tree navigators adapt concrete tree-shaped values to Qwiratry's generic tree operations.
Use this extension point when the existing tree walker semantics are right, but Qwiratry needs to learn how to inspect a new node model. A navigator answers questions such as:
Which types or roles it supports.
Which direct children a node has.
Which parent a node has, either from the node model or by walking from an origin.
Which attribute-like values a node exposes.
Do not add a new walker just because a tree uses custom node objects. Add a walker when traversal or query execution semantics change.
When To Add A Navigator
Add a TreeNavigator when:
A format parses into custom node objects rather than ordinary hashes and arrays.
An external object model, such as
RakuAST::Node, already owns parsing and rendering but still needs Qwiratry traversal.The default container navigator cannot identify children or parents for the data.
Do not add one when the data is already ordinary Positional or Associative
values that expose child values in the default shape.
Contract
A tree navigator does Qwiratry::Tree::Navigator::Base.
Prefer typed multi candidates so multiple dispatch selects the relevant
implementation. Broad Mu candidates should only be empty fallbacks.
When overriding tree-children in a custom navigator, also override the
Positional and/or Associative candidates you care about. A Mu-only
override does not hide the base typed candidates from the multi dispatch set.
use Qwiratry::Tree::Navigator::Base;
class Qwiratry::Tree::Navigator::Example
does Qwiratry::Tree::Navigator::Base {
method supported-types(--> List) {
(Example::Node,)
}
multi method tree-children(Example::Node $node --> List) {
$node.children.list
}
multi method tree-child-results(Example::Node $node, Mu $selector, Int :$limit --> List) {
# Prefer constrained lookup over fetching every child first.
self.tree-children($node).grep({ ... }).head($limit // Inf).list
}
}
supported-types
method supported-types(--> List)
Returns the types or roles this navigator supports. Qwiratry::Tree::Navigator
uses these to choose a navigator for a node.
tree-children
multi method tree-children(Positional $node --> List)
multi method tree-children(Associative $node --> List)
multi method tree-children(Mu $node --> List)
Returns direct children of $node.
The default associative behavior keeps demo trees that store a positional
children key. Other associative maps expose nested containers only, so scalar
JSON fields are not treated as structural children. Use tree-child-results or
tree-attribute-value to follow or read those keys.
tree-child-results
multi method tree-child-results(Positional $node, Mu $selector, Int :$limit --> List)
multi method tree-child-results(Associative $node, Mu $selector, Int :$limit --> List)
multi method tree-child-results(Mu $node, Mu $selector, Int :$limit --> List)
Returns children matching $selector, optionally capped by $limit.
Evaluators call this instead of fetching all children and filtering locally.
For ordinary associative maps, a string selector first tries hash-key lookup so
⪪ <owner> follows JSON keys. XML/HTML navigators should override this to use
element-child semantics instead.
Descendant walks (< ⪪⪪ >) are owned by the query evaluator
(DescendantEvaluator), which composes this one-step lookup with
tree-children. Navigators should not implement recursive descendant walks.
tree-parent
method tree-parent(Mu $node, Mu :$origin --> Mu)
Returns the direct parent of $node when known.
The base role first asks the node model for parent, if available. If
origin is supplied, it then falls back to walking from origin using the
navigator's tree-children method. Custom navigators only need to override
this when the node model has a better parent lookup.
tree-attributes
multi method tree-attributes(Associative $node --> Associative)
multi method tree-attributes(Mu $node --> Associative)
Returns attribute-like values when the node model has them. The default returns the node itself for associative values and an empty hash otherwise.
tree-attribute-value
multi method tree-attribute-value(Associative $node, Mu $key --> Mu)
multi method tree-attribute-value(Mu $node, Mu $key --> Mu)
Return one attribute or key value from $node. Attribute access goes through
the navigator so XML attributes and JSON keys can differ from child navigation.
⥷ uses tree-attribute-value.
Discovery
Navigator discovery is separate from format discovery. A navigator class lives under the tree navigator namespace:
Qwiratry::Tree::Navigator::<NAVIGATOR>
Add the navigator module to META6.json provides so discovery can load it:
"Qwiratry::Tree::Navigator::Example": "lib/Qwiratry/Tree/Navigator/Example.rakumod"
Then callers can inspect or create the navigator:
my @navigators = Qwiratry::Tree::Navigator.navigators;
my $navigator = Qwiratry::Tree::Navigator.make(:navigator<Example>);
Selection Order
Qwiratry::Tree::Navigator.tree-navigator-for($node) selects navigators in this order:
A caller-supplied navigator passed as
:navigator.Discovered navigators under
Qwiratry::Tree::Navigator::*.Domain-supplied navigators registered for metadata attached through
Qwiratry::Walker::Providing.Explicitly registered navigators selected by their
supported-types.The built-in default navigator for ordinary
PositionalandAssociativevalues.
Pass :origin when domain metadata or parent fallback should be resolved from
the traversal root:
my $navigator = Qwiratry::Tree::Navigator.tree-navigator-for($node, :origin($root));
Explicit Registration
Register a navigator directly when it is not supplied by a format module:
Qwiratry::Tree::Navigator.register-tree-navigator($navigator);
The navigator is selected for nodes matching one of its supported-types.
Register a navigator for one exact type or role when the registration site should own the mapping:
Qwiratry::Tree::Navigator.register-tree-navigator-for-type(Example::Node, $navigator);
Domain Registration
Register a navigator for roots or nodes carrying a domain label:
Qwiratry::Tree::Navigator.register-tree-navigator-for-domain('xml', $navigator);
Domain labels come from Qwiratry::Walker::Providing. This keeps navigator
domain metadata aligned with existing walker and schema metadata.
Navigator-Only Models
Some representations do not need Qwiratry parse or render classes. RakuAST is
the built-in example: the compiler owns parsing and rendering, but Qwiratry can
still provide Qwiratry::Tree::Navigator::RakuAST.
A navigator-only model should provide Qwiratry::Tree::Navigator::<NAVIGATOR>
without format ::Parse or ::Render classes.
Testing
Add tests that prove:
Qwiratry::Tree::Navigator.navigatorsdiscovers the navigator.Qwiratry::Tree::Navigator.make(:navigator<NAVIGATOR>)returns a navigator that doesQwiratry::Tree::Navigator::Base.supported-typesincludes the intended node type or role.Qwiratry::Tree::Navigator.tree-navigator-for($node)selects the navigator.Caller-supplied, domain-registered, and explicitly type-registered navigators are selected in the documented order.
tree-childrenexposes direct children.tree-parent($node, :origin($root))works when parent lookup is required.
Reference Material
Main Types
Qwiratry::Tree::Navigator::Base
Role implemented by tree navigators, and the built-in navigator for ordinary
Positional and Associative tree-shaped values.
Qwiratry::Tree::Navigator
Discovery service and registry for tree navigator implementations.
=end rakudoc