metrale_circuit/venn/
discover.rs1use std::collections::{BTreeMap, BTreeSet};
15
16use super::families::{Discover, Families, Values};
17use crate::precision::glob;
18
19#[derive(Debug, Clone, Default)]
21pub struct KernelSources {
22 pub paths: BTreeSet<String>,
24 pub texts: BTreeMap<String, String>,
26}
27
28pub fn macro_files(families: &Families) -> BTreeSet<String> {
30 families
31 .families
32 .iter()
33 .flat_map(|f| f.discover.iter())
34 .filter_map(|d| match d {
35 Discover::Macro { file, .. } => Some(file.clone()),
36 Discover::File { .. } => None,
37 })
38 .collect()
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
43pub struct Found {
44 pub family: String,
46 pub values: Values,
48 pub file: String,
50}
51
52pub fn discover(families: &Families, src: &KernelSources) -> (BTreeSet<Found>, Vec<String>) {
55 let mut found = BTreeSet::new();
56 let mut problems = Vec::new();
57 for f in &families.families {
58 for rule in &f.discover {
59 match rule {
60 Discover::File { glob: g, values } => {
61 for p in src.paths.iter().filter(|p| glob(g, p)) {
62 found.insert(Found {
63 family: f.id.clone(),
64 values: values.clone(),
65 file: p.clone(),
66 });
67 }
68 }
69 Discover::Macro {
70 file,
71 name,
72 args,
73 map,
74 } => {
75 let Some(text) = src.texts.get(file) else {
76 problems.push(format!("{}: macro file {file} was not supplied", f.id));
77 continue;
78 };
79 for call in invocations(text, name) {
80 match point_of(&call, args, map) {
81 Ok(values) => {
82 found.insert(Found {
83 family: f.id.clone(),
84 values,
85 file: file.clone(),
86 });
87 }
88 Err(e) => problems.push(format!("{}: {file}: {name}: {e}", f.id)),
89 }
90 }
91 }
92 }
93 }
94 }
95 (found, problems)
96}
97
98pub fn drift(families: &Families, src: &KernelSources) -> Vec<String> {
101 let (found, mut problems) = discover(families, src);
102 for f in &families.families {
103 for p in &f.points {
104 for file in &p.files {
105 if !src.paths.contains(file) {
106 problems.push(format!(
107 "{}: point {:?} names {file}, which does not exist",
108 f.id, p.values
109 ));
110 }
111 }
112 }
113 for d in found.iter().filter(|d| d.family == f.id) {
114 let declared = f.points.iter().any(|p| {
115 p.files.contains(&d.file)
116 && d.values.iter().all(|(k, v)| p.values.get(k) == Some(v))
117 });
118 if !declared {
119 problems.push(format!(
120 "{}: {} realises {:?}, which no declared point lists at that file",
121 f.id, d.file, d.values
122 ));
123 }
124 }
125 for p in &f.points {
127 for file in &p.files {
128 let covered = f.discover.iter().any(|r| match r {
129 Discover::File { glob: g, .. } => glob(g, file),
130 Discover::Macro { file: m, .. } => m == file,
131 });
132 let seen = found.iter().any(|d| {
133 d.family == f.id
134 && &d.file == file
135 && d.values.iter().all(|(k, v)| p.values.get(k) == Some(v))
136 });
137 if covered && !seen {
138 problems.push(format!(
139 "{}: point {:?} is declared at {file}, but no rule finds it there",
140 f.id, p.values
141 ));
142 }
143 }
144 }
145 }
146 problems
147}
148
149fn invocations(text: &str, name: &str) -> Vec<Vec<String>> {
152 let open = format!("{name}(");
153 text.lines()
154 .filter_map(|l| l.trim_start().strip_prefix(open.as_str()))
155 .map(|rest| {
156 let mut depth = 0usize;
157 let mut args = vec![String::new()];
158 for c in rest.chars() {
159 match c {
160 '(' | '<' => depth += 1,
161 ')' if depth == 0 => break,
162 ')' | '>' => depth = depth.saturating_sub(1),
163 ',' if depth == 0 => {
164 args.push(String::new());
165 continue;
166 }
167 _ => {}
168 }
169 if let Some(last) = args.last_mut() {
170 last.push(c);
171 }
172 }
173 args.into_iter().map(|a| a.trim().to_string()).collect()
174 })
175 .collect()
176}
177
178fn point_of(
179 call: &[String],
180 args: &BTreeMap<String, usize>,
181 map: &BTreeMap<String, BTreeMap<String, String>>,
182) -> Result<Values, String> {
183 let mut values = Values::new();
184 for (param, &i) in args {
185 let raw = call
186 .get(i)
187 .ok_or_else(|| format!("argument {i} missing in {call:?}"))?;
188 let v = match map.get(param) {
189 Some(m) => m
190 .get(raw)
191 .cloned()
192 .ok_or_else(|| format!("argument `{raw}` of `{param}` has no map entry"))?,
193 None => raw.clone(),
194 };
195 values.insert(param.clone(), v);
196 }
197 Ok(values)
198}
199
200#[cfg(test)]
201#[path = "discover_tests.rs"]
202mod discover_tests;