fork of https://github.com/tree-sitter/tree-sitter-graph

Compare changes

Choose any two refs to compare.

+10 -10
.github/workflows/ci.yml
··· 19 19 uses: hecrj/setup-rust-action@v1 20 20 with: 21 21 rust-version: ${{ matrix.rust }} 22 + - name: Install Cargo plugins 23 + run: | 24 + rustup toolchain install nightly 25 + cargo install cargo-hack cargo-minimal-versions 22 26 - name: Checkout code 23 27 uses: actions/checkout@v2 24 28 - name: Check formatting ··· 33 37 key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }} 34 38 restore-keys: | 35 39 ${{ runner.OS }}-cargo- 36 - - name: Build library (default features) 37 - run: cargo build 38 - - name: Test library (default features) 39 - run: cargo test 40 - - name: Build library (all features) 41 - run: cargo build --all-features 42 - - name: Test library (all features) 43 - run: cargo test --all-features 44 - - name: Build program 45 - run: cargo build --bin tree-sitter-graph --features=cli 40 + - name: Build library (all feature combinations) 41 + run: cargo hack --feature-powerset --no-dev-deps build 42 + - name: Test library (all feature combinations) 43 + run: cargo hack --feature-powerset test 44 + - name: Build library (minimal versions) 45 + run: cargo minimal-versions build
+50
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## v0.12.0 -- 2024-12-12 9 + 10 + Upgraded the `tree-sitter` dependency to version 0.24. 11 + 12 + ## v0.11.3 -- 2024-05-29 13 + 14 + ### Library 15 + 16 + #### Fixed 17 + 18 + - Excerpts shown as part of errors now use formatting that works both in light and dark mode. 19 + 20 + ## v0.11.2 -- 2024-03-08 21 + 22 + ### DSL 23 + 24 + #### Added 25 + 26 + - Support adding an edge multiple times, or setting an attribute multiple times with the same value. Previously these would raise runtime errors. 27 + 28 + ## v0.11.1 -- 2024-03-06 29 + 30 + Updated the `tree-sitter` dependency to include the required minimal patch version. 31 + 32 + ## v0.11.0 -- 2023-07-17 33 + 34 + ### Library 35 + 36 + #### Added 37 + 38 + - Store a debug attribute for the matched syntax node, providing information about the syntactic category of the match that gave rise to the graph node. 39 + 40 + ## v0.10.5 -- 2023-06-26 41 + 42 + #### Fixed 43 + 44 + - A panic that sometimes occurred in lazy execution mode. 45 + 46 + ## v0.10.4 -- 2023-06-02 47 + 48 + ### Library 49 + 50 + #### Added 51 + 52 + - Several errors include more context in the error message: Duplicate errors report both statements using source snippets. Edge statements report which argument (the source or the sink) triggered an evluation error. 53 + 54 + #### Fixed 55 + 56 + - Ensure that edge attribute statements are executed after edges are created, to prevent non-deterministic ordering bugs in lazy execution mode. 57 + 8 58 ## v0.10.3 -- 2023-06-01 9 59 10 60 ### DSL
+20 -44
Cargo.toml
··· 1 1 [package] 2 2 name = "tree-sitter-graph" 3 - version = "0.10.3" 3 + version = "0.12.0" 4 4 description = "Construct graphs from parsed source code" 5 5 homepage = "https://github.com/tree-sitter/tree-sitter-graph/" 6 6 repository = "https://github.com/tree-sitter/tree-sitter-graph/" ··· 15 15 # All of our tests are in the tests/it "integration" test executable. 16 16 test = false 17 17 18 - [dependencies] 19 - log = "0.4" 20 - regex = "1" 21 - serde = "1.0" 22 - serde_json = "1.0" 23 - smallvec = { version="1.6", features=["union"] } 24 - thiserror = "1.0" 25 - tree-sitter = "0.20" 26 - 27 - [dependencies.string-interner] 28 - version = "0.12" 29 - default-features = false 30 - features = ["std", "inline-more", "backends"] 31 - 32 - [dev-dependencies] 33 - env_logger = "0.9" 34 - indoc = "1.0" 35 - tree-sitter-python = "0.19.1" 36 - 37 - # cli-only dependencies below 38 - 39 18 [[bin]] 40 19 name = "tree-sitter-graph" 41 20 required-features = ["cli"] ··· 44 23 cli = ["anyhow", "clap", "env_logger", "term-colors", "tree-sitter-config", "tree-sitter-loader"] 45 24 term-colors = ["colored"] 46 25 47 - [dependencies.anyhow] 48 - optional = true 49 - version = "1.0" 50 - 51 - [dependencies.clap] 52 - optional = true 53 - version = "3.2" 54 - 55 - [dependencies.colored] 56 - optional = true 57 - version = "2" 58 - 59 - [dependencies.env_logger] 60 - optional = true 61 - version = "0.9" 62 - 63 - [dependencies.tree-sitter-config] 64 - optional = true 65 - version = "0.19" 26 + [dependencies] 27 + anyhow = { version = "1.0", optional = true } 28 + clap = { version = "3.2", optional = true } 29 + colored = { version = "2", optional = true } 30 + env_logger = { version = "0.9", optional = true } 31 + log = "0.4" 32 + regex = "1.3.2" 33 + serde = "1.0" 34 + serde_json = "1.0" 35 + smallvec = { version="1.6", features=["union"] } 36 + streaming-iterator = "0.1.9" 37 + thiserror = "1.0.7" 38 + tree-sitter = "0.25" 39 + tree-sitter-config = { version = "0.25", optional = true } 40 + tree-sitter-loader = { version = "0.25", optional = true } 66 41 67 - [dependencies.tree-sitter-loader] 68 - optional = true 69 - version = "0.19" 42 + [dev-dependencies] 43 + env_logger = "0.9" 44 + indoc = "1.0" 45 + tree-sitter-python = "=0.23.5"
+1 -1
README.md
··· 20 20 21 21 ``` toml 22 22 [dependencies] 23 - tree-sitter-graph = "0.10" 23 + tree-sitter-graph = "0.12" 24 24 ``` 25 25 26 26 To use it as a program, install it via `cargo install`:
+25
src/ast.rs
··· 81 81 DeclareImmutable(DeclareImmutable), 82 82 DeclareMutable(DeclareMutable), 83 83 Assign(Assign), 84 + Expr(ExpressionStatement), 84 85 // Graph nodes 85 86 CreateGraphNode(CreateGraphNode), 86 87 AddGraphNodeAttribute(AddGraphNodeAttribute), ··· 103 104 Self::DeclareImmutable(stmt) => stmt.fmt(f), 104 105 Self::DeclareMutable(stmt) => stmt.fmt(f), 105 106 Self::Assign(stmt) => stmt.fmt(f), 107 + Self::Expr(stmt) => stmt.fmt(f), 106 108 Self::CreateGraphNode(stmt) => stmt.fmt(f), 107 109 Self::AddGraphNodeAttribute(stmt) => stmt.fmt(f), 108 110 Self::CreateEdge(stmt) => stmt.fmt(f), ··· 161 163 write!(f, " {}", attr)?; 162 164 } 163 165 write!(f, " at {}", self.location) 166 + } 167 + } 168 + 169 + /// An `expression` statement whose output is ignored 170 + #[derive(Debug, Eq, PartialEq)] 171 + pub struct ExpressionStatement { 172 + pub value: Expression, 173 + pub location: Location, 174 + } 175 + 176 + impl From<ExpressionStatement> for Statement { 177 + fn from(statement: ExpressionStatement) -> Statement { 178 + Statement::Expr(statement) 179 + } 180 + } 181 + 182 + impl std::fmt::Display for ExpressionStatement { 183 + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 184 + write!( 185 + f, 186 + "{} at {}", 187 + self.value, self.location, 188 + ) 164 189 } 165 190 } 166 191
+3 -3
src/bin/tree-sitter-graph/main.rs
··· 89 89 )?; 90 90 } 91 91 92 - let config = Config::load()?; 92 + let config = Config::load(None)?; 93 93 let mut loader = Loader::new()?; 94 94 let loader_config = config.get()?; 95 95 loader.find_all_languages(&loader_config)?; ··· 98 98 let tsg = std::fs::read(tsg_path) 99 99 .with_context(|| format!("Cannot read TSG file {}", tsg_path.display()))?; 100 100 let tsg = String::from_utf8(tsg)?; 101 - let file = match File::from_str(language, &tsg) { 101 + let file = match File::from_str(language.clone(), &tsg) { 102 102 Ok(file) => file, 103 103 Err(err) => { 104 104 eprintln!("{}", err.display_pretty(tsg_path, &tsg)); ··· 110 110 .with_context(|| format!("Cannot read source file {}", source_path.display()))?; 111 111 let source = String::from_utf8(source)?; 112 112 let mut parser = Parser::new(); 113 - parser.set_language(language)?; 113 + parser.set_language(&language)?; 114 114 let tree = parser 115 115 .parse(&source, None) 116 116 .ok_or_else(|| anyhow!("Cannot parse {}", source_path.display()))?;
+11 -1
src/checker.rs
··· 188 188 .expect("capture should have index") 189 189 != self.full_match_stanza_capture_index as u32 190 190 }) 191 - .map(|cn| Identifier::from(cn.as_str())) 191 + .map(|cn| Identifier::from(*cn)) 192 192 .collect::<HashSet<_>>(); 193 193 let unused_captures = all_captures 194 194 .difference(&used_captures) ··· 220 220 Self::DeclareImmutable(stmt) => stmt.check(ctx), 221 221 Self::DeclareMutable(stmt) => stmt.check(ctx), 222 222 Self::Assign(stmt) => stmt.check(ctx), 223 + Self::Expr(stmt) => stmt.check(ctx), 223 224 Self::CreateGraphNode(stmt) => stmt.check(ctx), 224 225 Self::AddGraphNodeAttribute(stmt) => stmt.check(ctx), 225 226 Self::CreateEdge(stmt) => stmt.check(ctx), ··· 261 262 used_captures.extend(value.used_captures.iter().cloned()); 262 263 let var_result = self.variable.check_set(ctx, value.into())?; 263 264 used_captures.extend(var_result.used_captures); 265 + Ok(StatementResult { used_captures }) 266 + } 267 + } 268 + 269 + impl ast::ExpressionStatement { 270 + fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> { 271 + let mut used_captures = HashSet::new(); 272 + let value = self.value.check(ctx)?; 273 + used_captures.extend(value.used_captures.iter().cloned()); 264 274 Ok(StatementResult { used_captures }) 265 275 } 266 276 }
+1 -17
src/execution/lazy/statements.rs
··· 226 226 .sink 227 227 .evaluate_as_graph_node(exec) 228 228 .with_context(|| "Evaluating edge sink".to_string().into())?; 229 - let prev_debug_info = exec 230 - .prev_element_debug_info 231 - .insert(GraphElementKey::Edge(source, sink), self.debug_info.clone()); 232 229 let edge = match exec.graph[source].add_edge(sink) { 233 - Ok(edge) => edge, 234 - Err(_) => { 235 - return Err(ExecutionError::DuplicateEdge(format!( 236 - "({} -> {})", 237 - source, sink, 238 - ))) 239 - .with_context(|| { 240 - ( 241 - prev_debug_info.unwrap().into(), 242 - self.debug_info.clone().into(), 243 - ) 244 - .into() 245 - }); 246 - } 230 + Ok(edge) | Err(edge) => edge, 247 231 }; 248 232 edge.attributes = self.attributes.clone(); 249 233 Ok(())
+14 -6
src/execution/lazy/store.rs
··· 193 193 ) -> Result<HashMap<SyntaxNodeID, LazyValue>, ExecutionError> { 194 194 match values { 195 195 ScopedValues::Unforced(pairs) => { 196 - let mut map = HashMap::new(); 196 + let mut values = HashMap::new(); 197 197 let mut debug_infos = HashMap::new(); 198 198 for (scope, value, debug_info) in pairs.into_iter() { 199 199 let node = scope 200 200 .evaluate_as_syntax_node(exec) 201 201 .with_context(|| format!("Evaluating scope of variable _.{}", name,).into()) 202 202 .with_context(|| debug_info.0.clone().into())?; 203 - let prev_debug_info = debug_infos.insert(node, debug_info.clone()); 204 - match map.insert(node.index, value.clone()) { 205 - Some(_) => { 203 + match ( 204 + values.insert(node.index, value.clone()), 205 + debug_infos.insert(node.index, debug_info.clone()), 206 + ) { 207 + (Some(_), Some(prev_debug_info)) => { 206 208 return Err(ExecutionError::DuplicateVariable(format!( 207 209 "{}.{}", 208 210 node, name, 209 211 ))) 210 - .with_context(|| (prev_debug_info.unwrap().0, debug_info.0).into()); 212 + .with_context(|| (prev_debug_info.0, debug_info.0).into()); 213 + } 214 + (Some(_), None) => { 215 + unreachable!( 216 + "previous value for syntax node {} without previous debug info", 217 + node 218 + ) 211 219 } 212 220 _ => {} 213 221 }; 214 222 } 215 - Ok(map) 223 + Ok(values) 216 224 } 217 225 ScopedValues::Forcing => Err(ExecutionError::RecursivelyDefinedScopedVariable( 218 226 format!("_.{}", name),
+32 -4
src/execution/lazy.rs
··· 18 18 use tree_sitter::QueryMatch; 19 19 use tree_sitter::Tree; 20 20 21 + use streaming_iterator::StreamingIterator; 22 + 21 23 use crate::ast; 22 24 use crate::execution::error::ExecutionError; 23 25 use crate::execution::error::ResultWithExecutionError; ··· 60 62 lazy: config.lazy, 61 63 location_attr: config.location_attr.clone(), 62 64 variable_name_attr: config.variable_name_attr.clone(), 65 + match_node_attr: config.match_node_attr.clone(), 63 66 }; 64 67 65 68 let mut locals = VariableMap::new(); ··· 115 118 mut visit: F, 116 119 ) -> Result<(), E> 117 120 where 118 - F: FnMut(&ast::Stanza, QueryMatch<'_, 'tree>) -> Result<(), E>, 121 + F: FnMut(&ast::Stanza, &QueryMatch<'_, 'tree>) -> Result<(), E>, 119 122 { 120 123 let mut cursor = QueryCursor::new(); 121 124 let query = self.query.as_ref().unwrap(); 122 - let matches = cursor.matches(query, tree.root_node(), source.as_bytes()); 123 - for mat in matches { 125 + let mut matches = cursor.matches(query, tree.root_node(), source.as_bytes()); 126 + while let Some(mat) = matches.next() { 124 127 let stanza = &self.stanzas[mat.pattern_index]; 125 128 visit(stanza, mat)?; 126 129 } ··· 136 139 locals: &'a mut dyn MutVariables<LazyValue>, 137 140 current_regex_captures: &'a Vec<String>, 138 141 mat: &'a QueryMatch<'a, 'tree>, 142 + full_match_file_capture_index: usize, 139 143 store: &'a mut LazyStore, 140 144 scoped_store: &'a mut LazyScopedVariables, 141 145 lazy_graph: &'a mut LazyGraph, ··· 163 167 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 164 168 pub(super) enum GraphElementKey { 165 169 NodeAttribute(graph::GraphNodeRef, Identifier), 166 - Edge(graph::GraphNodeRef, graph::GraphNodeRef), 167 170 EdgeAttribute(graph::GraphNodeRef, graph::GraphNodeRef, Identifier), 168 171 } 169 172 ··· 201 204 locals, 202 205 current_regex_captures: &current_regex_captures, 203 206 mat, 207 + full_match_file_capture_index: self.full_match_file_capture_index, 204 208 store, 205 209 scoped_store, 206 210 lazy_graph, ··· 227 231 Self::DeclareImmutable(statement) => statement.execute_lazy(exec), 228 232 Self::DeclareMutable(statement) => statement.execute_lazy(exec), 229 233 Self::Assign(statement) => statement.execute_lazy(exec), 234 + Self::Expr(statement) => statement.value.evaluate_lazy(exec).map(|_| ()), 230 235 Self::CreateGraphNode(statement) => statement.execute_lazy(exec), 231 236 Self::AddGraphNodeAttribute(statement) => statement.execute_lazy(exec), 232 237 Self::CreateEdge(statement) => statement.execute_lazy(exec), ··· 265 270 let graph_node = exec.graph.add_graph_node(); 266 271 self.node 267 272 .add_debug_attrs(&mut exec.graph[graph_node].attributes, exec.config)?; 273 + if let Some(match_node_attr) = &exec.config.match_node_attr { 274 + let match_node = exec 275 + .mat 276 + .nodes_for_capture_index(exec.full_match_file_capture_index as u32) 277 + .next() 278 + .expect("missing capture for full match"); 279 + let syn_node = exec.graph.add_syntax_node(match_node); 280 + exec.graph[graph_node] 281 + .attributes 282 + .add(match_node_attr.clone(), syn_node) 283 + .map_err(|_| { 284 + ExecutionError::DuplicateAttribute(format!( 285 + " {} on graph node ({}) in {}", 286 + match_node_attr, graph_node, self, 287 + )) 288 + })?; 289 + } 268 290 self.node.add_lazy(exec, graph_node.into(), false) 269 291 } 270 292 } ··· 365 387 locals: &mut arm_locals, 366 388 current_regex_captures: &current_regex_captures, 367 389 mat: exec.mat, 390 + full_match_file_capture_index: exec.full_match_file_capture_index, 368 391 store: exec.store, 369 392 scoped_store: exec.scoped_store, 370 393 lazy_graph: exec.lazy_graph, ··· 431 454 locals: &mut arm_locals, 432 455 current_regex_captures: exec.current_regex_captures, 433 456 mat: exec.mat, 457 + full_match_file_capture_index: exec.full_match_file_capture_index, 434 458 store: exec.store, 435 459 scoped_store: exec.scoped_store, 436 460 lazy_graph: exec.lazy_graph, ··· 478 502 locals: &mut loop_locals, 479 503 current_regex_captures: exec.current_regex_captures, 480 504 mat: exec.mat, 505 + full_match_file_capture_index: exec.full_match_file_capture_index, 481 506 store: exec.store, 482 507 scoped_store: exec.scoped_store, 483 508 lazy_graph: exec.lazy_graph, ··· 572 597 locals: &mut loop_locals, 573 598 current_regex_captures: exec.current_regex_captures, 574 599 mat: exec.mat, 600 + full_match_file_capture_index: exec.full_match_file_capture_index, 575 601 store: exec.store, 576 602 scoped_store: exec.scoped_store, 577 603 lazy_graph: exec.lazy_graph, ··· 615 641 locals: &mut loop_locals, 616 642 current_regex_captures: exec.current_regex_captures, 617 643 mat: exec.mat, 644 + full_match_file_capture_index: exec.full_match_file_capture_index, 618 645 store: exec.store, 619 646 scoped_store: exec.scoped_store, 620 647 lazy_graph: exec.lazy_graph, ··· 830 857 locals: &mut shorthand_locals, 831 858 current_regex_captures: exec.current_regex_captures, 832 859 mat: exec.mat, 860 + full_match_file_capture_index: exec.full_match_file_capture_index, 833 861 store: exec.store, 834 862 scoped_store: exec.scoped_store, 835 863 lazy_graph: exec.lazy_graph,
+41 -11
src/execution/strict.rs
··· 8 8 use std::collections::BTreeSet; 9 9 use std::collections::HashMap; 10 10 use std::collections::HashSet; 11 + use streaming_iterator::StreamingIterator; 11 12 use tree_sitter::QueryCursor; 12 13 use tree_sitter::QueryMatch; 13 14 use tree_sitter::Tree; ··· 15 16 use crate::ast::AddEdgeAttribute; 16 17 use crate::ast::AddGraphNodeAttribute; 17 18 use crate::ast::Assign; 19 + use crate::ast::ExpressionStatement; 18 20 use crate::ast::Attribute; 19 21 use crate::ast::AttributeShorthand; 20 22 use crate::ast::AttributeShorthands; ··· 81 83 lazy: config.lazy, 82 84 location_attr: config.location_attr.clone(), 83 85 variable_name_attr: config.variable_name_attr.clone(), 86 + match_node_attr: config.match_node_attr.clone(), 84 87 }; 85 88 86 89 let mut locals = VariableMap::new(); ··· 114 117 mut visit: F, 115 118 ) -> Result<(), E> 116 119 where 117 - F: FnMut(&Stanza, QueryMatch<'_, 'tree>) -> Result<(), E>, 120 + F: FnMut(&Stanza, &QueryMatch<'_, 'tree>) -> Result<(), E>, 118 121 { 119 122 for stanza in &self.stanzas { 120 123 stanza.try_visit_matches_strict(tree, source, |mat| visit(stanza, mat))?; ··· 133 136 current_regex_captures: &'a Vec<String>, 134 137 function_parameters: &'a mut Vec<Value>, 135 138 mat: &'a QueryMatch<'a, 'tree>, 139 + full_match_stanza_capture_index: usize, 136 140 error_context: StatementContext, 137 141 inherited_variables: &'a HashSet<Identifier>, 138 142 shorthands: &'a AttributeShorthands, ··· 192 196 current_regex_captures, 193 197 function_parameters, 194 198 mat: &mat, 199 + full_match_stanza_capture_index: self.full_match_stanza_capture_index, 195 200 error_context, 196 201 inherited_variables, 197 202 shorthands, ··· 211 216 mut visit: F, 212 217 ) -> Result<(), E> 213 218 where 214 - F: FnMut(QueryMatch<'_, 'tree>) -> Result<(), E>, 219 + F: FnMut(&QueryMatch<'_, 'tree>) -> Result<(), E>, 215 220 { 216 221 let mut cursor = QueryCursor::new(); 217 - let matches = cursor.matches(&self.query, tree.root_node(), source.as_bytes()); 218 - for mat in matches { 222 + let mut matches = cursor.matches(&self.query, tree.root_node(), source.as_bytes()); 223 + while let Some(mat) = matches.next() { 219 224 visit(mat)?; 220 225 } 221 226 Ok(()) ··· 228 233 Statement::DeclareImmutable(s) => s.location, 229 234 Statement::DeclareMutable(s) => s.location, 230 235 Statement::Assign(s) => s.location, 236 + Statement::Expr(s) => s.location, 231 237 Statement::CreateGraphNode(s) => s.location, 232 238 Statement::AddGraphNodeAttribute(s) => s.location, 233 239 Statement::CreateEdge(s) => s.location, ··· 245 251 Statement::DeclareImmutable(statement) => statement.execute(exec), 246 252 Statement::DeclareMutable(statement) => statement.execute(exec), 247 253 Statement::Assign(statement) => statement.execute(exec), 254 + Statement::Expr(statement) => statement.execute(exec), 248 255 Statement::CreateGraphNode(statement) => statement.execute(exec), 249 256 Statement::AddGraphNodeAttribute(statement) => statement.execute(exec), 250 257 Statement::CreateEdge(statement) => statement.execute(exec), ··· 278 285 } 279 286 } 280 287 288 + impl ExpressionStatement { 289 + fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 290 + self.value.evaluate(exec).map(|_| ()) 291 + } 292 + } 293 + 281 294 impl CreateGraphNode { 282 295 fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 283 296 let graph_node = exec.graph.add_graph_node(); 284 297 self.node 285 298 .add_debug_attrs(&mut exec.graph[graph_node].attributes, exec.config)?; 299 + if let Some(match_node_attr) = &exec.config.match_node_attr { 300 + let match_node = exec 301 + .mat 302 + .nodes_for_capture_index(exec.full_match_stanza_capture_index as u32) 303 + .next() 304 + .expect("missing capture for full match"); 305 + let syn_node = exec.graph.add_syntax_node(match_node); 306 + exec.graph[graph_node] 307 + .attributes 308 + .add(match_node_attr.clone(), syn_node) 309 + .map_err(|_| { 310 + ExecutionError::DuplicateAttribute(format!( 311 + " {} on graph node ({}) in {}", 312 + match_node_attr, graph_node, self, 313 + )) 314 + })?; 315 + } 286 316 let value = Value::GraphNode(graph_node); 287 317 self.node.add(exec, value, false) 288 318 } ··· 314 344 let source = self.source.evaluate(exec)?.into_graph_node_ref()?; 315 345 let sink = self.sink.evaluate(exec)?.into_graph_node_ref()?; 316 346 let edge = match exec.graph[source].add_edge(sink) { 317 - Ok(edge) => edge, 318 - Err(_) => { 319 - return Err(ExecutionError::DuplicateEdge(format!( 320 - "({} -> {}) in {}", 321 - source, sink, self, 322 - )))? 323 - } 347 + Ok(edge) | Err(edge) => edge, 324 348 }; 325 349 self.add_debug_attrs(&mut edge.attributes, exec.config)?; 326 350 Ok(()) ··· 408 432 current_regex_captures: &current_regex_captures, 409 433 function_parameters: exec.function_parameters, 410 434 mat: exec.mat, 435 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 411 436 error_context: exec.error_context.clone(), 412 437 inherited_variables: exec.inherited_variables, 413 438 shorthands: exec.shorthands, ··· 468 493 current_regex_captures: exec.current_regex_captures, 469 494 function_parameters: exec.function_parameters, 470 495 mat: exec.mat, 496 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 471 497 error_context: exec.error_context.clone(), 472 498 inherited_variables: exec.inherited_variables, 473 499 shorthands: exec.shorthands, ··· 510 536 current_regex_captures: exec.current_regex_captures, 511 537 function_parameters: exec.function_parameters, 512 538 mat: exec.mat, 539 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 513 540 error_context: exec.error_context.clone(), 514 541 inherited_variables: exec.inherited_variables, 515 542 shorthands: exec.shorthands, ··· 585 612 current_regex_captures: exec.current_regex_captures, 586 613 function_parameters: exec.function_parameters, 587 614 mat: exec.mat, 615 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 588 616 error_context: exec.error_context.clone(), 589 617 inherited_variables: exec.inherited_variables, 590 618 shorthands: exec.shorthands, ··· 625 653 current_regex_captures: exec.current_regex_captures, 626 654 function_parameters: exec.function_parameters, 627 655 mat: exec.mat, 656 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 628 657 error_context: exec.error_context.clone(), 629 658 inherited_variables: exec.inherited_variables, 630 659 shorthands: exec.shorthands, ··· 882 911 current_regex_captures: exec.current_regex_captures, 883 912 function_parameters: exec.function_parameters, 884 913 mat: exec.mat, 914 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 885 915 error_context: exec.error_context.clone(), 886 916 inherited_variables: exec.inherited_variables, 887 917 shorthands: exec.shorthands,
+15 -10
src/execution.rs
··· 119 119 .iter() 120 120 .map(|name| { 121 121 let index = file_query 122 - .capture_index_for_name(name) 122 + .capture_index_for_name(*name) 123 123 .expect("missing index for capture"); 124 124 let quantifier = 125 125 file_query.capture_quantifiers(mat.pattern_index)[index as usize]; 126 - (name, quantifier, index) 126 + (*name, quantifier, index) 127 127 }) 128 128 .filter(|c| c.2 != stanza.full_match_file_capture_index as u32) 129 129 .collect(); ··· 143 143 .map(|name| { 144 144 let index = stanza 145 145 .query 146 - .capture_index_for_name(name) 146 + .capture_index_for_name(*name) 147 147 .expect("missing index for capture"); 148 148 let quantifier = stanza.query.capture_quantifiers(0)[index as usize]; 149 - (name, quantifier, index) 149 + (*name, quantifier, index) 150 150 }) 151 151 .filter(|c| c.2 != stanza.full_match_stanza_capture_index as u32) 152 152 .collect(); ··· 179 179 .map(|name| { 180 180 let index = self 181 181 .query 182 - .capture_index_for_name(name) 182 + .capture_index_for_name(*name) 183 183 .expect("missing index for capture"); 184 184 let quantifier = self.query.capture_quantifiers(0)[index as usize]; 185 - (name, quantifier, index) 185 + (*name, quantifier, index) 186 186 }) 187 187 .filter(|c| c.2 != self.full_match_stanza_capture_index as u32) 188 188 .collect(); ··· 197 197 } 198 198 199 199 pub struct Match<'a, 'tree> { 200 - mat: QueryMatch<'a, 'tree>, 200 + mat: &'a QueryMatch<'a, 'tree>, 201 201 full_capture_index: u32, 202 - named_captures: Vec<(&'a String, CaptureQuantifier, u32)>, 202 + named_captures: Vec<(&'a str, CaptureQuantifier, u32)>, 203 203 query_location: Location, 204 204 } 205 205 ··· 217 217 &'s self, 218 218 ) -> impl Iterator< 219 219 Item = ( 220 - &String, 220 + &'a str, 221 221 CaptureQuantifier, 222 222 impl Iterator<Item = Node<'tree>> + 's, 223 223 ), ··· 239 239 } 240 240 241 241 /// Return an iterator over all capture names. 242 - pub fn capture_names(&self) -> impl Iterator<Item = &String> { 242 + pub fn capture_names(&self) -> impl Iterator<Item = &str> { 243 243 self.named_captures.iter().map(|c| c.0) 244 244 } 245 245 ··· 256 256 pub(crate) lazy: bool, 257 257 pub(crate) location_attr: Option<Identifier>, 258 258 pub(crate) variable_name_attr: Option<Identifier>, 259 + pub(crate) match_node_attr: Option<Identifier>, 259 260 } 260 261 261 262 impl<'a, 'g> ExecutionConfig<'a, 'g> { ··· 266 267 lazy: false, 267 268 location_attr: None, 268 269 variable_name_attr: None, 270 + match_node_attr: None, 269 271 } 270 272 } 271 273 ··· 273 275 self, 274 276 location_attr: Identifier, 275 277 variable_name_attr: Identifier, 278 + match_node_attr: Identifier, 276 279 ) -> Self { 277 280 Self { 278 281 functions: self.functions, ··· 280 283 lazy: self.lazy, 281 284 location_attr: location_attr.into(), 282 285 variable_name_attr: variable_name_attr.into(), 286 + match_node_attr: match_node_attr.into(), 283 287 } 284 288 } 285 289 ··· 290 294 lazy, 291 295 location_attr: self.location_attr, 292 296 variable_name_attr: self.variable_name_attr, 297 + match_node_attr: self.match_node_attr, 293 298 } 294 299 } 295 300 }
+1 -1
src/functions.rs
··· 575 575 let replacement = parameters.param()?.into_string()?; 576 576 parameters.finish()?; 577 577 Ok(Value::String( 578 - pattern.replace_all(&text, replacement).to_string(), 578 + pattern.replace_all(&text, replacement.as_str()).to_string(), 579 579 )) 580 580 } 581 581 }
+7 -3
src/graph.rs
··· 275 275 276 276 /// Adds an attribute to this attribute set. If there was already an attribute with the same 277 277 /// name, replaces its value and returns `Err`. 278 - pub fn add<V: Into<Value>>(&mut self, name: Identifier, value: V) -> Result<(), ()> { 278 + pub fn add<V: Into<Value>>(&mut self, name: Identifier, value: V) -> Result<(), Value> { 279 279 match self.values.entry(name) { 280 280 Entry::Occupied(mut o) => { 281 - o.insert(value.into()); 282 - Err(()) 281 + let value = value.into(); 282 + if o.get() != &value { 283 + Err(o.insert(value.into())) 284 + } else { 285 + Ok(()) 286 + } 283 287 } 284 288 Entry::Vacant(v) => { 285 289 v.insert(value.into());
+17 -17
src/parse_error.rs
··· 39 39 } 40 40 41 41 /// Return all parse errors in the given tree. 42 - pub fn all(tree: &'tree Tree) -> Vec<ParseError> { 42 + pub fn all(tree: &'tree Tree) -> Vec<ParseError<'tree>> { 43 43 let mut errors = Vec::new(); 44 44 find_errors(tree, &mut errors, false); 45 45 errors ··· 396 396 f, 397 397 "{}{}:{}:{}:", 398 398 " ".repeat(self.indent), 399 - white_bold(&self.path.to_string_lossy()), 400 - white_bold(&format!("{}", self.row + 1)), 401 - white_bold(&format!("{}", self.columns.start + 1)), 399 + header_style(&self.path.to_string_lossy()), 400 + header_style(&format!("{}", self.row + 1)), 401 + header_style(&format!("{}", self.columns.start + 1)), 402 402 )?; 403 403 if let Some(source) = self.source { 404 404 // first line: line number & source ··· 406 406 f, 407 407 "{}{}{}{}", 408 408 " ".repeat(self.indent), 409 - blue(&format!("{}", self.row + 1)), 410 - blue(" | "), 409 + prefix_style(&format!("{}", self.row + 1)), 410 + prefix_style(" | "), 411 411 source, 412 412 )?; 413 413 // second line: caret ··· 416 416 "{}{}{}{}{}", 417 417 " ".repeat(self.indent), 418 418 " ".repeat(self.gutter_width()), 419 - blue(" | "), 419 + prefix_style(" | "), 420 420 " ".repeat(self.columns.start), 421 - green_bold(&"^".repeat(self.columns.len())) 421 + underline_style(&"^".repeat(self.columns.len())), 422 422 )?; 423 423 } else { 424 424 writeln!(f, "{}{}", " ".repeat(self.indent), "<missing source>",)?; ··· 430 430 // coloring functions 431 431 432 432 #[cfg(feature = "term-colors")] 433 - fn blue(str: &str) -> impl std::fmt::Display { 434 - str.blue() 433 + fn header_style(str: &str) -> impl std::fmt::Display { 434 + str.bold() 435 435 } 436 436 #[cfg(not(feature = "term-colors"))] 437 - fn blue<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 437 + fn header_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 438 438 str 439 439 } 440 440 441 441 #[cfg(feature = "term-colors")] 442 - fn green_bold(str: &str) -> impl std::fmt::Display { 443 - str.green().bold() 442 + fn prefix_style(str: &str) -> impl std::fmt::Display { 443 + str.dimmed() 444 444 } 445 445 #[cfg(not(feature = "term-colors"))] 446 - fn green_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 446 + fn prefix_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 447 447 str 448 448 } 449 449 450 450 #[cfg(feature = "term-colors")] 451 - fn white_bold(str: &str) -> impl std::fmt::Display { 452 - str.white().bold() 451 + fn underline_style(str: &str) -> impl std::fmt::Display { 452 + str.green() 453 453 } 454 454 #[cfg(not(feature = "term-colors"))] 455 - fn white_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 455 + fn underline_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 456 456 str 457 457 }
+12 -4
src/parser.rs
··· 305 305 let name = self.parse_identifier("inherit")?; 306 306 file.inherited_variables.insert(name); 307 307 } else { 308 - let stanza = self.parse_stanza(file.language)?; 308 + let stanza = self.parse_stanza(&file.language)?; 309 309 file.stanzas.push(stanza); 310 310 } 311 311 self.consume_whitespace(); 312 312 } 313 313 // we can unwrap here because all queries have already been parsed before 314 - file.query = Some(Query::new(file.language, &self.query_source).unwrap()); 314 + file.query = Some(Query::new(&file.language, &self.query_source).unwrap()); 315 315 Ok(()) 316 316 } 317 317 ··· 369 369 Ok(quantifier) 370 370 } 371 371 372 - fn parse_stanza(&mut self, language: Language) -> Result<ast::Stanza, ParseError> { 372 + fn parse_stanza(&mut self, language: &Language) -> Result<ast::Stanza, ParseError> { 373 373 let start = self.location; 374 374 let (query, full_match_stanza_capture_index) = self.parse_query(language)?; 375 375 self.consume_whitespace(); ··· 385 385 }) 386 386 } 387 387 388 - fn parse_query(&mut self, language: Language) -> Result<(Query, usize), ParseError> { 388 + fn parse_query(&mut self, language: &Language) -> Result<(Query, usize), ParseError> { 389 389 let location = self.location; 390 390 let query_start = self.offset; 391 391 self.skip_query()?; ··· 482 482 } 483 483 484 484 fn parse_statement(&mut self) -> Result<ast::Statement, ParseError> { 485 + if matches!(self.peek(), Ok( '#' | '"' | '@' | '$' | '(' | '[' | '{')) { 486 + if let Ok(value) = self.parse_expression() { 487 + return Ok(ast::Statement::Expr(ast::ExpressionStatement { 488 + location: self.location, 489 + value, 490 + })); 491 + } 492 + } 485 493 let keyword_location = self.location; 486 494 let keyword = self.parse_name("keyword")?; 487 495 self.consume_whitespace();
+1 -1
src/variables.rs
··· 104 104 v.into_key().to_string(), 105 105 ))), 106 106 Occupied(mut o) => { 107 - let mut variable = o.get_mut(); 107 + let variable = o.get_mut(); 108 108 if variable.mutable { 109 109 variable.value = value; 110 110 Ok(())
+68 -2
tests/it/execution.rs
··· 27 27 fn execute(python_source: &str, dsl_source: &str) -> Result<String, ExecutionError> { 28 28 init_log(); 29 29 let mut parser = Parser::new(); 30 - parser.set_language(tree_sitter_python::language()).unwrap(); 30 + parser 31 + .set_language(&tree_sitter_python::LANGUAGE.into()) 32 + .unwrap(); 31 33 let tree = parser.parse(python_source, None).unwrap(); 32 34 let file = 33 - File::from_str(tree_sitter_python::language(), dsl_source).expect("Cannot parse file"); 35 + File::from_str(tree_sitter_python::LANGUAGE.into(), dsl_source).expect("Cannot parse file"); 34 36 let functions = Functions::stdlib(); 35 37 let mut globals = Variables::new(); 36 38 globals ··· 1008 1010 "#}, 1009 1011 ); 1010 1012 } 1013 + 1014 + #[test] 1015 + fn can_add_edge_twice() { 1016 + check_execution( 1017 + indoc! { r#" 1018 + pass 1019 + "#}, 1020 + indoc! {r#" 1021 + (module) { 1022 + node n1; 1023 + node n2; 1024 + edge n1 -> n2; 1025 + edge n1 -> n2; 1026 + } 1027 + "#}, 1028 + indoc! {r#" 1029 + node 0 1030 + edge 0 -> 1 1031 + node 1 1032 + "#}, 1033 + ); 1034 + } 1035 + 1036 + #[test] 1037 + fn can_set_node_attribute_value_twice() { 1038 + check_execution( 1039 + indoc! { r#" 1040 + pass 1041 + "#}, 1042 + indoc! {r#" 1043 + (module) { 1044 + node n; 1045 + attr (n) foo = #true; 1046 + } 1047 + "#}, 1048 + indoc! {r#" 1049 + node 0 1050 + foo: #true 1051 + "#}, 1052 + ); 1053 + } 1054 + 1055 + #[test] 1056 + fn cannot_change_attribute_value() { 1057 + check_execution( 1058 + indoc! { r#" 1059 + pass 1060 + "#}, 1061 + indoc! {r#" 1062 + (module) { 1063 + node n1; 1064 + node n2; 1065 + edge n1 -> n2; 1066 + attr (n1 -> n2) foo = #true; 1067 + } 1068 + "#}, 1069 + indoc! {r#" 1070 + node 0 1071 + edge 0 -> 1 1072 + foo: #true 1073 + node 1 1074 + "#}, 1075 + ); 1076 + }
+4 -2
tests/it/functions.rs
··· 27 27 fn execute(python_source: &str, dsl_source: &str) -> Result<String, ExecutionError> { 28 28 init_log(); 29 29 let mut parser = Parser::new(); 30 - parser.set_language(tree_sitter_python::language()).unwrap(); 30 + parser 31 + .set_language(&tree_sitter_python::LANGUAGE.into()) 32 + .unwrap(); 31 33 let tree = parser.parse(python_source, None).unwrap(); 32 34 let file = 33 - File::from_str(tree_sitter_python::language(), dsl_source).expect("Cannot parse file"); 35 + File::from_str(tree_sitter_python::LANGUAGE.into(), dsl_source).expect("Cannot parse file"); 34 36 let functions = Functions::stdlib(); 35 37 let mut globals = Variables::new(); 36 38 globals
+3 -1
tests/it/graph.rs
··· 51 51 fn can_display_graph() { 52 52 let python_source = "pass"; 53 53 let mut parser = Parser::new(); 54 - parser.set_language(tree_sitter_python::language()).unwrap(); 54 + parser 55 + .set_language(&tree_sitter_python::LANGUAGE.into()) 56 + .unwrap(); 55 57 let tree = parser.parse(python_source, None).unwrap(); 56 58 57 59 let mut graph = Graph::new();
+68 -2
tests/it/lazy_execution.rs
··· 26 26 fn execute(python_source: &str, dsl_source: &str) -> Result<String, ExecutionError> { 27 27 init_log(); 28 28 let mut parser = Parser::new(); 29 - parser.set_language(tree_sitter_python::language()).unwrap(); 29 + parser 30 + .set_language(&tree_sitter_python::LANGUAGE.into()) 31 + .unwrap(); 30 32 let tree = parser.parse(python_source, None).unwrap(); 31 33 let file = 32 - File::from_str(tree_sitter_python::language(), dsl_source).expect("Cannot parse file"); 34 + File::from_str(tree_sitter_python::LANGUAGE.into(), dsl_source).expect("Cannot parse file"); 33 35 let functions = Functions::stdlib(); 34 36 let mut globals = Variables::new(); 35 37 globals ··· 1527 1529 "#}, 1528 1530 ); 1529 1531 } 1532 + 1533 + #[test] 1534 + fn can_add_edge_twice() { 1535 + check_execution( 1536 + indoc! { r#" 1537 + pass 1538 + "#}, 1539 + indoc! {r#" 1540 + (module) { 1541 + node n1; 1542 + node n2; 1543 + edge n1 -> n2; 1544 + edge n1 -> n2; 1545 + } 1546 + "#}, 1547 + indoc! {r#" 1548 + node 0 1549 + edge 0 -> 1 1550 + node 1 1551 + "#}, 1552 + ); 1553 + } 1554 + 1555 + #[test] 1556 + fn can_set_node_attribute_value_twice() { 1557 + check_execution( 1558 + indoc! { r#" 1559 + pass 1560 + "#}, 1561 + indoc! {r#" 1562 + (module) { 1563 + node n; 1564 + attr (n) foo = #true; 1565 + } 1566 + "#}, 1567 + indoc! {r#" 1568 + node 0 1569 + foo: #true 1570 + "#}, 1571 + ); 1572 + } 1573 + 1574 + #[test] 1575 + fn cannot_change_attribute_value() { 1576 + check_execution( 1577 + indoc! { r#" 1578 + pass 1579 + "#}, 1580 + indoc! {r#" 1581 + (module) { 1582 + node n1; 1583 + node n2; 1584 + edge n1 -> n2; 1585 + attr (n1 -> n2) foo = #true; 1586 + } 1587 + "#}, 1588 + indoc! {r#" 1589 + node 0 1590 + edge 0 -> 1 1591 + foo: #true 1592 + node 1 1593 + "#}, 1594 + ); 1595 + }
+3 -1
tests/it/parse_errors.rs
··· 23 23 fn parse(python_source: &str) -> Tree { 24 24 init_log(); 25 25 let mut parser = Parser::new(); 26 - parser.set_language(tree_sitter_python::language()).unwrap(); 26 + parser 27 + .set_language(&tree_sitter_python::LANGUAGE.into()) 28 + .unwrap(); 27 29 parser.parse(python_source, None).unwrap() 28 30 } 29 31
+64 -39
tests/it/parser.rs
··· 27 27 set @cap2.var1 = loc1 28 28 } 29 29 "#; 30 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 30 + let file = 31 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 31 32 32 33 let loc1 = Identifier::from("loc1"); 33 34 let precedence = Identifier::from("precedence"); ··· 228 229 let t = #true 229 230 } 230 231 "#; 231 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 232 + let file = 233 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 232 234 233 235 let f = Identifier::from("f"); 234 236 let n = Identifier::from("n"); ··· 284 286 let loc1 = "\"abc,\ndef\\" 285 287 } 286 288 "#; 287 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 289 + let file = 290 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 288 291 289 292 let loc1 = Identifier::from("loc1"); 290 293 ··· 318 321 let list3 = ["hello", "world",] 319 322 } 320 323 "#; 321 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 324 + let file = 325 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 322 326 323 327 let list1 = Identifier::from("list1"); 324 328 let list2 = Identifier::from("list2"); ··· 395 399 let set3 = {"hello", "world",} 396 400 } 397 401 "#; 398 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 402 + let file = 403 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 399 404 400 405 let set1 = Identifier::from("set1"); 401 406 let set2 = Identifier::from("set2"); ··· 470 475 print "x =", 5 471 476 } 472 477 "#; 473 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 478 + let file = 479 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 474 480 475 481 let statements = file 476 482 .stanzas ··· 505 511 node n 506 512 } 507 513 "#; 508 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 514 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 509 515 panic!("Parse succeeded unexpectedly"); 510 516 } 511 517 } ··· 518 524 print @stmts 519 525 } 520 526 "#; 521 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 527 + let file = 528 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 522 529 523 530 let stmts = Identifier::from("stmts"); 524 531 ··· 553 560 print @stmts 554 561 } 555 562 "#; 556 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 563 + let file = 564 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 557 565 558 566 let stmt = Identifier::from("stmt"); 559 567 let stmts = Identifier::from("stmts"); ··· 602 610 print @stmts 603 611 } 604 612 "#; 605 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 613 + let file = 614 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 606 615 607 616 let stmts = Identifier::from("stmts"); 608 617 ··· 636 645 print @stmt 637 646 } 638 647 "#; 639 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 648 + let file = 649 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 640 650 641 651 let stmt = Identifier::from("stmt"); 642 652 ··· 670 680 print @stmt 671 681 } 672 682 "#; 673 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 683 + let file = 684 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 674 685 675 686 let stmt = Identifier::from("stmt"); 676 687 ··· 704 715 print @stmt 705 716 } 706 717 "#; 707 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 718 + let file = 719 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 708 720 709 721 let stmt = Identifier::from("stmt"); 710 722 ··· 738 750 print @stmt 739 751 } 740 752 "#; 741 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 753 + let file = 754 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 742 755 743 756 let stmt = Identifier::from("stmt"); 744 757 ··· 774 787 } 775 788 } 776 789 "#; 777 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 790 + let file = 791 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 778 792 779 793 let x = Identifier::from("x"); 780 794 ··· 826 840 } 827 841 } 828 842 "#; 829 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 843 + let file = 844 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 830 845 831 846 let x = Identifier::from("x"); 832 847 ··· 903 918 } 904 919 } 905 920 "#; 906 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 921 + let file = 922 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 907 923 908 924 let x = Identifier::from("x"); 909 925 ··· 967 983 } 968 984 } 969 985 "#; 970 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 986 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 971 987 panic!("Parse succeeded unexpectedly"); 972 988 } 973 989 } ··· 982 998 } 983 999 } 984 1000 "#; 985 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1001 + let file = 1002 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 986 1003 987 1004 let xs = Identifier::from("xs"); 988 1005 let x = Identifier::from("x"); ··· 1032 1049 } 1033 1050 } 1034 1051 "#; 1035 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1052 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1036 1053 panic!("Parse succeeded unexpectedly"); 1037 1054 } 1038 1055 } ··· 1051 1068 } 1052 1069 } 1053 1070 "#; 1054 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1071 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1055 1072 panic!("Parse succeeded unexpectedly"); 1056 1073 } 1057 1074 } ··· 1071 1088 } 1072 1089 } 1073 1090 "#; 1074 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1091 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1075 1092 panic!("Parse succeeded unexpectedly"); 1076 1093 } 1077 1094 } ··· 1084 1101 print [ (named-child-index x) for x in @xs ] 1085 1102 } 1086 1103 "#; 1087 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1104 + let file = 1105 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1088 1106 1089 1107 let statements = file 1090 1108 .stanzas ··· 1137 1155 print { (named-child-index x) for x in @xs } 1138 1156 } 1139 1157 "#; 1140 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1158 + let file = 1159 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1141 1160 1142 1161 let statements = file 1143 1162 .stanzas ··· 1192 1211 edge n -> root 1193 1212 } 1194 1213 "#; 1195 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1214 + let file = 1215 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1196 1216 1197 1217 assert_eq!( 1198 1218 file.globals, ··· 1248 1268 print PKG_NAME 1249 1269 } 1250 1270 "#; 1251 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1271 + let file = 1272 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1252 1273 1253 1274 assert_eq!( 1254 1275 file.globals, ··· 1287 1308 edge n -> root 1288 1309 } 1289 1310 "#; 1290 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1311 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1291 1312 panic!("Parse succeeded unexpectedly"); 1292 1313 } 1293 1314 } ··· 1304 1325 } 1305 1326 } 1306 1327 "#; 1307 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1328 + let file = 1329 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1308 1330 1309 1331 assert_eq!( 1310 1332 file.globals, ··· 1377 1399 } 1378 1400 } 1379 1401 "#; 1380 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1402 + let file = 1403 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1381 1404 1382 1405 assert_eq!( 1383 1406 file.globals, ··· 1448 1471 node root 1449 1472 } 1450 1473 "#; 1451 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1474 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1452 1475 panic!("Parse succeeded unexpectedly"); 1453 1476 } 1454 1477 } ··· 1462 1485 node root 1463 1486 } 1464 1487 "#; 1465 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1488 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1466 1489 panic!("Parse succeeded unexpectedly"); 1467 1490 } 1468 1491 } ··· 1476 1499 set root = #null 1477 1500 } 1478 1501 "#; 1479 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1502 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1480 1503 panic!("Parse succeeded unexpectedly"); 1481 1504 } 1482 1505 } ··· 1490 1513 attr (n) sh = @name 1491 1514 } 1492 1515 "#; 1493 - let file = File::from_str(tree_sitter_python::language(), source).expect("Cannot parse file"); 1516 + let file = 1517 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("Cannot parse file"); 1494 1518 1495 1519 let shorthands = file.shorthands.into_iter().collect::<Vec<_>>(); 1496 1520 assert_eq!( ··· 1536 1560 { 1537 1561 } 1538 1562 "#; 1539 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1563 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1540 1564 panic!("Parse succeeded unexpectedly"); 1541 1565 } 1542 1566 } ··· 1548 1572 (module (non_existing_node)) 1549 1573 {} 1550 1574 "#; 1551 - let err = match File::from_str(tree_sitter_python::language(), source) { 1575 + let err = match File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1552 1576 Ok(_) => panic!("Parse succeeded unexpectedly"), 1553 1577 Err(ParseError::QueryError(e)) => e, 1554 1578 Err(e) => panic!("Unexpected error: {}", e), ··· 1570 1594 ] 1571 1595 {} 1572 1596 "#; 1573 - let err = match File::from_str(tree_sitter_python::language(), source) { 1597 + let err = match File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1574 1598 Ok(_) => panic!("Parse succeeded unexpectedly"), 1575 1599 Err(ParseError::QueryError(e)) => e, 1576 1600 Err(e) => panic!("Unexpected error: {}", e), ··· 1586 1610 (function_definition name: (identifier) @name) { 1587 1611 } 1588 1612 "#; 1589 - if let Ok(_) = File::from_str(tree_sitter_python::language(), source) { 1613 + if let Ok(_) = File::from_str(tree_sitter_python::LANGUAGE.into(), source) { 1590 1614 panic!("Parse succeeded unexpectedly"); 1591 1615 } 1592 1616 } ··· 1597 1621 (function_definition name: (identifier) @_name) { 1598 1622 } 1599 1623 "#; 1600 - File::from_str(tree_sitter_python::language(), source).expect("parse to succeed"); 1624 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("parse to succeed"); 1601 1625 } 1602 1626 1603 1627 #[test] ··· 1605 1629 let source = r#" 1606 1630 inherit .scope 1607 1631 "#; 1608 - let file = File::from_str(tree_sitter_python::language(), source).expect("parse to succeed"); 1632 + let file = 1633 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("parse to succeed"); 1609 1634 assert!(file.inherited_variables.contains("scope".into())); 1610 1635 }
+6
vscode/CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## 0.1.2 -- 2023-12-12 9 + 10 + ### Added 11 + 12 + - Add missing `else` keyword 13 + 8 14 ## 0.1.1 -- 2023-06-01 9 15 10 16 ### Added
+1 -1
vscode/package.json
··· 1 1 { 2 2 "name": "tree-sitter-graph", 3 - "version": "0.1.1", 3 + "version": "0.1.2", 4 4 "publisher": "tree-sitter", 5 5 "engines": { 6 6 "vscode": "^1.60.0"
+1 -1
vscode/syntaxes/tree-sitter-graph.tmLanguage.json
··· 16 16 "keywords": { 17 17 "patterns": [{ 18 18 "name": "keyword.control.tsg", 19 - "match": "^\\s*(attr|attribute|edge|for|global|if|inherit|let|node|none|print|scan|set|some|var)\\b" 19 + "match": "^\\s*(attr|attribute|edge|else|for|global|if|inherit|let|node|none|print|scan|set|some|var)\\b" 20 20 }] 21 21 }, 22 22 "functions": {