Building an AST
The generated AST includes:
- Structs representing AST nodes.
- Implementations of the AstNode trait.
- A TryFrom implementation to build nodes from Tree-sitter.
Building an AST requires a salsa::Database
, which is used to accumulate errors during parsing.
Using TryFrom
Each AST node type provides a TryFrom
implementation that accepts a TryFromParams
tuple. This is used to convert a Tree-sitter node into an AST node.
/// Parameters passed to `TryFrom` implementations for AST nodes.
pub type TryFromParams<'from> = (
&'from Node<'from>, // Tree-sitter node
&'from dyn salsa::Database, // Salsa database
&'from mut Builder, // AST builder
usize, // Node ID (auto-incremented by the builder)
Option<usize>, // Optional parent node ID
);
Example: Building a root node
// Create the AST builder
let mut builder = auto_lsp::core::ast::Builder::default();
// Build the root node from the Tree-sitter parse tree
let root = ast::generated::SourceFile::try_from((
&tree.root_node(),
db, // Your salsa database
&mut builder,
0, // Root node ID
None, // Root has no parent
))?;
// Retrieve all non-root nodes from the builder
let mut nodes = builder.take_nodes();
// Add the root node manually
nodes.push(std::sync::Arc::new(root));
// Optional: Sort the nodes by ID
nodes.sort_unstable();
Retrieving Errors
Errors that occur during AST construction are accumulated using the ParseErrorAccumulator
struct. This allows partial AST construction even when some nodes fail to parse.
It’s recommended to use TryFrom
inside a salsa::tracked
function so you can retrieve errors using salsa::accumulated
.
The default crate provides a get_ast
query that builds the AST and collects errors. It is compatible with BaseDatabase
.
ParseError Structure
The ParseError
enum represents errors that can be encountered during parsing.
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
#[error("{error:?}")]
LexerError {
range: lsp_types::Range,
#[source]
error: LexerError,
},
#[error("{error:?}")]
AstError {
range: lsp_types::Range,
#[source]
error: AstError,
},
}
- LexerError — Issues from Tree-sitter's lexer
- AstError — Issues from a TryFrom implementation
You can retrieve lexer errors via get_tree_sitter_errors() from the default
crate.
LexerError
can either be a missing symbol error or a syntax error.
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum LexerError {
#[error("{error:?}")]
Missing {
range: lsp_types::Range,
error: String,
},
#[error("{error:?}")]
Syntax {
range: lsp_types::Range,
error: String,
},
}
ParsedAst struct
The result of get_ast
is a ParsedAst
struct, which holds the list of AST nodes and implements Deref
for direct iteration.
pub struct ParsedAst {
pub nodes: Arc<Vec<Arc<dyn AstNode>>>,
}
You can work with AST nodes in two ways:
- Downcast a node to a concrete type and access its fields.
- Iterate over all nodes and filter or match on their type.
Methods
get_root
: Returns the root node.descendant_at
: Returns the first node that contains the given offset.
Example: Filtering nodes by type
let functions = get_ast(db file)
.iter()
.filter_map(|node| node.is::<FunctionDefinition>())
.collect();
For convenience when calling methods on multiple node types, use the dispatch
or dispatch_once
macros.
See Dispatch Pattern.