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

Merge pull request #135 from tree-sitter/inherited-scoped-variables

Inherited scoped variables

authored by Hendrik van Antwerpen and committed by GitHub a037d0c8 c363c83e

+8
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.10.3 -- 2023-06-01 9 + 10 + ### DSL 11 + 12 + #### Added 13 + 14 + - 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. 15 + 8 16 ## v0.10.2 -- 2023-05-25 9 17 10 18 ### Library
+1 -1
Cargo.toml
··· 1 1 [package] 2 2 name = "tree-sitter-graph" 3 - version = "0.10.2" 3 + version = "0.10.3" 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/"
+4
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; ··· 24 25 pub language: Language, 25 26 /// The expected global variables used in this file 26 27 pub globals: Vec<Global>, 28 + /// The scoped variables that are inherited by child nodes 29 + pub inherited_variables: HashSet<Identifier>, 27 30 /// The combined query of all stanzas in the file 28 31 pub query: Option<Query>, 29 32 /// The list of stanzas in the file ··· 37 40 File { 38 41 language, 39 42 globals: Vec::new(), 43 + inherited_variables: HashSet::new(), 40 44 query: None, 41 45 stanzas: Vec::new(), 42 46 shorthands: AttributeShorthands::new(),
+14
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 17 use tree_sitter::QueryCursor; 17 18 use tree_sitter::QueryMatch; ··· 81 82 &mut lazy_graph, 82 83 &mut function_parameters, 83 84 &mut prev_element_debug_info, 85 + &self.inherited_variables, 84 86 &self.shorthands, 85 87 cancellation_flag, 86 88 ) ··· 92 94 functions: config.functions, 93 95 store: &store, 94 96 scoped_store: &scoped_store, 97 + inherited_variables: &self.inherited_variables, 95 98 function_parameters: &mut function_parameters, 96 99 prev_element_debug_info: &mut prev_element_debug_info, 97 100 cancellation_flag, ··· 141 144 function_parameters: &'a mut Vec<graph::Value>, // re-usable buffer to reduce memory allocations 142 145 prev_element_debug_info: &'a mut HashMap<GraphElementKey, DebugInfo>, 143 146 error_context: StatementContext, 147 + inherited_variables: &'a HashSet<Identifier>, 144 148 shorthands: &'a ast::AttributeShorthands, 145 149 cancellation_flag: &'a dyn CancellationFlag, 146 150 } ··· 152 156 pub functions: &'a Functions, 153 157 pub store: &'a LazyStore, 154 158 pub scoped_store: &'a LazyScopedVariables, 159 + pub inherited_variables: &'a HashSet<Identifier>, 155 160 pub function_parameters: &'a mut Vec<graph::Value>, // re-usable buffer to reduce memory allocations 156 161 pub prev_element_debug_info: &'a mut HashMap<GraphElementKey, DebugInfo>, 157 162 pub cancellation_flag: &'a dyn CancellationFlag, ··· 177 182 lazy_graph: &mut Vec<LazyStatement>, 178 183 function_parameters: &mut Vec<graph::Value>, 179 184 prev_element_debug_info: &mut HashMap<GraphElementKey, DebugInfo>, 185 + inherited_variables: &HashSet<Identifier>, 180 186 shorthands: &ast::AttributeShorthands, 181 187 cancellation_flag: &dyn CancellationFlag, 182 188 ) -> Result<(), ExecutionError> { ··· 203 209 function_parameters, 204 210 prev_element_debug_info, 205 211 error_context, 212 + inherited_variables, 206 213 shorthands, 207 214 cancellation_flag, 208 215 }; ··· 366 373 function_parameters: exec.function_parameters, 367 374 prev_element_debug_info: exec.prev_element_debug_info, 368 375 error_context: exec.error_context.clone(), 376 + inherited_variables: exec.inherited_variables, 369 377 shorthands: exec.shorthands, 370 378 cancellation_flag: exec.cancellation_flag, 371 379 }; ··· 431 439 function_parameters: exec.function_parameters, 432 440 prev_element_debug_info: exec.prev_element_debug_info, 433 441 error_context: exec.error_context.clone(), 442 + inherited_variables: exec.inherited_variables, 434 443 shorthands: exec.shorthands, 435 444 cancellation_flag: exec.cancellation_flag, 436 445 }; ··· 477 486 function_parameters: exec.function_parameters, 478 487 prev_element_debug_info: exec.prev_element_debug_info, 479 488 error_context: exec.error_context.clone(), 489 + inherited_variables: exec.inherited_variables, 480 490 shorthands: exec.shorthands, 481 491 cancellation_flag: exec.cancellation_flag, 482 492 }; ··· 520 530 functions: exec.config.functions, 521 531 store: exec.store, 522 532 scoped_store: exec.scoped_store, 533 + inherited_variables: exec.inherited_variables, 523 534 function_parameters: exec.function_parameters, 524 535 prev_element_debug_info: exec.prev_element_debug_info, 525 536 cancellation_flag: exec.cancellation_flag, ··· 569 580 function_parameters: exec.function_parameters, 570 581 prev_element_debug_info: exec.prev_element_debug_info, 571 582 error_context: exec.error_context.clone(), 583 + inherited_variables: exec.inherited_variables, 572 584 shorthands: exec.shorthands, 573 585 cancellation_flag: exec.cancellation_flag, 574 586 }; ··· 611 623 function_parameters: exec.function_parameters, 612 624 prev_element_debug_info: exec.prev_element_debug_info, 613 625 error_context: exec.error_context.clone(), 626 + inherited_variables: exec.inherited_variables, 614 627 shorthands: exec.shorthands, 615 628 cancellation_flag: exec.cancellation_flag, 616 629 }; ··· 825 838 function_parameters: exec.function_parameters, 826 839 prev_element_debug_info: exec.prev_element_debug_info, 827 840 error_context: exec.error_context.clone(), 841 + inherited_variables: exec.inherited_variables, 828 842 shorthands: exec.shorthands, 829 843 cancellation_flag: exec.cancellation_flag, 830 844 };
+25 -11
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 196 let mut map = HashMap::new(); ··· 187 201 .with_context(|| format!("Evaluating scope of variable _.{}", name,).into()) 188 202 .with_context(|| debug_info.0.clone().into())?; 189 203 let prev_debug_info = debug_infos.insert(node, debug_info.clone()); 190 - match map.insert(node, value.clone()) { 204 + match map.insert(node.index, value.clone()) { 191 205 Some(_) => { 192 206 return Err(ExecutionError::DuplicateVariable(format!( 193 207 "{}.{}", ··· 211 225 enum ScopedValues { 212 226 Unforced(Vec<(LazyValue, LazyValue, DebugInfo)>), 213 227 Forcing, 214 - Forced(HashMap<SyntaxNodeRef, LazyValue>), 228 + Forced(HashMap<SyntaxNodeID, LazyValue>), 215 229 } 216 230 217 231 impl ScopedValues {
+53 -13
src/execution/strict.rs
··· 7 7 8 8 use std::collections::BTreeSet; 9 9 use std::collections::HashMap; 10 + use std::collections::HashSet; 10 11 use tree_sitter::QueryCursor; 11 12 use tree_sitter::QueryMatch; 12 13 use tree_sitter::Tree; ··· 48 49 use crate::execution::CancellationFlag; 49 50 use crate::execution::ExecutionConfig; 50 51 use crate::graph::Graph; 52 + use crate::graph::SyntaxNodeID; 51 53 use crate::graph::SyntaxNodeRef; 52 54 use crate::graph::Value; 53 55 use crate::variables::Globals; ··· 96 98 &mut scoped, 97 99 &current_regex_captures, 98 100 &mut function_parameters, 101 + &self.inherited_variables, 99 102 &self.shorthands, 100 103 cancellation_flag, 101 104 ) ··· 131 134 function_parameters: &'a mut Vec<Value>, 132 135 mat: &'a QueryMatch<'a, 'tree>, 133 136 error_context: StatementContext, 137 + inherited_variables: &'a HashSet<Identifier>, 134 138 shorthands: &'a AttributeShorthands, 135 139 cancellation_flag: &'a dyn CancellationFlag, 136 140 } 137 141 138 142 struct ScopedVariables<'a> { 139 - scopes: HashMap<SyntaxNodeRef, VariableMap<'a, Value>>, 143 + scopes: HashMap<SyntaxNodeID, VariableMap<'a, Value>>, 140 144 } 141 145 142 146 impl<'a> ScopedVariables<'a> { ··· 146 150 } 147 151 } 148 152 149 - fn get(&mut self, scope: SyntaxNodeRef) -> &mut VariableMap<'a, Value> { 150 - self.scopes.entry(scope).or_insert(VariableMap::new()) 153 + fn get_mut(&mut self, scope: SyntaxNodeRef) -> &mut VariableMap<'a, Value> { 154 + self.scopes.entry(scope.index).or_insert(VariableMap::new()) 155 + } 156 + 157 + fn try_get(&self, index: SyntaxNodeID) -> Option<&VariableMap<'a, Value>> { 158 + self.scopes.get(&index) 151 159 } 152 160 } 153 161 ··· 162 170 scoped: &mut ScopedVariables<'s>, 163 171 current_regex_captures: &Vec<String>, 164 172 function_parameters: &mut Vec<Value>, 173 + inherited_variables: &HashSet<Identifier>, 165 174 shorthands: &AttributeShorthands, 166 175 cancellation_flag: &dyn CancellationFlag, 167 176 ) -> Result<(), ExecutionError> { ··· 184 193 function_parameters, 185 194 mat: &mat, 186 195 error_context, 196 + inherited_variables, 187 197 shorthands, 188 198 cancellation_flag, 189 199 }; ··· 399 409 function_parameters: exec.function_parameters, 400 410 mat: exec.mat, 401 411 error_context: exec.error_context.clone(), 412 + inherited_variables: exec.inherited_variables, 402 413 shorthands: exec.shorthands, 403 414 cancellation_flag: exec.cancellation_flag, 404 415 }; ··· 458 469 function_parameters: exec.function_parameters, 459 470 mat: exec.mat, 460 471 error_context: exec.error_context.clone(), 472 + inherited_variables: exec.inherited_variables, 461 473 shorthands: exec.shorthands, 462 474 cancellation_flag: exec.cancellation_flag, 463 475 }; ··· 499 511 function_parameters: exec.function_parameters, 500 512 mat: exec.mat, 501 513 error_context: exec.error_context.clone(), 514 + inherited_variables: exec.inherited_variables, 502 515 shorthands: exec.shorthands, 503 516 cancellation_flag: exec.cancellation_flag, 504 517 }; ··· 573 586 function_parameters: exec.function_parameters, 574 587 mat: exec.mat, 575 588 error_context: exec.error_context.clone(), 589 + inherited_variables: exec.inherited_variables, 576 590 shorthands: exec.shorthands, 577 591 cancellation_flag: exec.cancellation_flag, 578 592 }; ··· 612 626 function_parameters: exec.function_parameters, 613 627 mat: exec.mat, 614 628 error_context: exec.error_context.clone(), 629 + inherited_variables: exec.inherited_variables, 615 630 shorthands: exec.shorthands, 616 631 cancellation_flag: exec.cancellation_flag, 617 632 }; ··· 709 724 ))) 710 725 } 711 726 }; 712 - let variables = exec.scoped.get(scope); 713 - if let Some(value) = variables.get(&self.name) { 714 - Ok(value) 715 - } else { 716 - Err(ExecutionError::UndefinedVariable(format!( 717 - "{} on node {}", 718 - self, scope 719 - ))) 727 + 728 + // search this node 729 + if let Some(value) = exec 730 + .scoped 731 + .try_get(scope.index) 732 + .and_then(|v| v.get(&self.name)) 733 + { 734 + return Ok(value); 720 735 } 736 + 737 + // search parent nodes 738 + if exec.inherited_variables.contains(&self.name) { 739 + let mut parent = exec 740 + .graph 741 + .syntax_nodes 742 + .get(&scope.index) 743 + .and_then(|n| n.parent()); 744 + while let Some(scope) = parent { 745 + if let Some(value) = exec 746 + .scoped 747 + .try_get(scope.id() as u32) 748 + .and_then(|v| v.get(&self.name)) 749 + { 750 + return Ok(value); 751 + } 752 + parent = scope.parent(); 753 + } 754 + } 755 + 756 + Err(ExecutionError::UndefinedVariable(format!( 757 + "{} on node {}", 758 + self, scope 759 + ))) 721 760 } 722 761 723 762 fn add( ··· 736 775 ))) 737 776 } 738 777 }; 739 - let variables = exec.scoped.get(scope); 778 + let variables = exec.scoped.get_mut(scope); 740 779 variables 741 780 .add(self.name.clone(), value, mutable) 742 781 .map_err(|_| ExecutionError::DuplicateVariable(format!("{}", self))) ··· 753 792 ))) 754 793 } 755 794 }; 756 - let variables = exec.scoped.get(scope); 795 + let variables = exec.scoped.get_mut(scope); 757 796 variables 758 797 .set(self.name.clone(), value) 759 798 .map_err(|_| ExecutionError::DuplicateVariable(format!("{}", self))) ··· 844 883 function_parameters: exec.function_parameters, 845 884 mat: exec.mat, 846 885 error_context: exec.error_context.clone(), 886 + inherited_variables: exec.inherited_variables, 847 887 shorthands: exec.shorthands, 848 888 cancellation_flag: exec.cancellation_flag, 849 889 };
+3 -3
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> { ··· 635 635 /// A reference to a syntax node in a graph 636 636 #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] 637 637 pub struct SyntaxNodeRef { 638 - index: SyntaxNodeID, 638 + pub(crate) index: SyntaxNodeID, 639 639 kind: &'static str, 640 640 position: tree_sitter::Point, 641 641 }
+9 -4
src/parser.rs
··· 291 291 fn parse_into_file(&mut self, file: &mut ast::File) -> Result<(), ParseError> { 292 292 self.consume_whitespace(); 293 293 while self.try_peek().is_some() { 294 - 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") { 295 299 self.consume_whitespace(); 296 300 let global = self.parse_global()?; 297 301 file.globals.push(global); 298 - } else if let Ok(_) = self.consume_token("attribute") { 302 + } else if let Ok(_) = self.consume_token("inherit") { 299 303 self.consume_whitespace(); 300 - let shorthand = self.parse_shorthand()?; 301 - file.shorthands.add(shorthand); 304 + self.consume_token(".")?; 305 + let name = self.parse_identifier("inherit")?; 306 + file.inherited_variables.insert(name); 302 307 } else { 303 308 let stanza = self.parse_stanza(file.language)?; 304 309 file.stanzas.push(stanza);
+72
tests/it/execution.rs
··· 936 936 "#}, 937 937 ); 938 938 } 939 + 940 + #[test] 941 + fn can_access_inherited_attribute() { 942 + check_execution( 943 + indoc! { r#" 944 + def get_f(): 945 + pass 946 + "#}, 947 + indoc! {r#" 948 + inherit .test 949 + (function_definition)@def { 950 + node @def.test 951 + attr (@def.test) in_def 952 + } 953 + (pass_statement)@pass { 954 + attr (@pass.test) in_pass 955 + } 956 + "#}, 957 + indoc! {r#" 958 + node 0 959 + in_def: #true 960 + in_pass: #true 961 + "#}, 962 + ); 963 + } 964 + 965 + #[test] 966 + fn can_overwrite_inherited_attribute() { 967 + check_execution( 968 + indoc! { r#" 969 + def get_f(): 970 + pass 971 + "#}, 972 + indoc! {r#" 973 + inherit .test 974 + (function_definition)@def { 975 + node @def.test 976 + attr (@def.test) in_def 977 + } 978 + (pass_statement)@pass { 979 + node @pass.test 980 + } 981 + (pass_statement)@pass { 982 + attr (@pass.test) in_pass 983 + } 984 + "#}, 985 + indoc! {r#" 986 + node 0 987 + in_def: #true 988 + node 1 989 + in_pass: #true 990 + "#}, 991 + ); 992 + } 993 + 994 + #[test] 995 + fn cannot_access_non_inherited_variable() { 996 + fail_execution( 997 + indoc! { r#" 998 + def get_f(): 999 + pass 1000 + "#}, 1001 + indoc! {r#" 1002 + (function_definition)@def { 1003 + node @def.test 1004 + } 1005 + (pass_statement)@pass { 1006 + attr (@pass.test) in_pass 1007 + } 1008 + "#}, 1009 + ); 1010 + }
+72
tests/it/lazy_execution.rs
··· 1455 1455 "#}, 1456 1456 ); 1457 1457 } 1458 + 1459 + #[test] 1460 + fn can_access_inherited_attribute() { 1461 + check_execution( 1462 + indoc! { r#" 1463 + def get_f(): 1464 + pass 1465 + "#}, 1466 + indoc! {r#" 1467 + inherit .test 1468 + (function_definition)@def { 1469 + node @def.test 1470 + attr (@def.test) in_def 1471 + } 1472 + (pass_statement)@pass { 1473 + attr (@pass.test) in_pass 1474 + } 1475 + "#}, 1476 + indoc! {r#" 1477 + node 0 1478 + in_def: #true 1479 + in_pass: #true 1480 + "#}, 1481 + ); 1482 + } 1483 + 1484 + #[test] 1485 + fn can_overwrite_inherited_attribute() { 1486 + check_execution( 1487 + indoc! { r#" 1488 + def get_f(): 1489 + pass 1490 + "#}, 1491 + indoc! {r#" 1492 + inherit .test 1493 + (function_definition)@def { 1494 + node @def.test 1495 + attr (@def.test) in_def 1496 + } 1497 + (pass_statement)@pass { 1498 + node @pass.test 1499 + } 1500 + (pass_statement)@pass { 1501 + attr (@pass.test) in_pass 1502 + } 1503 + "#}, 1504 + indoc! {r#" 1505 + node 0 1506 + in_def: #true 1507 + node 1 1508 + in_pass: #true 1509 + "#}, 1510 + ); 1511 + } 1512 + 1513 + #[test] 1514 + fn cannot_access_non_inherited_variable() { 1515 + fail_execution( 1516 + indoc! { r#" 1517 + def get_f(): 1518 + pass 1519 + "#}, 1520 + indoc! {r#" 1521 + (function_definition)@def { 1522 + node @def.test 1523 + } 1524 + (pass_statement)@pass { 1525 + attr (@pass.test) in_pass 1526 + } 1527 + "#}, 1528 + ); 1529 + }
+9
tests/it/parser.rs
··· 1599 1599 "#; 1600 1600 File::from_str(tree_sitter_python::language(), source).expect("parse to succeed"); 1601 1601 } 1602 + 1603 + #[test] 1604 + fn can_parse_inherit_directives() { 1605 + let source = r#" 1606 + inherit .scope 1607 + "#; 1608 + let file = File::from_str(tree_sitter_python::language(), source).expect("parse to succeed"); 1609 + assert!(file.inherited_variables.contains("scope".into())); 1610 + }
+6
vscode/CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## 0.1.1 -- 2023-06-01 9 + 10 + ### Added 11 + 12 + - Add new `inherit` keyword 13 + 8 14 ## 0.1.0 -- 2022-05-11 9 15 10 16 ### Added
+1 -1
vscode/package.json
··· 1 1 { 2 2 "name": "tree-sitter-graph", 3 - "version": "0.1.0", 3 + "version": "0.1.1", 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|for|global|if|inherit|let|node|none|print|scan|set|some|var)\\b" 20 20 }] 21 21 }, 22 22 "functions": {