on f5c1cb2 help sign in
bat/src/syntax_dependencies.rs
184 lines 6.5 kB history raw
1use std::collections::HashMap;
2use syntect::parsing::syntax_definition::{
3 ContextReference, MatchOperation, MatchPattern, Pattern, SyntaxDefinition,
4};
5use syntect::parsing::{Scope, SyntaxSet, SyntaxSetBuilder};
7type SyntaxName = String;
9/// Used to look up what dependencies a given [SyntaxDefinition] has
10type SyntaxToDependencies = HashMap<SyntaxName, Vec<Dependency>>;
12/// Used to look up which [SyntaxDefinition] corresponds to a given [Dependency]
13type DependencyToSyntax<'a> = HashMap<Dependency, &'a SyntaxDefinition>;
15/// Represents a dependency on an external `.sublime-syntax` file.
16#[derive(Debug, Eq, PartialEq, Clone, Hash)]
17enum Dependency {
18 /// By name. Example YAML: `include: C.sublime-syntax`
19 ByName(String),
21 /// By scope. Example YAML: `embed: scope:source.c`
22 ByScope(Scope),
25/// Generates independent [SyntaxSet]s after analyzing dependencies between syntaxes
26/// in a [SyntaxSetBuilder], and then prints the reults.
27pub(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);
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 }
42/// Analyzes dependencies between syntaxes in a [SyntaxSetBuilder].
43/// From that, it builds independent [SyntaxSet]s.
44fn build_independent_syntax_sets(
45 syntax_set_builder: &'_ SyntaxSetBuilder,
46) -> impl Iterator<Item = SyntaxSet> + '_ {
47 let syntaxes = syntax_set_builder.syntaxes();
49 // Build the data structures we need for dependency resolution
50 let (syntax_to_dependencies, dependency_to_syntax) = generate_maps(syntaxes);
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 }
58 let mut builder = SyntaxSetDependencyBuilder::new();
59 builder.add_with_dependencies(syntax, &syntax_to_dependencies, &dependency_to_syntax);
60 Some(builder.build())
61 })
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.
68fn generate_maps(syntaxes: &[SyntaxDefinition]) -> (SyntaxToDependencies, DependencyToSyntax) {
69 let mut syntax_to_dependencies = HashMap::new();
70 let mut dependency_to_syntax = HashMap::new();
72 for syntax in syntaxes {
73 syntax_to_dependencies.insert(syntax.name.clone(), dependencies_for_syntax(syntax));
75 dependency_to_syntax.insert(Dependency::ByName(syntax.name.clone()), syntax);
76 dependency_to_syntax.insert(Dependency::ByScope(syntax.scope), syntax);
77 }
79 (syntax_to_dependencies, dependency_to_syntax)
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`
87fn 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();
95 // No need to track a dependency more than once
96 dependencies.dedup();
98 dependencies
101fn 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()
120fn 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 }
128/// Helper to construct a [SyntaxSetBuilder] that contains only [SyntaxDefinition]s
129/// that have dependencies among them.
130struct SyntaxSetDependencyBuilder {
131 syntax_set_builder: SyntaxSetBuilder,
134impl SyntaxSetDependencyBuilder {
135 fn new() -> Self {
136 SyntaxSetDependencyBuilder {
137 syntax_set_builder: SyntaxSetBuilder::new(),
138 }
139 }
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 }
155 self.syntax_set_builder.add(syntax.clone());
157 let dependencies = syntax_to_dependencies.get(name);
158 if dependencies.is_none() {
159 eprintln!("ERROR: Unknown dependencies for {}", name);
160 return;
161 }
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 }
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 }
181 fn build(self) -> SyntaxSet {
182 self.syntax_set_builder.build()
183 }