From f930ae3f266b5b4cc925f2c62be228d253ef3ed9 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 29 Apr 2025 00:48:38 +0800 Subject: [PATCH 1/2] cli: add flag for including debug symbols Add clap as a dependency and use it to parse the command line arguments. Also add a --debug cli option to include debug symbols. --- Cargo.lock | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/main.rs | 99 +++++++++++++++++------------ 3 files changed, 235 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55d3b22..a381af1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + [[package]] name = "arbitrary" version = "1.1.3" @@ -138,6 +188,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "codegen" version = "0.1.0" @@ -145,6 +222,12 @@ dependencies = [ "simfony", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "cpufeatures" version = "0.2.9" @@ -246,6 +329,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.13.0" @@ -300,9 +389,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "pest" @@ -537,6 +626,7 @@ version = "0.1.0" dependencies = [ "arbitrary", "base64", + "clap", "either", "getrandom", "itertools", @@ -575,6 +665,12 @@ dependencies = [ "cc", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -635,6 +731,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.4" @@ -700,3 +802,76 @@ name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index e1b0054..8a9772d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ miniscript = "12.3.1" either = "1.12.0" itertools = "0.13.0" arbitrary = { version = "1", optional = true, features = ["derive"] } +clap = "4.5.37" [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/src/main.rs b/src/main.rs index 644f736..3b5c510 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; +use clap::{Arg, ArgAction, Command}; use simfony::{Arguments, CompiledProgram}; use std::env; @@ -13,30 +14,69 @@ fn main() { } } -#[cfg(feature = "serde")] fn run() -> Result<(), String> { - let args: Vec = env::args().collect(); + let command = { + Command::new(env!("CARGO_BIN_NAME")) + .about( + "\ + Compile the given Simfony program and print the resulting Simplicity base64 string.\n\ + If a Simfony witness is provided, then use it to satisfy the program (requires \ + feature 'serde' to be enabled).\ + " + ) + .arg( + Arg::new("prog_file") + .required(true) + .value_name("PROGRAM_FILE") + .action(ArgAction::Set) + .help("Simfony program file to build") + ) + }; - if args.len() < 2 { - println!("Usage: {} PROGRAM_FILE [WITNESS_FILE]", args[0]); - println!( - "Compile the given Simfony program and print the resulting Simplicity base64 string." - ); - println!("If a Simfony witness is provided, then use it to satisfy the program."); - return Ok(()); - } + #[cfg(feature = "serde")] + let command = { + command.arg( + Arg::new("wit_file") + .value_name("WITNESS_FILE") + .action(ArgAction::Set) + .help("File containing the witness data") + ) + }; - let prog_file = &args[1]; + let matches = { + command + .arg( + Arg::new("debug") + .long("debug") + .action(ArgAction::SetTrue) + .help("Include debug symbols in the output") + ) + .get_matches() + }; + + let prog_file = matches.get_one::("prog_file").unwrap(); let prog_path = std::path::Path::new(prog_file); let prog_text = std::fs::read_to_string(prog_path).map_err(|e| e.to_string())?; - let compiled = CompiledProgram::new(prog_text, Arguments::default(), false)?; + let include_debug_symbols = matches.get_flag("debug"); + + let compiled = CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols)?; - if args.len() >= 3 { - let wit_file = &args[2]; - let wit_path = std::path::Path::new(wit_file); - let wit_text = std::fs::read_to_string(wit_path).map_err(|e| e.to_string())?; - let witness = serde_json::from_str::(&wit_text).unwrap(); + #[cfg(feature = "serde")] + let witness_opt = { + matches + .get_one::("wit_file") + .map(|wit_file| -> Result { + let wit_path = std::path::Path::new(wit_file); + let wit_text = std::fs::read_to_string(wit_path).map_err(|e| e.to_string())?; + let witness = serde_json::from_str::(&wit_text).unwrap(); + Ok(witness) + }) + .transpose()? + }; + #[cfg(not(feature = "serde"))] + let witness_opt: Option = None; + if let Some(witness) = witness_opt { let satisfied = compiled.satisfy(witness)?; let (program_bytes, witness_bytes) = satisfied.redeem().encode_to_vec(); println!( @@ -58,28 +98,3 @@ fn run() -> Result<(), String> { Ok(()) } -#[cfg(not(feature = "serde"))] -fn run() -> Result<(), String> { - let args: Vec = env::args().collect(); - - if args.len() < 2 { - println!("Usage: {} PROGRAM_FILE", args[0]); - println!( - "Compile the given Simfony program and print the resulting Simplicity base64 string." - ); - return Ok(()); - } - - let prog_file = &args[1]; - let prog_path = std::path::Path::new(prog_file); - let prog_text = std::fs::read_to_string(prog_path).map_err(|e| e.to_string())?; - let compiled = CompiledProgram::new(prog_text, Arguments::default(), false)?; - - let program_bytes = compiled.commit().encode_to_vec(); - println!( - "Program:\n{}", - Base64Display::new(&program_bytes, &STANDARD) - ); - - Ok(()) -} From 2051f877514d46377685bc96452466a8c9485eca Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Mon, 5 May 2025 21:44:52 +0800 Subject: [PATCH 2/2] refactor: use anyhow::Error in place of String Use the anyhow crate for errors where we're currently just using String. This makes it easier to add context to errors and avoids the need to convert to a string when bubbling up errors. Also box the `Error` field in `RichError` to reduce its size and keep clippy happy. --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + clippy.toml | 2 -- src/error.rs | 6 +++--- src/lib.rs | 26 ++++++++++++-------------- src/main.rs | 29 +++++++++++++++-------------- src/witness.rs | 3 ++- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a381af1..c0c84d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,6 +61,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "arbitrary" version = "1.1.3" @@ -624,6 +630,7 @@ dependencies = [ name = "simfony" version = "0.1.0" dependencies = [ + "anyhow", "arbitrary", "base64", "clap", diff --git a/Cargo.toml b/Cargo.toml index 8a9772d..10d9723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ either = "1.12.0" itertools = "0.13.0" arbitrary = { version = "1", optional = true, features = ["derive"] } clap = "4.5.37" +anyhow = "1.0.98" [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/clippy.toml b/clippy.toml index 8d622bc..a93e948 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,3 +1 @@ msrv = "1.78.0" -# We have an error type, `RichError`, of size 144. This is pushing it but probably fine. -large-error-threshold = 145 diff --git a/src/error.rs b/src/error.rs index d169697..870567e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -195,7 +195,7 @@ impl WithFile for Result { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct RichError { /// The error that occurred. - error: Error, + error: Box, /// Area that the error spans inside the file. span: Span, /// File in which the error occurred. @@ -208,7 +208,7 @@ impl RichError { /// Create a new error with context. pub fn new(error: Error, span: Span) -> RichError { RichError { - error, + error: Box::new(error), span, file: None, } @@ -266,7 +266,7 @@ impl std::error::Error for RichError {} impl From for Error { fn from(error: RichError) -> Self { - error.error + *error.error } } diff --git a/src/lib.rs b/src/lib.rs index b16e31b..45be16c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ impl TemplateProgram { /// ## Errors /// /// The string is not a valid Simfony program. - pub fn new>>(s: Str) -> Result { + pub fn new>>(s: Str) -> anyhow::Result { let file = s.into(); let parse_program = parse::Program::parse_from_str(&file)?; let ast_program = ast::Program::analyze(&parse_program).with_file(Arc::clone(&file))?; @@ -76,10 +76,9 @@ impl TemplateProgram { &self, arguments: Arguments, include_debug_symbols: bool, - ) -> Result { + ) -> anyhow::Result { arguments - .is_consistent(self.simfony.parameters()) - .map_err(|error| error.to_string())?; + .is_consistent(self.simfony.parameters())?; Ok(CompiledProgram { debug_symbols: self.simfony.debug_symbols(self.file.as_ref()), simplicity: self @@ -121,7 +120,7 @@ impl CompiledProgram { s: Str, arguments: Arguments, include_debug_symbols: bool, - ) -> Result { + ) -> anyhow::Result { TemplateProgram::new(s) .and_then(|template| template.instantiate(arguments, include_debug_symbols)) } @@ -142,7 +141,7 @@ impl CompiledProgram { /// /// - Witness values have a different type than declared in the Simfony program. /// - There are missing witness values. - pub fn satisfy(&self, witness_values: WitnessValues) -> Result { + pub fn satisfy(&self, witness_values: WitnessValues) -> anyhow::Result { self.satisfy_with_env(witness_values, None) } @@ -157,17 +156,16 @@ impl CompiledProgram { &self, witness_values: WitnessValues, env: Option<&ElementsEnv>>, - ) -> Result { + ) -> anyhow::Result { witness_values - .is_consistent(&self.witness_types) - .map_err(|e| e.to_string())?; + .is_consistent(&self.witness_types)?; let simplicity_witness = named::to_witness_node(&self.simplicity, witness_values); let simplicity_redeem = match env { - Some(env) => simplicity_witness.finalize_pruned(env), - None => simplicity_witness.finalize_unpruned(), + Some(env) => simplicity_witness.finalize_pruned(env)?, + None => simplicity_witness.finalize_unpruned()?, }; Ok(SatisfiedProgram { - simplicity: simplicity_redeem.map_err(|e| e.to_string())?, + simplicity: simplicity_redeem, debug_symbols: self.debug_symbols.clone(), }) } @@ -193,7 +191,7 @@ impl SatisfiedProgram { arguments: Arguments, witness_values: WitnessValues, include_debug_symbols: bool, - ) -> Result { + ) -> anyhow::Result { let compiled = CompiledProgram::new(s, arguments, include_debug_symbols)?; compiled.satisfy(witness_values) } @@ -620,7 +618,7 @@ fn main() { ) { Ok(_) => panic!("Accepted faulty program"), Err(error) => { - if !error.contains("Expected expression of type `bool`, found type `()`") { + if !error.to_string().contains("Expected expression of type `bool`, found type `()`") { panic!("Unexpected error: {error}") } } diff --git a/src/main.rs b/src/main.rs index 3b5c510..0b1097a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,12 @@ use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use clap::{Arg, ArgAction, Command}; +use anyhow::Context; use simfony::{Arguments, CompiledProgram}; use std::env; -// Directly returning Result<(), String> prints the error using Debug -// Add indirection via run() to print errors using Display -fn main() { - if let Err(error) = run() { - eprintln!("{error}"); - std::process::exit(1); - } -} - -fn run() -> Result<(), String> { +fn main() -> anyhow::Result<()> { let command = { Command::new(env!("CARGO_BIN_NAME")) .about( @@ -56,7 +48,10 @@ fn run() -> Result<(), String> { let prog_file = matches.get_one::("prog_file").unwrap(); let prog_path = std::path::Path::new(prog_file); - let prog_text = std::fs::read_to_string(prog_path).map_err(|e| e.to_string())?; + let prog_text = { + std::fs::read_to_string(prog_path) + .with_context(|| format!("Failed to read {}", prog_path.display()))? + }; let include_debug_symbols = matches.get_flag("debug"); let compiled = CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols)?; @@ -65,10 +60,16 @@ fn run() -> Result<(), String> { let witness_opt = { matches .get_one::("wit_file") - .map(|wit_file| -> Result { + .map(|wit_file| -> anyhow::Result { let wit_path = std::path::Path::new(wit_file); - let wit_text = std::fs::read_to_string(wit_path).map_err(|e| e.to_string())?; - let witness = serde_json::from_str::(&wit_text).unwrap(); + let wit_text = { + std::fs::read_to_string(wit_path) + .with_context(|| format!("Failed to read {}", wit_path.display()))? + }; + let witness = { + serde_json::from_str::(&wit_text) + .with_context(|| format!("Failed to parse file {}", wit_path.display()))? + }; Ok(witness) }) .transpose()? diff --git a/src/witness.rs b/src/witness.rs index 7aa17ec..0c14d41 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -240,7 +240,7 @@ mod tests { Ok(_) => panic!("Ill-typed witness assignment was falsely accepted"), Err(error) => assert_eq!( "Witness `A` was declared with type `u32` but its assigned value is of type `u16`", - error + error.to_string(), ), } } @@ -259,6 +259,7 @@ fn main() { Ok(_) => panic!("Witness outside main was falsely accepted"), Err(error) => { assert!(error + .to_string() .contains("Witness expressions are not allowed outside the `main` function")) } }