tree_haver v5.0.0 released!
5.0.0 - 2026-01-11
- TAG: v5.0.0
- COVERAGE: 92.04% – 2289/2487 lines in 30 files
- BRANCH COVERAGE: 79.33% – 929/1171 branches in 30 files
- 96.21% documented
Added
- Shared Example Groups for Backend API Compliance Testing
node_api_examples.rb- Tests for Node API compliance:"node api compliance"- Core Node interface (type, start_byte, end_byte, children)"node position api"- Position API (start_point, end_point, start_line, end_line, source_position)"node children api"- Children traversal (#child, #first_child, #last_child)"node enumerable behavior"- Enumerable methods (#each, #map, #select, #find)"node comparison behavior"- Comparison and equality (#==, #<=>, #hash)"node text extraction"- Text content (#text, #to_s)"node inspection"- Debug output (#inspect)
tree_api_examples.rb- Tests for Tree API compliance:"tree api compliance"- Core Tree interface (root_node, source, errors, warnings, comments)"tree error handling"- Error detection (#has_error?, #errors)"tree traversal"- Depth-first traversal via root_node
parser_api_examples.rb- Tests for Parser API compliance:"parser api compliance"- Core Parser interface (#parse, #parse_string, #language=)"parser incremental parsing"- Incremental parsing support"parser error handling"- Error recovery behavior
language_api_examples.rb- Tests for Language API compliance:"language api compliance"- Core Language interface (#backend, #name/#language_name)"language comparison"- Comparison and equality"language factory methods"- Factory methods (.from_library, .from_path)
backend_api_examples.rb- Tests for Backend module API compliance:"backend module api"- Backend availability and capabilities"backend class structure"- Nested class verification"backend integration"- Full parse cycle testing
spec/support/shared_examples.rb- Master loader for all shared examplesspec/integration/backend_api_compliance_spec.rb- Integration tests using all shared examples
- Parslet Backend: New pure Ruby PEG parser backend (
TreeHaver::Backends::Parslet)- Wraps Parslet-based parsers (like the
tomlgem) to provide a pure Ruby alternative to tree-sitter Parslet.available?- Check if parslet gem is availableParslet.capabilities- Returns{ backend: :parslet, query: false, bytes_field: true, incremental: false, pure_ruby: true }Parslet::Language- Wrapper for Parslet grammar classesLanguage.new(grammar_class)- Create from a Parslet::Parser subclassLanguage.from_library(path, symbol:, name:)- API-compatible lookup via LanguageRegistry#language_name/#name- Derive language name from grammar class
Parslet::Parser- Wrapper that creates parser instances from grammar classes- Accepts both raw grammar class and Language wrapper (normalized API)
Parslet::Tree- Wraps Parslet parse results, inherits fromBase::TreeParslet::Node- Unified node interface, inherits fromBase::Node- Supports both Hash nodes (with named children) and Array nodes (with indexed children)
#type- Returns the node type (key name or “array”/”document”)#children- Returns child nodes#child_by_field_name(name)- Access named children in Hash nodes#text- Returns the matched text from Parslet::Slice#start_byte,#end_byte- Byte positions from Parslet::Slice#start_point,#end_point- Line/column positions (computed from source)
- Registered with
BackendRegistry.register_availability_checker(:parslet)
- Wraps Parslet-based parsers (like the
- RSpec Dependency Tags: Added
parslet_available?method- Checks if parslet gem is installed via
BackendRegistry.available?(:parslet) :parslet_backendtag for specs requiring Parslet:not_parslet_backendnegated tag for specs that should skip when Parslet is available
- Checks if parslet gem is installed via
- RSpec Dependency Tags: Added
toml_gem_available?method and updatedany_toml_backend_available?:toml_gemtag for specs requiring thetomlgem to be available:not_toml_gemnegated tag for specs that should skip when thetomlgem is not available
- ParsletGrammarFinder: Utility for discovering and registering Parslet grammar gems
ParsletGrammarFinder.new(language:, gem_name:, grammar_const:, require_path:)- Find Parslet grammars#available?- Check if the Parslet grammar gem is installed and functional#grammar_class- Get the resolved Parslet::Parser subclass#register!- Register the grammar with TreeHaver- Auto-loads via
TreeHaver::PARSLET_DEFAULTSfor known languages (toml)
- TreeHaver.register_language: Extended with
grammar_class:parameter for Parslet grammars - TreeHaver.parser_for: Extended with
parslet_config:parameter for explicit Parslet configuration MRI::Language#language_name/#name- Derive language name from symbol or pathFFI::Language#language_name/#name- Derive language name from symbol or path- spec_helper.rb: Added
require "toml"to load the toml gem for Parslet backend tests
Changed
- BREAKING:
TreeHaver::Languageconverted from class to module- Previously
TreeHaver::Languagewas a class that wrapped backend language objects - Now
TreeHaver::Languageis a module providing factory methods (method_missingfor dynamic language loading) - Backend-specific language classes (e.g.,
TreeHaver::Backends::MRI::Language) are now the concrete implementations - Code that instantiated
TreeHaver::Language.new(...)directly must be updated to use backend-specific classes or the factory methods
- Previously
- BREAKING:
TreeHaver::Treenow inherits fromTreeHaver::Base::TreeTreeHaver::Treeis now a proper subclass ofTreeHaver::Base::Tree- Inherits
inner_tree,source,linesattributes from base class - Base class provides default implementations; subclass documents divergence
- BREAKING:
TreeHaver::Nodenow inherits fromTreeHaver::Base::NodeTreeHaver::Nodeis now a proper subclass ofTreeHaver::Base::Node- Inherits
inner_node,source,linesattributes from base class - Base class documents the API contract; subclass documents divergence
- BREAKING:
Citrus::NodeandCitrus::Treenow inherit from Base classesCitrus::Nodenow inherits fromTreeHaver::Base::NodeCitrus::Treenow inherits fromTreeHaver::Base::Tree- Removes duplicated methods, uses inherited implementations
- Adds
#language_name/#namemethods for API compliance
- BREAKING:
Parslet::NodeandParslet::Treenow inherit from Base classesParslet::Nodenow inherits fromTreeHaver::Base::NodeParslet::Treenow inherits fromTreeHaver::Base::Tree- Removes duplicated methods, uses inherited implementations
- Base::Node#child now returns nil for negative indices (tree-sitter API compatibility)
- Citrus::Parser#language= now accepts Language wrapper or raw grammar module
- Both patterns now work:
parser.language = TomlRB::Documentorparser.language = Citrus::Language.new(TomlRB::Document)
- Both patterns now work:
- Parslet::Parser#language= now accepts Language wrapper or raw grammar class
- Both patterns now work:
parser.language = TOML::Parsletorparser.language = Parslet::Language.new(TOML::Parslet)
- Both patterns now work:
- TreeHaver::Parser#unwrap_language now passes Language wrappers directly to Citrus/Parslet backends
- Previously unwrapped to raw grammar; now backends handle their own Language wrappers
- Language.method_missing: Now recognizes
:parsletbackend type and createsParslet::Languageinstances - Parser: Updated to recognize Parslet languages and switch to Parslet parser automatically
#backendnow returns:parsletfor Parslet-based parsers#language=detectsParslet::Languageand switches implementationhandle_parser_creation_failuretries Parslet as fallback after Citrusunwrap_languageextractsgrammar_classfor Parslet languages
Fixed
- FFI Backend Compliance Tests: Fixed tests to use
TreeHaver::Parserwrapper instead of rawFFI::Parser- Raw FFI classes (
FFI::Tree,FFI::Node) don’t have full API (missing#children,#text,#source, etc.) - TreeHaver wrapper classes (
TreeHaver::Tree,TreeHaver::Node) provide the complete unified API - Tests now properly test the wrapped API that users actually interact with
- Raw FFI classes (
- Parslet TOML Sources: Fixed test sources to be valid for the
tomlgem’s Parslet grammar- Grammar requires table sections (not bare key-value pairs at root)
- Grammar requires trailing newlines
- Examples: Fixed broken markdown examples that referenced non-existent TreeHaver backends
commonmarker_markdown.rb- Rewrote to use commonmarker gem directly (not a TreeHaver backend)markly_markdown.rb- Rewrote to use markly gem directly with correctsource_positionAPIcommonmarker_merge_example.rb- Fixed to usecommonmarker/mergegem properlymarkly_merge_example.rb- Fixed to usemarkly/mergegem properlyparslet_toml.rb- Rewrote to properly use TreeHaver’s Parslet backend with language registration
- Examples: Fixed
run_all.rbtest runner- Added parslet example to the test list
- Changed markdown examples to use
backend: "standalone"(they’re not TreeHaver backends) - Added MRI+TOML to known incompatibilities (parse returns nil)
- Added proper skip reason messages for all known incompatibilities
- Examples: Updated
examples/README.mddocumentation- Added Parslet backend section with usage examples
- Renamed “Commonmarker Backend” and “Markly Backend” to “Commonmarker (Standalone)” and “Markly (Standalone)”
- Clarified that commonmarker and markly are standalone parsers, not TreeHaver backends
- Duplicate Constants: Removed duplicate
CITRUS_DEFAULTSandPARSLET_DEFAULTSdefinitions- Constants were defined twice in
tree_haver.rb(lines 170 and 315) - This was causing “already initialized constant” warnings on every require
- Constants were defined twice in
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?