1use super::types::{
6 CallSiteInfo, DiffInfo, EquationBinding, ProjectCoverage, ProofStatusInfo, QueryOutput,
7 QueryResult, ScoreInfo, ViolationInfo,
8};
9
10impl QueryResult {
11 pub(crate) fn fmt_enrichment(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 fmt_score(f, self.score.as_ref())?;
13 fmt_proof_status(f, self.proof_status.as_ref())?;
14 fmt_bindings(f, &self.bindings)?;
15 fmt_diff(f, self.diff.as_ref())?;
16 fmt_pagerank(f, self.pagerank)?;
17 fmt_call_sites(f, &self.call_sites)?;
18 fmt_violations(f, &self.violations)?;
19 fmt_coverage_map(f, &self.coverage_map)?;
20 Ok(())
21 }
22
23 pub(crate) fn to_markdown_item(&self) -> String {
24 use crate::schema::ContractKind;
25 let mut out = format!("### {}. {}\n\n", self.rank, self.stem);
26 out.push_str(&format!("- **Relevance:** {:.2}\n", self.relevance));
27 if self.kind != ContractKind::Kernel {
28 out.push_str(&format!("- **Kind:** {}\n", self.kind));
29 }
30 if !self.equations.is_empty() {
31 out.push_str(&format!("- **Equations:** {}\n", self.equations.join(", ")));
32 }
33 out.push_str(&format!("- **Obligations:** {}\n", self.obligation_count));
34 md_score(&mut out, self.score.as_ref());
35 md_proof_status(&mut out, self.proof_status.as_ref());
36 md_bindings(&mut out, &self.bindings);
37 md_diff(&mut out, self.diff.as_ref());
38 md_pagerank(&mut out, self.pagerank);
39 md_call_sites(&mut out, &self.call_sites);
40 md_violations(&mut out, &self.violations);
41 md_coverage_map(&mut out, &self.coverage_map);
42 if !self.references.is_empty() {
43 out.push_str(&format!("- **Papers:** {}\n", self.references.join("; ")));
44 }
45 if !self.depends_on.is_empty() {
46 out.push_str(&format!(
47 "- **Depends on:** {}\n",
48 self.depends_on.join(", ")
49 ));
50 }
51 if !self.depended_by.is_empty() {
52 out.push_str(&format!(
53 "- **Depended by:** {}\n",
54 self.depended_by.join(", ")
55 ));
56 }
57 out.push('\n');
58 out
59 }
60}
61
62impl std::fmt::Display for QueryResult {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 use crate::schema::ContractKind;
65 let kind_tag = if self.kind == ContractKind::Kernel {
66 String::new()
67 } else {
68 format!(" [{}]", self.kind)
69 };
70 writeln!(
71 f,
72 "[{}] {}{} (relevance: {:.2})",
73 self.rank, self.stem, kind_tag, self.relevance
74 )?;
75 writeln!(f, " {}", self.description)?;
76 if !self.equations.is_empty() {
77 writeln!(f, " Equations: {}", self.equations.join(", "))?;
78 }
79 writeln!(f, " Obligations: {}", self.obligation_count)?;
80 if !self.references.is_empty() {
81 writeln!(f, " Papers: {}", self.references.join("; "))?;
82 }
83 self.fmt_enrichment(f)?;
84 if !self.depends_on.is_empty() {
85 writeln!(f, " Depends on: {}", self.depends_on.join(", "))?;
86 }
87 if !self.depended_by.is_empty() {
88 writeln!(f, " Depended by: {}", self.depended_by.join(", "))?;
89 }
90 Ok(())
91 }
92}
93
94impl QueryOutput {
95 pub fn to_markdown(&self) -> String {
97 let mut out = format!("## Query: \"{}\"\n\n", self.query);
98 for r in &self.results {
99 out.push_str(&r.to_markdown_item());
100 }
101 out.push_str(&format!("*{} matches*\n", self.total_matches));
102 out
103 }
104}
105
106impl std::fmt::Display for QueryOutput {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 for r in &self.results {
109 write!(f, "{r}")?;
110 writeln!(f, " ---")?;
111 }
112 writeln!(f, "\n{} matches for \"{}\"", self.total_matches, self.query)
113 }
114}
115
116fn fmt_score(f: &mut std::fmt::Formatter<'_>, score: Option<&ScoreInfo>) -> std::fmt::Result {
119 let Some(s) = score else { return Ok(()) };
120 writeln!(f, " Score: {:.2} (Grade {})", s.composite, s.grade)?;
121 writeln!(
122 f,
123 " Spec: {:.2} | Falsify: {:.2} | Kani: {:.2} | Lean: {:.2} | Bind: {:.2}",
124 s.spec_depth, s.falsification, s.kani, s.lean, s.binding
125 )
126}
127
128fn fmt_proof_status(
129 f: &mut std::fmt::Formatter<'_>,
130 ps: Option<&ProofStatusInfo>,
131) -> std::fmt::Result {
132 let Some(ps) = ps else { return Ok(()) };
133 writeln!(
134 f,
135 " Proof Level: {} (ob:{} ft:{} kani:{} lean:{})",
136 ps.level, ps.obligations, ps.falsification_tests, ps.kani_harnesses, ps.lean_proved
137 )
138}
139
140fn fmt_bindings(f: &mut std::fmt::Formatter<'_>, bindings: &[EquationBinding]) -> std::fmt::Result {
141 if bindings.is_empty() {
142 return Ok(());
143 }
144 writeln!(f, " Bindings:")?;
145 for b in bindings {
146 let loc = b.module_path.as_deref().unwrap_or("unbound");
147 writeln!(f, " {}: {} ({})", b.equation, b.status, loc)?;
148 }
149 Ok(())
150}
151
152fn fmt_diff(f: &mut std::fmt::Formatter<'_>, diff: Option<&DiffInfo>) -> std::fmt::Result {
153 let Some(d) = diff else { return Ok(()) };
154 writeln!(
155 f,
156 " Last modified: {} ({} days ago, {})",
157 d.last_modified,
158 d.days_ago,
159 &d.commit_hash[..7.min(d.commit_hash.len())]
160 )
161}
162
163fn fmt_pagerank(f: &mut std::fmt::Formatter<'_>, pr: Option<f64>) -> std::fmt::Result {
164 let Some(pr) = pr else { return Ok(()) };
165 writeln!(f, " PageRank: {pr:.4}")
166}
167
168fn fmt_call_sites(f: &mut std::fmt::Formatter<'_>, sites: &[CallSiteInfo]) -> std::fmt::Result {
169 if sites.is_empty() {
170 return Ok(());
171 }
172 writeln!(
173 f,
174 " Call sites ({} projects):",
175 count_unique_projects(sites)
176 )?;
177 for cs in sites {
178 let eq = cs.equation.as_deref().unwrap_or("");
179 writeln!(f, " {}/{}:{} {eq}", cs.project, cs.file, cs.line)?;
180 }
181 Ok(())
182}
183
184fn fmt_violations(
185 f: &mut std::fmt::Formatter<'_>,
186 violations: &[ViolationInfo],
187) -> std::fmt::Result {
188 if violations.is_empty() {
189 return Ok(());
190 }
191 writeln!(f, " Violations ({}):", violations.len())?;
192 for v in violations {
193 writeln!(f, " {}: {} — {}", v.project, v.kind, v.detail)?;
194 }
195 Ok(())
196}
197
198fn fmt_coverage_map(f: &mut std::fmt::Formatter<'_>, map: &[ProjectCoverage]) -> std::fmt::Result {
199 if map.is_empty() {
200 return Ok(());
201 }
202 writeln!(f, " Coverage map:")?;
203 for c in map {
204 let bar = coverage_bar(c.binding_implemented, c.binding_total);
205 writeln!(
206 f,
207 " {:<12} {bar} ({} sites, {}/{} bound)",
208 c.project, c.call_sites, c.binding_implemented, c.binding_total
209 )?;
210 }
211 Ok(())
212}
213
214fn md_score(out: &mut String, score: Option<&ScoreInfo>) {
217 let Some(s) = score else { return };
218 out.push_str(&format!(
219 "- **Score:** {:.2} (Grade {})\n",
220 s.composite, s.grade
221 ));
222 out.push_str(&format!(
223 "- Spec: {:.2} | Falsify: {:.2} | Kani: {:.2} | Lean: {:.2} | Bind: {:.2}\n",
224 s.spec_depth, s.falsification, s.kani, s.lean, s.binding
225 ));
226}
227
228fn md_proof_status(out: &mut String, ps: Option<&ProofStatusInfo>) {
229 let Some(ps) = ps else { return };
230 out.push_str(&format!(
231 "- **Proof Level:** {} (ob:{} ft:{} kani:{} lean:{})\n",
232 ps.level, ps.obligations, ps.falsification_tests, ps.kani_harnesses, ps.lean_proved
233 ));
234}
235
236fn md_bindings(out: &mut String, bindings: &[EquationBinding]) {
237 if bindings.is_empty() {
238 return;
239 }
240 out.push_str("- **Bindings:**\n");
241 for b in bindings {
242 let loc = b.module_path.as_deref().unwrap_or("unbound");
243 out.push_str(&format!(" - `{}`: {} (`{}`)\n", b.equation, b.status, loc));
244 }
245}
246
247fn md_diff(out: &mut String, diff: Option<&DiffInfo>) {
248 let Some(d) = diff else { return };
249 out.push_str(&format!(
250 "- **Last modified:** {} ({} days ago)\n",
251 d.last_modified, d.days_ago
252 ));
253}
254
255fn md_pagerank(out: &mut String, pr: Option<f64>) {
256 let Some(pr) = pr else { return };
257 out.push_str(&format!("- **PageRank:** {pr:.4}\n"));
258}
259
260fn md_call_sites(out: &mut String, sites: &[CallSiteInfo]) {
261 if sites.is_empty() {
262 return;
263 }
264 out.push_str(&format!(
265 "- **Call sites** ({} projects):\n",
266 count_unique_projects(sites)
267 ));
268 for cs in sites {
269 let eq = cs
270 .equation
271 .as_deref()
272 .map_or(String::new(), |e| format!(" eq={e}"));
273 out.push_str(&format!(
274 " - `{}/{}:{}`{eq}\n",
275 cs.project, cs.file, cs.line
276 ));
277 }
278}
279
280fn md_violations(out: &mut String, violations: &[ViolationInfo]) {
281 if violations.is_empty() {
282 return;
283 }
284 out.push_str(&format!("- **Violations ({}):**\n", violations.len()));
285 for v in violations {
286 out.push_str(&format!(" - `{}`: {} — {}\n", v.project, v.kind, v.detail));
287 }
288}
289
290fn md_coverage_map(out: &mut String, map: &[ProjectCoverage]) {
291 if map.is_empty() {
292 return;
293 }
294 out.push_str("- **Coverage map:**\n");
295 for c in map {
296 let pct = if c.binding_total > 0 {
297 #[allow(clippy::cast_precision_loss)]
298 {
299 c.binding_implemented as f64 / c.binding_total as f64 * 100.0
300 }
301 } else {
302 0.0
303 };
304 out.push_str(&format!(
305 " - `{}`: {:.0}% ({}/{} bound, {} sites)\n",
306 c.project, pct, c.binding_implemented, c.binding_total, c.call_sites
307 ));
308 }
309}
310
311pub(crate) fn coverage_bar(implemented: usize, total: usize) -> String {
314 if total == 0 {
315 return "--".to_string();
316 }
317 let filled = (implemented * 6 + total / 2) / total.max(1);
318 let filled = filled.min(6);
319 let empty = 6 - filled;
320 format!("{}{}", "█".repeat(filled), "░".repeat(empty))
321}
322
323fn count_unique_projects(sites: &[CallSiteInfo]) -> usize {
324 let mut seen = std::collections::HashSet::new();
325 for s in sites {
326 seen.insert(&s.project);
327 }
328 seen.len()
329}