auto_lsp_core/ast/
builder.rs

1/*
2This file is part of auto-lsp.
3Copyright (C) 2025 CLAUZEL Adrien
4
5auto-lsp is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <http://www.gnu.org/licenses/>
17*/
18
19use crate::errors::ParseErrorAccumulator;
20use crate::{ast::AstNode, errors::AstError};
21use salsa::Accumulator;
22use std::ops::ControlFlow;
23use std::sync::Arc;
24use tree_sitter::{Node, TreeCursor};
25
26/// Parameters for [`TryFrom`] implementations of AST nodes.
27pub type TryFromParams<'from> = (
28    &'from Node<'from>,         // Last node to convert
29    &'from dyn salsa::Database, // Salsa Database
30    &'from mut Builder,         // Builder
31    usize,                      // Node ID (incremented by Builder struct)
32    Option<usize>,              // Parent ID (if any)
33);
34
35/// A builder for creating AST nodes during the parsing process.
36///
37/// This struct is responsible for assigning unique IDs to nodes and storing them.
38/// The ID counter is incremented every time a node is created, which may result in a gap
39/// between the number of created IDs and the final number of successfully built nodes.
40#[derive(Default)]
41pub struct Builder {
42    id_ctr: usize,
43    nodes: Vec<Arc<dyn AstNode>>,
44}
45
46impl Builder {
47    pub fn len(&self) -> usize {
48        self.nodes.len()
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.nodes.is_empty()
53    }
54
55    pub fn take_nodes(self) -> Vec<Arc<dyn AstNode>> {
56        self.nodes
57    }
58
59    fn next_id(&mut self) -> usize {
60        self.id_ctr += 1;
61        self.id_ctr
62    }
63
64    /// Creates a new AST node of type `T` from the current cursor position.
65    ///
66    /// The node is built using the [`AstNode`] try_from implementation for `T`.
67    fn create<'db, T: AstNode + for<'from> TryFrom<TryFromParams<'from>, Error = AstError>>(
68        &mut self,
69        db: &'db dyn salsa::Database,
70        cursor: &TreeCursor,
71        parent_id: Option<usize>,
72    ) -> Result<Arc<T>, AstError> {
73        let node = cursor.node();
74        // Gets the next ID for the new node
75        let id = self.next_id();
76        let result = T::try_from((&node, db, self, id, parent_id)).map(Arc::new)?;
77        // Stores the node
78        self.nodes.push(result.clone());
79        Ok(result)
80    }
81
82    /// Starts a [`TreeWalk`] traversal using the given closure.
83    pub fn builder<'cursor, F>(
84        &'cursor mut self,
85        db: &'cursor dyn salsa::Database,
86        node: &'cursor Node<'cursor>,
87        parent: Option<usize>,
88        mut f: F,
89    ) where
90        F: for<'cb> FnMut(
91            &'cb mut TreeWalk<'cursor>,
92        ) -> ControlFlow<(), &'cb mut TreeWalk<'cursor>>,
93    {
94        TreeWalk::new(self, db, node, parent).walk(&mut f);
95    }
96}
97
98/// A struct that walks through the current tree and calls the provided closure while traversing the tree.
99pub struct TreeWalk<'cursor> {
100    db: &'cursor dyn salsa::Database,
101    builder: &'cursor mut Builder, // Builder instance
102    cursor: TreeCursor<'cursor>,   // cursor (initialized at the beginning with the root node)
103    parent: Option<usize>,
104}
105
106impl<'cursor> TreeWalk<'cursor> {
107    fn new(
108        builder: &'cursor mut Builder,
109        db: &'cursor dyn salsa::Database,
110        node: &'cursor Node<'cursor>,
111        parent: Option<usize>,
112    ) -> Self {
113        let cursor = node.walk();
114        Self {
115            builder,
116            db,
117            cursor,
118            parent,
119        }
120    }
121
122    /// Recursively walks the tree starting from the root node.
123    ///
124    /// The provided closure is called on each child. If the closure returns
125    /// [`ControlFlow::Break`], traversal stops at that node. Otherwise, it continues.
126    fn walk<F>(&mut self, mut f: F)
127    where
128        F: FnMut(&mut Self) -> ControlFlow<(), &mut Self>,
129    {
130        if self.cursor.goto_first_child() {
131            let _ = f(self);
132
133            while self.cursor.goto_next_sibling() {
134                let _ = f(self);
135            }
136        }
137    }
138    /// Attempts to create an AST node of type `T` if the current cursor points to a field with the given ID.
139    ///
140    /// If the field ID matches, the node is built and stored in `result`. Parsing then stops at this node.
141    /// Returns [`ControlFlow::Break`] if a match is found and parsed, or `Continue` otherwise.
142    pub fn on_field_id<
143        T: AstNode + for<'from> TryFrom<TryFromParams<'from>, Error = AstError>,
144        const FIELD_ID: u16,
145    >(
146        &mut self,
147        result: &mut Result<Option<Arc<T>>, AstError>,
148    ) -> ControlFlow<(), &mut Self> {
149        if let Some(field) = self.cursor.field_id() {
150            if field == std::num::NonZero::new(FIELD_ID).expect("FIELD_ID should be non-zero") {
151                *result = self
152                    .builder
153                    .create(self.db, &self.cursor, self.parent)
154                    .map(Some);
155                return ControlFlow::Break(());
156            }
157        }
158        ControlFlow::Continue(self)
159    }
160
161    /// Like [`Self::on_field_id`], but collects all matching nodes into a vector.
162    ///
163    /// Parsing errors are accumulated into the database via [`ParseErrorAccumulator`] instead of propagating them.
164    pub fn on_vec_field_id<
165        T: AstNode + for<'from> TryFrom<TryFromParams<'from>, Error = AstError>,
166        const FIELD_ID: u16,
167    >(
168        &mut self,
169        result: &mut Vec<Arc<T>>,
170    ) -> ControlFlow<(), &mut Self> {
171        if let Some(field) = self.cursor.field_id() {
172            if field == std::num::NonZero::new(FIELD_ID).expect("FIELD_ID should be non-zero") {
173                match self.builder.create(self.db, &self.cursor, self.parent) {
174                    Ok(node) => result.push(node),
175                    // Instead of propagating the error, we accumulate it in the database.
176                    // This way we have a cheap "fault tolerant" parser.
177                    Err(e) => ParseErrorAccumulator::accumulate(e.into(), self.db),
178                };
179                return ControlFlow::Break(());
180            }
181        }
182        ControlFlow::Continue(self)
183    }
184
185    /// Attempts to create an AST node of type `T` if the current cursor matches the `T::contains` predicate.
186    ///
187    /// Useful for children without a specific field ID. The result is stored in `result`.
188    pub fn on_children_id<
189        T: AstNode + for<'from> TryFrom<TryFromParams<'from>, Error = AstError>,
190    >(
191        &mut self,
192        result: &mut Result<Option<Arc<T>>, AstError>,
193    ) -> ControlFlow<(), &mut Self> {
194        let node = self.cursor.node();
195        if T::contains(&node) {
196            *result = self
197                .builder
198                .create(self.db, &self.cursor, self.parent)
199                .map(Some);
200            return ControlFlow::Break(());
201        }
202        ControlFlow::Continue(self)
203    }
204
205    /// Like [`Self::on_children_id`], but collects all matching nodes into a vector.
206    ///
207    /// Errors are accumulated rather than returned, so the walk can continue through the tree.
208    pub fn on_vec_children_id<
209        T: AstNode + for<'from> TryFrom<TryFromParams<'from>, Error = AstError>,
210    >(
211        &mut self,
212        result: &mut Vec<Arc<T>>,
213    ) -> ControlFlow<(), &mut Self> {
214        if T::contains(&self.cursor.node()) {
215            match self.builder.create(self.db, &self.cursor, self.parent) {
216                Ok(node) => result.push(node),
217                // Instead of propagating the error, we accumulate it in the database.
218                // This way we have a cheap "fault tolerant" parser.
219                Err(e) => ParseErrorAccumulator::accumulate(e.into(), self.db),
220            };
221            return ControlFlow::Break(());
222        }
223        ControlFlow::Continue(self)
224    }
225}