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
+78
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 + 8 86 ## v0.10.0 -- 2023-05-10 9 87 10 88 ### DSL
+20 -44
Cargo.toml
··· 1 1 [package] 2 2 name = "tree-sitter-graph" 3 - version = "0.10.0" 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`:
+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 }
+79 -27
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; ··· 25 27 use crate::execution::ExecutionConfig; 26 28 use crate::functions::Functions; 27 29 use crate::graph; 30 + use crate::graph::Attributes; 28 31 use crate::graph::Graph; 29 32 use crate::graph::Value; 30 33 use crate::variables::Globals; ··· 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 = Value::from_nodes(graph, self.full_capture_from_file_match(mat), One); 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 }; ··· 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 };
+156 -85
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; ··· 47 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; ··· 80 83 lazy: config.lazy, 81 84 location_attr: config.location_attr.clone(), 82 85 variable_name_attr: config.variable_name_attr.clone(), 86 + match_node_attr: config.match_node_attr.clone(), 83 87 }; 84 88 85 89 let mut locals = VariableMap::new(); 86 90 let mut scoped = ScopedVariables::new(); 87 91 let current_regex_captures = Vec::new(); 88 92 let mut function_parameters = Vec::new(); 89 - let mut cursor = QueryCursor::new(); 90 - for stanza in &self.stanzas { 91 - cancellation_flag.check("executing stanza")?; 93 + 94 + self.try_visit_matches_strict(tree, source, |stanza, mat| { 92 95 stanza.execute( 93 - tree, 94 96 source, 97 + &mat, 95 98 graph, 96 99 &mut config, 97 100 &mut locals, 98 101 &mut scoped, 99 102 &current_regex_captures, 100 103 &mut function_parameters, 101 - &mut cursor, 104 + &self.inherited_variables, 102 105 &self.shorthands, 103 106 cancellation_flag, 104 - )?; 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))?; 105 124 } 106 125 Ok(()) 107 126 } ··· 117 136 current_regex_captures: &'a Vec<String>, 118 137 function_parameters: &'a mut Vec<Value>, 119 138 mat: &'a QueryMatch<'a, 'tree>, 139 + full_match_stanza_capture_index: usize, 120 140 error_context: StatementContext, 141 + inherited_variables: &'a HashSet<Identifier>, 121 142 shorthands: &'a AttributeShorthands, 122 143 cancellation_flag: &'a dyn CancellationFlag, 123 144 } 124 145 125 146 struct ScopedVariables<'a> { 126 - scopes: HashMap<SyntaxNodeRef, VariableMap<'a, Value>>, 147 + scopes: HashMap<SyntaxNodeID, VariableMap<'a, Value>>, 127 148 } 128 149 129 150 impl<'a> ScopedVariables<'a> { ··· 133 154 } 134 155 } 135 156 136 - fn get(&mut self, scope: SyntaxNodeRef) -> &mut VariableMap<'a, Value> { 137 - 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) 138 163 } 139 164 } 140 165 141 166 impl Stanza { 142 167 fn execute<'a, 'g, 'l, 's, 'tree>( 143 168 &self, 144 - tree: &'tree Tree, 145 169 source: &'tree str, 170 + mat: &QueryMatch<'_, 'tree>, 146 171 graph: &mut Graph<'tree>, 147 172 config: &ExecutionConfig<'_, 'g>, 148 173 locals: &mut VariableMap<'l, Value>, 149 174 scoped: &mut ScopedVariables<'s>, 150 175 current_regex_captures: &Vec<String>, 151 176 function_parameters: &mut Vec<Value>, 152 - cursor: &mut QueryCursor, 177 + inherited_variables: &HashSet<Identifier>, 153 178 shorthands: &AttributeShorthands, 154 179 cancellation_flag: &dyn CancellationFlag, 155 180 ) -> Result<(), ExecutionError> { 156 - let matches = cursor.matches(&self.query, tree.root_node(), source.as_bytes()); 157 - for mat in matches { 158 - cancellation_flag.check("processing matches")?; 159 - locals.clear(); 160 - for statement in &self.statements { 161 - let error_context = { 162 - let node = mat 163 - .captures 164 - .iter() 165 - .find(|c| c.index as usize == self.full_match_stanza_capture_index) 166 - .expect("missing capture for full match") 167 - .node; 168 - StatementContext::new(&statement, &self, &node) 169 - }; 170 - let mut exec = ExecutionContext { 171 - source, 172 - graph, 173 - config, 174 - locals, 175 - scoped, 176 - current_regex_captures, 177 - function_parameters, 178 - mat: &mat, 179 - error_context, 180 - shorthands, 181 - cancellation_flag, 182 - }; 183 - statement 184 - .execute(&mut exec) 185 - .with_context(|| exec.error_context.into())?; 186 - } 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)?; 187 225 } 188 226 Ok(()) 189 227 } ··· 195 233 Statement::DeclareImmutable(s) => s.location, 196 234 Statement::DeclareMutable(s) => s.location, 197 235 Statement::Assign(s) => s.location, 236 + Statement::Expr(s) => s.location, 198 237 Statement::CreateGraphNode(s) => s.location, 199 238 Statement::AddGraphNodeAttribute(s) => s.location, 200 239 Statement::CreateEdge(s) => s.location, ··· 212 251 Statement::DeclareImmutable(statement) => statement.execute(exec), 213 252 Statement::DeclareMutable(statement) => statement.execute(exec), 214 253 Statement::Assign(statement) => statement.execute(exec), 254 + Statement::Expr(statement) => statement.execute(exec), 215 255 Statement::CreateGraphNode(statement) => statement.execute(exec), 216 256 Statement::AddGraphNodeAttribute(statement) => statement.execute(exec), 217 257 Statement::CreateEdge(statement) => statement.execute(exec), ··· 245 285 } 246 286 } 247 287 288 + impl ExpressionStatement { 289 + fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 290 + self.value.evaluate(exec).map(|_| ()) 291 + } 292 + } 293 + 248 294 impl CreateGraphNode { 249 295 fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 250 296 let graph_node = exec.graph.add_graph_node(); 251 297 self.node 252 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 + } 253 316 let value = Value::GraphNode(graph_node); 254 317 self.node.add(exec, value, false) 255 318 } ··· 280 343 fn execute(&self, exec: &mut ExecutionContext) -> Result<(), ExecutionError> { 281 344 let source = self.source.evaluate(exec)?.into_graph_node_ref()?; 282 345 let sink = self.sink.evaluate(exec)?.into_graph_node_ref()?; 283 - if let Err(_) = exec.graph[source].add_edge(sink) { 284 - Err(ExecutionError::DuplicateEdge(format!( 285 - "({} -> {}) in {}", 286 - source, sink, self, 287 - )))?; 288 - } 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)?; 289 350 Ok(()) 290 351 } 291 352 } ··· 371 432 current_regex_captures: &current_regex_captures, 372 433 function_parameters: exec.function_parameters, 373 434 mat: exec.mat, 435 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 374 436 error_context: exec.error_context.clone(), 437 + inherited_variables: exec.inherited_variables, 375 438 shorthands: exec.shorthands, 376 439 cancellation_flag: exec.cancellation_flag, 377 440 }; ··· 430 493 current_regex_captures: exec.current_regex_captures, 431 494 function_parameters: exec.function_parameters, 432 495 mat: exec.mat, 496 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 433 497 error_context: exec.error_context.clone(), 498 + inherited_variables: exec.inherited_variables, 434 499 shorthands: exec.shorthands, 435 500 cancellation_flag: exec.cancellation_flag, 436 501 }; ··· 471 536 current_regex_captures: exec.current_regex_captures, 472 537 function_parameters: exec.function_parameters, 473 538 mat: exec.mat, 539 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 474 540 error_context: exec.error_context.clone(), 541 + inherited_variables: exec.inherited_variables, 475 542 shorthands: exec.shorthands, 476 543 cancellation_flag: exec.cancellation_flag, 477 544 }; ··· 545 612 current_regex_captures: exec.current_regex_captures, 546 613 function_parameters: exec.function_parameters, 547 614 mat: exec.mat, 615 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 548 616 error_context: exec.error_context.clone(), 617 + inherited_variables: exec.inherited_variables, 549 618 shorthands: exec.shorthands, 550 619 cancellation_flag: exec.cancellation_flag, 551 620 }; ··· 584 653 current_regex_captures: exec.current_regex_captures, 585 654 function_parameters: exec.function_parameters, 586 655 mat: exec.mat, 656 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 587 657 error_context: exec.error_context.clone(), 658 + inherited_variables: exec.inherited_variables, 588 659 shorthands: exec.shorthands, 589 660 cancellation_flag: exec.cancellation_flag, 590 661 }; ··· 682 753 ))) 683 754 } 684 755 }; 685 - let variables = exec.scoped.get(scope); 686 - if let Some(value) = variables.get(&self.name) { 687 - Ok(value) 688 - } else { 689 - Err(ExecutionError::UndefinedVariable(format!( 690 - "{} on node {}", 691 - self, scope 692 - ))) 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); 693 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 + ))) 694 789 } 695 790 696 791 fn add( ··· 709 804 ))) 710 805 } 711 806 }; 712 - let variables = exec.scoped.get(scope); 807 + let variables = exec.scoped.get_mut(scope); 713 808 variables 714 809 .add(self.name.clone(), value, mutable) 715 810 .map_err(|_| ExecutionError::DuplicateVariable(format!("{}", self))) ··· 726 821 ))) 727 822 } 728 823 }; 729 - let variables = exec.scoped.get(scope); 824 + let variables = exec.scoped.get_mut(scope); 730 825 variables 731 826 .set(self.name.clone(), value) 732 827 .map_err(|_| ExecutionError::DuplicateVariable(format!("{}", self))) ··· 777 872 } 778 873 } 779 874 780 - impl Variable { 781 - pub(crate) fn add_debug_attrs( 782 - &self, 783 - attributes: &mut Attributes, 784 - config: &ExecutionConfig, 785 - ) -> Result<(), ExecutionError> { 786 - if let Some(variable_name_attr) = &config.variable_name_attr { 787 - attributes 788 - .add(variable_name_attr.clone(), format!("{}", self)) 789 - .map_err(|_| { 790 - ExecutionError::DuplicateAttribute(variable_name_attr.as_str().into()) 791 - })?; 792 - } 793 - if let Some(location_attr) = &config.location_attr { 794 - let location = match &self { 795 - Variable::Scoped(v) => v.location, 796 - Variable::Unscoped(v) => v.location, 797 - }; 798 - attributes 799 - .add(location_attr.clone(), format!("{}", location)) 800 - .map_err(|_| ExecutionError::DuplicateAttribute(location_attr.as_str().into()))?; 801 - } 802 - Ok(()) 803 - } 804 - } 805 - 806 875 impl Attribute { 807 876 fn execute<F>( 808 877 &self, ··· 842 911 current_regex_captures: exec.current_regex_captures, 843 912 function_parameters: exec.function_parameters, 844 913 mat: exec.mat, 914 + full_match_stanza_capture_index: exec.full_match_stanza_capture_index, 845 915 error_context: exec.error_context.clone(), 916 + inherited_variables: exec.inherited_variables, 846 917 shorthands: exec.shorthands, 847 918 cancellation_flag: exec.cancellation_flag, 848 919 };
+208 -18
src/execution.rs
··· 11 11 use tree_sitter::QueryMatch; 12 12 use tree_sitter::Tree; 13 13 14 + use crate::ast::CreateEdge; 14 15 use crate::ast::File; 16 + use crate::ast::Stanza; 17 + use crate::ast::Variable; 15 18 use crate::execution::error::ExecutionError; 16 19 use crate::functions::Functions; 20 + use crate::graph::Attributes; 17 21 use crate::graph::Graph; 18 22 use crate::graph::Value; 19 23 use crate::variables::Globals; 20 24 use crate::Identifier; 25 + use crate::Location; 21 26 22 27 pub(crate) mod error; 23 28 mod lazy; ··· 94 99 95 100 Ok(()) 96 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 249 + } 97 250 } 98 251 99 252 /// Configuration for the execution of a File ··· 103 256 pub(crate) lazy: bool, 104 257 pub(crate) location_attr: Option<Identifier>, 105 258 pub(crate) variable_name_attr: Option<Identifier>, 259 + pub(crate) match_node_attr: Option<Identifier>, 106 260 } 107 261 108 262 impl<'a, 'g> ExecutionConfig<'a, 'g> { ··· 113 267 lazy: false, 114 268 location_attr: None, 115 269 variable_name_attr: None, 270 + match_node_attr: None, 116 271 } 117 272 } 118 273 ··· 120 275 self, 121 276 location_attr: Identifier, 122 277 variable_name_attr: Identifier, 278 + match_node_attr: Identifier, 123 279 ) -> Self { 124 280 Self { 125 281 functions: self.functions, ··· 127 283 lazy: self.lazy, 128 284 location_attr: location_attr.into(), 129 285 variable_name_attr: variable_name_attr.into(), 286 + match_node_attr: match_node_attr.into(), 130 287 } 131 288 } 132 289 ··· 137 294 lazy, 138 295 location_attr: self.location_attr, 139 296 variable_name_attr: self.variable_name_attr, 297 + match_node_attr: self.match_node_attr, 140 298 } 141 299 } 142 300 } ··· 157 315 #[error("Cancelled at \"{0}\"")] 158 316 pub struct CancellationError(pub &'static str); 159 317 160 - impl crate::ast::Stanza { 161 - /// Return the top-level matched node from a file query match result. 162 - pub fn full_capture_from_file_match<'a, 'cursor: 'a, 'tree: 'a>( 163 - &self, 164 - mat: &'a QueryMatch<'cursor, 'tree>, 165 - ) -> impl Iterator<Item = Node<'tree>> + 'a { 166 - mat.nodes_for_capture_index(self.full_match_file_capture_index as u32) 167 - } 168 - 169 - /// Return the top-level matched node from a stanza query match result. 170 - pub fn full_capture_from_stanza_match<'a, 'cursor: 'a, 'tree: 'a>( 171 - &self, 172 - mat: &'a QueryMatch<'cursor, 'tree>, 173 - ) -> impl Iterator<Item = Node<'tree>> + 'a { 174 - mat.nodes_for_capture_index(self.full_match_stanza_capture_index as u32) 175 - } 176 - } 177 - 178 318 impl Value { 179 319 pub fn from_nodes<'tree, NI: IntoIterator<Item = Node<'tree>>>( 180 320 graph: &mut Graph<'tree>, ··· 204 344 } 205 345 } 206 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 + })?; 381 + } 382 + if let Some(location_attr) = &config.location_attr { 383 + let location = match &self { 384 + Variable::Scoped(v) => v.location, 385 + Variable::Unscoped(v) => v.location, 386 + }; 387 + attributes 388 + .add( 389 + location_attr.clone(), 390 + format!("line {} column {}", location.row + 1, location.column + 1), 391 + ) 392 + .map_err(|_| ExecutionError::DuplicateAttribute(location_attr.as_str().into()))?; 393 + } 394 + Ok(()) 395 + } 396 + }
+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": {