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
+98
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 + 58 + ## v0.10.3 -- 2023-06-01 59 + 60 + ### DSL 61 + 62 + #### Added 63 + 64 + - Scoped variables can be inherited by child nodes by specifying `inherit .var_name` as part of the source. Using `@node.var_name` will either use the value set on the node itself, or otherwise the one set on the closest enclosing node. 65 + 66 + ## v0.10.2 -- 2023-05-25 67 + 68 + ### Library 69 + 70 + #### Added 71 + 72 + - Edge debug information now contains the TSG location of the `edge` statement if `ExecutionConfig::location_attr` is set. 73 + 74 + #### Changed 75 + 76 + - The TSG location in the debug information is formatted more explicitly as `line XX column YY` instead of `(XX, YY)`. 77 + 78 + ## v0.10.1 -- 2023-05-15 79 + 80 + ### Library 81 + 82 + #### Added 83 + 84 + - The `ast::File::try_visit_matches` method can be used to execute the stanza query matching without executing the stanza bodies. 85 + 86 + ## v0.10.0 -- 2023-05-10 87 + 88 + ### DSL 89 + 90 + #### Changed 91 + 92 + - Unused captures in patterns are now considered errors, unless they start with an underscore. 93 + 94 + ### Library 95 + 96 + #### Fixed 97 + 98 + - Some execution errors were not always reported in lazy execution mode, depending on whether variables were used or not. This has been fixed to behave more consistently, so that errors are always reported regardless of variable use. 99 + 100 + ### CLI 101 + 102 + #### Added 103 + 104 + - Errors from setting a scoped variable twice on the same node are now reported with source snippets for both statements involved. 105 + 8 106 ## v0.9.2 -- 2023-04-14 9 107 10 108 ### Library
+20 -44
Cargo.toml
··· 1 1 [package] 2 2 name = "tree-sitter-graph" 3 - version = "0.9.2" 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.9" 23 + tree-sitter-graph = "0.12" 24 24 ``` 25 25 26 26 To use it as a program, install it via `cargo install`:
+31 -1
src/ast.rs
··· 9 9 10 10 use regex::Regex; 11 11 use std::collections::HashMap; 12 + use std::collections::HashSet; 12 13 use std::fmt; 13 14 use tree_sitter::CaptureQuantifier; 14 15 use tree_sitter::Language; 15 16 use tree_sitter::Query; 16 17 18 + use crate::parser::Range; 17 19 use crate::Identifier; 18 20 use crate::Location; 19 21 ··· 23 25 pub language: Language, 24 26 /// The expected global variables used in this file 25 27 pub globals: Vec<Global>, 28 + /// The scoped variables that are inherited by child nodes 29 + pub inherited_variables: HashSet<Identifier>, 26 30 /// The combined query of all stanzas in the file 27 31 pub query: Option<Query>, 28 32 /// The list of stanzas in the file ··· 36 40 File { 37 41 language, 38 42 globals: Vec::new(), 43 + inherited_variables: HashSet::new(), 39 44 query: None, 40 45 stanzas: Vec::new(), 41 46 shorthands: AttributeShorthands::new(), ··· 66 71 pub full_match_stanza_capture_index: usize, 67 72 /// Capture index of the full match in the file query 68 73 pub full_match_file_capture_index: usize, 69 - pub location: Location, 74 + pub range: Range, 70 75 } 71 76 72 77 /// A statement that can appear in a graph DSL stanza ··· 76 81 DeclareImmutable(DeclareImmutable), 77 82 DeclareMutable(DeclareMutable), 78 83 Assign(Assign), 84 + Expr(ExpressionStatement), 79 85 // Graph nodes 80 86 CreateGraphNode(CreateGraphNode), 81 87 AddGraphNodeAttribute(AddGraphNodeAttribute), ··· 98 104 Self::DeclareImmutable(stmt) => stmt.fmt(f), 99 105 Self::DeclareMutable(stmt) => stmt.fmt(f), 100 106 Self::Assign(stmt) => stmt.fmt(f), 107 + Self::Expr(stmt) => stmt.fmt(f), 101 108 Self::CreateGraphNode(stmt) => stmt.fmt(f), 102 109 Self::AddGraphNodeAttribute(stmt) => stmt.fmt(f), 103 110 Self::CreateEdge(stmt) => stmt.fmt(f), ··· 156 163 write!(f, " {}", attr)?; 157 164 } 158 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 + ) 159 189 } 160 190 } 161 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()))?;
+12 -2
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) ··· 198 198 if !unused_captures.is_empty() { 199 199 return Err(CheckError::UnusedCaptures( 200 200 unused_captures.join(" "), 201 - self.location, 201 + self.range.start, 202 202 )); 203 203 } 204 204 ··· 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 -1
src/execution/error.rs
··· 95 95 Self { 96 96 statement: format!("{}", stmt), 97 97 statement_location: stmt.location(), 98 - stanza_location: stanza.location, 98 + stanza_location: stanza.range.start, 99 99 source_location: Location::from(source_node.range().start_point), 100 100 node_kind: source_node.kind().to_string(), 101 101 }
+100 -40
src/execution/lazy/statements.rs
··· 14 14 15 15 use crate::execution::error::ExecutionError; 16 16 use crate::execution::error::ResultWithExecutionError; 17 + use crate::graph::Attributes; 17 18 use crate::Identifier; 18 19 19 20 use super::store::DebugInfo; 20 21 use super::values::*; 21 22 use super::EvaluationContext; 22 23 use super::GraphElementKey; 24 + 25 + /// Lazy graph 26 + #[derive(Debug)] 27 + pub(super) struct LazyGraph { 28 + edge_statements: Vec<LazyStatement>, 29 + attr_statements: Vec<LazyStatement>, 30 + print_statements: Vec<LazyStatement>, 31 + } 32 + 33 + impl LazyGraph { 34 + pub(super) fn new() -> Self { 35 + LazyGraph { 36 + edge_statements: Vec::new(), 37 + attr_statements: Vec::new(), 38 + print_statements: Vec::new(), 39 + } 40 + } 41 + 42 + pub(super) fn push(&mut self, stmt: LazyStatement) { 43 + match stmt { 44 + LazyStatement::AddGraphNodeAttribute(_) => self.attr_statements.push(stmt), 45 + LazyStatement::CreateEdge(_) => self.edge_statements.push(stmt), 46 + LazyStatement::AddEdgeAttribute(_) => self.attr_statements.push(stmt), 47 + LazyStatement::Print(_) => self.print_statements.push(stmt), 48 + } 49 + } 50 + 51 + pub(super) fn evaluate(&self, exec: &mut EvaluationContext) -> Result<(), ExecutionError> { 52 + for stmt in &self.edge_statements { 53 + stmt.evaluate(exec)?; 54 + } 55 + for stmt in &self.attr_statements { 56 + stmt.evaluate(exec)?; 57 + } 58 + for stmt in &self.print_statements { 59 + stmt.evaluate(exec)?; 60 + } 61 + Ok(()) 62 + } 63 + } 23 64 24 65 /// Lazy graph statements 25 66 #[derive(Debug)] ··· 111 152 } 112 153 113 154 pub(super) fn evaluate(&self, exec: &mut EvaluationContext) -> Result<(), ExecutionError> { 114 - let node = self.node.evaluate_as_graph_node(exec)?; 155 + let node = self 156 + .node 157 + .evaluate_as_graph_node(exec) 158 + .with_context(|| "Evaluating target node".to_string().into())?; 115 159 for attribute in &self.attributes { 116 160 let value = attribute.value.evaluate(exec)?; 117 161 let prev_debug_info = exec.prev_element_debug_info.insert( 118 162 GraphElementKey::NodeAttribute(node, attribute.name.clone()), 119 163 self.debug_info.clone(), 120 164 ); 121 - exec.graph[node] 165 + if let Err(_) = exec.graph[node] 122 166 .attributes 123 167 .add(attribute.name.clone(), value) 124 - .map_err(|_| { 125 - ExecutionError::DuplicateAttribute(format!( 126 - "{} on {} at {} and {}", 127 - attribute.name, 128 - node, 129 - prev_debug_info.unwrap(), 130 - self.debug_info, 131 - )) 132 - })?; 168 + { 169 + return Err(ExecutionError::DuplicateAttribute(format!( 170 + "{} on {}", 171 + attribute.name, node, 172 + ))) 173 + .with_context(|| { 174 + ( 175 + prev_debug_info.unwrap().into(), 176 + self.debug_info.clone().into(), 177 + ) 178 + .into() 179 + }); 180 + }; 133 181 } 134 182 Ok(()) 135 183 } ··· 150 198 pub(super) struct LazyCreateEdge { 151 199 source: LazyValue, 152 200 sink: LazyValue, 201 + attributes: Attributes, 153 202 debug_info: DebugInfo, 154 203 } 155 204 156 205 impl LazyCreateEdge { 157 - pub(super) fn new(source: LazyValue, sink: LazyValue, debug_info: DebugInfo) -> Self { 206 + pub(super) fn new( 207 + source: LazyValue, 208 + sink: LazyValue, 209 + attributes: Attributes, 210 + debug_info: DebugInfo, 211 + ) -> Self { 158 212 Self { 159 213 source, 160 214 sink, 215 + attributes, 161 216 debug_info, 162 217 } 163 218 } 164 219 165 220 pub(super) fn evaluate(&self, exec: &mut EvaluationContext) -> Result<(), ExecutionError> { 166 - let source = self.source.evaluate_as_graph_node(exec)?; 167 - let sink = self.sink.evaluate_as_graph_node(exec)?; 168 - let prev_debug_info = exec 169 - .prev_element_debug_info 170 - .insert(GraphElementKey::Edge(source, sink), self.debug_info.clone()); 171 - if let Err(_) = exec.graph[source].add_edge(sink) { 172 - Err(ExecutionError::DuplicateEdge(format!( 173 - "({} -> {}) at {} and {}", 174 - source, 175 - sink, 176 - prev_debug_info.unwrap(), 177 - self.debug_info, 178 - )))?; 179 - } 221 + let source = self 222 + .source 223 + .evaluate_as_graph_node(exec) 224 + .with_context(|| "Evaluating edge source".to_string().into())?; 225 + let sink = self 226 + .sink 227 + .evaluate_as_graph_node(exec) 228 + .with_context(|| "Evaluating edge sink".to_string().into())?; 229 + let edge = match exec.graph[source].add_edge(sink) { 230 + Ok(edge) | Err(edge) => edge, 231 + }; 232 + edge.attributes = self.attributes.clone(); 180 233 Ok(()) 181 234 } 182 235 } ··· 216 269 } 217 270 218 271 pub(super) fn evaluate(&self, exec: &mut EvaluationContext) -> Result<(), ExecutionError> { 219 - let source = self.source.evaluate_as_graph_node(exec)?; 220 - let sink = self.sink.evaluate_as_graph_node(exec)?; 272 + let source = self 273 + .source 274 + .evaluate_as_graph_node(exec) 275 + .with_context(|| "Evaluating edge source".to_string().into())?; 276 + let sink = self 277 + .sink 278 + .evaluate_as_graph_node(exec) 279 + .with_context(|| "Evaluating edge sink".to_string().into())?; 221 280 for attribute in &self.attributes { 222 281 let value = attribute.value.evaluate(exec)?; 223 282 let edge = match exec.graph[source].get_edge_mut(sink) { ··· 231 290 GraphElementKey::EdgeAttribute(source, sink, attribute.name.clone()), 232 291 self.debug_info.clone(), 233 292 ); 234 - edge.attributes 235 - .add(attribute.name.clone(), value) 236 - .map_err(|_| { 237 - ExecutionError::DuplicateAttribute(format!( 238 - "{} on edge ({} -> {}) at {} and {}", 239 - attribute.name, 240 - source, 241 - sink, 242 - prev_debug_info.unwrap(), 243 - self.debug_info, 244 - )) 245 - })?; 293 + if let Err(_) = edge.attributes.add(attribute.name.clone(), value) { 294 + return Err(ExecutionError::DuplicateAttribute(format!( 295 + "{} on edge ({} -> {})", 296 + attribute.name, source, sink, 297 + ))) 298 + .with_context(|| { 299 + ( 300 + prev_debug_info.unwrap().into(), 301 + self.debug_info.clone().into(), 302 + ) 303 + .into() 304 + }); 305 + } 246 306 } 247 307 Ok(()) 248 308 }
+38 -16
src/execution/lazy/store.rs
··· 20 20 use crate::execution::error::ResultWithExecutionError; 21 21 use crate::execution::error::StatementContext; 22 22 use crate::graph; 23 + use crate::graph::SyntaxNodeID; 23 24 use crate::graph::SyntaxNodeRef; 24 25 use crate::Identifier; 25 26 ··· 151 152 }; 152 153 let values = cell.replace(ScopedValues::Forcing); 153 154 let map = self.force(name, values, exec)?; 154 - let result = map 155 - .get(&scope) 156 - .ok_or(ExecutionError::UndefinedScopedVariable(format!( 157 - "{}.{}", 158 - scope, name, 159 - )))? 160 - .clone(); 155 + 156 + let mut result = None; 157 + 158 + if let Some(value) = map.get(&scope.index) { 159 + result = Some(value.clone()); 160 + } else if exec.inherited_variables.contains(name) { 161 + let mut parent = exec 162 + .graph 163 + .syntax_nodes 164 + .get(&scope.index) 165 + .and_then(|n| n.parent()); 166 + while let Some(scope) = parent { 167 + if let Some(value) = map.get(&(scope.id() as u32)) { 168 + result = Some(value.clone()); 169 + break; 170 + } 171 + parent = scope.parent(); 172 + } 173 + } 174 + 161 175 cell.replace(ScopedValues::Forced(map)); 162 - Ok(result) 176 + result.ok_or_else(|| ExecutionError::UndefinedScopedVariable(format!("{}.{}", scope, name))) 163 177 } 164 178 165 179 pub(super) fn evaluate_all(&self, exec: &mut EvaluationContext) -> Result<(), ExecutionError> { ··· 176 190 name: &Identifier, 177 191 values: ScopedValues, 178 192 exec: &mut EvaluationContext, 179 - ) -> Result<HashMap<SyntaxNodeRef, LazyValue>, ExecutionError> { 193 + ) -> Result<HashMap<SyntaxNodeID, LazyValue>, ExecutionError> { 180 194 match values { 181 195 ScopedValues::Unforced(pairs) => { 182 - let mut map = HashMap::new(); 196 + let mut values = HashMap::new(); 183 197 let mut debug_infos = HashMap::new(); 184 198 for (scope, value, debug_info) in pairs.into_iter() { 185 199 let node = scope 186 200 .evaluate_as_syntax_node(exec) 187 201 .with_context(|| format!("Evaluating scope of variable _.{}", name,).into()) 188 202 .with_context(|| debug_info.0.clone().into())?; 189 - let prev_debug_info = debug_infos.insert(node, debug_info.clone()); 190 - match map.insert(node, value.clone()) { 191 - 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)) => { 192 208 return Err(ExecutionError::DuplicateVariable(format!( 193 209 "{}.{}", 194 210 node, name, 195 211 ))) 196 - .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 + ) 197 219 } 198 220 _ => {} 199 221 }; 200 222 } 201 - Ok(map) 223 + Ok(values) 202 224 } 203 225 ScopedValues::Forcing => Err(ExecutionError::RecursivelyDefinedScopedVariable( 204 226 format!("_.{}", name), ··· 211 233 enum ScopedValues { 212 234 Unforced(Vec<(LazyValue, LazyValue, DebugInfo)>), 213 235 Forcing, 214 - Forced(HashMap<SyntaxNodeRef, LazyValue>), 236 + Forced(HashMap<SyntaxNodeID, LazyValue>), 215 237 } 216 238 217 239 impl ScopedValues {
+6 -1
src/execution/lazy/values.rs
··· 13 13 use std::fmt; 14 14 15 15 use crate::execution::error::ExecutionError; 16 + use crate::execution::error::ResultWithExecutionError; 16 17 use crate::graph::GraphNodeRef; 17 18 use crate::graph::SyntaxNodeRef; 18 19 use crate::graph::Value; ··· 184 185 } 185 186 186 187 fn resolve<'a>(&self, exec: &'a mut EvaluationContext) -> Result<LazyValue, ExecutionError> { 187 - let scope = self.scope.as_ref().evaluate_as_syntax_node(exec)?; 188 + let scope = self 189 + .scope 190 + .as_ref() 191 + .evaluate_as_syntax_node(exec) 192 + .with_context(|| format!("Evaluating scope of variable _.{}", self.name).into())?; 188 193 let scoped_store = &exec.scoped_store; 189 194 scoped_store.evaluate(&scope, &self.name, exec) 190 195 }
+84 -32
src/execution/lazy.rs
··· 12 12 use log::{debug, trace}; 13 13 14 14 use std::collections::HashMap; 15 + use std::collections::HashSet; 15 16 16 - use tree_sitter::CaptureQuantifier::One; 17 17 use tree_sitter::QueryCursor; 18 18 use tree_sitter::QueryMatch; 19 19 use tree_sitter::Tree; 20 + 21 + use streaming_iterator::StreamingIterator; 20 22 21 23 use crate::ast; 22 24 use crate::execution::error::ExecutionError; 23 25 use crate::execution::error::ResultWithExecutionError; 24 26 use crate::execution::error::StatementContext; 25 - use crate::execution::query_capture_value; 26 27 use crate::execution::ExecutionConfig; 27 28 use crate::functions::Functions; 28 29 use crate::graph; 30 + use crate::graph::Attributes; 29 31 use crate::graph::Graph; 32 + use crate::graph::Value; 30 33 use crate::variables::Globals; 31 34 use crate::variables::MutVariables; 32 35 use crate::variables::VariableMap; ··· 59 62 lazy: config.lazy, 60 63 location_attr: config.location_attr.clone(), 61 64 variable_name_attr: config.variable_name_attr.clone(), 65 + match_node_attr: config.match_node_attr.clone(), 62 66 }; 63 67 64 68 let mut locals = VariableMap::new(); 65 - let mut cursor = QueryCursor::new(); 66 69 let mut store = LazyStore::new(); 67 70 let mut scoped_store = LazyScopedVariables::new(); 68 - let mut lazy_graph = Vec::new(); 71 + let mut lazy_graph = LazyGraph::new(); 69 72 let mut function_parameters = Vec::new(); 70 73 let mut prev_element_debug_info = HashMap::new(); 71 74 72 - let query = &self.query.as_ref().unwrap(); 73 - let matches = cursor.matches(query, tree.root_node(), source.as_bytes()); 74 - for mat in matches { 75 + self.try_visit_matches_lazy(tree, source, |stanza, mat| { 75 76 cancellation_flag.check("processing matches")?; 76 - let stanza = &self.stanzas[mat.pattern_index]; 77 77 stanza.execute_lazy( 78 78 source, 79 79 &mat, ··· 85 85 &mut lazy_graph, 86 86 &mut function_parameters, 87 87 &mut prev_element_debug_info, 88 + &self.inherited_variables, 88 89 &self.shorthands, 89 90 cancellation_flag, 90 - )?; 91 - } 91 + ) 92 + })?; 92 93 93 94 let mut exec = EvaluationContext { 94 95 source, ··· 96 97 functions: config.functions, 97 98 store: &store, 98 99 scoped_store: &scoped_store, 100 + inherited_variables: &self.inherited_variables, 99 101 function_parameters: &mut function_parameters, 100 102 prev_element_debug_info: &mut prev_element_debug_info, 101 103 cancellation_flag, 102 104 }; 103 - for graph_stmt in &lazy_graph { 104 - graph_stmt.evaluate(&mut exec)?; 105 - } 105 + lazy_graph.evaluate(&mut exec)?; 106 106 // make sure any unforced values are now forced, to surface any problems 107 107 // hidden by the fact that the values were unused 108 108 store.evaluate_all(&mut exec)?; ··· 110 110 111 111 Ok(()) 112 112 } 113 + 114 + pub(super) fn try_visit_matches_lazy<'tree, E, F>( 115 + &self, 116 + tree: &'tree Tree, 117 + source: &'tree str, 118 + mut visit: F, 119 + ) -> Result<(), E> 120 + where 121 + F: FnMut(&ast::Stanza, &QueryMatch<'_, 'tree>) -> Result<(), E>, 122 + { 123 + let mut cursor = QueryCursor::new(); 124 + let query = self.query.as_ref().unwrap(); 125 + let mut matches = cursor.matches(query, tree.root_node(), source.as_bytes()); 126 + while let Some(mat) = matches.next() { 127 + let stanza = &self.stanzas[mat.pattern_index]; 128 + visit(stanza, mat)?; 129 + } 130 + Ok(()) 131 + } 113 132 } 114 133 115 134 /// Context for execution, which executes stanzas to build the lazy graph ··· 120 139 locals: &'a mut dyn MutVariables<LazyValue>, 121 140 current_regex_captures: &'a Vec<String>, 122 141 mat: &'a QueryMatch<'a, 'tree>, 142 + full_match_file_capture_index: usize, 123 143 store: &'a mut LazyStore, 124 144 scoped_store: &'a mut LazyScopedVariables, 125 - lazy_graph: &'a mut Vec<LazyStatement>, 145 + lazy_graph: &'a mut LazyGraph, 126 146 function_parameters: &'a mut Vec<graph::Value>, // re-usable buffer to reduce memory allocations 127 147 prev_element_debug_info: &'a mut HashMap<GraphElementKey, DebugInfo>, 128 148 error_context: StatementContext, 149 + inherited_variables: &'a HashSet<Identifier>, 129 150 shorthands: &'a ast::AttributeShorthands, 130 151 cancellation_flag: &'a dyn CancellationFlag, 131 152 } ··· 137 158 pub functions: &'a Functions, 138 159 pub store: &'a LazyStore, 139 160 pub scoped_store: &'a LazyScopedVariables, 161 + pub inherited_variables: &'a HashSet<Identifier>, 140 162 pub function_parameters: &'a mut Vec<graph::Value>, // re-usable buffer to reduce memory allocations 141 163 pub prev_element_debug_info: &'a mut HashMap<GraphElementKey, DebugInfo>, 142 164 pub cancellation_flag: &'a dyn CancellationFlag, ··· 145 167 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 146 168 pub(super) enum GraphElementKey { 147 169 NodeAttribute(graph::GraphNodeRef, Identifier), 148 - Edge(graph::GraphNodeRef, graph::GraphNodeRef), 149 170 EdgeAttribute(graph::GraphNodeRef, graph::GraphNodeRef, Identifier), 150 171 } 151 172 ··· 159 180 locals: &mut VariableMap<'l, LazyValue>, 160 181 store: &mut LazyStore, 161 182 scoped_store: &mut LazyScopedVariables, 162 - lazy_graph: &mut Vec<LazyStatement>, 183 + lazy_graph: &mut LazyGraph, 163 184 function_parameters: &mut Vec<graph::Value>, 164 185 prev_element_debug_info: &mut HashMap<GraphElementKey, DebugInfo>, 186 + inherited_variables: &HashSet<Identifier>, 165 187 shorthands: &ast::AttributeShorthands, 166 188 cancellation_flag: &dyn CancellationFlag, 167 189 ) -> Result<(), ExecutionError> { 168 190 let current_regex_captures = vec![]; 169 191 locals.clear(); 170 - let node = query_capture_value(self.full_match_file_capture_index, One, &mat, graph); 171 - debug!("match {} at {}", node, self.location); 192 + let node = mat 193 + .nodes_for_capture_index(self.full_match_file_capture_index as u32) 194 + .next() 195 + .expect("missing capture for full match"); 196 + debug!("match {:?} at {}", node, self.range.start); 172 197 trace!("{{"); 173 198 for statement in &self.statements { 174 - let error_context = { 175 - let node = mat 176 - .captures 177 - .iter() 178 - .find(|c| c.index as usize == self.full_match_file_capture_index) 179 - .expect("missing capture for full match") 180 - .node; 181 - StatementContext::new(&statement, &self, &node) 182 - }; 199 + let error_context = { StatementContext::new(&statement, &self, &node) }; 183 200 let mut exec = ExecutionContext { 184 201 source, 185 202 graph, ··· 187 204 locals, 188 205 current_regex_captures: &current_regex_captures, 189 206 mat, 207 + full_match_file_capture_index: self.full_match_file_capture_index, 190 208 store, 191 209 scoped_store, 192 210 lazy_graph, 193 211 function_parameters, 194 212 prev_element_debug_info, 195 213 error_context, 214 + inherited_variables, 196 215 shorthands, 197 216 cancellation_flag, 198 217 }; ··· 212 231 Self::DeclareImmutable(statement) => statement.execute_lazy(exec), 213 232 Self::DeclareMutable(statement) => statement.execute_lazy(exec), 214 233 Self::Assign(statement) => statement.execute_lazy(exec), 234 + Self::Expr(statement) => statement.value.evaluate_lazy(exec).map(|_| ()), 215 235 Self::CreateGraphNode(statement) => statement.execute_lazy(exec), 216 236 Self::AddGraphNodeAttribute(statement) => statement.execute_lazy(exec), 217 237 Self::CreateEdge(statement) => statement.execute_lazy(exec), ··· 250 270 let graph_node = exec.graph.add_graph_node(); 251 271 self.node 252 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 + } 253 290 self.node.add_lazy(exec, graph_node.into(), false) 254 291 } 255 292 } ··· 273 310 fn execute_lazy(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 274 311 let source = self.source.evaluate_lazy(exec)?; 275 312 let sink = self.sink.evaluate_lazy(exec)?; 276 - let stmt = LazyCreateEdge::new(source, sink, exec.error_context.clone().into()); 313 + let mut attributes = Attributes::new(); 314 + self.add_debug_attrs(&mut attributes, exec.config)?; 315 + let stmt = LazyCreateEdge::new(source, sink, attributes, exec.error_context.clone().into()); 277 316 exec.lazy_graph.push(stmt.into()); 278 317 Ok(()) 279 318 } ··· 348 387 locals: &mut arm_locals, 349 388 current_regex_captures: &current_regex_captures, 350 389 mat: exec.mat, 390 + full_match_file_capture_index: exec.full_match_file_capture_index, 351 391 store: exec.store, 352 392 scoped_store: exec.scoped_store, 353 393 lazy_graph: exec.lazy_graph, 354 394 function_parameters: exec.function_parameters, 355 395 prev_element_debug_info: exec.prev_element_debug_info, 356 396 error_context: exec.error_context.clone(), 397 + inherited_variables: exec.inherited_variables, 357 398 shorthands: exec.shorthands, 358 399 cancellation_flag: exec.cancellation_flag, 359 400 }; ··· 413 454 locals: &mut arm_locals, 414 455 current_regex_captures: exec.current_regex_captures, 415 456 mat: exec.mat, 457 + full_match_file_capture_index: exec.full_match_file_capture_index, 416 458 store: exec.store, 417 459 scoped_store: exec.scoped_store, 418 460 lazy_graph: exec.lazy_graph, 419 461 function_parameters: exec.function_parameters, 420 462 prev_element_debug_info: exec.prev_element_debug_info, 421 463 error_context: exec.error_context.clone(), 464 + inherited_variables: exec.inherited_variables, 422 465 shorthands: exec.shorthands, 423 466 cancellation_flag: exec.cancellation_flag, 424 467 }; ··· 459 502 locals: &mut loop_locals, 460 503 current_regex_captures: exec.current_regex_captures, 461 504 mat: exec.mat, 505 + full_match_file_capture_index: exec.full_match_file_capture_index, 462 506 store: exec.store, 463 507 scoped_store: exec.scoped_store, 464 508 lazy_graph: exec.lazy_graph, 465 509 function_parameters: exec.function_parameters, 466 510 prev_element_debug_info: exec.prev_element_debug_info, 467 511 error_context: exec.error_context.clone(), 512 + inherited_variables: exec.inherited_variables, 468 513 shorthands: exec.shorthands, 469 514 cancellation_flag: exec.cancellation_flag, 470 515 }; ··· 508 553 functions: exec.config.functions, 509 554 store: exec.store, 510 555 scoped_store: exec.scoped_store, 556 + inherited_variables: exec.inherited_variables, 511 557 function_parameters: exec.function_parameters, 512 558 prev_element_debug_info: exec.prev_element_debug_info, 513 559 cancellation_flag: exec.cancellation_flag, ··· 551 597 locals: &mut loop_locals, 552 598 current_regex_captures: exec.current_regex_captures, 553 599 mat: exec.mat, 600 + full_match_file_capture_index: exec.full_match_file_capture_index, 554 601 store: exec.store, 555 602 scoped_store: exec.scoped_store, 556 603 lazy_graph: exec.lazy_graph, 557 604 function_parameters: exec.function_parameters, 558 605 prev_element_debug_info: exec.prev_element_debug_info, 559 606 error_context: exec.error_context.clone(), 607 + inherited_variables: exec.inherited_variables, 560 608 shorthands: exec.shorthands, 561 609 cancellation_flag: exec.cancellation_flag, 562 610 }; ··· 593 641 locals: &mut loop_locals, 594 642 current_regex_captures: exec.current_regex_captures, 595 643 mat: exec.mat, 644 + full_match_file_capture_index: exec.full_match_file_capture_index, 596 645 store: exec.store, 597 646 scoped_store: exec.scoped_store, 598 647 lazy_graph: exec.lazy_graph, 599 648 function_parameters: exec.function_parameters, 600 649 prev_element_debug_info: exec.prev_element_debug_info, 601 650 error_context: exec.error_context.clone(), 651 + inherited_variables: exec.inherited_variables, 602 652 shorthands: exec.shorthands, 603 653 cancellation_flag: exec.cancellation_flag, 604 654 }; ··· 613 663 614 664 impl ast::Capture { 615 665 fn evaluate_lazy(&self, exec: &mut ExecutionContext) -> Result<LazyValue, ExecutionError> { 616 - Ok(query_capture_value( 617 - self.file_capture_index, 618 - self.quantifier, 619 - exec.mat, 666 + Ok(Value::from_nodes( 620 667 exec.graph, 668 + exec.mat 669 + .nodes_for_capture_index(self.file_capture_index as u32), 670 + self.quantifier, 621 671 ) 622 672 .into()) 623 673 } ··· 807 857 locals: &mut shorthand_locals, 808 858 current_regex_captures: exec.current_regex_captures, 809 859 mat: exec.mat, 860 + full_match_file_capture_index: exec.full_match_file_capture_index, 810 861 store: exec.store, 811 862 scoped_store: exec.scoped_store, 812 863 lazy_graph: exec.lazy_graph, 813 864 function_parameters: exec.function_parameters, 814 865 prev_element_debug_info: exec.prev_element_debug_info, 815 866 error_context: exec.error_context.clone(), 867 + inherited_variables: exec.inherited_variables, 816 868 shorthands: exec.shorthands, 817 869 cancellation_flag: exec.cancellation_flag, 818 870 };
+163 -93
src/execution/strict.rs
··· 7 7 8 8 use std::collections::BTreeSet; 9 9 use std::collections::HashMap; 10 + use std::collections::HashSet; 11 + use streaming_iterator::StreamingIterator; 10 12 use tree_sitter::QueryCursor; 11 13 use tree_sitter::QueryMatch; 12 14 use tree_sitter::Tree; ··· 14 16 use crate::ast::AddEdgeAttribute; 15 17 use crate::ast::AddGraphNodeAttribute; 16 18 use crate::ast::Assign; 19 + use crate::ast::ExpressionStatement; 17 20 use crate::ast::Attribute; 18 21 use crate::ast::AttributeShorthand; 19 22 use crate::ast::AttributeShorthands; ··· 44 47 use crate::ast::Variable; 45 48 use crate::execution::error::ExecutionError; 46 49 use crate::execution::error::ResultWithExecutionError; 47 - use crate::execution::query_capture_value; 50 + use crate::execution::error::StatementContext; 48 51 use crate::execution::CancellationFlag; 49 52 use crate::execution::ExecutionConfig; 50 - use crate::graph::Attributes; 51 53 use crate::graph::Graph; 54 + use crate::graph::SyntaxNodeID; 52 55 use crate::graph::SyntaxNodeRef; 53 56 use crate::graph::Value; 54 57 use crate::variables::Globals; ··· 58 61 use crate::Identifier; 59 62 use crate::Location; 60 63 61 - use super::error::StatementContext; 62 - 63 64 impl File { 64 65 /// Executes this graph DSL file against a source file, saving the results into an existing 65 66 /// `Graph` instance. You must provide the parsed syntax tree (`tree`) as well as the source ··· 82 83 lazy: config.lazy, 83 84 location_attr: config.location_attr.clone(), 84 85 variable_name_attr: config.variable_name_attr.clone(), 86 + match_node_attr: config.match_node_attr.clone(), 85 87 }; 86 88 87 89 let mut locals = VariableMap::new(); 88 90 let mut scoped = ScopedVariables::new(); 89 91 let current_regex_captures = Vec::new(); 90 92 let mut function_parameters = Vec::new(); 91 - let mut cursor = QueryCursor::new(); 92 - for stanza in &self.stanzas { 93 - cancellation_flag.check("executing stanza")?; 93 + 94 + self.try_visit_matches_strict(tree, source, |stanza, mat| { 94 95 stanza.execute( 95 - tree, 96 96 source, 97 + &mat, 97 98 graph, 98 99 &mut config, 99 100 &mut locals, 100 101 &mut scoped, 101 102 &current_regex_captures, 102 103 &mut function_parameters, 103 - &mut cursor, 104 + &self.inherited_variables, 104 105 &self.shorthands, 105 106 cancellation_flag, 106 - )?; 107 + ) 108 + })?; 109 + 110 + Ok(()) 111 + } 112 + 113 + pub(super) fn try_visit_matches_strict<'tree, E, F>( 114 + &self, 115 + tree: &'tree Tree, 116 + source: &'tree str, 117 + mut visit: F, 118 + ) -> Result<(), E> 119 + where 120 + F: FnMut(&Stanza, &QueryMatch<'_, 'tree>) -> Result<(), E>, 121 + { 122 + for stanza in &self.stanzas { 123 + stanza.try_visit_matches_strict(tree, source, |mat| visit(stanza, mat))?; 107 124 } 108 125 Ok(()) 109 126 } ··· 119 136 current_regex_captures: &'a Vec<String>, 120 137 function_parameters: &'a mut Vec<Value>, 121 138 mat: &'a QueryMatch<'a, 'tree>, 139 + full_match_stanza_capture_index: usize, 122 140 error_context: StatementContext, 141 + inherited_variables: &'a HashSet<Identifier>, 123 142 shorthands: &'a AttributeShorthands, 124 143 cancellation_flag: &'a dyn CancellationFlag, 125 144 } 126 145 127 146 struct ScopedVariables<'a> { 128 - scopes: HashMap<SyntaxNodeRef, VariableMap<'a, Value>>, 147 + scopes: HashMap<SyntaxNodeID, VariableMap<'a, Value>>, 129 148 } 130 149 131 150 impl<'a> ScopedVariables<'a> { ··· 135 154 } 136 155 } 137 156 138 - fn get(&mut self, scope: SyntaxNodeRef) -> &mut VariableMap<'a, Value> { 139 - self.scopes.entry(scope).or_insert(VariableMap::new()) 157 + fn get_mut(&mut self, scope: SyntaxNodeRef) -> &mut VariableMap<'a, Value> { 158 + self.scopes.entry(scope.index).or_insert(VariableMap::new()) 159 + } 160 + 161 + fn try_get(&self, index: SyntaxNodeID) -> Option<&VariableMap<'a, Value>> { 162 + self.scopes.get(&index) 140 163 } 141 164 } 142 165 143 166 impl Stanza { 144 167 fn execute<'a, 'g, 'l, 's, 'tree>( 145 168 &self, 146 - tree: &'tree Tree, 147 169 source: &'tree str, 170 + mat: &QueryMatch<'_, 'tree>, 148 171 graph: &mut Graph<'tree>, 149 172 config: &ExecutionConfig<'_, 'g>, 150 173 locals: &mut VariableMap<'l, Value>, 151 174 scoped: &mut ScopedVariables<'s>, 152 175 current_regex_captures: &Vec<String>, 153 176 function_parameters: &mut Vec<Value>, 154 - cursor: &mut QueryCursor, 177 + inherited_variables: &HashSet<Identifier>, 155 178 shorthands: &AttributeShorthands, 156 179 cancellation_flag: &dyn CancellationFlag, 157 180 ) -> Result<(), ExecutionError> { 158 - let matches = cursor.matches(&self.query, tree.root_node(), source.as_bytes()); 159 - for mat in matches { 160 - cancellation_flag.check("processing matches")?; 161 - locals.clear(); 162 - for statement in &self.statements { 163 - let error_context = { 164 - let node = mat 165 - .captures 166 - .iter() 167 - .find(|c| c.index as usize == self.full_match_stanza_capture_index) 168 - .expect("missing capture for full match") 169 - .node; 170 - StatementContext::new(&statement, &self, &node) 171 - }; 172 - let mut exec = ExecutionContext { 173 - source, 174 - graph, 175 - config, 176 - locals, 177 - scoped, 178 - current_regex_captures, 179 - function_parameters, 180 - mat: &mat, 181 - error_context, 182 - shorthands, 183 - cancellation_flag, 184 - }; 185 - statement 186 - .execute(&mut exec) 187 - .with_context(|| exec.error_context.into())?; 188 - } 181 + locals.clear(); 182 + for statement in &self.statements { 183 + let error_context = { 184 + let node = mat 185 + .nodes_for_capture_index(self.full_match_stanza_capture_index as u32) 186 + .next() 187 + .expect("missing full capture"); 188 + StatementContext::new(&statement, &self, &node) 189 + }; 190 + let mut exec = ExecutionContext { 191 + source, 192 + graph, 193 + config, 194 + locals, 195 + scoped, 196 + current_regex_captures, 197 + function_parameters, 198 + mat: &mat, 199 + full_match_stanza_capture_index: self.full_match_stanza_capture_index, 200 + error_context, 201 + inherited_variables, 202 + shorthands, 203 + cancellation_flag, 204 + }; 205 + statement 206 + .execute(&mut exec) 207 + .with_context(|| exec.error_context.into())?; 208 + } 209 + Ok(()) 210 + } 211 + 212 + pub(super) fn try_visit_matches_strict<'tree, E, F>( 213 + &self, 214 + tree: &'tree Tree, 215 + source: &'tree str, 216 + mut visit: F, 217 + ) -> Result<(), E> 218 + where 219 + F: FnMut(&QueryMatch<'_, 'tree>) -> Result<(), E>, 220 + { 221 + let mut cursor = QueryCursor::new(); 222 + let mut matches = cursor.matches(&self.query, tree.root_node(), source.as_bytes()); 223 + while let Some(mat) = matches.next() { 224 + visit(mat)?; 189 225 } 190 226 Ok(()) 191 227 } ··· 197 233 Statement::DeclareImmutable(s) => s.location, 198 234 Statement::DeclareMutable(s) => s.location, 199 235 Statement::Assign(s) => s.location, 236 + Statement::Expr(s) => s.location, 200 237 Statement::CreateGraphNode(s) => s.location, 201 238 Statement::AddGraphNodeAttribute(s) => s.location, 202 239 Statement::CreateEdge(s) => s.location, ··· 214 251 Statement::DeclareImmutable(statement) => statement.execute(exec), 215 252 Statement::DeclareMutable(statement) => statement.execute(exec), 216 253 Statement::Assign(statement) => statement.execute(exec), 254 + Statement::Expr(statement) => statement.execute(exec), 217 255 Statement::CreateGraphNode(statement) => statement.execute(exec), 218 256 Statement::AddGraphNodeAttribute(statement) => statement.execute(exec), 219 257 Statement::CreateEdge(statement) => statement.execute(exec), ··· 244 282 fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 245 283 let value = self.value.evaluate(exec)?; 246 284 self.variable.set(exec, value) 285 + } 286 + } 287 + 288 + impl ExpressionStatement { 289 + fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 290 + self.value.evaluate(exec).map(|_| ()) 247 291 } 248 292 } 249 293 ··· 252 296 let graph_node = exec.graph.add_graph_node(); 253 297 self.node 254 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 + } 255 316 let value = Value::GraphNode(graph_node); 256 317 self.node.add(exec, value, false) 257 318 } ··· 282 343 fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 283 344 let source = self.source.evaluate(exec)?.into_graph_node_ref()?; 284 345 let sink = self.sink.evaluate(exec)?.into_graph_node_ref()?; 285 - if let Err(_) = exec.graph[source].add_edge(sink) { 286 - Err(ExecutionError::DuplicateEdge(format!( 287 - "({} -> {}) in {}", 288 - source, sink, self, 289 - )))?; 290 - } 346 + let edge = match exec.graph[source].add_edge(sink) { 347 + Ok(edge) | Err(edge) => edge, 348 + }; 349 + self.add_debug_attrs(&mut edge.attributes, exec.config)?; 291 350 Ok(()) 292 351 } 293 352 } ··· 373 432 current_regex_captures: &current_regex_captures, 374 433 function_parameters: exec.function_parameters, 375 434 mat: exec.mat, 435 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 376 436 error_context: exec.error_context.clone(), 437 + inherited_variables: exec.inherited_variables, 377 438 shorthands: exec.shorthands, 378 439 cancellation_flag: exec.cancellation_flag, 379 440 }; ··· 432 493 current_regex_captures: exec.current_regex_captures, 433 494 function_parameters: exec.function_parameters, 434 495 mat: exec.mat, 496 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 435 497 error_context: exec.error_context.clone(), 498 + inherited_variables: exec.inherited_variables, 436 499 shorthands: exec.shorthands, 437 500 cancellation_flag: exec.cancellation_flag, 438 501 }; ··· 473 536 current_regex_captures: exec.current_regex_captures, 474 537 function_parameters: exec.function_parameters, 475 538 mat: exec.mat, 539 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 476 540 error_context: exec.error_context.clone(), 541 + inherited_variables: exec.inherited_variables, 477 542 shorthands: exec.shorthands, 478 543 cancellation_flag: exec.cancellation_flag, 479 544 }; ··· 547 612 current_regex_captures: exec.current_regex_captures, 548 613 function_parameters: exec.function_parameters, 549 614 mat: exec.mat, 615 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 550 616 error_context: exec.error_context.clone(), 617 + inherited_variables: exec.inherited_variables, 551 618 shorthands: exec.shorthands, 552 619 cancellation_flag: exec.cancellation_flag, 553 620 }; ··· 586 653 current_regex_captures: exec.current_regex_captures, 587 654 function_parameters: exec.function_parameters, 588 655 mat: exec.mat, 656 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 589 657 error_context: exec.error_context.clone(), 658 + inherited_variables: exec.inherited_variables, 590 659 shorthands: exec.shorthands, 591 660 cancellation_flag: exec.cancellation_flag, 592 661 }; ··· 600 669 601 670 impl Capture { 602 671 fn evaluate(&self, exec: &mut ExecutionContext) -> Result<Value, ExecutionError> { 603 - Ok(query_capture_value( 604 - self.stanza_capture_index, 605 - self.quantifier, 606 - exec.mat, 672 + Ok(Value::from_nodes( 607 673 exec.graph, 608 - )) 674 + exec.mat 675 + .nodes_for_capture_index(self.stanza_capture_index as u32), 676 + self.quantifier, 677 + ) 678 + .into()) 609 679 } 610 680 } 611 681 ··· 683 753 ))) 684 754 } 685 755 }; 686 - let variables = exec.scoped.get(scope); 687 - if let Some(value) = variables.get(&self.name) { 688 - Ok(value) 689 - } else { 690 - Err(ExecutionError::UndefinedVariable(format!( 691 - "{} on node {}", 692 - self, scope 693 - ))) 756 + 757 + // search this node 758 + if let Some(value) = exec 759 + .scoped 760 + .try_get(scope.index) 761 + .and_then(|v| v.get(&self.name)) 762 + { 763 + return Ok(value); 694 764 } 765 + 766 + // search parent nodes 767 + if exec.inherited_variables.contains(&self.name) { 768 + let mut parent = exec 769 + .graph 770 + .syntax_nodes 771 + .get(&scope.index) 772 + .and_then(|n| n.parent()); 773 + while let Some(scope) = parent { 774 + if let Some(value) = exec 775 + .scoped 776 + .try_get(scope.id() as u32) 777 + .and_then(|v| v.get(&self.name)) 778 + { 779 + return Ok(value); 780 + } 781 + parent = scope.parent(); 782 + } 783 + } 784 + 785 + Err(ExecutionError::UndefinedVariable(format!( 786 + "{} on node {}", 787 + self, scope 788 + ))) 695 789 } 696 790 697 791 fn add( ··· 710 804 ))) 711 805 } 712 806 }; 713 - let variables = exec.scoped.get(scope); 807 + let variables = exec.scoped.get_mut(scope); 714 808 variables 715 809 .add(self.name.clone(), value, mutable) 716 810 .map_err(|_| ExecutionError::DuplicateVariable(format!("{}", self))) ··· 727 821 ))) 728 822 } 729 823 }; 730 - let variables = exec.scoped.get(scope); 824 + let variables = exec.scoped.get_mut(scope); 731 825 variables 732 826 .set(self.name.clone(), value) 733 827 .map_err(|_| ExecutionError::DuplicateVariable(format!("{}", self))) ··· 778 872 } 779 873 } 780 874 781 - impl Variable { 782 - pub(crate) fn add_debug_attrs( 783 - &self, 784 - attributes: &mut Attributes, 785 - config: &ExecutionConfig, 786 - ) -> Result<(), ExecutionError> { 787 - if let Some(variable_name_attr) = &config.variable_name_attr { 788 - attributes 789 - .add(variable_name_attr.clone(), format!("{}", self)) 790 - .map_err(|_| { 791 - ExecutionError::DuplicateAttribute(variable_name_attr.as_str().into()) 792 - })?; 793 - } 794 - if let Some(location_attr) = &config.location_attr { 795 - let location = match &self { 796 - Variable::Scoped(v) => v.location, 797 - Variable::Unscoped(v) => v.location, 798 - }; 799 - attributes 800 - .add(location_attr.clone(), format!("{}", location)) 801 - .map_err(|_| ExecutionError::DuplicateAttribute(location_attr.as_str().into()))?; 802 - } 803 - Ok(()) 804 - } 805 - } 806 - 807 875 impl Attribute { 808 876 fn execute<F>( 809 877 &self, ··· 843 911 current_regex_captures: exec.current_regex_captures, 844 912 function_parameters: exec.function_parameters, 845 913 mat: exec.mat, 914 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 846 915 error_context: exec.error_context.clone(), 916 + inherited_variables: exec.inherited_variables, 847 917 shorthands: exec.shorthands, 848 918 cancellation_flag: exec.cancellation_flag, 849 919 };
+234 -29
src/execution.rs
··· 7 7 8 8 use thiserror::Error; 9 9 use tree_sitter::CaptureQuantifier; 10 + use tree_sitter::Node; 10 11 use tree_sitter::QueryMatch; 11 12 use tree_sitter::Tree; 12 13 14 + use crate::ast::CreateEdge; 13 15 use crate::ast::File; 16 + use crate::ast::Stanza; 17 + use crate::ast::Variable; 14 18 use crate::execution::error::ExecutionError; 15 19 use crate::functions::Functions; 20 + use crate::graph::Attributes; 16 21 use crate::graph::Graph; 17 22 use crate::graph::Value; 18 23 use crate::variables::Globals; 19 24 use crate::Identifier; 25 + use crate::Location; 20 26 21 27 pub(crate) mod error; 22 28 mod lazy; ··· 92 98 } 93 99 94 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 + 164 + impl 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 + 199 + pub 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 + 206 + impl<'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 95 249 } 96 250 } 97 251 ··· 102 256 pub(crate) lazy: bool, 103 257 pub(crate) location_attr: Option<Identifier>, 104 258 pub(crate) variable_name_attr: Option<Identifier>, 259 + pub(crate) match_node_attr: Option<Identifier>, 105 260 } 106 261 107 262 impl<'a, 'g> ExecutionConfig<'a, 'g> { ··· 112 267 lazy: false, 113 268 location_attr: None, 114 269 variable_name_attr: None, 270 + match_node_attr: None, 115 271 } 116 272 } 117 273 ··· 119 275 self, 120 276 location_attr: Identifier, 121 277 variable_name_attr: Identifier, 278 + match_node_attr: Identifier, 122 279 ) -> Self { 123 280 Self { 124 281 functions: self.functions, ··· 126 283 lazy: self.lazy, 127 284 location_attr: location_attr.into(), 128 285 variable_name_attr: variable_name_attr.into(), 286 + match_node_attr: match_node_attr.into(), 129 287 } 130 288 } 131 289 ··· 136 294 lazy, 137 295 location_attr: self.location_attr, 138 296 variable_name_attr: self.variable_name_attr, 297 + match_node_attr: self.match_node_attr, 139 298 } 140 299 } 141 300 } ··· 156 315 #[error("Cancelled at \"{0}\"")] 157 316 pub struct CancellationError(pub &'static str); 158 317 159 - /// Get the value for the given capture, considering the suffix 160 - pub(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() 318 + impl 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 + 348 + impl 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 + } 369 + impl 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 + })?; 176 381 } 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() 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()))?; 182 393 } 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 - }, 394 + Ok(()) 190 395 } 191 396 }
+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 }
+11 -6
src/graph.rs
··· 36 36 /// that they don't outlive the tree-sitter syntax tree that they are generated from. 37 37 #[derive(Default)] 38 38 pub struct Graph<'tree> { 39 - syntax_nodes: HashMap<SyntaxNodeID, Node<'tree>>, 39 + pub(crate) syntax_nodes: HashMap<SyntaxNodeID, Node<'tree>>, 40 40 graph_nodes: Vec<GraphNode>, 41 41 } 42 42 43 - type SyntaxNodeID = u32; 43 + pub(crate) type SyntaxNodeID = u32; 44 44 type GraphNodeID = u32; 45 45 46 46 impl<'tree> Graph<'tree> { ··· 260 260 } 261 261 262 262 /// A set of attributes associated with a graph node or edge 263 + #[derive(Clone, Debug)] 263 264 pub struct Attributes { 264 265 values: HashMap<Identifier, Value>, 265 266 } ··· 274 275 275 276 /// Adds an attribute to this attribute set. If there was already an attribute with the same 276 277 /// name, replaces its value and returns `Err`. 277 - 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> { 278 279 match self.values.entry(name) { 279 280 Entry::Occupied(mut o) => { 280 - o.insert(value.into()); 281 - Err(()) 281 + let value = value.into(); 282 + if o.get() != &value { 283 + Err(o.insert(value.into())) 284 + } else { 285 + Ok(()) 286 + } 282 287 } 283 288 Entry::Vacant(v) => { 284 289 v.insert(value.into()); ··· 634 639 /// A reference to a syntax node in a graph 635 640 #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] 636 641 pub struct SyntaxNodeRef { 637 - index: SyntaxNodeID, 642 + pub(crate) index: SyntaxNodeID, 638 643 kind: &'static str, 639 644 position: tree_sitter::Point, 640 645 }
+1
src/lib.rs
··· 41 41 pub use execution::CancellationError; 42 42 pub use execution::CancellationFlag; 43 43 pub use execution::ExecutionConfig; 44 + pub use execution::Match; 44 45 pub use execution::NoCancellation; 45 46 pub use parser::Location; 46 47 pub use parser::ParseError;
+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 }
+42 -12
src/parser.rs
··· 7 7 8 8 use std::fmt::Display; 9 9 use std::iter::Peekable; 10 - use std::ops::Range; 11 10 use std::path::Path; 12 11 use std::str::Chars; 13 12 ··· 161 160 } 162 161 } 163 162 164 - pub(crate) fn to_column_range(&self) -> Range<usize> { 163 + pub(crate) fn to_column_range(&self) -> std::ops::Range<usize> { 165 164 self.column..self.column + 1 166 165 } 167 166 } ··· 169 168 impl Display for Location { 170 169 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 171 170 write!(f, "({}, {})", self.row + 1, self.column + 1) 171 + } 172 + } 173 + 174 + // ---------------------------------------------------------------------------- 175 + // Range 176 + 177 + /// The range of a graph DSL entity within its file 178 + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 179 + pub struct Range { 180 + pub start: Location, 181 + pub end: Location, 182 + } 183 + 184 + impl Display for Range { 185 + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 186 + write!(f, "{} - {}", self.start, self.end) 172 187 } 173 188 } 174 189 ··· 276 291 fn parse_into_file(&mut self, file: &mut ast::File) -> Result<(), ParseError> { 277 292 self.consume_whitespace(); 278 293 while self.try_peek().is_some() { 279 - if let Ok(_) = self.consume_token("global") { 294 + if let Ok(_) = self.consume_token("attribute") { 295 + self.consume_whitespace(); 296 + let shorthand = self.parse_shorthand()?; 297 + file.shorthands.add(shorthand); 298 + } else if let Ok(_) = self.consume_token("global") { 280 299 self.consume_whitespace(); 281 300 let global = self.parse_global()?; 282 301 file.globals.push(global); 283 - } else if let Ok(_) = self.consume_token("attribute") { 302 + } else if let Ok(_) = self.consume_token("inherit") { 284 303 self.consume_whitespace(); 285 - let shorthand = self.parse_shorthand()?; 286 - file.shorthands.add(shorthand); 304 + self.consume_token(".")?; 305 + let name = self.parse_identifier("inherit")?; 306 + file.inherited_variables.insert(name); 287 307 } else { 288 - let stanza = self.parse_stanza(file.language)?; 308 + let stanza = self.parse_stanza(&file.language)?; 289 309 file.stanzas.push(stanza); 290 310 } 291 311 self.consume_whitespace(); 292 312 } 293 313 // we can unwrap here because all queries have already been parsed before 294 - file.query = Some(Query::new(file.language, &self.query_source).unwrap()); 314 + file.query = Some(Query::new(&file.language, &self.query_source).unwrap()); 295 315 Ok(()) 296 316 } 297 317 ··· 349 369 Ok(quantifier) 350 370 } 351 371 352 - fn parse_stanza(&mut self, language: Language) -> Result<ast::Stanza, ParseError> { 353 - let location = self.location; 372 + fn parse_stanza(&mut self, language: &Language) -> Result<ast::Stanza, ParseError> { 373 + let start = self.location; 354 374 let (query, full_match_stanza_capture_index) = self.parse_query(language)?; 355 375 self.consume_whitespace(); 356 376 let statements = self.parse_statements()?; 377 + let end = self.location; 378 + let range = Range { start, end }; 357 379 Ok(ast::Stanza { 358 380 query, 359 381 statements, 360 382 full_match_stanza_capture_index, 361 383 full_match_file_capture_index: usize::MAX, // set in checker 362 - location, 384 + range, 363 385 }) 364 386 } 365 387 366 - fn parse_query(&mut self, language: Language) -> Result<(Query, usize), ParseError> { 388 + fn parse_query(&mut self, language: &Language) -> Result<(Query, usize), ParseError> { 367 389 let location = self.location; 368 390 let query_start = self.offset; 369 391 self.skip_query()?; ··· 460 482 } 461 483 462 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 + } 463 493 let keyword_location = self.location; 464 494 let keyword = self.parse_name("keyword")?; 465 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(())
+140 -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 ··· 936 938 "#}, 937 939 ); 938 940 } 941 + 942 + #[test] 943 + fn can_access_inherited_attribute() { 944 + check_execution( 945 + indoc! { r#" 946 + def get_f(): 947 + pass 948 + "#}, 949 + indoc! {r#" 950 + inherit .test 951 + (function_definition)@def { 952 + node @def.test 953 + attr (@def.test) in_def 954 + } 955 + (pass_statement)@pass { 956 + attr (@pass.test) in_pass 957 + } 958 + "#}, 959 + indoc! {r#" 960 + node 0 961 + in_def: #true 962 + in_pass: #true 963 + "#}, 964 + ); 965 + } 966 + 967 + #[test] 968 + fn can_overwrite_inherited_attribute() { 969 + check_execution( 970 + indoc! { r#" 971 + def get_f(): 972 + pass 973 + "#}, 974 + indoc! {r#" 975 + inherit .test 976 + (function_definition)@def { 977 + node @def.test 978 + attr (@def.test) in_def 979 + } 980 + (pass_statement)@pass { 981 + node @pass.test 982 + } 983 + (pass_statement)@pass { 984 + attr (@pass.test) in_pass 985 + } 986 + "#}, 987 + indoc! {r#" 988 + node 0 989 + in_def: #true 990 + node 1 991 + in_pass: #true 992 + "#}, 993 + ); 994 + } 995 + 996 + #[test] 997 + fn cannot_access_non_inherited_variable() { 998 + fail_execution( 999 + indoc! { r#" 1000 + def get_f(): 1001 + pass 1002 + "#}, 1003 + indoc! {r#" 1004 + (function_definition)@def { 1005 + node @def.test 1006 + } 1007 + (pass_statement)@pass { 1008 + attr (@pass.test) in_pass 1009 + } 1010 + "#}, 1011 + ); 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();
+143 -5
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 ··· 116 118 "#}, 117 119 indoc! {r#" 118 120 node 0 119 - name: "alpha" 120 - edge 0 -> 2 121 + edge 0 -> 1 121 122 node 1 122 - edge 1 -> 0 123 + name: "alpha" 124 + edge 1 -> 2 123 125 node 2 124 126 name: "beta" 125 127 edge 2 -> 3 ··· 1455 1457 "#}, 1456 1458 ); 1457 1459 } 1460 + 1461 + #[test] 1462 + fn can_access_inherited_attribute() { 1463 + check_execution( 1464 + indoc! { r#" 1465 + def get_f(): 1466 + pass 1467 + "#}, 1468 + indoc! {r#" 1469 + inherit .test 1470 + (function_definition)@def { 1471 + node @def.test 1472 + attr (@def.test) in_def 1473 + } 1474 + (pass_statement)@pass { 1475 + attr (@pass.test) in_pass 1476 + } 1477 + "#}, 1478 + indoc! {r#" 1479 + node 0 1480 + in_def: #true 1481 + in_pass: #true 1482 + "#}, 1483 + ); 1484 + } 1485 + 1486 + #[test] 1487 + fn can_overwrite_inherited_attribute() { 1488 + check_execution( 1489 + indoc! { r#" 1490 + def get_f(): 1491 + pass 1492 + "#}, 1493 + indoc! {r#" 1494 + inherit .test 1495 + (function_definition)@def { 1496 + node @def.test 1497 + attr (@def.test) in_def 1498 + } 1499 + (pass_statement)@pass { 1500 + node @pass.test 1501 + } 1502 + (pass_statement)@pass { 1503 + attr (@pass.test) in_pass 1504 + } 1505 + "#}, 1506 + indoc! {r#" 1507 + node 0 1508 + in_def: #true 1509 + node 1 1510 + in_pass: #true 1511 + "#}, 1512 + ); 1513 + } 1514 + 1515 + #[test] 1516 + fn cannot_access_non_inherited_variable() { 1517 + fail_execution( 1518 + indoc! { r#" 1519 + def get_f(): 1520 + pass 1521 + "#}, 1522 + indoc! {r#" 1523 + (function_definition)@def { 1524 + node @def.test 1525 + } 1526 + (pass_statement)@pass { 1527 + attr (@pass.test) in_pass 1528 + } 1529 + "#}, 1530 + ); 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
+72 -38
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"); 1625 + } 1626 + 1627 + #[test] 1628 + fn can_parse_inherit_directives() { 1629 + let source = r#" 1630 + inherit .scope 1631 + "#; 1632 + let file = 1633 + File::from_str(tree_sitter_python::LANGUAGE.into(), source).expect("parse to succeed"); 1634 + assert!(file.inherited_variables.contains("scope".into())); 1601 1635 }
+12
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 + 14 + ## 0.1.1 -- 2023-06-01 15 + 16 + ### Added 17 + 18 + - Add new `inherit` keyword 19 + 8 20 ## 0.1.0 -- 2022-05-11 9 21 10 22 ### Added
+1 -1
vscode/package.json
··· 1 1 { 2 2 "name": "tree-sitter-graph", 3 - "version": "0.1.0", 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|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": {