1use crate::computation::{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.value.as_str();
113 let line = if value == "true" {
114 cause.condition.clone()
115 } else {
116 format!("{} is {}", cause.condition, value)
117 };
118 self.push_line(connector, &line);
119 let child_indent = self.child_indent(connector);
120 let mut child_ctx = FormatContext {
121 lines: self.lines,
122 indent: child_indent,
123 };
124 child_ctx.render_nodes(&cause.children, None);
125 }
126
127 if body_shown {
128 self.push_line(Connector::Last, body);
129 let child_indent = self.child_indent(Connector::Last);
130 let mut child_ctx = FormatContext {
131 lines: self.lines,
132 indent: child_indent,
133 };
134 child_ctx.render_nodes(children, Some(body));
135 } else if !children.is_empty() {
136 self.render_nodes(children, None);
137 }
138 }
139
140 fn render_nodes(&mut self, nodes: &[ExplanationNode], parent_body: Option<&str>) {
141 let len = nodes.len();
142 for (i, node) in nodes.iter().enumerate() {
143 let connector = if i + 1 == len {
144 Connector::Last
145 } else {
146 Connector::Branch
147 };
148 self.render_node(node, connector, parent_body);
149 }
150 }
151
152 fn render_conversion_contents(
153 &mut self,
154 steps: &[SerializedConversionTraceStep],
155 operands: &[ExplanationNode],
156 ) {
157 let total = steps.len() + operands.len();
158 let mut index = 0;
159 for step in steps {
160 index += 1;
161 let connector = if index == total {
162 Connector::Last
163 } else {
164 Connector::Branch
165 };
166 self.push_line(connector, &step.text);
167 }
168 for operand in operands {
169 index += 1;
170 let connector = if index == total {
171 Connector::Last
172 } else {
173 Connector::Branch
174 };
175 self.render_node(operand, connector, None);
176 }
177 }
178
179 fn render_node(
180 &mut self,
181 node: &ExplanationNode,
182 connector: Connector,
183 parent_body: Option<&str>,
184 ) {
185 match node {
186 ExplanationNode::Rule {
187 name,
188 result,
189 body,
190 causes,
191 children,
192 } => {
193 let result_str = result
194 .as_deref()
195 .expect("BUG: ExplanationNode::Rule.result not filled by eval");
196 self.push_line(connector, &format!("{}: {result_str}", name.rule));
197 let child_indent = self.child_indent(connector);
198 let mut child_ctx = FormatContext {
199 lines: self.lines,
200 indent: child_indent,
201 };
202 child_ctx.render_rule_contents(result_str, body, causes, children);
203 }
204 ExplanationNode::Compose {
205 expression,
206 operands,
207 } => {
208 if parent_body.is_some_and(|body| body == expression) {
209 self.render_nodes(operands, None);
210 } else {
211 self.push_line(connector, expression);
212 let child_indent = self.child_indent(connector);
213 let mut child_ctx = FormatContext {
214 lines: self.lines,
215 indent: child_indent,
216 };
217 child_ctx.render_nodes(operands, None);
218 }
219 }
220 ExplanationNode::Data { name, display } => {
221 if name.data.is_empty() {
222 self.push_line(connector, display);
223 } else {
224 self.push_line(connector, &format!("{name}: {display}"));
225 }
226 }
227 ExplanationNode::DataUnused { name } => {
228 self.push_line(connector, &name.to_string());
229 }
230 ExplanationNode::Conversion {
231 expression,
232 steps,
233 operands,
234 } => {
235 let expression_is_parent_body = parent_body.is_some_and(|body| body == expression);
236 if expression_is_parent_body {
237 let steps_without_outcome: Vec<SerializedConversionTraceStep> = steps
238 .iter()
239 .filter(|step| !matches!(step.role, ConversionTraceRole::Outcome))
240 .cloned()
241 .collect();
242 self.render_conversion_contents(&steps_without_outcome, operands);
243 } else {
244 self.push_line(connector, expression);
245 let child_indent = self.child_indent(connector);
246 let mut child_ctx = FormatContext {
247 lines: self.lines,
248 indent: child_indent,
249 };
250 child_ctx.render_conversion_contents(steps, operands);
251 }
252 }
253 ExplanationNode::Veto { message } => {
254 let text = match message.as_deref() {
255 Some(msg) if !msg.is_empty() => format!("veto \"{msg}\""),
256 _ => "veto".to_string(),
257 };
258 self.push_line(connector, &text);
259 }
260 ExplanationNode::Piecewise { .. } => {
261 unreachable!("BUG: Piecewise must be lowered before format")
262 }
263 }
264 }
265}
266
267fn connector_str(connector: Connector) -> &'static str {
268 match connector {
269 Connector::Branch => "├─",
270 Connector::Last => "└─",
271 }
272}