Skip to content

Commit d67fb00

Browse files
committed
Merge #722: Eliminate a bunch more recursion; expand the TreeLike iterator trait a bit
d0c0c14 miniscript: nonrecursive implementation of PartialEq/Eq/Hash (Andrew Poelstra) 117003f miniscript: implement PartialOrd/Ord nonrecursively (Andrew Poelstra) cccaaec miniscript: non-recursive Display implementation (Andrew Poelstra) 47bed0c iter: get rid of Arc::clones and allocations for n-ary nodes (Andrew Poelstra) 67d6ff7 iter: introduce Ternary variant (Andrew Poelstra) Pull request description: This PR replaces the recursive derived implementations of `PartialEq`, `PartialOrd`, `Hash`, `fmt::Debug` and `fmt::Display`. Along the way it expands the `iter::TreeLike` trait to make it a bit more useful (adding a `Ternary` variant and making the existing `Nary` variant generic as long as you can get a length out of it and index into it). The new `fmt::Debug` method works using a new `DisplayNode` wrapper around `Terminal` which allows iterating over a script in the same way as it's displayed, treating things like `or_i(0, X)` as `l:X` and implementing the `c:pk_k` aliases and so on. This seems generally useful but this PR does not expose it in the public API because I'd like to make some breaking changes to `Terminal` down the line and don't want to expand the API. The new `Ord` impl orders things alphabetically (assuming it is bug-free at least) by using the same iterator as the display logic. This is both nonrecursive and a more useful ordering for anybody who cares about the exact ordering. On the other hand, if anybody is depending on the *existing* ordering this will break their code. I would assume not, since the existing ordering is a weird ad-hoc thing based on the order that `derive(PartialOrd)` happens to use. Similarly, it adds a `Terminal::fragment_name` accessor which returns a `&'static str` representing the fragment name as displayed. This is also not put into the public API. The block of type parameters used in the debug output of Miniscripts is moved into a `fmt::Display` impl on `Type` itself. This is part of the public API. This PR does **not** replace the `Clone` impl, which is currently derived. This impl actually isn't recursive, since it just clones the `Arc`s in the first layer of the script. Maybe we want to implement this manually and do a "deep copy"? This would be useful for users who have keys with interior mutability or something. I have a followup which does this, but didn't include it because I felt it might be controversial and need its own discussion. This PR also does **not** replace the implicit `Drop` impl, because that's hard to do and I haven't done it. But we're making progress. ACKs for top commit: sanket1729: ACK d0c0c14. Tree-SHA512: edc42b5ed7d2b562e93d3c061328c2020f710534a5cad3878b849d900d697be4919232e1f1c178fdc5338bd2e6ef71ebcfed90b2b8b8e577d8f781d887771bc1
2 parents b729fdd + d0c0c14 commit d67fb00

File tree

9 files changed

+622
-272
lines changed

9 files changed

+622
-272
lines changed

src/iter/mod.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ use crate::sync::Arc;
1818
use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal};
1919

2020
impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk, Ctx> {
21-
fn as_node(&self) -> Tree<Self> {
21+
type NaryChildren = &'a [Arc<Miniscript<Pk, Ctx>>];
22+
23+
fn nary_len(tc: &Self::NaryChildren) -> usize { tc.len() }
24+
fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self { Arc::as_ref(&tc[idx]) }
25+
26+
fn as_node(&self) -> Tree<Self, Self::NaryChildren> {
2227
use Terminal::*;
2328
match self.node {
2429
PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..)
@@ -36,14 +41,19 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk,
3641
| OrD(ref left, ref right)
3742
| OrC(ref left, ref right)
3843
| OrI(ref left, ref right) => Tree::Binary(left, right),
39-
AndOr(ref a, ref b, ref c) => Tree::Nary(Arc::from([a.as_ref(), b, c])),
40-
Thresh(ref thresh) => Tree::Nary(thresh.iter().map(Arc::as_ref).collect()),
44+
AndOr(ref a, ref b, ref c) => Tree::Ternary(a, b, c),
45+
Thresh(ref thresh) => Tree::Nary(thresh.data()),
4146
}
4247
}
4348
}
4449

45-
impl<Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for Arc<Miniscript<Pk, Ctx>> {
46-
fn as_node(&self) -> Tree<Self> {
50+
impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc<Miniscript<Pk, Ctx>> {
51+
type NaryChildren = &'a [Arc<Miniscript<Pk, Ctx>>];
52+
53+
fn nary_len(tc: &Self::NaryChildren) -> usize { tc.len() }
54+
fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self { &tc[idx] }
55+
56+
fn as_node(&self) -> Tree<Self, Self::NaryChildren> {
4757
use Terminal::*;
4858
match self.node {
4959
PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..)
@@ -54,17 +64,45 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for Arc<Miniscript<Pk, Ctx>
5464
| DupIf(ref sub)
5565
| Verify(ref sub)
5666
| NonZero(ref sub)
57-
| ZeroNotEqual(ref sub) => Tree::Unary(Arc::clone(sub)),
67+
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
68+
AndV(ref left, ref right)
69+
| AndB(ref left, ref right)
70+
| OrB(ref left, ref right)
71+
| OrD(ref left, ref right)
72+
| OrC(ref left, ref right)
73+
| OrI(ref left, ref right) => Tree::Binary(left, right),
74+
AndOr(ref a, ref b, ref c) => Tree::Ternary(a, b, c),
75+
Thresh(ref thresh) => Tree::Nary(thresh.data()),
76+
}
77+
}
78+
}
79+
80+
impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal<Pk, Ctx> {
81+
type NaryChildren = &'a [Arc<Miniscript<Pk, Ctx>>];
82+
83+
fn nary_len(tc: &Self::NaryChildren) -> usize { tc.len() }
84+
fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self { tc[idx].as_inner() }
85+
86+
fn as_node(&self) -> Tree<Self, Self::NaryChildren> {
87+
use Terminal::*;
88+
match self {
89+
PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..)
90+
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary,
91+
Alt(ref sub)
92+
| Swap(ref sub)
93+
| Check(ref sub)
94+
| DupIf(ref sub)
95+
| Verify(ref sub)
96+
| NonZero(ref sub)
97+
| ZeroNotEqual(ref sub) => Tree::Unary(sub.as_inner()),
5898
AndV(ref left, ref right)
5999
| AndB(ref left, ref right)
60100
| OrB(ref left, ref right)
61101
| OrD(ref left, ref right)
62102
| OrC(ref left, ref right)
63-
| OrI(ref left, ref right) => Tree::Binary(Arc::clone(left), Arc::clone(right)),
64-
AndOr(ref a, ref b, ref c) => {
65-
Tree::Nary(Arc::from([Arc::clone(a), Arc::clone(b), Arc::clone(c)]))
66-
}
67-
Thresh(ref thresh) => Tree::Nary(thresh.iter().map(Arc::clone).collect()),
103+
| OrI(ref left, ref right) => Tree::Binary(left.as_inner(), right.as_inner()),
104+
AndOr(ref a, ref b, ref c) => Tree::Ternary(a.as_inner(), b.as_inner(), c.as_inner()),
105+
Thresh(ref thresh) => Tree::Nary(thresh.data()),
68106
}
69107
}
70108
}

src/iter/tree.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,22 @@
77
//!
88
99
use crate::prelude::*;
10-
use crate::sync::Arc;
1110

1211
/// Abstract node of a tree.
1312
///
1413
/// Tracks the arity (out-degree) of a node, which is the only thing that
1514
/// is needed for iteration purposes.
16-
pub enum Tree<T> {
15+
pub enum Tree<T, NT> {
1716
/// Combinator with no children.
1817
Nullary,
1918
/// Combinator with one child.
2019
Unary(T),
2120
/// Combinator with two children.
2221
Binary(T, T),
22+
/// Combinator with two children.
23+
Ternary(T, T, T),
2324
/// Combinator with more than two children.
24-
Nary(Arc<[T]>),
25+
Nary(NT),
2526
}
2627

2728
/// A trait for any structure which has the shape of a Miniscript tree.
@@ -30,22 +31,36 @@ pub enum Tree<T> {
3031
/// rather than nodes themselves, because it provides algorithms that
3132
/// assume copying is cheap.
3233
///
33-
/// To implement this trait, you only need to implement the [`TreeLike::as_node`]
34-
/// method, which will usually be very mechanical. Everything else is provided.
35-
/// However, to avoid allocations, it may make sense to also implement
36-
/// [`TreeLike::n_children`] and [`TreeLike::nth_child`] because the default
37-
/// implementations will allocate vectors for n-ary nodes.
34+
/// To implement this trait, you only need to implement the [`TreeLike::as_node`],
35+
/// [`TreeLike::nary_len`] and `[TreeLike::nary_index'] methods, which should
36+
/// be very mechanical. Everything else is provided.
3837
pub trait TreeLike: Clone + Sized {
38+
/// An abstraction over the children of n-ary nodes. Typically when
39+
/// implementing the trait for `&a T` this will be `&'a [T]`.
40+
type NaryChildren: Clone;
41+
42+
/// Accessor for the length of a [`Self::NaryChildren`].
43+
fn nary_len(tc: &Self::NaryChildren) -> usize;
44+
45+
/// Accessor for a specific child of a [`Self::NaryChildren`].
46+
///
47+
/// # Panics
48+
///
49+
/// May panic if asked for an element outside of the range
50+
/// `0..Self::nary_len(&tc)`.
51+
fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self;
52+
3953
/// Interpret the node as an abstract node.
40-
fn as_node(&self) -> Tree<Self>;
54+
fn as_node(&self) -> Tree<Self, Self::NaryChildren>;
4155

4256
/// Accessor for the number of children this node has.
4357
fn n_children(&self) -> usize {
4458
match self.as_node() {
4559
Tree::Nullary => 0,
4660
Tree::Unary(..) => 1,
4761
Tree::Binary(..) => 2,
48-
Tree::Nary(children) => children.len(),
62+
Tree::Ternary(..) => 3,
63+
Tree::Nary(ref children) => Self::nary_len(children),
4964
}
5065
}
5166

@@ -58,7 +73,14 @@ pub trait TreeLike: Clone + Sized {
5873
(0, Tree::Binary(sub, _)) => Some(sub),
5974
(1, Tree::Binary(_, sub)) => Some(sub),
6075
(_, Tree::Binary(..)) => None,
61-
(n, Tree::Nary(children)) => children.get(n).cloned(),
76+
(0, Tree::Ternary(sub, _, _)) => Some(sub),
77+
(1, Tree::Ternary(_, sub, _)) => Some(sub),
78+
(2, Tree::Ternary(_, _, sub)) => Some(sub),
79+
(_, Tree::Ternary(..)) => None,
80+
(n, Tree::Nary(children)) if n < Self::nary_len(&children) => {
81+
Some(Self::nary_index(children, n))
82+
}
83+
(_, Tree::Nary(..)) => None,
6284
}
6385
}
6486

@@ -210,8 +232,15 @@ impl<T: TreeLike> Iterator for PreOrderIter<T> {
210232
self.stack.push(right);
211233
self.stack.push(left);
212234
}
235+
Tree::Ternary(a, b, c) => {
236+
self.stack.push(c);
237+
self.stack.push(b);
238+
self.stack.push(a);
239+
}
213240
Tree::Nary(children) => {
214-
self.stack.extend(children.iter().rev().cloned());
241+
for i in (0..T::nary_len(&children)).rev() {
242+
self.stack.push(T::nary_index(children.clone(), i));
243+
}
215244
}
216245
}
217246
Some(top)

0 commit comments

Comments
 (0)