diff --git a/Cargo.lock b/Cargo.lock index 55d3b22..c0c84d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,62 @@ 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 = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "arbitrary" version = "1.1.3" @@ -138,6 +194,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 +228,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 +335,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 +395,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" @@ -535,8 +630,10 @@ dependencies = [ name = "simfony" version = "0.1.0" dependencies = [ + "anyhow", "arbitrary", "base64", + "clap", "either", "getrandom", "itertools", @@ -575,6 +672,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 +738,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 +809,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..10d9723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ miniscript = "12.3.1" 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 644f736..0b1097a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,83 @@ 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 main() -> anyhow::Result<()> { + 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") + ) + }; -#[cfg(feature = "serde")] -fn run() -> Result<(), String> { - let args: Vec = env::args().collect(); + #[cfg(feature = "serde")] + let command = { + command.arg( + Arg::new("wit_file") + .value_name("WITNESS_FILE") + .action(ArgAction::Set) + .help("File containing the witness data") + ) + }; - 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(()); - } + let matches = { + command + .arg( + Arg::new("debug") + .long("debug") + .action(ArgAction::SetTrue) + .help("Include debug symbols in the output") + ) + .get_matches() + }; - let prog_file = &args[1]; + 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 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)?; - 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| -> anyhow::Result { + let wit_path = std::path::Path::new(wit_file); + 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()? + }; + #[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 +99,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(()) -} 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")) } }