š“ TreeHaver: cross-Ruby AST parsing adapter for tree-sitter, parslet, & citrus
Published on December 19, 2025 by Peter Boling
š» Synopsis
TreeHaver is a cross-Ruby adapter for the tree-sitter, Citrus, and Parslet parsing libraries and other dedicated parsing tools that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using grammars, regardless of your Ruby implementation.
The Adapter Pattern: Like Faraday, but for Parsing
Ā
If you've used Faraday, multi_json, or multi_xml, you'll feel right at home with TreeHaver. These gems share a common philosophy:
Gem Unified API for Backend Examples
| FaradayĀ | HTTP requestsĀ | Net::HTTP, Typhoeus, Patron, Excon
| multi_jsonĀ | JSON parsingĀ | Oj, Yajl, JSON gem
| multi_xmlĀ | XML parsingĀ | Nokogiri, LibXML, Ox
| TreeHaverĀ | Code parsingĀ | MRI, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet
Learn once, write anywhere.
Write once, run anywhere.
Ā
Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets you swap tree-sitter backends. Your parsing code remains the same whether you're running on MRI with native C extensions, JRuby with FFI, or TruffleRuby.
Ā
If you've used Faraday, multi_json, or multi_xml, you'll feel right at home with TreeHaver. These gems share a common philosophy:
Gem Unified API for Backend Examples
| FaradayĀ | HTTP requestsĀ | Net::HTTP, Typhoeus, Patron, Excon
| multi_jsonĀ | JSON parsingĀ | Oj, Yajl, JSON gem
| multi_xmlĀ | XML parsingĀ | Nokogiri, LibXML, Ox
| TreeHaverĀ | Code parsingĀ | MRI, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet
Learn once, write anywhere.
Write once, run anywhere.
Ā
Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets you swap tree-sitter backends. Your parsing code remains the same whether you're running on MRI with native C extensions, JRuby with FFI, or TruffleRuby.
Ā
# Your code stays the same regardless of backend
parser = TreeHaver::Parser.new
parser.language = TreeHaver::Language.from_library("/path/to/grammar.so")
tree = parser.parse(source_code)
# TreeHaver automatically picks the best available backend:
# - MRI: ruby_tree_sitter, tree_stump, ffi, prism, psych, commonmarker, markly, citrus, parslet
# - JRuby: ffi, java-tree-sitter (not a gem, but the jtreesitter maven package), prism, psych, commonmarker, markly, citrus, parslet
# - TruffleRuby: prism, psych, commonmarker, markly, citrus, parslet
# (tree-sitter backends don't work on Truffleruby with ffi gem due to FFI STRUCT_BY_VALUE limitation)
Key Features
Ā
- Universal Ruby Support: Works on MRI Ruby, JRuby, and TruffleRuby
- 10 Parsing Backends - Choose the right backend for your needs:
- Tree-sitter Backends (high-performance, incremental parsing):
- MRI Backend: Leverages ruby_tree_sitter gem (C extension, fastest on MRI)
- Note: ruby_tree_sitter currently requires unreleased fixes in the pboling fork, tree_haver branch.
- Rust Backend: Uses tree_stump gem (Rust with precompiled binaries)Ā
- Note: Use tree_stump v0.2.0 or newer (fixes are released).
- FFI Backend: Pure Ruby FFI bindings to libtree-sitter (JRuby only; TruffleRuby's FFI doesn't support tree-sitter's struct-by-value returns)
- Java Backend: Native Java integration for JRuby with java-tree-sitter / jtreesitter grammar JARs
- MRI Backend: Leverages ruby_tree_sitter gem (C extension, fastest on MRI)
- Language-Specific Backends (native parser integration):
- Prism Backend: Ruby's official parser (stdlib in Ruby 3.4+)
- Psych Backend: Ruby's YAML parser (stdlib)
- Commonmarker Backend: Fast Markdown parser (comrak-based, Rust)
- Markly Backend: GitHub Flavored Markdown (cmark-gfm-based, C)
- Pure Ruby Fallback:Ā
- Tree-sitter Backends (high-performance, incremental parsing):
- Automatic Backend Selection: Intelligently selects the best backend for your Ruby implementation
- Language Agnostic: Parse any language - Ruby, Markdown, YAML, JSON, Bash, TOML, JavaScript, etc.
- Grammar Discovery: Built-in GrammarFinder utility for platform-aware grammar library discovery
- Unified Position API: Consistent start_line, end_line, source_position across all backends
- Thread-Safe: Built-in language registry with thread-safe caching
- Minimal API Surface: Simple, focused API that covers the most common use cases
TruffleRuby LimitationsĀ
TruffleRuby has no working tree-sitter backend:Ā
- FFI: TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)
- MRI/Rust: C and Rust extensions require MRI's C API internals (RBasic.flags, rb_gc_writebarrier, etc.) that TruffleRuby doesn't expose
TruffleRuby users should use: Prism (Ruby), Psych (YAML), Citrus/Parslet (e.g., TOML via toml-rb/toml), or potentially Commonmarker/Markly (Markdown).
JRuby Limitations
JRuby runs on the JVM and cannot load native .so extensions via Ruby's C API:
Ā
Ā
- MRI/Rust: C and Rust extensions simply cannot be loaded
- FFI: Works! JRuby has excellent FFI support
- Java: Works! The Java backend uses jtreesitter (requires >= 0.26.0)
JRuby users should use: Java backend (best performance, full API) or FFI backend for tree-sitter, plus Prism, Psych, Citrus/Parslet for other formats.
Why TreeHaver?
tree-sitter is a powerful parser generator that creates incremental parsers for many programming languages. However, integrating it into Ruby applications can be challenging:
Ā
Ā
- MRI-based C extensions don't work on JRuby
- FFI-based solutions may not be optimal for MRI
- Managing different backends for different Ruby implementations is cumbersome
TreeHaver solves these problems by providing a unified API that automatically selects the appropriate backend for your Ruby implementation, allowing you to write code once and run it anywhere.