diff --git a/Cargo.toml b/Cargo.toml index ad6180db..a6f93ed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ bitcoin = { version = "0.29.2", optional = true } bitcoin_hashes = "0.11" byteorder = "1.3" elements = { version = "0.21.1", optional = true } -elements-miniscript = { git = "https://github.com/ElementsProject/elements-miniscript", rev = "955f380" } +elements-miniscript = { git = "https://github.com/apoelstra/elements-miniscript", tag = "2023-07--rust-simplicity-patch" } simplicity-sys = { version = "0.1.0", path = "./simplicity-sys" } actual-serde = { package = "serde", version = "1.0.103", features = ["derive"], optional = true } diff --git a/jets-bench/benches/elements/main.rs b/jets-bench/benches/elements/main.rs index 3cb8dfd6..a79dca1d 100644 --- a/jets-bench/benches/elements/main.rs +++ b/jets-bench/benches/elements/main.rs @@ -471,7 +471,7 @@ fn bench(c: &mut Criterion) { let (src_ty, tgt_ty) = jet_arrow(jet); let env = env_sampler.env(); - let mut group = c.benchmark_group(&format!("{}", jet.to_string())); + let mut group = c.benchmark_group(&jet.to_string()); for i in 0..NUM_RANDOM_SAMPLES { let params = JetParams::with_rand_aligns(InputSampling::Random); let name = format!("{}", i); @@ -531,7 +531,7 @@ fn bench(c: &mut Criterion) { let (src_ty, tgt_ty) = jet_arrow(jet); let env = EnvSampling::Null.env(); - let mut group = c.benchmark_group(&format!("{}", jet.to_string())); + let mut group = c.benchmark_group(&jet.to_string()); for i in 0..NUM_RANDOM_SAMPLES { let params = JetParams::with_rand_aligns(InputSampling::Custom(inp_fn.clone())); let name = format!("{}", i); @@ -612,7 +612,7 @@ fn bench(c: &mut Criterion) { for (jet, index, env_type) in arr { let (src_ty, tgt_ty) = jet_arrow(jet); let env = env_type.env(); - let mut group = c.benchmark_group(&format!("{}", jet.to_string())); + let mut group = c.benchmark_group(&jet.to_string()); for i in 0..NUM_RANDOM_SAMPLES { // We always select the current input because this is where we diff --git a/src/analysis.rs b/src/analysis.rs index 039ec8ad..6af29690 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -14,7 +14,10 @@ use crate::jet::Jet; use crate::Value; -use std::{cmp, fmt, io}; +use std::{cmp, fmt}; + +#[cfg(feature = "elements")] +use std::io; #[cfg(feature = "elements")] use elements::encode::Encodable; diff --git a/src/bit_encoding/decode.rs b/src/bit_encoding/decode.rs index 014fd171..c9e18257 100644 --- a/src/bit_encoding/decode.rs +++ b/src/bit_encoding/decode.rs @@ -410,7 +410,7 @@ mod tests { #[test] fn root_unit_to_unit() { // main = jet_eq_32 :: 2^64 -> 2 # 7387d279 - let justjet = vec![0x6d, 0xb8, 0x80]; + let justjet = [0x6d, 0xb8, 0x80]; // Should be able to decode this as an expression... let mut iter = BitIter::from(&justjet[..]); decode_expression::<_, Core>(&mut iter).unwrap(); diff --git a/src/bit_machine/mod.rs b/src/bit_machine/mod.rs index b3f16cd2..007cb9a8 100644 --- a/src/bit_machine/mod.rs +++ b/src/bit_machine/mod.rs @@ -528,7 +528,7 @@ mod tests { prog.cmr().to_string(), cmr_str, "CMR mismatch (got {} expected {}) for program {}", - prog.cmr().to_string(), + prog.cmr(), cmr_str, prog_hex, ); @@ -536,7 +536,7 @@ mod tests { prog.imr().to_string(), imr_str, "IMR mismatch (got {} expected {}) for program {}", - prog.imr().to_string(), + prog.imr(), imr_str, prog_hex, ); @@ -544,7 +544,7 @@ mod tests { prog.amr().to_string(), amr_str, "AMR mismatch (got {} expected {}) for program {}", - prog.amr().to_string(), + prog.amr(), amr_str, prog_hex, ); diff --git a/src/human_encoding/README.md b/src/human_encoding/README.md index e3a8db8f..6bfe21e9 100644 --- a/src/human_encoding/README.md +++ b/src/human_encoding/README.md @@ -52,8 +52,8 @@ and EXPRESSION is * `unit`, `iden`, or `witness`; * `injl`, `injr`, `take`, or `drop` followed by another EXPRESSION; * `case`, `comp`, or `pair` followed by two EXPRESSIONs; -* `assertl` followed by an EXPRESSION, a literal `#`, and another EXPRESSION; -* `assertr` followed by a literal `#` and two EXPRESSIONs; +* `assertl` followed by an EXPRESSION and a CMR (defined below); +* `assertr` followed by CMR and an EXPRESSION; * a jet, which begins with `jet_` and must belong to the list of jets (FIXME define this list); * `const` followed by a VALUE (defined below); * `fail` followed by an ENTROPY (defined below); or @@ -63,6 +63,14 @@ Note that while we allow parenthesis to help group parts of expressions for huma understanding, they are never needed for disambiguation and are essentially ignored by the parser. +A CMR is + +* `#{` followed by an expression followed by `}`; or +* `#` followed by 64 hex bytes. + +The first case indicates that an expression should be replaced by its commitment +Merkle root; the second case just directly encodes the Merkle root. + A HOLE is the literal `?` followed by a NAME. It indicates an expression that has yet to be defined. Holes have a different namespace than other names. @@ -151,7 +159,7 @@ Expressions may be * one of the core combinators `unit`, `iden`, `comp`, `injl`, `injr`, `case`, `take`, `drop`, `pair`, followed by subexpression(s) as needed; * the `disconnect` combinator followed by an expression and a hole; * the `witness` combinator which currently allows no subexpressions; -* the assertions, `assertl` or `assertr`, which take two subexpressions, one of which will be hidden in the decoded program. The hidden subexpression should be prefixed by `#` which indicates to the parser to take the CMR of that expression, not the expression itself. +* the assertions, `assertl` or `assertr`, which take a subexpressions and a CMR. The CMR is encoded as a full expression prefixed by `#{` and suffixed by `}`; but in the bit-encoding the expression does not appear, only its CMR; * `fail` followed by a 128-to-512-bit entropy value, which should occur only in the pruned branch of an assertion, though this is not enforced; * `const` followed by a value, which is a "constant-word jet" and is equivalent to constructing the given value by a tree of `pair`s whose leaves are `injl unit` (0) or `injr unit` (1); diff --git a/src/human_encoding/error.rs b/src/human_encoding/error.rs new file mode 100644 index 00000000..3cbcbef3 --- /dev/null +++ b/src/human_encoding/error.rs @@ -0,0 +1,217 @@ +// Simplicity "Human-Readable" Language +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Parsing Errors + +use std::collections::BTreeMap; +use std::sync::{Arc, Mutex}; +use std::{error, fmt, iter}; + +use crate::types; + +use super::Position; + +/// A set of errors found in a human-readable encoding of a Simplicity program. +#[derive(Clone, Debug, Default)] +pub struct ErrorSet { + context: Option>, + line_map: Arc>>, + errors: BTreeMap, Vec>, +} + +impl ErrorSet { + /// Constructs a new empty error set. + pub fn new() -> Self { + ErrorSet::default() + } + + /// Returns the first (and presumably most important) error in the set, if it + /// is non-empty, along with its position. + pub fn first_error(&self) -> Option<(Option, &Error)> { + self.errors.iter().next().map(|(a, b)| (*a, &b[0])) + } + + /// Constructs a new error set with a single error in it. + pub fn single, E: Into>(position: P, err: E) -> Self { + let mut errors = BTreeMap::default(); + errors.insert(Some(position.into()), vec![err.into()]); + ErrorSet { + context: None, + line_map: Arc::new(Mutex::new(vec![])), + errors, + } + } + + /// Constructs a new error set with a single error in it. + pub fn single_no_position>(err: E) -> Self { + let mut errors = BTreeMap::default(); + errors.insert(None, vec![err.into()]); + ErrorSet { + context: None, + line_map: Arc::new(Mutex::new(vec![])), + errors, + } + } + + /// Adds an error to the error set. + pub fn add, E: Into>(&mut self, position: P, err: E) { + self.errors + .entry(Some(position.into())) + .or_insert(vec![]) + .push(err.into()); + } + + /// Merges another set of errors into the current set. + /// + /// # Panics + /// + /// Panics if the two sets have different contexts attached. + pub fn merge(&mut self, other: &Self) { + match (self.context.as_ref(), other.context.as_ref()) { + (None, None) => {} + (Some(_), None) => {} + (None, Some(b)) => self.context = Some(Arc::clone(b)), + (Some(a), Some(b)) => { + assert_eq!(a, b, "cannot merge error sets for different source input"); + } + }; + + for (pos, errs) in &other.errors { + self.errors + .entry(*pos) + .or_insert(vec![]) + .extend(errs.iter().cloned()); + } + } + + /// Attaches the input code to the error set, so that error messages can include + /// line numbers etc. + /// + /// # Panics + /// + /// Panics if it is called twice on the same error set. You should call this once + /// with the complete input code. + pub fn add_context(&mut self, s: Arc) { + if self.context.is_some() { + panic!("tried to add context to the same error context twice"); + } + self.context = Some(s); + } + + /// Returns a boolean indicating whether the set is empty. + pub fn is_empty(&self) -> bool { + self.errors.is_empty() + } + + /// Returns the number of errors currently in the set. + pub fn len(&self) -> usize { + self.errors.len() + } + + /// Converts the error set into a result. + /// + /// If the set is empty, returns Ok with the given value. Otherwise + /// returns Err with itself. + pub fn into_result(self, ok: T) -> Result { + if self.is_empty() { + Ok(ok) + } else { + Err(self) + } + } + + /// Converts the error set into a result. + /// + /// If the set is empty, returns Ok with the result of calling the given closure. + /// Otherwise returns Err with itself. + pub fn into_result_with T>(self, okfn: F) -> Result { + if self.is_empty() { + Ok(okfn()) + } else { + Err(self) + } + } +} + +impl error::Error for ErrorSet { + fn cause(&self) -> Option<&(dyn error::Error + 'static)> { + match self.first_error()?.1 { + Error::TypeCheck(ref e) => Some(e), + } + } +} + +impl fmt::Display for ErrorSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut line_map = self.line_map.lock().unwrap(); + if line_map.is_empty() { + if let Some(ref s) = self.context { + *line_map = iter::repeat(0) + .take(2) + .chain( + s.char_indices() + .filter_map(|(n, ch)| if ch == '\n' { Some(n) } else { None }), + ) + .collect(); + } + } + + for (pos, errs) in &self.errors { + if let Some(pos) = pos { + for err in errs { + if let Some(ref s) = self.context { + let end = line_map.get(pos.line + 1).copied().unwrap_or(s.len()); + let line = &s[line_map[pos.line] + 1..end]; + writeln!(f, "{:5} | {}", pos.line, line)?; + writeln!(f, " | {:>width$}", "^", width = pos.column)?; + writeln!(f, " \\-- {}", err)?; + writeln!(f)?; + } else { + writeln!(f, "{:4}:{:2}: {}", pos.line, pos.column, err,)?; + writeln!(f)?; + } + } + } else { + for err in errs { + writeln!(f, "Error: {}", err)?; + } + } + } + Ok(()) + } +} + +/// An individual error. +/// +/// Generally this structure should not be used on its own, but only wrapped in an +/// [`ErrorSet`]. This is because in the human-readable encoding errors it is usually +/// possible to continue past individual errors, and the user would prefer to see as +/// many as possible at once. +#[derive(Clone, Debug)] +pub enum Error { + /// Simplicity type-checking error + TypeCheck(types::Error), +} + +impl From for Error { + fn from(e: types::Error) -> Self { + Error::TypeCheck(e) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::TypeCheck(ref e) => fmt::Display::fmt(e, f), + } + } +} diff --git a/src/human_encoding/mod.rs b/src/human_encoding/mod.rs index 019c3892..39ccc1ba 100644 --- a/src/human_encoding/mod.rs +++ b/src/human_encoding/mod.rs @@ -20,6 +20,7 @@ //! in a human-readable format. //! +mod error; mod named_node; mod serialize; @@ -31,8 +32,20 @@ use std::collections::HashMap; use std::str; use std::sync::Arc; +pub use self::error::{Error, ErrorSet}; pub use self::named_node::NamedCommitNode; +/// Line/column pair +/// +/// There is a similar type provided by the `santiago` library but it does not implement +/// `Copy`, among many other traits, which makes it unergonomic to use. Santiago positions +/// can be converted using `.into()`. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] +pub struct Position { + line: usize, + column: usize, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct Forest { roots: HashMap, Arc>>, @@ -80,6 +93,7 @@ impl Forest { let node = data.node; let name = node.name(); let mut expr_str = match node.inner() { + node::Inner::AssertR(cmr, _) => format!("{} := assertr #{}", name, cmr), node::Inner::Fail(entropy) => format!("{} := fail {}", name, entropy), node::Inner::Jet(ref j) => format!("{} := jet_{}", name, j), node::Inner::Word(ref v) => { @@ -94,6 +108,9 @@ impl Forest { if let Some(child) = node.right_child() { expr_str.push(' '); expr_str.push_str(child.name()); + } else if let node::Inner::AssertL(_, cmr) = node.inner() { + expr_str.push_str(" #"); + expr_str.push_str(&cmr.to_string()); } let arrow = node.arrow(); diff --git a/src/human_encoding/named_node.rs b/src/human_encoding/named_node.rs index b498db3f..e5f398d3 100644 --- a/src/human_encoding/named_node.rs +++ b/src/human_encoding/named_node.rs @@ -14,14 +14,18 @@ //! Human-readable Nodes -use crate::dag::{MaxSharing, PostOrderIterItem}; +use crate::dag::{InternalSharing, MaxSharing, PostOrderIterItem}; use crate::encode; +use crate::human_encoding::{ErrorSet, Position}; use crate::jet::Jet; use crate::node::{self, Commit, CommitData, CommitNode, Converter, NoDisconnect, NoWitness, Node}; -use crate::types::arrow::FinalArrow; +use crate::node::{Construct, ConstructData, Constructible}; +use crate::types; +use crate::types::arrow::{Arrow, FinalArrow}; use crate::{BitWriter, Cmr}; use std::io; +use std::marker::PhantomData; use std::sync::Arc; pub type NamedCommitNode = Node>>; @@ -50,7 +54,7 @@ impl node::Marker for Named> { pub struct NamedCommitData { /// Data related to the node itself internal: Arc>, - /// Name assigned to the node + /// Name assigned to the node. name: Arc, } @@ -90,6 +94,209 @@ impl NamedCommitNode { } } +pub type NamedConstructNode = Node>>; + +impl node::Marker for Named> { + type CachedData = NamedConstructData; + type Witness = as node::Marker>::Witness; + type Disconnect = NoDisconnect; + type SharingId = Arc; + type Jet = J; + + fn compute_sharing_id(_: Cmr, cached_data: &Self::CachedData) -> Option> { + Some(Arc::clone(&cached_data.name)) + } +} + +#[derive(Clone, Debug)] +pub struct NamedConstructData { + /// Data related to the node itself + internal: ConstructData, + /// Name assigned to the node + name: Arc, + /// Position of the node, if it comes from source code. + position: Position, + /// User-provided type bounds on the source (will be checked for consistency + /// but only after the type checking has completed.) + user_source_types: Arc<[types::Type]>, + /// User-provided type bounds on the target (will be checked for consistency + /// but only after the type checking has completed.) + user_target_types: Arc<[types::Type]>, +} + +impl NamedConstructNode { + /// Construct a named construct node from parts. + pub fn new( + name: Arc, + position: Position, + user_source_types: Arc<[types::Type]>, + user_target_types: Arc<[types::Type]>, + inner: node::Inner, J, NoDisconnect, NoWitness>, + ) -> Result { + let construct_data = ConstructData::from_inner( + inner + .as_ref() + .map(|data| &data.cached_data().internal) + .map_disconnect(|_| &None) + .copy_witness(), + )?; + let named_data = NamedConstructData { + internal: construct_data, + name, + position, + user_source_types, + user_target_types, + }; + Ok(Node::from_parts(inner, named_data)) + } + + /// Accessor for the node's name + pub fn name(&self) -> &Arc { + &self.cached_data().name + } + + /// Accessor for the node's position + pub fn position(&self) -> Position { + self.cached_data().position + } + + /// Accessor for the node's arrow + pub fn arrow(&self) -> &Arrow { + self.cached_data().internal.arrow() + } + + /// Finalizes the types of the underlying [`ConstructNode`]. + pub fn finalize_types_main(&self) -> Result>, ErrorSet> { + self.finalize_types_inner(true) + } + + /// Finalizes the types of the underlying [`ConstructNode`], without setting + /// the root node's arrow to 1->1. + pub fn finalize_types_non_main(&self) -> Result>, ErrorSet> { + self.finalize_types_inner(false) + } + + pub fn finalize_types_inner( + &self, + for_main: bool, + ) -> Result>, ErrorSet> { + struct FinalizeTypes { + for_main: bool, + errors: ErrorSet, + phantom: PhantomData, + } + + impl Converter>, Named>> for FinalizeTypes { + type Error = ErrorSet; + fn convert_witness( + &mut self, + _: &PostOrderIterItem<&NamedConstructNode>, + _: &NoWitness, + ) -> Result { + Ok(NoWitness) + } + + fn convert_disconnect( + &mut self, + _: &PostOrderIterItem<&NamedConstructNode>, + _: Option<&Arc>>, + _: &NoDisconnect, + ) -> Result { + Ok(NoDisconnect) + } + + fn convert_data( + &mut self, + data: &PostOrderIterItem<&NamedConstructNode>, + inner: node::Inner<&Arc>, J, &NoDisconnect, &NoWitness>, + ) -> Result, Self::Error> { + let converted_data = inner.map(|node| &node.cached_data().internal); + + if !self.for_main { + // For non-`main` fragments, treat the ascriptions as normative, and apply them + // before finalizing the type. + let arrow = data.node.arrow(); + for ty in data.node.cached_data().user_source_types.as_ref() { + if let Err(e) = arrow.source.unify(ty, "binding source type annotation") { + self.errors.add(data.node.position(), e); + } + } + for ty in data.node.cached_data().user_target_types.as_ref() { + if let Err(e) = arrow.target.unify(ty, "binding target type annotation") { + self.errors.add(data.node.position(), e); + } + } + } + + let commit_data = match CommitData::new(data.node.arrow(), converted_data) { + Ok(commit_data) => Arc::new(commit_data), + Err(e) => { + self.errors.add(data.node.position(), e); + return Err(self.errors.clone()); + } + }; + + if self.for_main { + // For `main`, only apply type ascriptions *after* inference has completely + // determined the type. + let source_bound = + types::Bound::Complete(Arc::clone(&commit_data.arrow().source)); + let source_ty = types::Type::from(source_bound); + for ty in data.node.cached_data().user_source_types.as_ref() { + if let Err(e) = source_ty.unify(ty, "binding source type annotation") { + self.errors.add(data.node.position(), e); + } + } + let target_bound = + types::Bound::Complete(Arc::clone(&commit_data.arrow().target)); + let target_ty = types::Type::from(target_bound); + for ty in data.node.cached_data().user_target_types.as_ref() { + if let Err(e) = target_ty.unify(ty, "binding target type annotation") { + self.errors.add(data.node.position(), e); + } + } + } + + Ok(NamedCommitData { + name: Arc::clone(&data.node.cached_data().name), + internal: commit_data, + }) + } + } + + let mut finalizer = FinalizeTypes { + for_main, + errors: ErrorSet::default(), + phantom: PhantomData, + }; + + if for_main { + let unit_ty = types::Type::unit(); + if self.cached_data().user_source_types.is_empty() { + if let Err(e) = self + .arrow() + .source + .unify(&unit_ty, "setting root source to unit") + { + finalizer.errors.add(self.position(), e); + } + } + if self.cached_data().user_target_types.is_empty() { + if let Err(e) = self + .arrow() + .target + .unify(&unit_ty, "setting root source to unit") + { + finalizer.errors.add(self.position(), e); + } + } + } + + let root = self.convert::(&mut finalizer)?; + finalizer.errors.into_result(root) + } +} + pub struct Namer { const_idx: usize, wit_idx: usize, diff --git a/src/node/commit.rs b/src/node/commit.rs index 7f63332c..3ecd121e 100644 --- a/src/node/commit.rs +++ b/src/node/commit.rs @@ -314,7 +314,7 @@ mod tests { prog.cmr().to_string(), cmr_str, "CMR mismatch (got {} expected {}) for program {}", - prog.cmr().to_string(), + prog.cmr(), cmr_str, prog_hex, ); diff --git a/src/node/inner.rs b/src/node/inner.rs index ca298c24..465a666b 100644 --- a/src/node/inner.rs +++ b/src/node/inner.rs @@ -303,7 +303,7 @@ impl, W> Inner { } } -impl Inner, J, Option, W> { +impl Inner, J, X, W> { /// Convert an `Inner, J, W>` to an `Option>`. pub fn transpose(self) -> Option> { Some(match self { @@ -318,7 +318,31 @@ impl Inner, J, Option, W> { Inner::AssertL(c, cmr) => Inner::AssertL(c?, cmr), Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c?), Inner::Pair(cl, cr) => Inner::Pair(cl?, cr?), - Inner::Disconnect(cl, cr) => Inner::Disconnect(cl?, cr?), + Inner::Disconnect(cl, cr) => Inner::Disconnect(cl?, cr), + Inner::Witness(w) => Inner::Witness(w), + Inner::Fail(entropy) => Inner::Fail(entropy), + Inner::Jet(j) => Inner::Jet(j), + Inner::Word(w) => Inner::Word(w), + }) + } +} + +impl Inner, W> { + /// Convert an `Inner, J, W>` to an `Option>`. + pub fn transpose_disconnect(self) -> Option> { + Some(match self { + Inner::Iden => Inner::Iden, + Inner::Unit => Inner::Unit, + Inner::InjL(c) => Inner::InjL(c), + Inner::InjR(c) => Inner::InjR(c), + Inner::Take(c) => Inner::Take(c), + Inner::Drop(c) => Inner::Drop(c), + Inner::Comp(cl, cr) => Inner::Comp(cl, cr), + Inner::Case(cl, cr) => Inner::Case(cl, cr), + Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr), + Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c), + Inner::Pair(cl, cr) => Inner::Pair(cl, cr), + Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, cr?), Inner::Witness(w) => Inner::Witness(w), Inner::Fail(entropy) => Inner::Fail(entropy), Inner::Jet(j) => Inner::Jet(j), @@ -351,7 +375,7 @@ impl Inner> { } } -impl Inner { +impl Inner { /// Copies witness data. /// /// Useful in conjunction with [`Inner::as_ref`] when you don't want to @@ -361,6 +385,16 @@ impl Inner { } } +impl Inner { + /// Copies disconnect data. + /// + /// Useful in conjunction with [`Inner::as_ref`] when you don't want to + /// take a reference to disconnect data. + pub fn copy_disconnect(self) -> Inner { + self.map_disconnect(X::clone) + } +} + impl fmt::Display for Inner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/node/redeem.rs b/src/node/redeem.rs index 29d66ddd..e55cd5dd 100644 --- a/src/node/redeem.rs +++ b/src/node/redeem.rs @@ -366,7 +366,7 @@ mod tests { prog.cmr().to_string(), cmr_str, "CMR mismatch (got {} expected {}) for program {}", - prog.cmr().to_string(), + prog.cmr(), cmr_str, prog_hex, ); @@ -375,7 +375,7 @@ mod tests { prog.amr().to_string(), amr_str, "AMR mismatch (got {} expected {}) for program {}", - prog.amr().to_string(), + prog.amr(), amr_str, prog_hex, ); @@ -383,7 +383,7 @@ mod tests { prog.imr().to_string(), imr_str, "IMR mismatch (got {} expected {}) for program {}", - prog.imr().to_string(), + prog.imr(), imr_str, prog_hex, ); @@ -426,7 +426,7 @@ mod tests { // // wits_are_equal = comp (pair wit1 wit2) jet_eq_32 :: 1 -> 2 // main = comp wits_are_equal jet_verify :: 1 -> 1 - let eqwits = vec![0xcd, 0xdc, 0x51, 0xb6, 0xe2, 0x08, 0xc0, 0x40]; + let eqwits = [0xcd, 0xdc, 0x51, 0xb6, 0xe2, 0x08, 0xc0, 0x40]; let mut iter = BitIter::from(&eqwits[..]); let eqwits_prog = CommitNode::::decode(&mut iter).unwrap(); @@ -474,7 +474,7 @@ mod tests { #[test] fn witness_consumed() { // "main = unit", but with a witness attached. Found by fuzzer. - let badwit = vec![0x27, 0x00]; + let badwit = [0x27, 0x00]; let mut iter = BitIter::from(&badwit[..]); if let Err(Error::InconsistentWitnessLength) = RedeemNode::::decode(&mut iter) diff --git a/src/types/final_data.rs b/src/types/final_data.rs index 4033e135..30b7f36b 100644 --- a/src/types/final_data.rs +++ b/src/types/final_data.rs @@ -114,11 +114,29 @@ impl fmt::Display for Final { (CompleteBound::Unit, _) => { f.write_str("1")?; } + // special-case 1 + A as A? + (CompleteBound::Sum(ref left, _), 0) + if matches!(left.bound, CompleteBound::Unit) => + { + skipping = Some(Tmr::unit()); + } + (CompleteBound::Sum(ref left, _), 1) + if matches!(left.bound, CompleteBound::Unit) => {} + (CompleteBound::Sum(ref left, _), 2) + if matches!(left.bound, CompleteBound::Unit) => + { + f.write_str("?")?; + } + // other sums and products (CompleteBound::Sum(..), 0) | (CompleteBound::Product(..), 0) => { - f.write_str("(")?; + if data.index > 0 { + f.write_str("(")?; + } } (CompleteBound::Sum(..), 2) | (CompleteBound::Product(..), 2) => { - f.write_str(")")?; + if data.index > 0 { + f.write_str(")")?; + } } (CompleteBound::Sum(..), _) => f.write_str(" + ")?, (CompleteBound::Product(..), _) => f.write_str(" × ")?, @@ -216,9 +234,15 @@ mod tests { assert_eq!(ty1.to_string(), "2^1024"); let sum = Final::sum(Final::two_two_n(5), Final::two_two_n(10)); - assert_eq!(sum.to_string(), "(2^32 + 2^1024)"); + assert_eq!(sum.to_string(), "2^32 + 2^1024"); let prod = Final::product(Final::two_two_n(5), Final::two_two_n(10)); - assert_eq!(prod.to_string(), "(2^32 × 2^1024)"); + assert_eq!(prod.to_string(), "2^32 × 2^1024"); + + let ty1 = Final::two_two_n(0); + assert_eq!(ty1.to_string(), "2"); + + let ty1 = Final::sum(Arc::new(Final::unit()), Final::two_two_n(2)); + assert_eq!(ty1.to_string(), "2^4?"); } } diff --git a/src/types/mod.rs b/src/types/mod.rs index e2a51562..76e8a08f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -332,8 +332,16 @@ impl fmt::Display for Bound { match (&*data.node, data.n_children_yielded) { (Bound::Free(ref s), _) => f.write_str(s)?, (Bound::Complete(ref comp), _) => fmt::Display::fmt(comp, f)?, - (Bound::Sum(..), 0) | (Bound::Product(..), 0) => f.write_str("(")?, - (Bound::Sum(..), 2) | (Bound::Product(..), 2) => f.write_str(")")?, + (Bound::Sum(..), 0) | (Bound::Product(..), 0) => { + if data.index > 0 { + f.write_str("(")? + } + } + (Bound::Sum(..), 2) | (Bound::Product(..), 2) => { + if data.index > 0 { + f.write_str(")")? + } + } (Bound::Sum(..), _) => f.write_str(" + ")?, (Bound::Product(..), _) => f.write_str(" × ")?, }