Adding Tree Navigators

Teaching the generic tree walker about new tree-shaped data

=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 Positional and Associative values.

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.navigators discovers the navigator.

  • Qwiratry::Tree::Navigator.make(:navigator<NAVIGATOR>) returns a navigator that does Qwiratry::Tree::Navigator::Base.

  • supported-types includes 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-children exposes 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

Qwiratry v0.0.9

Declarative query and data-walking architecture for Raku, with transformers, molds, walkers, and I/O pipelines.

Authors

  • Tim Nelson

License

Dependencies

SlangifyImplementation::Loader:ver<0.0.9+>Glob::Grammar

Test Dependencies

Provides

  • Qwiratry
  • Qwiratry::Context
  • Qwiratry::Format
  • Qwiratry::Format::Base
  • Qwiratry::Format::CSVdemo
  • Qwiratry::Format::JSONdemo
  • Qwiratry::Location
  • Qwiratry::Location::Base
  • Qwiratry::Location::File
  • Qwiratry::Mold
  • Qwiratry::Mold::Compiler
  • Qwiratry::Mold::Registry
  • Qwiratry::Mold::Slang
  • Qwiratry::Operator::Capability
  • Qwiratry::Operator::IO
  • Qwiratry::Operator::MapReduce
  • Qwiratry::Operator::Navigation
  • Qwiratry::Operator::Set
  • Qwiratry::Query::Evaluator::Eager
  • Qwiratry::Query::Evaluator::Filter
  • Qwiratry::Query::Evaluator::Join
  • Qwiratry::Query::Evaluator::Lazy
  • Qwiratry::Query::Evaluator::MapReduce
  • Qwiratry::Query::Evaluator::Navigation
  • Qwiratry::Query::Evaluator::Relational
  • Qwiratry::Query::Evaluator::Row
  • Qwiratry::Query::Evaluator::Set
  • Qwiratry::Query::Evaluator::Union
  • Qwiratry::Query::Extract
  • Qwiratry::Query::NamedJoins
  • Qwiratry::Query::RelationCommon
  • Qwiratry::Query::Runtime
  • Qwiratry::Query::Selector
  • Qwiratry::Query::Slang
  • Qwiratry::Query::Specificity
  • Qwiratry::Query::Topic
  • Qwiratry::QueryIterator
  • Qwiratry::QueryMatch
  • Qwiratry::Setup
  • Qwiratry::Strategy
  • Qwiratry::Strategy::ControlSignal
  • Qwiratry::Strategy::FinishResult
  • Qwiratry::Strategy::RewriteSpec
  • Qwiratry::Strategy::Traversal
  • Qwiratry::Suggest
  • Qwiratry::Table
  • Qwiratry::Table::Schema
  • Qwiratry::Transformer
  • Qwiratry::Transformer::Copy
  • Qwiratry::Transformer::TreeRewrite
  • Qwiratry::Tree::Navigator
  • Qwiratry::Tree::Navigator::Base
  • Qwiratry::Tree::Navigator::Filesystem
  • Qwiratry::Tree::Navigator::Match
  • Qwiratry::Tree::Navigator::RakuAST
  • Qwiratry::Tree::Replace
  • Qwiratry::Walker
  • Qwiratry::Walker::Capabilities
  • Qwiratry::Walker::Factory
  • Qwiratry::Walker::Implementation::Table
  • Qwiratry::Walker::Implementation::Tree
  • Qwiratry::Walker::Master
  • Qwiratry::Walker::Providing
  • X::Qwiratry

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.