diff --git a/simpcli/src/main.rs b/simpcli/src/main.rs index 36c0bccd..cb7660a7 100644 --- a/simpcli/src/main.rs +++ b/simpcli/src/main.rs @@ -28,6 +28,7 @@ fn usage(process_name: &str) { eprintln!("Usage:"); eprintln!(" {} assemble ", process_name); eprintln!(" {} disassemble ", process_name); + eprintln!(" {} graph ", process_name); eprintln!(" {} relabel ", process_name); eprintln!(); eprintln!("For commands which take an optional expression, the default value is \"main\"."); @@ -43,6 +44,7 @@ fn invalid_usage(process_name: &str) -> Result<(), String> { enum Command { Assemble, Disassemble, + Graph, Relabel, Help, } @@ -53,6 +55,7 @@ impl FromStr for Command { match s { "assemble" => Ok(Command::Assemble), "disassemble" => Ok(Command::Disassemble), + "graphviz" | "dot" | "graph" => Ok(Command::Graph), "relabel" => Ok(Command::Relabel), "help" => Ok(Command::Help), x => Err(format!("unknown command {}", x)), @@ -65,6 +68,7 @@ impl Command { match *self { Command::Assemble => false, Command::Disassemble => false, + Command::Graph => false, Command::Relabel => false, Command::Help => false, } @@ -155,6 +159,14 @@ fn main() -> Result<(), String> { let prog = Forest::::from_program(commit); println!("{}", prog.string_serialize()); } + Command::Graph => { + let v = simplicity::base64::Engine::decode(&STANDARD, first_arg.as_bytes()) + .map_err(|e| format!("failed to parse base64: {}", e))?; + let iter = BitIter::from(v.into_iter()); + let commit = CommitNode::::decode(iter) + .map_err(|e| format!("failed to decode program: {}", e))?; + println!("{}", commit.display_as_dot()); + } Command::Relabel => { let prog = parse_file(&first_arg)?; println!("{}", prog.string_serialize()); diff --git a/src/node/display.rs b/src/node/display.rs index 42a34a6c..940e7e75 100644 --- a/src/node/display.rs +++ b/src/node/display.rs @@ -197,6 +197,39 @@ where } } +pub struct DisplayAsDot<'a, M: Marker>(&'a Node); + +impl<'a, M: Marker> From<&'a Node> for DisplayAsDot<'a, M> { + fn from(node: &'a Node) -> Self { + Self(node) + } +} + +impl<'a, M: Marker> fmt::Display for DisplayAsDot<'a, M> +where + &'a Node: DagLike, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "digraph G {{")?; + writeln!(f, "ordering=\"out\";")?; + + for data in self.0.post_order_iter::() { + let node_name = data.node.inner().to_string(); + writeln!(f, " node{}[label=\"{}\"];", data.index, node_name)?; + if let Some(left) = data.left_index { + writeln!(f, " node{}->node{};", data.index, left)?; + } + if let Some(right) = data.right_index { + writeln!(f, " node{}->node{};", data.index, right)?; + } + } + + writeln!(f, "}}")?; + + Ok(()) + } +} + #[cfg(test)] mod tests { use crate::human_encoding::Forest; @@ -241,4 +274,54 @@ mod tests { program.display_expr().to_string() ) } + + #[test] + fn display_as_dot() { + let s = " + oih := take drop iden + input := pair (pair unit unit) unit + output := unit + main := comp input (comp (pair oih (take unit)) output)"; + let program = parse_program(s); + let str = program + .display_as_dot() + .to_string() + .replace(" ", "") + .replace("\n", ""); + let expected = " + digraph G { +ordering=\"out\"; + node0[label=\"unit\"]; + node1[label=\"unit\"]; + node2[label=\"pair\"]; + node2->node0; + node2->node1; + node3[label=\"unit\"]; + node4[label=\"pair\"]; + node4->node2; + node4->node3; + node5[label=\"iden\"]; + node6[label=\"drop\"]; + node6->node5; + node7[label=\"take\"]; + node7->node6; + node8[label=\"unit\"]; + node9[label=\"take\"]; + node9->node8; + node10[label=\"pair\"]; + node10->node7; + node10->node9; + node11[label=\"unit\"]; + node12[label=\"comp\"]; + node12->node10; + node12->node11; + node13[label=\"comp\"]; + node13->node4; + node13->node12; +}" + .replace(" ", "") + .replace("\n", ""); + + assert_eq!(str, expected); + } } diff --git a/src/node/mod.rs b/src/node/mod.rs index bda0997b..fc93bbc8 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -66,6 +66,7 @@ use crate::dag::{DagLike, MaxSharing, SharingTracker}; use crate::encode; use crate::jet::Jet; +use crate::node::display::DisplayAsDot; use crate::{types, BitWriter, Cmr, FailEntropy, HasCmr, Value}; use std::sync::Arc; @@ -723,6 +724,10 @@ impl Node { DisplayExpr::from(self) } + pub fn display_as_dot(&self) -> DisplayAsDot<'_, N> { + DisplayAsDot::from(self) + } + /// Encode a Simplicity expression to bits without any witness data. pub fn encode_without_witness(&self, prog: W) -> io::Result { let mut w = BitWriter::new(prog);