1use crate::pass;
19
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct Gates {
26 rules: Vec<Rule>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
32struct Rule {
33 pass: String,
35 on: bool,
37 scope: Scope,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43enum Scope {
44 Everything,
46 These(Vec<Pick>),
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52enum Pick {
53 Id(u32),
55 Span(u32, u32),
57 Name(String),
59}
60
61impl Pick {
62 fn covers(&self, id: u32, name: &str) -> bool {
64 match self {
65 Pick::Id(want) => *want == id,
66 Pick::Span(low, high) => (*low..=*high).contains(&id),
67 Pick::Name(want) => want == name,
68 }
69 }
70}
71
72impl Scope {
73 fn covers(&self, id: u32, name: &str) -> bool {
75 match self {
76 Scope::Everything => true,
77 Scope::These(picks) => picks.iter().any(|pick| pick.covers(id, name)),
78 }
79 }
80}
81
82impl Gates {
83 pub fn add(&mut self, on: bool, spec: &str) -> Result<(), String> {
93 let (name, list) = match spec.split_once('=') {
94 Some((name, list)) => (name, Some(list)),
95 None => (spec, None),
96 };
97 if pass::find(name).is_none() {
98 return Err(format!("`{name}` is not a pass this compiler has, see --print-pipeline"));
99 }
100 let scope = match list {
101 None => Scope::Everything,
102 Some(list) => Scope::These(picks(list)?),
103 };
104 self.rules.push(Rule { pass: name.to_owned(), on, scope });
105 Ok(())
106 }
107
108 #[must_use]
110 pub fn is_empty(&self) -> bool {
111 self.rules.is_empty()
112 }
113
114 #[must_use]
121 pub fn allows(&self, pass: &str, default: bool, id: u32, name: &str) -> bool {
122 let mut answer = default;
123 for rule in &self.rules {
124 if rule.pass == pass && rule.scope.covers(id, name) {
125 answer = rule.on;
126 }
127 }
128 answer
129 }
130
131 #[must_use]
137 pub fn enabled(&self) -> Vec<&str> {
138 let mut out: Vec<&str> = Vec::new();
139 for rule in self.rules.iter().filter(|rule| rule.on) {
140 let name = rule.pass.as_str();
141 if !out.contains(&name) {
142 out.push(name);
143 }
144 }
145 out
146 }
147
148 #[must_use]
154 pub fn note(&self, pass: &str) -> Option<String> {
155 let mut parts: Vec<String> = Vec::new();
156 for rule in self.rules.iter().filter(|rule| rule.pass == pass) {
157 let word = if rule.on { "on" } else { "off" };
158 parts.push(match &rule.scope {
159 Scope::Everything => word.to_owned(),
160 Scope::These(picks) => format!("{word} for {}", render(picks)),
161 });
162 }
163 match parts.is_empty() {
164 true => None,
165 false => Some(parts.join(", ")),
166 }
167 }
168}
169
170fn picks(list: &str) -> Result<Vec<Pick>, String> {
172 if list.is_empty() {
173 return Err("the list of functions after the `=` is empty".to_owned());
174 }
175 let mut out = Vec::new();
176 for item in list.split(',') {
177 out.push(pick(item)?);
178 }
179 Ok(out)
180}
181
182fn pick(item: &str) -> Result<Pick, String> {
187 if item.is_empty() {
188 return Err("there is an empty item in the list of functions".to_owned());
189 }
190 if !item.starts_with(|c: char| c.is_ascii_digit()) {
191 return Ok(Pick::Name(item.to_owned()));
192 }
193 let Some((low, high)) = item.split_once('-') else {
194 return Ok(Pick::Id(number(item)?));
195 };
196 let (low, high) = (number(low)?, number(high)?);
197 if low > high {
198 return Err(format!("the range `{item}` ends before it starts"));
199 }
200 Ok(Pick::Span(low, high))
201}
202
203fn number(text: &str) -> Result<u32, String> {
205 text.parse().map_err(|_| format!("`{text}` is not the number of a function"))
206}
207
208fn render(picks: &[Pick]) -> String {
210 let parts: Vec<String> = picks
211 .iter()
212 .map(|pick| match pick {
213 Pick::Id(id) => id.to_string(),
214 Pick::Span(low, high) => format!("{low}-{high}"),
215 Pick::Name(name) => name.clone(),
216 })
217 .collect();
218 parts.join(",")
219}
220
221#[cfg(test)]
222mod tests {
223 use super::Gates;
224
225 fn gates(flags: &[(bool, &str)]) -> Gates {
227 let mut gates = Gates::default();
228 for (on, spec) in flags {
229 gates.add(*on, spec).expect("the test asked for a gate this compiler refuses");
230 }
231 gates
232 }
233
234 #[test]
235 fn nothing_asked_for_means_the_level_decides() {
236 let gates = Gates::default();
237 assert!(gates.is_empty());
238 assert!(gates.allows("fold", true, 0, "main"));
239 assert!(!gates.allows("fold", false, 0, "main"));
240 assert_eq!(gates.note("fold"), None);
241 }
242
243 #[test]
244 fn disabling_a_pass_with_no_range_takes_it_away_from_every_function() {
245 let gates = gates(&[(false, "fold")]);
246 assert!(!gates.allows("fold", true, 0, "main"));
247 assert!(!gates.allows("fold", true, 7, "other"));
248 assert!(gates.allows("dce", true, 0, "main"), "one pass named is not every pass named");
249 }
250
251 #[test]
252 fn a_range_leaves_every_function_it_does_not_name_alone() {
253 let gates = gates(&[(false, "fold=1-3")]);
254 assert!(gates.allows("fold", true, 0, "a"));
255 assert!(!gates.allows("fold", true, 1, "b"));
256 assert!(!gates.allows("fold", true, 3, "d"));
257 assert!(gates.allows("fold", true, 4, "e"));
258 }
259
260 #[test]
261 fn a_function_can_be_named_as_well_as_numbered() {
262 let gates = gates(&[(false, "dce=parse_line,9")]);
263 assert!(!gates.allows("dce", true, 0, "parse_line"));
264 assert!(!gates.allows("dce", true, 9, "whatever"));
265 assert!(gates.allows("dce", true, 0, "main"));
266 }
267
268 #[test]
269 fn the_last_rule_that_covers_a_function_is_the_one_that_decides() {
270 let gates = gates(&[(false, "fold"), (true, "fold=2")]);
271 assert!(!gates.allows("fold", true, 1, "a"));
272 assert!(gates.allows("fold", true, 2, "b"), "the second rule covers this one");
273 }
274
275 #[test]
276 fn enabling_a_pass_reaches_one_the_level_did_not_choose() {
277 let gates = gates(&[(true, "narrow=2")]);
278 assert!(!gates.allows("narrow", false, 1, "a"));
279 assert!(gates.allows("narrow", false, 2, "b"));
280 assert_eq!(gates.enabled(), ["narrow"]);
281 }
282
283 #[test]
284 fn a_pass_enabled_twice_is_named_once() {
285 let gates = gates(&[(true, "narrow=2"), (false, "fold"), (true, "narrow=5")]);
286 assert_eq!(gates.enabled(), ["narrow"]);
287 }
288
289 #[test]
290 fn the_listing_says_what_was_asked_for() {
291 let gates = gates(&[(false, "fold"), (true, "fold=2-4,main")]);
292 assert_eq!(gates.note("fold").as_deref(), Some("off, on for 2-4,main"));
293 assert_eq!(gates.note("dce"), None);
294 }
295
296 #[test]
297 fn a_pass_this_compiler_does_not_have_is_refused_rather_than_ignored() {
298 let mut gates = Gates::default();
299 let why = gates.add(false, "nosuch").expect_err("a pass that does not exist was accepted");
300 assert!(why.contains("not a pass"), "{why}");
301 assert!(gates.is_empty());
302 }
303
304 #[test]
305 fn a_range_list_that_says_nothing_is_refused() {
306 let mut gates = Gates::default();
307 assert!(gates.add(false, "fold=").is_err());
308 assert!(gates.add(false, "fold=1,,3").is_err());
309 }
310
311 #[test]
312 fn a_range_that_runs_backwards_is_refused() {
313 let mut gates = Gates::default();
314 let why = gates.add(false, "fold=9-2").expect_err("a backwards range was accepted");
315 assert!(why.contains("ends before it starts"), "{why}");
316 }
317
318 #[test]
319 fn a_number_that_is_not_one_is_refused() {
320 let mut gates = Gates::default();
321 assert!(gates.add(false, "fold=1x").is_err());
322 assert!(gates.add(false, "fold=1-x").is_err());
323 }
324}