1use std::fmt;
2use std::sync::OnceLock;
3
4use crate::dag::{
5 Dag, DagLike, InternalSharing, MaxSharing, NoSharing, PostOrderIterItem, SharingTracker,
6};
7use crate::encode;
8use crate::node::{Inner, Marker, Node};
9use crate::BitWriter;
10
11pub struct Display<'n, M: Marker> {
14 node: &'n Node<M>,
15 #[cfg_attr(not(feature = "base64"), allow(dead_code))]
16 prog_bytes: OnceLock<Vec<u8>>,
17 wit_bytes: OnceLock<Vec<u8>>,
18}
19
20impl<'n, M: Marker> From<&'n Node<M>> for Display<'n, M> {
21 fn from(node: &'n Node<M>) -> Self {
22 Self {
29 node,
30 prog_bytes: OnceLock::new(),
31 wit_bytes: OnceLock::new(),
32 }
33 }
34}
35
36impl<'n, M: Marker> Display<'n, M> {
37 #[cfg(feature = "base64")]
39 pub fn program(&self) -> impl fmt::Display + '_ {
40 use crate::base64::display::Base64Display;
41 use crate::base64::engine::general_purpose;
42
43 let prog_bytes = self
44 .prog_bytes
45 .get_or_init(|| self.node.to_vec_without_witness());
46 Base64Display::new(prog_bytes, &general_purpose::STANDARD)
47 }
48}
49
50impl<'n, M: Marker<Witness = crate::Value>> Display<'n, M> {
51 pub fn witness(&self) -> impl fmt::Display + '_ {
53 use crate::hex::DisplayHex;
54
55 let wit_bytes = self.wit_bytes.get_or_init(|| {
56 let mut wit_v = vec![];
57 let mut witness = BitWriter::new(&mut wit_v);
58 let sharing_iter = self.node.post_order_iter::<MaxSharing<M>>();
59
60 encode::encode_witness(sharing_iter.into_witnesses(), &mut witness)
61 .expect("Vec::write is infallible");
62 witness.flush_all().expect("Vec::write is infallible");
63
64 wit_v
65 });
66
67 wit_bytes.as_hex()
68 }
69}
70
71pub struct DisplayExpr<'a, M: Marker>(&'a Node<M>);
102
103impl<'a, M: Marker> From<&'a Node<M>> for DisplayExpr<'a, M> {
104 fn from(node: &'a Node<M>) -> Self {
105 Self(node)
106 }
107}
108
109impl<'a, M: Marker> fmt::Display for DisplayExpr<'a, M>
110where
111 &'a Node<M>: DagLike,
112{
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 for data in self.0.verbose_pre_order_iter::<NoSharing>(None) {
115 match data.n_children_yielded {
116 1 => match data.node.inner() {
117 Inner::Comp(..) => f.write_str("; ")?,
118 Inner::Pair(..) => f.write_str(" & ")?,
119 Inner::Case(..) => f.write_str(") (")?,
120 x => debug_assert!(matches!(x.as_dag(), Dag::Unary(..))),
121 },
122 2 => {
123 debug_assert!(matches!(data.node.inner().as_dag(), Dag::Binary(..)));
124 if data.parent.is_some() {
125 f.write_str(")")?;
126 } else {
127 }
129 }
130 n => {
131 debug_assert!(n == 0, "Combinators are nullary, unary or binary");
132 match data.node.inner() {
133 Inner::Iden
134 if matches!(
135 data.parent.map(Node::inner),
136 Some(Inner::Take(..) | Inner::Drop(..))
137 ) =>
138 {
139 f.write_str("H")?
140 }
141 Inner::Take(child) if is_take_drop_iden(child) => f.write_str("O")?,
142 Inner::Drop(child) if is_take_drop_iden(child) => f.write_str("I")?,
143 Inner::Iden => f.write_str("iden")?,
144 Inner::Take(..) => f.write_str("take ")?,
145 Inner::Drop(..) => f.write_str("drop ")?,
146 Inner::Unit
147 if matches!(
148 data.parent.map(Node::inner),
149 Some(Inner::InjL(..) | Inner::InjR(..))
150 ) => {} Inner::InjL(child) if matches!(child.inner(), Inner::Unit) => {
152 f.write_str("false")?
153 }
154 Inner::InjR(child) if matches!(child.inner(), Inner::Unit) => {
155 f.write_str("true")?
156 }
157 Inner::Unit => f.write_str("unit")?,
158 Inner::InjL(..) => f.write_str("injl ")?,
159 Inner::InjR(..) => f.write_str("injr ")?,
160 Inner::Comp(..) | Inner::Pair(..) => {} Inner::Case(..) => f.write_str("case ")?,
162 Inner::AssertL(..) => f.write_str("assertl ")?,
163 Inner::AssertR(..) => f.write_str("assertr ")?,
164 Inner::Disconnect(..) => f.write_str("disconnect ")?,
165 Inner::Witness(..) => f.write_str("witness ")?,
166 Inner::Fail(..) => f.write_str("fail")?,
167 Inner::Jet(jet) => write!(f, "jet_{jet} ")?,
168 Inner::Word(value) => write!(f, "const {value} ")?,
169 }
170
171 match data.node.inner().as_dag() {
172 Dag::Binary(..) if data.parent.is_some() => f.write_str("(")?,
173 _ => {} }
175 }
176 };
177 }
178
179 Ok(())
180 }
181}
182
183fn is_take_drop_iden<M: Marker>(node: &Node<M>) -> bool {
184 for node in node.pre_order_iter::<InternalSharing>() {
185 match node.inner() {
186 Inner::Take(..) | Inner::Drop(..) | Inner::Iden => {}
187 _ => return false,
188 }
189 }
190 true
191}
192
193impl<'a, M: Marker> fmt::Debug for DisplayExpr<'a, M>
194where
195 &'a Node<M>: DagLike,
196{
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 fmt::Display::fmt(self, f)
199 }
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
204pub enum GraphFormat {
205 Dot,
207 Mermaid,
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub enum SharingLevel {
214 None,
216 Internal,
218 Max,
220}
221
222pub struct DisplayAsGraph<'a, M: Marker> {
230 node: &'a Node<M>,
231 pub format: GraphFormat,
233 pub sharing: SharingLevel,
235}
236
237impl<'a, M: Marker> DisplayAsGraph<'a, M> {
238 pub fn new(node: &'a Node<M>, format: GraphFormat, sharing: SharingLevel) -> Self {
240 Self {
241 node,
242 format,
243 sharing,
244 }
245 }
246
247 pub fn to_dot_string(&self) -> String
249 where
250 &'a Node<M>: DagLike,
251 {
252 let mut result = String::new();
253 match self.render(GraphFormat::Dot, &mut result) {
254 Ok(_) => result,
255 Err(e) => format!("Could not display as string: {}", e),
256 }
257 }
258
259 pub fn to_mermaid_string(&self) -> String
261 where
262 &'a Node<M>: DagLike,
263 {
264 let mut result = String::new();
265 match self.render(GraphFormat::Mermaid, &mut result) {
266 Ok(_) => result,
267 Err(e) => format!("Could not display as string: {}", e),
268 }
269 }
270
271 fn render<W: fmt::Write>(&self, graph_format: GraphFormat, w: &mut W) -> fmt::Result
272 where
273 &'a Node<M>: DagLike,
274 {
275 match self.sharing {
276 SharingLevel::None => self.render_with::<NoSharing, _>(graph_format, w),
277 SharingLevel::Internal => self.render_with::<InternalSharing, _>(graph_format, w),
278 SharingLevel::Max => self.render_with::<MaxSharing<M>, _>(graph_format, w),
279 }
280 }
281
282 fn render_with<S, W>(&self, graph_format: GraphFormat, w: &mut W) -> fmt::Result
283 where
284 S: SharingTracker<&'a Node<M>> + Default,
285 W: fmt::Write,
286 {
287 let node_label = |data: &PostOrderIterItem<&Node<M>>| -> String {
288 match data.node.inner() {
289 Inner::Witness(_) => format!("witness({})", data.index),
290 Inner::Word(word) => format!("word({})", shorten(word.to_string(), 12)),
291 _ => data.node.inner().to_string(),
292 }
293 };
294
295 match graph_format {
296 GraphFormat::Dot => {
297 writeln!(w, "digraph G {{")?;
298 writeln!(w, "ordering=\"out\";")?;
299 for data in self.node.post_order_iter::<S>() {
300 writeln!(w, " node{}[label=\"{}\"];", data.index, node_label(&data))?;
301 if let Some(left) = data.left_index {
302 writeln!(w, " node{}->node{};", data.index, left)?;
303 }
304 if let Some(right) = data.right_index {
305 writeln!(w, " node{}->node{};", data.index, right)?;
306 }
307 }
308 writeln!(w, "}}")?;
309 }
310 GraphFormat::Mermaid => {
311 writeln!(w, "flowchart TD")?;
312 for data in self.node.post_order_iter::<S>() {
313 match data.node.inner() {
314 Inner::Case(..) => {
315 writeln!(w, " node{}{{\"{}\"}}", data.index, node_label(&data))?;
316 }
317 _ => {
318 writeln!(w, " node{}[\"{}\"]", data.index, node_label(&data))?;
319 }
320 }
321
322 if let Some(left) = data.left_index {
323 writeln!(w, " node{} --> node{}", data.index, left)?;
324 }
325 if let Some(right) = data.right_index {
326 writeln!(w, " node{} --> node{}", data.index, right)?;
327 }
328 }
329 }
330 }
331
332 Ok(())
333 }
334}
335
336impl<'a, M: Marker> fmt::Display for DisplayAsGraph<'a, M>
337where
338 &'a Node<M>: DagLike,
339{
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 self.render(self.format, f)
342 }
343}
344
345fn shorten<S: AsRef<str>>(s: S, max_len: usize) -> String {
346 let s = s.as_ref();
347 let chars: Vec<char> = s.chars().collect();
348 if chars.len() <= max_len {
349 s.to_string()
350 } else {
351 let dots = "...";
352 let available = max_len.saturating_sub(dots.len());
353 let start_len = available.div_ceil(2); let end_len = available / 2;
355
356 let start: String = chars[..start_len].iter().collect();
357 let end: String = chars[chars.len() - end_len..].iter().collect();
358 format!("{}{}{}", start, dots, end)
359 }
360}
361
362#[cfg(all(test, feature = "human_encoding"))]
363mod tests {
364 use crate::human_encoding::Forest;
365 use crate::jet::Core;
366 use crate::types;
367 use crate::RedeemNode;
368 use std::collections::HashMap;
369 use std::sync::Arc;
370
371 fn parse_program(s: &str) -> Arc<RedeemNode> {
372 types::Context::with_context(|ctx| {
373 let empty_witness = HashMap::new();
374 Forest::parse::<Core>(s)
375 .unwrap()
376 .to_witness_node(&ctx, &empty_witness)
377 .unwrap()
378 .finalize_unpruned()
379 .unwrap()
380 })
381 }
382
383 #[test]
384 fn display_boolean() {
385 let s = "
386 false := injl unit
387 true := injr unit
388 main := comp pair false true unit";
389 let program = parse_program(s);
390 assert_eq!("(false & true); unit", program.display_expr().to_string())
391 }
392
393 #[test]
394 fn display_oih() {
395 let s = "
396 oih := take drop iden
397 input := pair (pair unit unit) unit
398 output := unit
399 main := comp input (comp (pair oih (take unit)) output)";
400 let program = parse_program(s);
401 assert_eq!(
402 "((unit & unit) & unit); ((OIH & take unit); unit)",
403 program.display_expr().to_string()
404 )
405 }
406
407 #[test]
408 fn display_as_dot() {
409 let s = "
410 oih := take drop iden
411 input := pair (pair unit unit) unit
412 output := unit
413 main := comp input (comp (pair oih (take unit)) output)";
414 let program = parse_program(s);
415 let str = program
416 .display_as_dot()
417 .to_string()
418 .replace(" ", "")
419 .replace("\n", "");
420 let expected = "
421 digraph G {
422ordering=\"out\";
423 node0[label=\"unit\"];
424 node1[label=\"unit\"];
425 node2[label=\"pair\"];
426 node2->node0;
427 node2->node1;
428 node3[label=\"unit\"];
429 node4[label=\"pair\"];
430 node4->node2;
431 node4->node3;
432 node5[label=\"iden\"];
433 node6[label=\"drop\"];
434 node6->node5;
435 node7[label=\"take\"];
436 node7->node6;
437 node8[label=\"unit\"];
438 node9[label=\"take\"];
439 node9->node8;
440 node10[label=\"pair\"];
441 node10->node7;
442 node10->node9;
443 node11[label=\"unit\"];
444 node12[label=\"comp\"];
445 node12->node10;
446 node12->node11;
447 node13[label=\"comp\"];
448 node13->node4;
449 node13->node12;
450}"
451 .replace(" ", "")
452 .replace("\n", "");
453
454 assert_eq!(str, expected);
455 }
456}