tree_sitter_graph/
execution.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, tree-sitter authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8use thiserror::Error;
9use tree_sitter::CaptureQuantifier;
10use tree_sitter::Node;
11use tree_sitter::QueryMatch;
12use tree_sitter::Tree;
13
14use crate::ast::CreateEdge;
15use crate::ast::File;
16use crate::ast::Stanza;
17use crate::ast::Variable;
18use crate::execution::error::ExecutionError;
19use crate::functions::Functions;
20use crate::graph::Attributes;
21use crate::graph::Graph;
22use crate::graph::Value;
23use crate::variables::Globals;
24use crate::Identifier;
25use crate::Location;
26
27pub(crate) mod error;
28mod lazy;
29mod strict;
30
31impl File {
32    /// Executes this graph DSL file against a source file.  You must provide the parsed syntax
33    /// tree (`tree`) as well as the source text that it was parsed from (`source`).  You also
34    /// provide the set of functions and global variables that are available during execution.
35    pub fn execute<'a, 'tree>(
36        &self,
37        tree: &'tree Tree,
38        source: &'tree str,
39        config: &ExecutionConfig,
40        cancellation_flag: &dyn CancellationFlag,
41    ) -> Result<Graph<'tree>, ExecutionError> {
42        let mut graph = Graph::new();
43        self.execute_into(&mut graph, tree, source, config, cancellation_flag)?;
44        Ok(graph)
45    }
46
47    /// Executes this graph DSL file against a source file, saving the results into an existing
48    /// `Graph` instance.  You must provide the parsed syntax tree (`tree`) as well as the source
49    /// text that it was parsed from (`source`).  You also provide the set of functions and global
50    /// variables that are available during execution. This variant is useful when you need to
51    /// “pre-seed” the graph with some predefined nodes and/or edges before executing the DSL file.
52    pub fn execute_into<'a, 'tree>(
53        &self,
54        graph: &mut Graph<'tree>,
55        tree: &'tree Tree,
56        source: &'tree str,
57        config: &ExecutionConfig,
58        cancellation_flag: &dyn CancellationFlag,
59    ) -> Result<(), ExecutionError> {
60        if config.lazy {
61            self.execute_lazy_into(graph, tree, source, config, cancellation_flag)
62        } else {
63            self.execute_strict_into(graph, tree, source, config, cancellation_flag)
64        }
65    }
66
67    pub(self) fn check_globals(&self, globals: &mut Globals) -> Result<(), ExecutionError> {
68        for global in &self.globals {
69            match globals.get(&global.name) {
70                None => {
71                    if let Some(default) = &global.default {
72                        globals
73                            .add(global.name.clone(), default.to_string().into())
74                            .map_err(|_| {
75                                ExecutionError::DuplicateVariable(format!(
76                                    "global variable {} already defined",
77                                    global.name
78                                ))
79                            })?;
80                    } else {
81                        return Err(ExecutionError::MissingGlobalVariable(
82                            global.name.as_str().to_string(),
83                        ));
84                    }
85                }
86                Some(value) => {
87                    if global.quantifier == CaptureQuantifier::ZeroOrMore
88                        || global.quantifier == CaptureQuantifier::OneOrMore
89                    {
90                        if value.as_list().is_err() {
91                            return Err(ExecutionError::ExpectedList(
92                                global.name.as_str().to_string(),
93                            ));
94                        }
95                    }
96                }
97            }
98        }
99
100        Ok(())
101    }
102
103    pub fn try_visit_matches<'tree, E, F>(
104        &self,
105        tree: &'tree Tree,
106        source: &'tree str,
107        lazy: bool,
108        mut visit: F,
109    ) -> Result<(), E>
110    where
111        F: FnMut(Match<'_, 'tree>) -> Result<(), E>,
112    {
113        if lazy {
114            let file_query = self.query.as_ref().expect("missing file query");
115            self.try_visit_matches_lazy(tree, source, |stanza, mat| {
116                let named_captures = stanza
117                    .query
118                    .capture_names()
119                    .iter()
120                    .map(|name| {
121                        let index = file_query
122                            .capture_index_for_name(*name)
123                            .expect("missing index for capture");
124                        let quantifier =
125                            file_query.capture_quantifiers(mat.pattern_index)[index as usize];
126                        (*name, quantifier, index)
127                    })
128                    .filter(|c| c.2 != stanza.full_match_file_capture_index as u32)
129                    .collect();
130                visit(Match {
131                    mat,
132                    full_capture_index: stanza.full_match_file_capture_index as u32,
133                    named_captures,
134                    query_location: stanza.range.start,
135                })
136            })
137        } else {
138            self.try_visit_matches_strict(tree, source, |stanza, mat| {
139                let named_captures = stanza
140                    .query
141                    .capture_names()
142                    .iter()
143                    .map(|name| {
144                        let index = stanza
145                            .query
146                            .capture_index_for_name(*name)
147                            .expect("missing index for capture");
148                        let quantifier = stanza.query.capture_quantifiers(0)[index as usize];
149                        (*name, quantifier, index)
150                    })
151                    .filter(|c| c.2 != stanza.full_match_stanza_capture_index as u32)
152                    .collect();
153                visit(Match {
154                    mat,
155                    full_capture_index: stanza.full_match_stanza_capture_index as u32,
156                    named_captures,
157                    query_location: stanza.range.start,
158                })
159            })
160        }
161    }
162}
163
164impl Stanza {
165    pub fn try_visit_matches<'tree, E, F>(
166        &self,
167        tree: &'tree Tree,
168        source: &'tree str,
169        mut visit: F,
170    ) -> Result<(), E>
171    where
172        F: FnMut(Match<'_, 'tree>) -> Result<(), E>,
173    {
174        self.try_visit_matches_strict(tree, source, |mat| {
175            let named_captures = self
176                .query
177                .capture_names()
178                .iter()
179                .map(|name| {
180                    let index = self
181                        .query
182                        .capture_index_for_name(*name)
183                        .expect("missing index for capture");
184                    let quantifier = self.query.capture_quantifiers(0)[index as usize];
185                    (*name, quantifier, index)
186                })
187                .filter(|c| c.2 != self.full_match_stanza_capture_index as u32)
188                .collect();
189            visit(Match {
190                mat,
191                full_capture_index: self.full_match_stanza_capture_index as u32,
192                named_captures,
193                query_location: self.range.start,
194            })
195        })
196    }
197}
198
199pub struct Match<'a, 'tree> {
200    mat: &'a QueryMatch<'a, 'tree>,
201    full_capture_index: u32,
202    named_captures: Vec<(&'a str, CaptureQuantifier, u32)>,
203    query_location: Location,
204}
205
206impl<'a, 'tree> Match<'a, 'tree> {
207    /// Return the top-level matched node.
208    pub fn full_capture(&self) -> Node<'tree> {
209        self.mat
210            .nodes_for_capture_index(self.full_capture_index)
211            .next()
212            .expect("missing full capture")
213    }
214
215    /// Return the matched nodes for a named capture.
216    pub fn named_captures<'s: 'a + 'tree>(
217        &'s self,
218    ) -> impl Iterator<
219        Item = (
220            &'a str,
221            CaptureQuantifier,
222            impl Iterator<Item = Node<'tree>> + 's,
223        ),
224    > {
225        self.named_captures
226            .iter()
227            .map(move |c| (c.0, c.1, self.mat.nodes_for_capture_index(c.2)))
228    }
229
230    /// Return the matched nodes for a named capture.
231    pub fn named_capture<'s: 'a + 'tree>(
232        &'s self,
233        name: &str,
234    ) -> Option<(CaptureQuantifier, impl Iterator<Item = Node<'tree>> + 's)> {
235        self.named_captures
236            .iter()
237            .find(|c| c.0 == name)
238            .map(|c| (c.1, self.mat.nodes_for_capture_index(c.2)))
239    }
240
241    /// Return an iterator over all capture names.
242    pub fn capture_names(&self) -> impl Iterator<Item = &str> {
243        self.named_captures.iter().map(|c| c.0)
244    }
245
246    /// Return the query location.
247    pub fn query_location(&self) -> &Location {
248        &self.query_location
249    }
250}
251
252/// Configuration for the execution of a File
253pub struct ExecutionConfig<'a, 'g> {
254    pub(crate) functions: &'a Functions,
255    pub(crate) globals: &'a Globals<'g>,
256    pub(crate) lazy: bool,
257    pub(crate) location_attr: Option<Identifier>,
258    pub(crate) variable_name_attr: Option<Identifier>,
259    pub(crate) match_node_attr: Option<Identifier>,
260}
261
262impl<'a, 'g> ExecutionConfig<'a, 'g> {
263    pub fn new(functions: &'a Functions, globals: &'a Globals<'g>) -> Self {
264        Self {
265            functions,
266            globals,
267            lazy: false,
268            location_attr: None,
269            variable_name_attr: None,
270            match_node_attr: None,
271        }
272    }
273
274    pub fn debug_attributes(
275        self,
276        location_attr: Identifier,
277        variable_name_attr: Identifier,
278        match_node_attr: Identifier,
279    ) -> Self {
280        Self {
281            functions: self.functions,
282            globals: self.globals,
283            lazy: self.lazy,
284            location_attr: location_attr.into(),
285            variable_name_attr: variable_name_attr.into(),
286            match_node_attr: match_node_attr.into(),
287        }
288    }
289
290    pub fn lazy(self, lazy: bool) -> Self {
291        Self {
292            functions: self.functions,
293            globals: self.globals,
294            lazy,
295            location_attr: self.location_attr,
296            variable_name_attr: self.variable_name_attr,
297            match_node_attr: self.match_node_attr,
298        }
299    }
300}
301
302/// Trait to signal that the execution is cancelled
303pub trait CancellationFlag {
304    fn check(&self, at: &'static str) -> Result<(), CancellationError>;
305}
306
307pub struct NoCancellation;
308impl CancellationFlag for NoCancellation {
309    fn check(&self, _at: &'static str) -> Result<(), CancellationError> {
310        Ok(())
311    }
312}
313
314#[derive(Debug, Error)]
315#[error("Cancelled at \"{0}\"")]
316pub struct CancellationError(pub &'static str);
317
318impl Value {
319    pub fn from_nodes<'tree, NI: IntoIterator<Item = Node<'tree>>>(
320        graph: &mut Graph<'tree>,
321        nodes: NI,
322        quantifier: CaptureQuantifier,
323    ) -> Value {
324        let mut nodes = nodes.into_iter();
325        match quantifier {
326            CaptureQuantifier::Zero => unreachable!(),
327            CaptureQuantifier::One => {
328                let syntax_node = graph.add_syntax_node(nodes.next().expect("missing capture"));
329                syntax_node.into()
330            }
331            CaptureQuantifier::ZeroOrMore | CaptureQuantifier::OneOrMore => {
332                let syntax_nodes = nodes
333                    .map(|n| graph.add_syntax_node(n.clone()).into())
334                    .collect::<Vec<Value>>();
335                syntax_nodes.into()
336            }
337            CaptureQuantifier::ZeroOrOne => match nodes.next() {
338                None => Value::Null.into(),
339                Some(node) => {
340                    let syntax_node = graph.add_syntax_node(node);
341                    syntax_node.into()
342                }
343            },
344        }
345    }
346}
347
348impl CreateEdge {
349    pub(crate) fn add_debug_attrs(
350        &self,
351        attributes: &mut Attributes,
352        config: &ExecutionConfig,
353    ) -> Result<(), ExecutionError> {
354        if let Some(location_attr) = &config.location_attr {
355            attributes
356                .add(
357                    location_attr.clone(),
358                    format!(
359                        "line {} column {}",
360                        self.location.row + 1,
361                        self.location.column + 1
362                    ),
363                )
364                .map_err(|_| ExecutionError::DuplicateAttribute(location_attr.as_str().into()))?;
365        }
366        Ok(())
367    }
368}
369impl Variable {
370    pub(crate) fn add_debug_attrs(
371        &self,
372        attributes: &mut Attributes,
373        config: &ExecutionConfig,
374    ) -> Result<(), ExecutionError> {
375        if let Some(variable_name_attr) = &config.variable_name_attr {
376            attributes
377                .add(variable_name_attr.clone(), format!("{}", self))
378                .map_err(|_| {
379                    ExecutionError::DuplicateAttribute(variable_name_attr.as_str().into())
380                })?;
381        }
382        if let Some(location_attr) = &config.location_attr {
383            let location = match &self {
384                Variable::Scoped(v) => v.location,
385                Variable::Unscoped(v) => v.location,
386            };
387            attributes
388                .add(
389                    location_attr.clone(),
390                    format!("line {} column {}", location.row + 1, location.column + 1),
391                )
392                .map_err(|_| ExecutionError::DuplicateAttribute(location_attr.as_str().into()))?;
393        }
394        Ok(())
395    }
396}