fork of https://github.com/tree-sitter/tree-sitter-graph
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::QueryMatch;
11use tree_sitter::Tree;
12
13use crate::ast::File;
14use crate::execution::error::ExecutionError;
15use crate::functions::Functions;
16use crate::graph::Graph;
17use crate::graph::Value;
18use crate::variables::Globals;
19use crate::Identifier;
20
21pub(crate) mod error;
22mod lazy;
23mod strict;
24
25impl File {
26 /// Executes this graph DSL file against a source file. You must provide the parsed syntax
27 /// tree (`tree`) as well as the source text that it was parsed from (`source`). You also
28 /// provide the set of functions and global variables that are available during execution.
29 pub fn execute<'a, 'tree>(
30 &self,
31 tree: &'tree Tree,
32 source: &'tree str,
33 config: &ExecutionConfig,
34 cancellation_flag: &dyn CancellationFlag,
35 ) -> Result<Graph<'tree>, ExecutionError> {
36 let mut graph = Graph::new();
37 self.execute_into(&mut graph, tree, source, config, cancellation_flag)?;
38 Ok(graph)
39 }
40
41 /// Executes this graph DSL file against a source file, saving the results into an existing
42 /// `Graph` instance. You must provide the parsed syntax tree (`tree`) as well as the source
43 /// text that it was parsed from (`source`). You also provide the set of functions and global
44 /// variables that are available during execution. This variant is useful when you need to
45 /// “pre-seed” the graph with some predefined nodes and/or edges before executing the DSL file.
46 pub fn execute_into<'a, 'tree>(
47 &self,
48 graph: &mut Graph<'tree>,
49 tree: &'tree Tree,
50 source: &'tree str,
51 config: &ExecutionConfig,
52 cancellation_flag: &dyn CancellationFlag,
53 ) -> Result<(), ExecutionError> {
54 if config.lazy {
55 self.execute_lazy_into(graph, tree, source, config, cancellation_flag)
56 } else {
57 self.execute_strict_into(graph, tree, source, config, cancellation_flag)
58 }
59 }
60
61 pub(self) fn check_globals(&self, globals: &mut Globals) -> Result<(), ExecutionError> {
62 for global in &self.globals {
63 match globals.get(&global.name) {
64 None => {
65 if let Some(default) = &global.default {
66 globals
67 .add(global.name.clone(), default.to_string().into())
68 .map_err(|_| {
69 ExecutionError::DuplicateVariable(format!(
70 "global variable {} already defined",
71 global.name
72 ))
73 })?;
74 } else {
75 return Err(ExecutionError::MissingGlobalVariable(
76 global.name.as_str().to_string(),
77 ));
78 }
79 }
80 Some(value) => {
81 if global.quantifier == CaptureQuantifier::ZeroOrMore
82 || global.quantifier == CaptureQuantifier::OneOrMore
83 {
84 if value.as_list().is_err() {
85 return Err(ExecutionError::ExpectedList(
86 global.name.as_str().to_string(),
87 ));
88 }
89 }
90 }
91 }
92 }
93
94 Ok(())
95 }
96}
97
98/// Configuration for the execution of a File
99pub struct ExecutionConfig<'a, 'g> {
100 pub(crate) functions: &'a Functions,
101 pub(crate) globals: &'a Globals<'g>,
102 pub(crate) lazy: bool,
103 pub(crate) location_attr: Option<Identifier>,
104 pub(crate) variable_name_attr: Option<Identifier>,
105}
106
107impl<'a, 'g> ExecutionConfig<'a, 'g> {
108 pub fn new(functions: &'a Functions, globals: &'a Globals<'g>) -> Self {
109 Self {
110 functions,
111 globals,
112 lazy: false,
113 location_attr: None,
114 variable_name_attr: None,
115 }
116 }
117
118 pub fn debug_attributes(
119 self,
120 location_attr: Identifier,
121 variable_name_attr: Identifier,
122 ) -> Self {
123 Self {
124 functions: self.functions,
125 globals: self.globals,
126 lazy: self.lazy,
127 location_attr: location_attr.into(),
128 variable_name_attr: variable_name_attr.into(),
129 }
130 }
131
132 pub fn lazy(self, lazy: bool) -> Self {
133 Self {
134 functions: self.functions,
135 globals: self.globals,
136 lazy,
137 location_attr: self.location_attr,
138 variable_name_attr: self.variable_name_attr,
139 }
140 }
141}
142
143/// Trait to signal that the execution is cancelled
144pub trait CancellationFlag {
145 fn check(&self, at: &'static str) -> Result<(), CancellationError>;
146}
147
148pub struct NoCancellation;
149impl CancellationFlag for NoCancellation {
150 fn check(&self, _at: &'static str) -> Result<(), CancellationError> {
151 Ok(())
152 }
153}
154
155#[derive(Debug, Error)]
156#[error("Cancelled at \"{0}\"")]
157pub struct CancellationError(pub &'static str);
158
159/// Get the value for the given capture, considering the suffix
160pub(self) fn query_capture_value<'tree>(
161 index: usize,
162 quantifier: CaptureQuantifier,
163 mat: &QueryMatch<'_, 'tree>,
164 graph: &mut Graph<'tree>,
165) -> Value {
166 let mut nodes = mat
167 .captures
168 .iter()
169 .filter(|c| c.index as usize == index)
170 .map(|c| c.node);
171 match quantifier {
172 CaptureQuantifier::Zero => unreachable!(),
173 CaptureQuantifier::One => {
174 let syntax_node = graph.add_syntax_node(nodes.next().expect("missing capture"));
175 syntax_node.into()
176 }
177 CaptureQuantifier::ZeroOrMore | CaptureQuantifier::OneOrMore => {
178 let syntax_nodes = nodes
179 .map(|n| graph.add_syntax_node(n.clone()).into())
180 .collect::<Vec<Value>>();
181 syntax_nodes.into()
182 }
183 CaptureQuantifier::ZeroOrOne => match nodes.next() {
184 None => Value::Null.into(),
185 Some(node) => {
186 let syntax_node = graph.add_syntax_node(node);
187 syntax_node.into()
188 }
189 },
190 }
191}