on master help sign in
Add code for analyzing dependencies between syntaxes
47d955aMartin Nordholts
src/assets.rs+6
@@ -111,6 +111,12 @@ impl HighlightingAssets {
111 );
111 );
112 }
112 }
113
113
114+ if std::env::var("BAT_PRINT_SYNTAX_DEPENDENCIES").is_ok() {
115+ // To trigger this code, run:
116+ // BAT_PRINT_SYNTAX_DEPENDENCIES=1 cargo run -- cache --build --source assets --blank --target /tmp
117+ crate::syntax_dependencies::print_syntax_dependencies(&syntax_set_builder);
118+ }
119+
114 let syntax_set = syntax_set_builder.build();
120 let syntax_set = syntax_set_builder.build();
115 let missing_contexts = syntax_set.find_unlinked_contexts();
121 let missing_contexts = syntax_set.find_unlinked_contexts();
116 if !missing_contexts.is_empty() {
122 if !missing_contexts.is_empty() {
src/lib.rs+1
@@ -40,6 +40,7 @@ mod preprocessor;
40mod pretty_printer;
40mod pretty_printer;
41pub(crate) mod printer;
41pub(crate) mod printer;
42pub mod style;
42pub mod style;
43+mod syntax_dependencies;
43pub(crate) mod syntax_mapping;
44pub(crate) mod syntax_mapping;
44mod terminal;
45mod terminal;
45pub(crate) mod wrapping;
46pub(crate) mod wrapping;
src/syntax_dependencies.rsnew file+184
@@ -0,0 +1,184 @@
1+use std::collections::HashMap;
2+use syntect::parsing::syntax_definition::{
3+ ContextReference, MatchOperation, MatchPattern, Pattern, SyntaxDefinition,
4+};
5+use syntect::parsing::{Scope, SyntaxSet, SyntaxSetBuilder};
6+
7+type SyntaxName = String;
8+
9+/// Used to look up what dependencies a given [SyntaxDefinition] has
10+type SyntaxToDependencies = HashMap<SyntaxName, Vec<Dependency>>;
11+
12+/// Used to look up which [SyntaxDefinition] corresponds to a given [Dependency]
13+type DependencyToSyntax<'a> = HashMap<Dependency, &'a SyntaxDefinition>;
14+
15+/// Represents a dependency on an external `.sublime-syntax` file.
16+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
17+enum Dependency {
18+ /// By name. Example YAML: `include: C.sublime-syntax`
19+ ByName(String),
20+
21+ /// By scope. Example YAML: `embed: scope:source.c`
22+ ByScope(Scope),
23+}
24+
25+/// Generates independent [SyntaxSet]s after analyzing dependencies between syntaxes
26+/// in a [SyntaxSetBuilder], and then prints the reults.
27+pub(crate) fn print_syntax_dependencies(syntax_set_builder: &SyntaxSetBuilder) {
28+ println!("Constructing independent SyntaxSets...");
29+ let independent_syntax_sets = build_independent_syntax_sets(syntax_set_builder);
30+
31+ println!("Independent SyntaxSets:");
32+ for syntax_set in independent_syntax_sets {
33+ let names = syntax_set
34+ .syntaxes()
35+ .iter()
36+ .map(|syntax| &syntax.name)
37+ .collect::<Vec<_>>();
38+ println!("{:?}", names);
39+ }
40+}
41+
42+/// Analyzes dependencies between syntaxes in a [SyntaxSetBuilder].
43+/// From that, it builds independent [SyntaxSet]s.
44+fn build_independent_syntax_sets(
45+ syntax_set_builder: &'_ SyntaxSetBuilder,
46+) -> impl Iterator<Item = SyntaxSet> + '_ {
47+ let syntaxes = syntax_set_builder.syntaxes();
48+
49+ // Build the data structures we need for dependency resolution
50+ let (syntax_to_dependencies, dependency_to_syntax) = generate_maps(syntaxes);
51+
52+ // Create one independent SyntaxSet from each (non-hidden) SyntaxDefinition
53+ syntaxes.iter().filter_map(move |syntax| {
54+ if syntax.hidden {
55+ return None;
56+ }
57+
58+ let mut builder = SyntaxSetDependencyBuilder::new();
59+ builder.add_with_dependencies(syntax, &syntax_to_dependencies, &dependency_to_syntax);
60+ Some(builder.build())
61+ })
62+}
63+
64+/// In order to analyze dependencies, we need two key pieces of data.
65+/// First, when we have a [Dependency], we need to know what [SyntaxDefinition] that
66+/// corresponds to. Second, when we have a [SyntaxDefinition], we need to know
67+/// what dependencies it has. This functions generates that data for each syntax.
68+fn generate_maps(syntaxes: &[SyntaxDefinition]) -> (SyntaxToDependencies, DependencyToSyntax) {
69+ let mut syntax_to_dependencies = HashMap::new();
70+ let mut dependency_to_syntax = HashMap::new();
71+
72+ for syntax in syntaxes {
73+ syntax_to_dependencies.insert(syntax.name.clone(), dependencies_for_syntax(syntax));
74+
75+ dependency_to_syntax.insert(Dependency::ByName(syntax.name.clone()), syntax);
76+ dependency_to_syntax.insert(Dependency::ByScope(syntax.scope), syntax);
77+ }
78+
79+ (syntax_to_dependencies, dependency_to_syntax)
80+}
81+
82+/// Gets what external dependencies a given [SyntaxDefinition] has.
83+/// An external dependency is another `.sublime-syntax` file.
84+/// It does that by looking for variants of the following YAML patterns:
85+/// - `include: C.sublime-syntax`
86+/// - `embed: scope:source.c`
87+fn dependencies_for_syntax(syntax: &SyntaxDefinition) -> Vec<Dependency> {
88+ let mut dependencies: Vec<Dependency> = syntax
89+ .contexts
90+ .values()
91+ .flat_map(|context| &context.patterns)
92+ .flat_map(dependencies_from_pattern)
93+ .collect();
94+
95+ // No need to track a dependency more than once
96+ dependencies.dedup();
97+
98+ dependencies
99+}
100+
101+fn dependencies_from_pattern(pattern: &Pattern) -> Vec<Dependency> {
102+ match *pattern {
103+ Pattern::Match(MatchPattern {
104+ operation: MatchOperation::Push(ref context_references),
105+ ..
106+ }) => context_references
107+ .iter()
108+ .map(dependency_from_context_reference)
109+ .collect(),
110+ Pattern::Include(ref context_reference) => {
111+ vec![dependency_from_context_reference(context_reference)]
112+ }
113+ _ => vec![],
114+ }
115+ .into_iter()
116+ .flatten()
117+ .collect()
118+}
119+
120+fn dependency_from_context_reference(context_reference: &ContextReference) -> Option<Dependency> {
121+ match &context_reference {
122+ ContextReference::File { ref name, .. } => Some(Dependency::ByName(name.clone())),
123+ ContextReference::ByScope { ref scope, .. } => Some(Dependency::ByScope(*scope)),
124+ _ => None,
125+ }
126+}
127+
128+/// Helper to construct a [SyntaxSetBuilder] that contains only [SyntaxDefinition]s
129+/// that have dependencies among them.
130+struct SyntaxSetDependencyBuilder {
131+ syntax_set_builder: SyntaxSetBuilder,
132+}
133+
134+impl SyntaxSetDependencyBuilder {
135+ fn new() -> Self {
136+ SyntaxSetDependencyBuilder {
137+ syntax_set_builder: SyntaxSetBuilder::new(),
138+ }
139+ }
140+
141+ /// Add a [SyntaxDefinition] to the underlying [SyntaxSetBuilder].
142+ /// Also resolve any dependencies it has and add those [SyntaxDefinition]s too.
143+ /// This is a recursive process.
144+ fn add_with_dependencies(
145+ &mut self,
146+ syntax: &SyntaxDefinition,
147+ syntax_to_dependencies: &SyntaxToDependencies,
148+ dependency_to_syntax: &DependencyToSyntax,
149+ ) {
150+ let name = &syntax.name;
151+ if self.is_syntax_already_added(name) {
152+ return;
153+ }
154+
155+ self.syntax_set_builder.add(syntax.clone());
156+
157+ let dependencies = syntax_to_dependencies.get(name);
158+ if dependencies.is_none() {
159+ eprintln!("ERROR: Unknown dependencies for {}", name);
160+ return;
161+ }
162+
163+ for dependency in dependencies.unwrap() {
164+ if let Some(syntax_definition_dependency) = dependency_to_syntax.get(dependency) {
165+ self.add_with_dependencies(
166+ syntax_definition_dependency,
167+ syntax_to_dependencies,
168+ dependency_to_syntax,
169+ )
170+ }
171+ }
172+ }
173+
174+ fn is_syntax_already_added(&self, name: &str) -> bool {
175+ self.syntax_set_builder
176+ .syntaxes()
177+ .iter()
178+ .any(|syntax| syntax.name == name)
179+ }
180+
181+ fn build(self) -> SyntaxSet {
182+ self.syntax_set_builder.build()
183+ }
184+}