tree-sitter-utils
A composable, parser-combinator-style abstraction over tree-sitter node
dispatch. Instead of writing ad-hoc loop { match node.kind() { ... } }
patterns, you build a chain of typed, zero-cost Handler combinators that
express "how to map a syntax-tree node (plus arbitrary context) to an output
value". The crate is fully language-agnostic; all grammar knowledge lives in
the consumer crate.
Quick start
Add the dependency:
[]
= "0.1"
= "0.26"
# plus your grammar crate, e.g. tree-sitter-python = "0.25"
Build a handler chain for a fictional language with four node kinds:
use ;
// Context your consumer crate supplies to every handler.
// Label nodes from a fictional grammar.
Illustrative example — Java-specific code lives in the consumer crate, not here
The original motivation was eliminating patterns like:
// Java consumer crate (NOT part of tree-sitter-utils)
private String
With tree-sitter-utils the same logic in the Java consumer crate collapses to:
// Java consumer crate — grammar strings stay here, NOT in tree-sitter-utils.
use ;
// type MyJavaCtx = ();
// fn label_method(_: &tree_sitter::Node<'_>, _: &()) -> String { String::new() }
// fn label_class(_: &tree_sitter::Node<'_>, _: &()) -> String { String::new() }
The handler is built once, stored cheaply (no heap allocation in the hot
path), and called with a single handler.handle(input) per node.
Combinator reference
| Combinator | Signature sketch | Semantics |
|---|---|---|
.or(other) |
(H, H2) -> Or<H, H2> |
Try self; on None, try other. |
.when(pred) |
(H, P: NodePredicate) -> When<H, P> |
Run self only when pred returns true. |
.for_kinds(kinds) |
(H, &'static [&'static str]) -> When<H, KindIs> |
Sugar for .when(kind_is(kinds)). |
.map(f) |
(H, Fn(R)->R2) -> Map<H, F, R> |
Transform a Some(out) result. |
.map_input(f) |
(H, Fn(Input)->Input) -> MapInput<H, F> |
Transform the Input before passing it to self. |
.and_then(f) |
(H, Fn(Input,R)->Option<R2>) -> AndThen<H,F,R> |
Flat-map: feed (input, out) into f on success. |
.climb(stop_kinds) |
(H, &'static [&'static str]) -> Climb<H> |
On None, walk parent() and retry self until a stop-kind or root. |
.or_else_climb(other, stop_kinds) |
(H, H2, &'static [&'static str]) -> OrElseClimb<H,H2> |
Try self; on None, try other on each ancestor up to stop-kind. |
.boxed() |
H -> BoxedHandler<Ctx, R> |
Erase the type for dynamic dispatch (heap-allocates). |
Free-function constructors:
| Function | Returns | Semantics |
|---|---|---|
handler_fn(f) |
HandlerFn<F> |
Wrap an infallible Fn(Input)->R; always returns Some. |
never() |
Never<Ctx, R> |
Always returns None. |
always(value) |
Always<R> |
Always returns Some(value.clone()). |
dispatch_on_kind(table) |
DispatchOnKind<Ctx, R> |
Static kind→handler lookup table. |
first_of(handlers) |
FirstOf<Ctx, R> |
Try a Vec<BoxedHandler> in order; return first Some. |