Skip to content

Commit cccaaec

Browse files
committed
miniscript: non-recursive Display implementation
This uses the new iterator extensions to implement a `DisplayNode` wrapper around a `Terminal` which understands that pubkeys, threshold k values, etc., count as "children" for display purposes. We can then embed all the alias/wrapper logic in the `as_node` method and the new `fragment_name` method. The latter is generally useful and ought to be public; given a fragment, it outputs its name, without recursing but with consideration of the aliasing rules. Previously this logic was embedded in the Terminal::condition_fmt method. The resulting display algorithm is much easier to follow, although it uses more lines of code (primarily in the form of large repetitive match statements). cargo bloat shows there is a slight reduction in code size attributed to the miniscript crate, though a slight increase in the total code size for an example binary which basically implements the `string_rtt` test. I think with some effort we should be able to reduce the code size of this algorithm. The important thing though is that it's not recursive. The exact results are as follows (though they have limited use without my committing the actual example program, which is a little ugly and not generally useful). Before: File .text Size Crate Name 0.0% 0.9% 7.3KiB miniscript miniscript::miniscript::astelem::<impl miniscript::expression::FromTree for miniscript::miniscript::decode::Terminal<Pk,Ctx>>::from_tree 0.0% 0.7% 5.8KiB miniscript miniscript::miniscript::types::Type::type_check 0.0% 0.6% 4.4KiB miniscript miniscript::miniscript::types::extra_props::ExtData::threshold 0.0% 0.5% 4.0KiB miniscript <miniscript::Error as core::fmt::Display>::fmt 0.0% 0.5% 4.0KiB miniscript miniscript::miniscript::astelem::<impl miniscript::miniscript::decode::Terminal<Pk,Ctx>>::conditional_fmt 0.0% 0.5% 3.7KiB miniscript miniscript::miniscript::wrap_into_miniscript 0.0% 0.4% 3.0KiB miniscript miniscript::miniscript::types::extra_props::ExtData::or_i 0.0% 0.4% 3.0KiB miniscript <miniscript::miniscript::decode::Terminal<Pk,Ctx> as core::cmp::PartialEq>::eq 0.0% 0.3% 2.8KiB miniscript <miniscript::miniscript::types::Error as core::fmt::Display>::fmt 0.0% 0.3% 2.5KiB miniscript miniscript::expression::Tree::from_slice_delim 0.0% 0.3% 2.4KiB miniscript miniscript::miniscript::types::extra_props::ExtData::type_check 0.0% 0.3% 2.4KiB miniscript alloc::collections::btree::node::BalancingContext<K,V>::bulk_steal_left 0.0% 0.3% 2.2KiB miniscript miniscript::miniscript::split_expression_name 0.0% 0.3% 2.2KiB miniscript <miniscript::Error as core::fmt::Debug>::fmt 0.0% 0.3% 2.1KiB miniscript miniscript::miniscript::types::extra_props::ExtData::and_or 0.0% 0.2% 1.9KiB miniscript miniscript::miniscript::types::extra_props::ExtData::or_b 0.0% 0.2% 1.9KiB miniscript core::slice::sort::merge 0.0% 0.2% 1.8KiB miniscript core::slice::sort::merge 0.0% 0.2% 1.8KiB miniscript core::slice::sort::merge 0.0% 0.2% 1.8KiB miniscript core::slice::sort::merge 0.7% 33.0% 262.1KiB And 1454 smaller methods. Use -n N to show more. 0.9% 40.7% 323.2KiB filtered data size, the file size is 34.8MiB After: File .text Size Crate Name 0.0% 0.9% 7.3KiB miniscript miniscript::miniscript::astelem::<impl miniscript::expression::FromTree for miniscript::miniscript::decode::Terminal<Pk,Ctx>>::from_tree 0.0% 0.7% 5.8KiB miniscript miniscript::miniscript::types::Type::type_check 0.0% 0.7% 5.7KiB miniscript miniscript::miniscript::display::<impl miniscript::miniscript::decode::Terminal<Pk,Ctx>>::conditional_fmt 0.0% 0.6% 4.4KiB miniscript miniscript::miniscript::types::extra_props::ExtData::threshold 0.0% 0.5% 4.3KiB miniscript <miniscript::miniscript::display::DisplayNode<Pk,Ctx> as miniscript::iter::tree::TreeLike>::as_node 0.0% 0.5% 4.0KiB miniscript <miniscript::Error as core::fmt::Display>::fmt 0.0% 0.5% 3.7KiB miniscript miniscript::miniscript::wrap_into_miniscript 0.0% 0.4% 3.0KiB miniscript miniscript::miniscript::types::extra_props::ExtData::or_i 0.0% 0.3% 2.8KiB miniscript <miniscript::miniscript::types::Error as core::fmt::Display>::fmt 0.0% 0.3% 2.5KiB miniscript miniscript::expression::Tree::from_slice_delim 0.0% 0.3% 2.4KiB miniscript miniscript::miniscript::types::extra_props::ExtData::type_check 0.0% 0.3% 2.4KiB miniscript alloc::collections::btree::node::BalancingContext<K,V>::bulk_steal_left 0.0% 0.3% 2.2KiB miniscript miniscript::miniscript::split_expression_name 0.0% 0.3% 2.2KiB miniscript <miniscript::Error as core::fmt::Debug>::fmt 0.0% 0.3% 2.1KiB miniscript miniscript::miniscript::types::extra_props::ExtData::and_or 0.0% 0.2% 1.9KiB miniscript miniscript::miniscript::types::extra_props::ExtData::or_b 0.0% 0.2% 1.9KiB miniscript core::slice::sort::merge 0.0% 0.2% 1.8KiB miniscript core::slice::sort::merge 0.0% 0.2% 1.8KiB miniscript core::slice::sort::merge 0.0% 0.2% 1.8KiB miniscript core::slice::sort::merge 0.7% 32.2% 256.3KiB And 1423 smaller methods. Use -n N to show more. 0.9% 40.2% 320.4KiB filtered data size, the file size is 35.2MiB
1 parent 47bed0c commit cccaaec

