Imported from MichaelHatherly/TreeSitter.jl (
AGENTS.md). Install upstream withnpx skills add MichaelHatherly/TreeSitter.jl. Copyright stays with the author.
AGENTS.md
This file provides guidance to agentic coding tools when working with code in this repository.
Project Overview
TreeSitter.jl provides Julia bindings for tree-sitter, an incremental parsing system for programming tools. The package wraps the C library via FFI and supports 14 languages (bash, c, cpp, go, html, java, javascript, json, julia, php, python, ruby, rust, typescript).
Running Tests
julia --project=. -e 'using Pkg; Pkg.test()'
To run tests interactively:
julia --project=.
using TreeSitter, Test
include("test/runtests.jl")
Architecture
Three-Layer Structure
-
API Layer (
src/api.jl): Low-level FFI bindings to tree-sitter C library- Defines C structs (TSNode, TSTree, TSParser, TSQuery, etc.)
- Wraps all
ccallfunctions withts_*prefix - Dynamically discovers and loads language parsers via JLL packages
- Language loading mechanism: Imports
tree_sitter_*_jllpackages at module load time using regex matching (LANGUAGE_REGEX), buildsLANGUAGESdict mapping language symbols to (function, queries_dir) tuples - Query files: Loads
.scmquery files from language JLL artifact directories, supports custom queries insrc/queries/<language>/that override JLL-provided queries
-
Interface Layer (
src/interface.jl): Julia-friendly wrapper typeslist_parsers(jll_mod): Discovers available parser variants in a JLL moduleLanguage: Wraps language pointer with name symbol, supports optionalvariantparameterParser: Manages parser state, auto-finalizes via GC, supports optionalvariantparameter and alanguagesinjection setTreeandNode: Represent parse trees,Nodeuses value type wrappingAPI.TSNode; aTreeis also the root layer of its injection tree, carryingsource,language, injectedchildren, and rootunresolvedQueryandQueryCursor: Pattern matching with tree-sitter query syntax, supports optionalvariantparameter- Core APIs:
parse(),traverse(),children(),named_children() - Query predicates: filtering predicates
eq?,not-eq?,match?,not-match?,any-of?,not-any-of?, the quantifiedany-eq?/any-not-eq?/any-match?/any-not-match?family, the ancestor familyhas-ancestor?/not-has-ancestor?/nearest-ancestor?/ancestor-match?/not-ancestor-match?, the descendant pairhas-descendant?/not-has-descendant?, andstructure-eq?/not-structure-eq?; property checksis?/is-not?(built-innamed/missing/extra, unknown properties are no-ops);set!attaches metadata read viaproperty_settings()/property(). Directives (names ending in!) never filter a match - The ancestor and descendant families and
structure-eq?read the tree rather than capture text, so they need the nodes a capture bound and warn when given only string literals.structure-eq?andancestor-match?also need the source, which is whyeval_predicatetakes it has-descendant?walks the subtree under a capture, so it costs the size of that subtree per match where the ancestor predicates cost its depth. A broad capture combined with it is the expensive shape- Language injection (
src/injection.jl):ts_rangeconverts aNodeto a 0-basedAPI.TSRange;injection_sitesruns a grammar'sinjections.scm, resolving static/dynamic languages and honoringinjection.combined,injection.include-children, and single-line#offset!;parserecursively parses embedded languages into the returnedTree'schildren, layers sharing onesourcestring. AParser'slanguagesset pins the injectable grammars (no dynamic loading); given none, injected languages resolve dynamically viadefault_language_resolver/INJECTION_ALIASES. Unavailable, cyclic, and over-depth sites land in the root tree'sunresolved. Navigate layers withlayers,layer_at, andslice(::Tree)
-
Module (
src/TreeSitter.jl): Main entry point, exports public API
Key Patterns
- Resource Management: All C resources (Parser, Tree, Query, QueryCursor) use finalizers for automatic cleanup
- 1-based Indexing: Julia convention - C API uses 0-based, interface adds/subtracts 1
- Traversal:
traverse(f, node, iter)callsf(node, enter::Bool)twice per node (enter=true descending, enter=false ascending) - Byte Ranges:
byte_range(n)returns 1-based Julia byte indices, useslice(source, node)to extract text - Query Syntax: Use
query```...```langstring macro orQuery(:lang, source)constructor
Language Support
Languages are loaded dynamically via JLL dependencies. To add a language:
- Add
tree_sitter_<lang>_jllto Project.toml dependencies and compat - Uncomment the import in
src/api.jllines 597-611 - The
LANGUAGESdict auto-populates via regex matching on module load
Common Patterns
Parsing
parser = Parser(:julia)
tree = parse(parser, "f(x) = x + 1")
root_node = root(tree)
Tree Traversal
# Visit all nodes
traverse(tree) do node, enter
enter && println(node_type(node))
end
# Visit only named nodes
traverse(tree, named_children) do node, enter
# ...
end
Queries
# Using query macro
q = query```
(identifier) @var
(#match? @var "^[a-z]")
```julia
# Or construct directly
q = Query(:c, ["highlights"]) # Load from language query files
# Execute queries
for capture in each_capture(tree, q, source_text)
name = capture_name(q, capture)
text = slice(source_text, capture.node)
end
Node Inspection
node_type(n) # "identifier", "call_expression", etc.
is_named(n) # true for named nodes, false for punctuation
count_nodes(n) # total children count
count_named_nodes(n) # named children only
child(n, i) # i-th child (1-based)
child(n, "field_name") # child by field name
slice(source, n) # extract node text from source
Multi-Parser Support
# Discover available parsers in a JLL module
parsers = list_parsers(tree_sitter_php_jll) # [:php, :php_only]
# Use default parser (inferred from module name)
p1 = Parser(tree_sitter_php_jll) # Uses :php
# Use specific variant
p2 = Parser(tree_sitter_php_jll, :php_only)
# Works with Language and Query constructors too
lang = Language(tree_sitter_php_jll, :php_only)
query = Query(tree_sitter_php_jll, "(identifier) @id", :php_only)
Some JLL packages provide multiple parser variants (e.g., tree_sitter_php_jll has both :php with HTML support and :php_only for pure PHP). Use list_parsers() to discover what's available, and pass the variant symbol as an optional second parameter to constructors.
Development Notes
- The package uses Julia 1.6+ (see Project.toml compat)
- tree-sitter C library version 0.16.9 via tree_sitter_jll
- Most language parsers use 0.16.x versions, julia parser is 0.0.4
- Test suite validates all supported languages parse empty strings and checks specific parse outputs