1use super::json::status_name;
3use super::{Report, Status, TraceReason, TraceStep};
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::fmt;
7use elenchus_compiler::{Origin, PlaceholderStatus, Value, kw};
8
9impl fmt::Display for Status {
10 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11 f.write_str(status_name(*self))
12 }
13}
14
15pub(crate) fn premise_tag(o: &Origin) -> String {
17 let name = o.premise.as_deref().unwrap_or("-");
18 alloc::format!("{} ({}) [{}:{}]", name, o.kind, o.source, o.line)
19}
20
21pub(crate) fn trace_line(step: &TraceStep) -> String {
23 let v = match step.value {
24 Value::True => "TRUE",
25 Value::False => "FALSE",
26 };
27 match &step.reason {
28 TraceReason::Asserted(o) => {
29 alloc::format!(
30 "{} = {} [{} {}:{}]",
31 step.atom,
32 v,
33 o.kind,
34 o.source,
35 o.line
36 )
37 }
38 TraceReason::Derived { origin, from } => alloc::format!(
39 "{} = {} from {} ({}) [{}:{}] <= {}",
40 step.atom,
41 v,
42 origin.premise.as_deref().unwrap_or("-"),
43 origin.kind,
44 origin.source,
45 origin.line,
46 from.join(", ")
47 ),
48 }
49}
50
51mod indent {
57 pub const ROOT: usize = 0;
59 pub const SECTION: usize = 2;
62 pub const ITEM: usize = 6;
64 pub const NESTED: usize = 8;
66}
67
68struct ReportWriter<'a, 'b> {
71 f: &'a mut fmt::Formatter<'b>,
72}
73
74impl ReportWriter<'_, '_> {
75 fn line(&mut self, indent: usize, args: fmt::Arguments<'_>) -> fmt::Result {
77 write!(self.f, "{:width$}{}", "", args, width = indent)?;
78 self.f.write_str("\n")
79 }
80
81 fn tail(&mut self, indent: usize, args: fmt::Arguments<'_>) -> fmt::Result {
84 write!(self.f, "{:width$}{}", "", args, width = indent)
85 }
86}
87
88macro_rules! emit {
92 ($out:expr, $indent:expr, $($arg:tt)*) => {
93 $out.line($indent, format_args!($($arg)*))
94 };
95}
96
97impl Report {
98 fn render(&self, f: &mut fmt::Formatter<'_>, show_placeholders: bool) -> fmt::Result {
102 use indent::{ITEM, NESTED, ROOT, SECTION};
103 let mut out = ReportWriter { f };
104
105 emit!(out, ROOT, "RESULT: {}", self.status)?;
106
107 if !self.retract.is_empty() {
111 emit!(out, SECTION, "RETRACT your FACTs and PREMISEs are fine.")?;
115 emit!(
116 out,
117 ITEM,
118 "But these ASSUME guesses cannot all be true together."
119 )?;
120 emit!(out, ITEM, "Remove or flip ONE of them, then check again:")?;
121 for it in &self.retract {
122 emit!(
123 out,
124 ITEM,
125 "ASSUME {} [{}:{}]",
126 it.label,
127 it.origin.source,
128 it.origin.line
129 )?;
130 }
131 } else {
132 for c in &self.conflicts {
133 emit!(out, SECTION, "CONFLICT {}", premise_tag(&c.origin))?;
134 for a in &c.atoms {
135 emit!(out, ITEM, "{}", a)?;
136 }
137 if !c.trace.is_empty() {
138 emit!(out, ITEM, "why:")?;
139 for step in &c.trace {
140 emit!(out, NESTED, "{}", trace_line(step))?;
141 }
142 }
143 }
144 if !self.unsat_core.is_empty() {
145 emit!(
146 out,
147 SECTION,
148 "CORE smallest jointly-unsatisfiable set ({}):",
149 self.unsat_core.len()
150 )?;
151 for it in &self.unsat_core {
152 let name = if it.label.is_empty() { "-" } else { &it.label };
153 emit!(
154 out,
155 NESTED,
156 "{} ({}) [{}:{}]",
157 name,
158 it.origin.kind,
159 it.origin.source,
160 it.origin.line
161 )?;
162 }
163 }
164 }
165
166 let mut shown_fixes: Vec<&str> = Vec::new();
171 for w in &self.warnings {
172 emit!(out, SECTION, "WARNING {}", premise_tag(&w.origin))?;
173 emit!(out, ITEM, "blocked by: {}", w.blocked_by.join(", "))?;
174 if let Some(hint) = &w.hint
175 && !shown_fixes.contains(&hint.as_str())
176 {
177 shown_fixes.push(hint);
178 emit!(out, ITEM, "fix: {hint}")?;
179 }
180 }
181 if let Some(atom) = &self.underdetermined {
182 emit!(out, SECTION, "UNDERDETERMINED an alternative model exists")?;
183 emit!(out, ITEM, "pin it down: add FACT {atom} or NOT {atom}")?;
184 }
185 for d in &self.derived {
186 let v = match d.value {
187 Value::True => "TRUE",
188 Value::False => "FALSE",
189 };
190 emit!(
191 out,
192 SECTION,
193 "DERIVED {} = {} from {}",
194 d.atom,
195 v,
196 premise_tag(&d.origin)
197 )?;
198 }
199 for h in &self.hints {
200 emit!(
201 out,
202 SECTION,
203 "HINT possible typo — '{}' and '{}' look like the same atom ({})",
204 h.a,
205 h.b,
206 h.reason
207 )?;
208 }
209 for o in &self.orphans {
210 let surface = if o.origin.kind == kw::ASSUME && matches!(o.value, Value::False) {
213 alloc::format!("{} {} {}", kw::ASSUME, kw::NOT, o.atom)
214 } else {
215 alloc::format!("{} {}", o.origin.kind, o.atom)
216 };
217 emit!(
218 out,
219 SECTION,
220 "ORPHAN {} — not used by any premise or rule (no effect on the result)",
221 surface
222 )?;
223 }
224 for u in &self.unused_imports {
225 let via = match &u.alias {
226 Some(a) => alloc::format!("{} AS {}", u.domain, a),
227 None => u.domain.clone(),
228 };
229 emit!(
230 out,
231 SECTION,
232 "UNUSED IMPORT {} — imported in {}:{} but never referenced (no effect on the result)",
233 via,
234 u.file,
235 u.line
236 )?;
237 }
238 if show_placeholders {
241 for p in &self.placeholders {
242 match p.status {
243 PlaceholderStatus::Supplied => emit!(
244 out,
245 SECTION,
246 "PARAM {} = {} (supplied{})",
247 p.key,
248 bool_word(p.value),
249 p.origin
250 .as_deref()
251 .map(|o| alloc::format!(": {o}"))
252 .unwrap_or_default()
253 )?,
254 PlaceholderStatus::DefaultUsed => emit!(
255 out,
256 SECTION,
257 "PARAM {} = {} (DEFAULT)",
258 p.key,
259 bool_word(p.value)
260 )?,
261 PlaceholderStatus::Unset => emit!(
262 out,
263 SECTION,
264 "PARAM {} = UNKNOWN (no value supplied, no DEFAULT)",
265 p.key
266 )?,
267 }
268 }
269 }
270
271 let underdetermined = usize::from(self.status == Status::Underdetermined);
272 emit!(
273 out,
274 ROOT,
275 "SUMMARY: {} conflicts, {} underdetermined, {} warnings, {} derived",
276 self.conflicts.len(),
277 underdetermined,
278 self.warnings.len(),
279 self.derived.len()
280 )?;
281 out.tail(ROOT, format_args!("EXIT_CODE: {}", self.exit_code()))
282 }
283
284 pub fn render_human(&self, show_placeholders: bool) -> String {
288 alloc::format!(
289 "{}",
290 HumanReport {
291 report: self,
292 show_placeholders
293 }
294 )
295 }
296}
297
298struct HumanReport<'a> {
301 report: &'a Report,
302 show_placeholders: bool,
303}
304
305impl fmt::Display for HumanReport<'_> {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 self.report.render(f, self.show_placeholders)
308 }
309}
310
311impl fmt::Display for Report {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 self.render(f, true)
314 }
315}
316
317pub(crate) fn bool_word(v: Option<bool>) -> &'static str {
320 match v {
321 Some(true) => "true",
322 Some(false) => "false",
323 None => "UNKNOWN",
324 }
325}