1use crate::computation::{OperationResult, VetoType};
9use crate::planning::semantics::{LemmaType, RulePath};
10use serde::Serialize;
11use std::sync::Arc;
12
13pub use crate::planning::explanation::{
15 Cause, ConversionTraceRole, ExplanationNode, SerializedConversionTraceStep,
16};
17
18#[derive(Debug, Clone)]
19pub struct Explanation {
20 pub name: RulePath,
21 pub result: OperationResult,
22 pub result_type: Arc<LemmaType>,
24 pub body: String,
25 pub causes: Vec<Cause>,
26 pub children: Vec<ExplanationNode>,
27}
28
29impl Serialize for Explanation {
30 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
31 where
32 S: serde::Serializer,
33 {
34 ExplanationNode::Rule {
35 name: self.name.clone(),
36 result: Some(format_operation_result(
37 &self.result,
38 self.result_type.as_ref(),
39 )),
40 body: self.body.clone(),
41 causes: self.causes.clone(),
42 children: self.children.clone(),
43 }
44 .serialize(serializer)
45 }
46}
47
48pub(crate) fn format_operation_result(result: &OperationResult, result_type: &LemmaType) -> String {
49 match result {
50 OperationResult::Value(value) => value.display_value_with_type(result_type),
51 OperationResult::Veto(VetoType::UserDefined { message: None }) => String::new(),
52 OperationResult::Veto(veto) => veto.to_string(),
53 }
54}
55
56pub fn format_explanation(explanation: &Explanation) -> String {
57 let mut lines = Vec::new();
58 let result_display =
59 format_operation_result(&explanation.result, explanation.result_type.as_ref());
60 lines.push(format!("{}: {}", explanation.name.rule, result_display));
61 let mut ctx = FormatContext {
62 lines: &mut lines,
63 indent: String::new(),
64 };
65 ctx.render_rule_contents(
66 &result_display,
67 &explanation.body,
68 &explanation.causes,
69 &explanation.children,
70 );
71 lines.join("\n")
72}
73
74#[derive(Copy, Clone)]
75enum Connector {
76 Branch,
77 Last,
78}
79
80struct FormatContext<'a> {
81 lines: &'a mut Vec<String>,
82 indent: String,
83}
84
85impl<'a> FormatContext<'a> {
86 fn push_line(&mut self, connector: Connector, text: &str) {
87 self.lines.push(format!(
88 "{}{} {text}",
89 self.indent,
90 connector_str(connector)
91 ));
92 }
93
94 fn child_indent(&self, connector: Connector) -> String {
95 match connector {
96 Connector::Branch => format!("{}│ ", self.indent),
97 Connector::Last => format!("{} ", self.indent),
98 }
99 }
100
101 fn render_rule_contents(
102 &mut self,
103 result_display: &str,
104 body: &str,
105 causes: &[Cause],
106 children: &[ExplanationNode],
107 ) {
108 let body_shown = !body.is_empty() && body != result_display;
109 let total = causes.len() + usize::from(body_shown);
110 let mut index = 0;
111
112 for cause in causes {
113 index += 1;
114 let connector = if index == total {
115 Connector::Last
116 } else {
117 Connector::Branch
118 };
119 let value = cause.value.as_str();
120 let line = if value == "true" {
121 cause.condition.clone()
122 } else {
123 format!("{} is {}", cause.condition, value)
124 };
125 self.push_line(connector, &line);
126 let child_indent = self.child_indent(connector);
127 let mut child_ctx = FormatContext {
128 lines: self.lines,
129 indent: child_indent,
130 };
131 child_ctx.render_nodes(&cause.children, None);
132 }
133
134 if body_shown {
135 self.push_line(Connector::Last, body);
136 let child_indent = self.child_indent(Connector::Last);
137 let mut child_ctx = FormatContext {
138 lines: self.lines,
139 indent: child_indent,
140 };
141 child_ctx.render_nodes(children, Some(body));
142 } else if !children.is_empty() {
143 self.render_nodes(children, None);
144 }
145 }
146
147 fn render_nodes(&mut self, nodes: &[ExplanationNode], parent_body: Option<&str>) {
148 let len = nodes.len();
149 for (i, node) in nodes.iter().enumerate() {
150 let connector = if i + 1 == len {
151 Connector::Last
152 } else {
153 Connector::Branch
154 };
155 self.render_node(node, connector, parent_body);
156 }
157 }
158
159 fn render_conversion_contents(
160 &mut self,
161 steps: &[SerializedConversionTraceStep],
162 operands: &[ExplanationNode],
163 ) {
164 let total = steps.len() + operands.len();
165 let mut index = 0;
166 for step in steps {
167 index += 1;
168 let connector = if index == total {
169 Connector::Last
170 } else {
171 Connector::Branch
172 };
173 self.push_line(connector, &step.text);
174 }
175 for operand in operands {
176 index += 1;
177 let connector = if index == total {
178 Connector::Last
179 } else {
180 Connector::Branch
181 };
182 self.render_node(operand, connector, None);
183 }
184 }
185
186 fn render_node(
187 &mut self,
188 node: &ExplanationNode,
189 connector: Connector,
190 parent_body: Option<&str>,
191 ) {
192 match node {
193 ExplanationNode::Rule {
194 name,
195 result,
196 body,
197 causes,
198 children,
199 } => {
200 let result_str = result
201 .as_deref()
202 .expect("BUG: ExplanationNode::Rule.result not filled by eval");
203 self.push_line(connector, &format!("{}: {result_str}", name.rule));
204 let child_indent = self.child_indent(connector);
205 let mut child_ctx = FormatContext {
206 lines: self.lines,
207 indent: child_indent,
208 };
209 child_ctx.render_rule_contents(result_str, body, causes, children);
210 }
211 ExplanationNode::Compose {
212 expression,
213 operands,
214 } => {
215 if parent_body.is_some_and(|body| body == expression) {
216 self.render_nodes(operands, None);
217 } else {
218 self.push_line(connector, expression);
219 let child_indent = self.child_indent(connector);
220 let mut child_ctx = FormatContext {
221 lines: self.lines,
222 indent: child_indent,
223 };
224 child_ctx.render_nodes(operands, None);
225 }
226 }
227 ExplanationNode::Data { name, display } => {
228 if name.data.is_empty() {
229 self.push_line(connector, display);
230 } else {
231 self.push_line(connector, &format!("{name}: {display}"));
232 }
233 }
234 ExplanationNode::DataUnused { name } => {
235 self.push_line(connector, &name.to_string());
236 }
237 ExplanationNode::Conversion {
238 expression,
239 steps,
240 operands,
241 } => {
242 let expression_is_parent_body = parent_body.is_some_and(|body| body == expression);
243 if expression_is_parent_body {
244 let steps_without_outcome: Vec<SerializedConversionTraceStep> = steps
245 .iter()
246 .filter(|step| !matches!(step.role, ConversionTraceRole::Outcome))
247 .cloned()
248 .collect();
249 self.render_conversion_contents(&steps_without_outcome, operands);
250 } else {
251 self.push_line(connector, expression);
252 let child_indent = self.child_indent(connector);
253 let mut child_ctx = FormatContext {
254 lines: self.lines,
255 indent: child_indent,
256 };
257 child_ctx.render_conversion_contents(steps, operands);
258 }
259 }
260 ExplanationNode::Veto { message } => {
261 let text = match message.as_deref() {
262 Some(msg) if !msg.is_empty() => format!("veto \"{msg}\""),
263 _ => "veto".to_string(),
264 };
265 self.push_line(connector, &text);
266 }
267 ExplanationNode::Piecewise { .. } => {
268 unreachable!("BUG: Piecewise must be lowered before format")
269 }
270 }
271 }
272}
273
274fn connector_str(connector: Connector) -> &'static str {
275 match connector {
276 Connector::Branch => "├─",
277 Connector::Last => "└─",
278 }
279}