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