File tree

4 files changed

+354
-227
lines changed

4 files changed

+354
-227
lines changed

src/miniscript/astelem.rs

Lines changed: 1 addition & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -7,238 +7,21 @@
77
//! encoding in Bitcoin script, as well as a datatype. Full details
88
//! are given on the Miniscript website.
99
10-
use core::fmt;
1110
use core::str::FromStr;
1211

1312
use bitcoin::hashes::{hash160, Hash};
1413
use bitcoin::{absolute, opcodes, script};
1514
use sync::Arc;
1615

1716
use crate::miniscript::context::SigType;
18-
use crate::miniscript::{types, ScriptContext};
17+
use crate::miniscript::ScriptContext;
1918
use crate::prelude::*;
2019
use crate::util::MsKeyBuilder;
2120
use crate::{
2221
expression, AbsLockTime, Error, FromStrKey, Miniscript, MiniscriptKey, RelLockTime, Terminal,
2322
ToPublicKey,
2423
};
2524

26-
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
27-
/// Internal helper function for displaying wrapper types; returns
28-
/// a character to display before the `:` as well as a reference
29-
/// to the wrapped type to allow easy recursion
30-
fn wrap_char(&self) -> Option<(char, &Arc<Miniscript<Pk, Ctx>>)> {
31-
match *self {
32-
Terminal::Alt(ref sub) => Some(('a', sub)),
33-
Terminal::Swap(ref sub) => Some(('s', sub)),
34-
Terminal::Check(ref sub) => Some(('c', sub)),
35-
Terminal::DupIf(ref sub) => Some(('d', sub)),
36-
Terminal::Verify(ref sub) => Some(('v', sub)),
37-
Terminal::NonZero(ref sub) => Some(('j', sub)),
38-
Terminal::ZeroNotEqual(ref sub) => Some(('n', sub)),
39-
Terminal::AndV(ref sub, ref r) if r.node == Terminal::True => Some(('t', sub)),
40-
Terminal::OrI(ref sub, ref r) if r.node == Terminal::False => Some(('u', sub)),
41-
Terminal::OrI(ref l, ref sub) if l.node == Terminal::False => Some(('l', sub)),
42-
_ => None,
43-
}
44-
}
45-
46-
fn conditional_fmt(&self, f: &mut fmt::Formatter, is_debug: bool) -> fmt::Result {
47-
match *self {
48-
Terminal::PkK(ref pk) => fmt_1(f, "pk_k(", pk, is_debug),
49-
Terminal::PkH(ref pk) => fmt_1(f, "pk_h(", pk, is_debug),
50-
Terminal::RawPkH(ref pkh) => fmt_1(f, "expr_raw_pk_h(", pkh, is_debug),
51-
Terminal::After(ref t) => fmt_1(f, "after(", t, is_debug),
52-
Terminal::Older(ref t) => fmt_1(f, "older(", t, is_debug),
53-
Terminal::Sha256(ref h) => fmt_1(f, "sha256(", h, is_debug),
54-
Terminal::Hash256(ref h) => fmt_1(f, "hash256(", h, is_debug),
55-
Terminal::Ripemd160(ref h) => fmt_1(f, "ripemd160(", h, is_debug),
56-
Terminal::Hash160(ref h) => fmt_1(f, "hash160(", h, is_debug),
57-
Terminal::True => f.write_str("1"),
58-
Terminal::False => f.write_str("0"),
59-
Terminal::AndV(ref l, ref r) if r.node != Terminal::True => {
60-
fmt_2(f, "and_v(", l, r, is_debug)
61-
}
62-
Terminal::AndB(ref l, ref r) => fmt_2(f, "and_b(", l, r, is_debug),
63-
Terminal::AndOr(ref a, ref b, ref c) => {
64-
if c.node == Terminal::False {
65-
fmt_2(f, "and_b(", a, b, is_debug)
66-
} else {
67-
f.write_str("andor(")?;
68-
conditional_fmt(f, a, is_debug)?;
69-
f.write_str(",")?;
70-
conditional_fmt(f, b, is_debug)?;
71-
f.write_str(",")?;
72-
conditional_fmt(f, c, is_debug)?;
73-
f.write_str(")")
74-
}
75-
}
76-
Terminal::OrB(ref l, ref r) => fmt_2(f, "or_b(", l, r, is_debug),
77-
Terminal::OrD(ref l, ref r) => fmt_2(f, "or_d(", l, r, is_debug),
78-
Terminal::OrC(ref l, ref r) => fmt_2(f, "or_c(", l, r, is_debug),
79-
Terminal::OrI(ref l, ref r)
80-
if l.node != Terminal::False && r.node != Terminal::False =>
81-
{
82-
fmt_2(f, "or_i(", l, r, is_debug)
83-
}
84-
Terminal::Thresh(ref thresh) => {
85-
if is_debug {
86-
fmt::Debug::fmt(&thresh.debug("thresh", true), f)
87-
} else {
88-
fmt::Display::fmt(&thresh.display("thresh", true), f)
89-
}
90-
}
91-
Terminal::Multi(ref thresh) => {
92-
if is_debug {
93-
fmt::Debug::fmt(&thresh.debug("multi", true), f)
94-
} else {
95-
fmt::Display::fmt(&thresh.display("multi", true), f)
96-
}
97-
}
98-
Terminal::MultiA(ref thresh) => {
99-
if is_debug {
100-
fmt::Debug::fmt(&thresh.debug("multi_a", true), f)
101-
} else {
102-
fmt::Display::fmt(&thresh.display("multi_a", true), f)
103-
}
104-
}
105-
// wrappers
106-
_ => {
107-
if let Some((ch, sub)) = self.wrap_char() {
108-
if ch == 'c' {
109-
if let Terminal::PkK(ref pk) = sub.node {
110-
// alias: pk(K) = c:pk_k(K)
111-
return fmt_1(f, "pk(", pk, is_debug);
112-
} else if let Terminal::RawPkH(ref pkh) = sub.node {
113-
// `RawPkH` is currently unsupported in the descriptor spec
114-
// alias: pkh(K) = c:pk_h(K)
115-
// We temporarily display there using raw_pkh, but these descriptors
116-
// are not defined in the spec yet. These are prefixed with `expr`
117-
// in the descriptor string.
118-
// We do not support parsing these descriptors yet.
119-
return fmt_1(f, "expr_raw_pkh(", pkh, is_debug);
120-
} else if let Terminal::PkH(ref pk) = sub.node {
121-
// alias: pkh(K) = c:pk_h(K)
122-
return fmt_1(f, "pkh(", pk, is_debug);
123-
}
124-
}
125-
126-
fmt::Write::write_char(f, ch)?;
127-
match sub.node.wrap_char() {
128-
None => {
129-
f.write_str(":")?;
130-
}
131-
// Add a ':' wrapper if there are other wrappers apart from c:pk_k()
132-
// tvc:pk_k() -> tv:pk()
133-
Some(('c', ms)) => match ms.node {
134-
Terminal::PkK(_) | Terminal::PkH(_) | Terminal::RawPkH(_) => {
135-
f.write_str(":")?;
136-
}
137-
_ => {}
138-
},
139-
_ => {}
140-
};
141-
if is_debug {
142-
write!(f, "{:?}", sub)
143-
} else {
144-
write!(f, "{}", sub)
145-
}
146-
} else {
147-
unreachable!();
148-
}
149-
}
150-
}
151-
}
152-
}
153-
154-
fn fmt_1<D: fmt::Debug + fmt::Display>(
155-
f: &mut fmt::Formatter,
156-
name: &str,
157-
a: &D,
158-
is_debug: bool,
159-
) -> fmt::Result {
160-
f.write_str(name)?;
161-
conditional_fmt(f, a, is_debug)?;
162-
f.write_str(")")
163-
}
164-
fn fmt_2<D: fmt::Debug + fmt::Display>(
165-
f: &mut fmt::Formatter,
166-
name: &str,
167-
a: &D,
168-
b: &D,
169-
is_debug: bool,
170-
) -> fmt::Result {
171-
f.write_str(name)?;
172-
conditional_fmt(f, a, is_debug)?;
173-
f.write_str(",")?;
174-
conditional_fmt(f, b, is_debug)?;
175-
f.write_str(")")
176-
}
177-
fn conditional_fmt<D: fmt::Debug + fmt::Display>(
178-
f: &mut fmt::Formatter,
179-
data: &D,
180-
is_debug: bool,
181-
) -> fmt::Result {
182-
if is_debug {
183-
fmt::Debug::fmt(data, f)
184-
} else {
185-
fmt::Display::fmt(data, f)
186-
}
187-
}
188-
189-
impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Debug for Terminal<Pk, Ctx> {
190-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191-
fn fmt_type_map(f: &mut fmt::Formatter<'_>, type_map: types::Type) -> fmt::Result {
192-
f.write_str(match type_map.corr.base {
193-
types::Base::B => "B",
194-
types::Base::K => "K",
195-
types::Base::V => "V",
196-
types::Base::W => "W",
197-
})?;
198-
f.write_str("/")?;
199-
f.write_str(match type_map.corr.input {
200-
types::Input::Zero => "z",
201-
types::Input::One => "o",
202-
types::Input::OneNonZero => "on",
203-
types::Input::Any => "",
204-
types::Input::AnyNonZero => "n",
205-
})?;
206-
if type_map.corr.dissatisfiable {
207-
f.write_str("d")?;
208-
}
209-
if type_map.corr.unit {
210-
f.write_str("u")?;
211-
}
212-
f.write_str(match type_map.mall.dissat {
213-
types::Dissat::None => "f",
214-
types::Dissat::Unique => "e",
215-
types::Dissat::Unknown => "",
216-
})?;
217-
if type_map.mall.safe {
218-
f.write_str("s")?;
219-
}
220-
if type_map.mall.non_malleable {
221-
f.write_str("m")?;
222-
}
223-
Ok(())
224-
}
225-
226-
f.write_str("[")?;
227-
if let Ok(type_map) = types::Type::type_check(self) {
228-
fmt_type_map(f, type_map)?;
229-
} else {
230-
f.write_str("TYPECHECK FAILED")?;
231-
}
232-
f.write_str("]")?;
233-
234-
self.conditional_fmt(f, true)
235-
}
236-
}
237-
238-
impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Display for Terminal<Pk, Ctx> {
239-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.conditional_fmt(f, false) }
240-
}
241-
24225
impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Arc<Terminal<Pk, Ctx>> {
24326
fn from_tree(top: &expression::Tree) -> Result<Arc<Terminal<Pk, Ctx>>, Error> {
24427
Ok(Arc::new(expression::FromTree::from_tree(top)?))

0 commit comments

Comments
 (0)