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::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: QueryMatch<'a, 'tree>, 201 full_capture_index: u32, 202 named_captures: Vec<(&'a String, 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 &String, 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 = &String> { 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}