From c391a5baaf499c788c6d826c818a5cb9fd9e05da Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 17 Feb 2026 14:30:34 +0200 Subject: [PATCH 1/2] feat: add graph command --- src/actions/simplicity/graph.rs | 76 +++++++++++++++++++ src/actions/simplicity/mod.rs | 2 + .../hal-simplicity/cmd/simplicity/graph.rs | 53 +++++++++++++ src/bin/hal-simplicity/cmd/simplicity/mod.rs | 3 + 4 files changed, 134 insertions(+) create mode 100644 src/actions/simplicity/graph.rs create mode 100644 src/bin/hal-simplicity/cmd/simplicity/graph.rs diff --git a/src/actions/simplicity/graph.rs b/src/actions/simplicity/graph.rs new file mode 100644 index 0000000..4a01f76 --- /dev/null +++ b/src/actions/simplicity/graph.rs @@ -0,0 +1,76 @@ +use std::str::FromStr; + +use simplicity::{ + dag::{MaxSharing, NoSharing}, + jet, +}; + +use crate::hal_simplicity::Program; + +#[derive(Debug, thiserror::Error)] +pub enum SimplicityGraphError { + #[error("invalid program: {0}")] + ProgramParse(simplicity::ParseError), +} + +#[derive(Debug, Clone, Copy)] +pub enum SharingLevel { + NoSharing, + MaxSharing, +} + +impl FromStr for SharingLevel { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "none" => Ok(SharingLevel::NoSharing), + "max" => Ok(SharingLevel::MaxSharing), + other => Err(format!("unknown sharing level: {}", other)), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum GraphFormat { + Dot, + Mermaid, +} + +impl FromStr for GraphFormat { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "graphviz" | "dot" => Ok(GraphFormat::Dot), + "mermaid" | "mermaidjs" => Ok(GraphFormat::Mermaid), + other => Err(format!("unknown graph format: {}", other)), + } + } +} + +pub fn simplicity_graph( + program: &str, + witness: Option<&str>, + sharing_level: SharingLevel, + graph_format: GraphFormat, +) -> Result { + let program = Program::::from_str(program, witness) + .map_err(SimplicityGraphError::ProgramParse)?; + + let sharing_level = match sharing_level { + SharingLevel::MaxSharing => simplicity::node::SharingLevel::Max, + SharingLevel::NoSharing => simplicity::node::SharingLevel::None, + }; + let graph_format = match graph_format { + GraphFormat::Dot => simplicity::node::GraphFormat::Dot, + GraphFormat::Mermaid => simplicity::node::GraphFormat::Mermaid, + }; + + let res = if let Some(node) = program.redeem_node() { + let graph = node.display_as_graph(graph_format, sharing_level); + graph.to_string() + } else { + let graph = program.commit_prog().display_as_graph(graph_format, sharing_level); + graph.to_string() + }; + Ok(res) +} diff --git a/src/actions/simplicity/mod.rs b/src/actions/simplicity/mod.rs index d2761a3..312cbc4 100644 --- a/src/actions/simplicity/mod.rs +++ b/src/actions/simplicity/mod.rs @@ -1,7 +1,9 @@ +pub mod graph; pub mod info; pub mod pset; pub mod sighash; +pub use graph::*; pub use info::*; pub use sighash::*; diff --git a/src/bin/hal-simplicity/cmd/simplicity/graph.rs b/src/bin/hal-simplicity/cmd/simplicity/graph.rs new file mode 100644 index 0000000..1cfaad1 --- /dev/null +++ b/src/bin/hal-simplicity/cmd/simplicity/graph.rs @@ -0,0 +1,53 @@ +// Copyright 2025 Andrew Poelstra +// SPDX-License-Identifier: CC0-1.0 + +use clap::value_t; +use hal_simplicity::actions::simplicity::{GraphFormat, SharingLevel}; + +use crate::cmd; + +use super::Error; + +pub fn cmd<'a>() -> clap::App<'a, 'a> { + cmd::subcommand("graph", "Parse a base64-encoded Simplicity program and display a graph").args( + &[ + cmd::arg("program", "a Simplicity program in base64").takes_value(true).required(true), + cmd::arg("witness", "a hex encoding of all the witness data for the program") + .takes_value(true) + .required(false), + cmd::arg( + "sharing", + "the level of node sharing to use when displaying. Either none or max", + ) + .short("s") + .long("sharing") + .takes_value(true) + .required(false) + .default_value("none") + .possible_values(&["none", "max"]), + cmd::arg("format", "the format for the graph, either graphviz (alias dot) or mermaid") + .long("format") + .takes_value(true) + .required(false) + .default_value("graphviz") + .possible_values(&["graphviz", "dot", "mermaid"]), + ], + ) +} + +pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { + let program = matches.value_of("program").expect("program is mandatory"); + let witness = matches.value_of("witness"); + let sharing = value_t!(matches, "sharing", SharingLevel).unwrap_or(SharingLevel::NoSharing); + let format = value_t!(matches, "format", GraphFormat).unwrap_or(GraphFormat::Dot); + + match hal_simplicity::actions::simplicity::simplicity_graph(program, witness, sharing, format) { + Ok(graph) => println!("{}", graph), + Err(e) => cmd::print_output( + matches, + &Error { + error: format!("{}", e), + }, + ), + } +} diff --git a/src/bin/hal-simplicity/cmd/simplicity/mod.rs b/src/bin/hal-simplicity/cmd/simplicity/mod.rs index 784798d..2207a4d 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/mod.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/mod.rs @@ -1,6 +1,7 @@ // Copyright 2025 Andrew Poelstra // SPDX-License-Identifier: CC0-1.0 +mod graph; mod info; mod pset; mod sighash; @@ -16,6 +17,7 @@ struct Error { pub fn subcommand<'a>() -> clap::App<'a, 'a> { cmd::subcommand_group("simplicity", "manipulate Simplicity programs") + .subcommand(self::graph::cmd()) .subcommand(self::info::cmd()) .subcommand(self::pset::cmd()) .subcommand(self::sighash::cmd()) @@ -24,6 +26,7 @@ pub fn subcommand<'a>() -> clap::App<'a, 'a> { pub fn execute<'a>(matches: &clap::ArgMatches<'a>) { match matches.subcommand() { ("info", Some(m)) => self::info::exec(m), + ("graph", Some(m)) => self::graph::exec(m), ("pset", Some(m)) => self::pset::exec(m), ("sighash", Some(m)) => self::sighash::exec(m), (_, _) => unreachable!("clap prints help"), From 5dd547e61e66e6c36ea18ece0e24970fb8a025d8 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 17 Feb 2026 14:43:02 +0200 Subject: [PATCH 2/2] fix clippy and other minor tweaks --- src/actions/simplicity/graph.rs | 5 +---- src/bin/hal-simplicity/cmd/simplicity/graph.rs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/actions/simplicity/graph.rs b/src/actions/simplicity/graph.rs index 4a01f76..174c311 100644 --- a/src/actions/simplicity/graph.rs +++ b/src/actions/simplicity/graph.rs @@ -1,9 +1,6 @@ use std::str::FromStr; -use simplicity::{ - dag::{MaxSharing, NoSharing}, - jet, -}; +use simplicity::jet; use crate::hal_simplicity::Program; diff --git a/src/bin/hal-simplicity/cmd/simplicity/graph.rs b/src/bin/hal-simplicity/cmd/simplicity/graph.rs index 1cfaad1..e233f70 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/graph.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/graph.rs @@ -1,6 +1,3 @@ -// Copyright 2025 Andrew Poelstra -// SPDX-License-Identifier: CC0-1.0 - use clap::value_t; use hal_simplicity::actions::simplicity::{GraphFormat, SharingLevel}; @@ -30,7 +27,7 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { .takes_value(true) .required(false) .default_value("graphviz") - .possible_values(&["graphviz", "dot", "mermaid"]), + .possible_values(&["graphviz", "dot", "mermaid", "mermaidjs"]), ], ) }