Tree-sitter typed AST generator
This crate provides a build script function for generating a typed AST from a tree-sitter grammar. Inspired by rowan.
Setup
In your project's Cargo.toml:
[]
= { = "/path/to/tree-sitter-your-lang" }
= "0.1"
[]
= { = "/path/to/tree-sitter-your-lang" }
= "0.1"
In your project's build script (located at build.rs):
Finally, add a module to your project's source (e.g., in lib.rs) that includes the generated typed AST:
Note that if you're actively working on the tree-sitter grammar, you'll need to re-run tree-sitter generate each time you edit grammar.js, otherwise the parser and generated AST will be out-of-date with the grammar.
The generated AST
For each named rule in the grammar, a Rust struct named with this rule is generated. For example, the following rule:
produces the Rust code:
The methods cast and node of the AstNode trait can be used to go back-and-forth between tree_sitter::Node and the typed AST. Then, specific methods function and arguments grant access to specific fields of the rule. Note that such functions are only generated when field('func_name', $.item) is used.
The grammar's supertypes each generate a Rust enum. For example, the following:
will generate:
Enums will also produce {node}Visitor traits with methods to visit each possible node of the enum:
Finally, unnamed choices generate enums on the fly. For example:
will produce:
Example
A complete example is found in examples/lambda. The subfolder tree-sitter-lambda declares a grammar for an ML-style lambda calculus with booleans, integers, abstraction, application, let-in, and conditionals. This grammar was created by calling tree-sitter init, removing all non-Rust bindings, editing the grammar.js and test/corpus/expr.txt files, and then calling tree-sitter generate. Note that you may of course keep the non-Rust bindings if you want them.
The eval module contains a tree-walking interpreter for the language, and a few tests are in the root module of lambda. In more complex projects, you will probably want to first convert the typed AST to an intermediate representation, before interpreting and/or compiling.
Why not a proc macro?
Because you can inspect the generated code more easily, either by manually opening the file ./target/debug/build/[crate-name]-[hash]/out/ast.rs, or more practically by jumping to definitions in your LSP.