Skip to content

Commit

Permalink
expression: hide all Tree fields and encapsulate API
Browse files Browse the repository at this point in the history
This removes the ability to randomly access children of tree nodes. You
can get them in order using the `children` iterator, and get them
recursively using the `TreeLike` iterator methods. (In the next commits
we will specialize these a bit, providing a `pre_order_iter` and
`rtl_post_order_iter` which let you efficiently skip over subtrees. We
will need these to parse Taproot expression trees and they don't fit
into the `TreeLike` trait, at least not efficiently.)
  • Loading branch information
apoelstra committed Nov 27, 2024
1 parent 11ccd87 commit e1349c6
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ impl Descriptor<DefiniteDescriptorKey> {
impl<Pk: FromStrKey> crate::expression::FromTree for Descriptor<Pk> {
/// Parse an expression tree into a descriptor.
fn from_tree(top: &expression::Tree) -> Result<Descriptor<Pk>, Error> {
Ok(match (top.name, top.args.len() as u32) {
Ok(match (top.name(), top.n_children()) {
("pkh", 1) => Descriptor::Pkh(Pkh::from_tree(top)?),
("wpkh", 1) => Descriptor::Wpkh(Wpkh::from_tree(top)?),
("sh", 1) => Descriptor::Sh(Sh::from_tree(top)?),
Expand Down
2 changes: 1 addition & 1 deletion src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Wsh<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?;

if top.name == "sortedmulti" {
if top.name() == "sortedmulti" {
return Ok(Wsh { inner: WshInner::SortedMulti(SortedMultiVec::from_tree(top)?) });
}
let sub = Miniscript::from_tree(top)?;
Expand Down
2 changes: 1 addition & 1 deletion src/descriptor/sh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Sh<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?;

let inner = match top.name {
let inner = match top.name() {
"wsh" => ShInner::Wsh(Wsh::from_tree(top)?),
"wpkh" => ShInner::Wpkh(Wpkh::from_tree(top)?),
"sortedmulti" => ShInner::SortedMulti(SortedMultiVec::from_tree(top)?),
Expand Down
12 changes: 6 additions & 6 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,20 +532,20 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
} else {
// From here on we are into the taptree.
if item.n_children_yielded == 0 {
match item.node.parens {
match item.node.parens() {
Parens::Curly => {
if !item.node.name.is_empty() {
if !item.node.name().is_empty() {
return Err(Error::Parse(ParseError::Tree(
ParseTreeError::IncorrectName {
actual: item.node.name.to_owned(),
actual: item.node.name().to_owned(),
expected: "",
},
)));
}
if round_paren_depth > 0 {
return Err(Error::Parse(ParseError::Tree(
ParseTreeError::IllegalCurlyBrace {
pos: item.node.children_pos,
pos: item.node.children_pos(),
},
)));
}
Expand All @@ -555,7 +555,7 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
}
}
if item.is_complete {
if item.node.parens == Parens::Curly {
if item.node.parens() == Parens::Curly {
if item.n_children_yielded == 2 {
let rchild = tree_stack.pop().unwrap();
let lchild = tree_stack.pop().unwrap();
Expand All @@ -571,7 +571,7 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
)));
}
} else {
if item.node.parens == Parens::Round {
if item.node.parens() == Parens::Round {
round_paren_depth -= 1;
}
if round_paren_depth == 0 {
Expand Down
51 changes: 39 additions & 12 deletions src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVW
/// A token of the form `x(...)` or `x`
pub struct Tree<'a> {
/// The name `x`
pub name: &'a str,
name: &'a str,
/// Position one past the last character of the node's name. If it has
/// children, the position of the '(' or '{'.
pub children_pos: usize,
children_pos: usize,
/// The type of parentheses surrounding the node's children.
pub parens: Parens,
parens: Parens,
/// The comma-separated contents of the `(...)`, if any
pub args: Vec<Tree<'a>>,
args: Vec<Tree<'a>>,
}

impl PartialEq for Tree<'_> {
Expand Down Expand Up @@ -79,6 +79,32 @@ pub trait FromTree: Sized {
}

impl<'a> Tree<'a> {
/// The name of this tree node.
pub fn name(&self) -> &str { self.name }

/// The 0-indexed byte-position of the name in the original expression tree.
pub fn name_pos(&self) -> usize { self.children_pos - self.name.len() - 1 }

/// The 0-indexed byte-position of the '(' or '{' character which starts the
/// expression's children.
///
/// If the expression has no children, returns one past the end of the name.
pub fn children_pos(&self) -> usize { self.children_pos - self.name.len() - 1 }

/// The number of children this node has.
pub fn n_children(&self) -> usize { self.args.len() }

/// The type of parenthesis surrounding this node's children.
///
/// If the node has no children, this will be `Parens::None`.
pub fn parens(&self) -> Parens { self.parens }

/// An iterator over the direct children of this node.
///
/// If you want to iterate recursively, use the [`TreeLike`] API which
/// provides methods `pre_order_iter` and `post_order_iter`.
pub fn children(&self) -> impl ExactSizeIterator<Item = &Self> { self.args.iter() }

/// Split the name by a separating character.
///
/// If the separator is present, returns the prefix before the separator and
Expand Down Expand Up @@ -260,20 +286,21 @@ impl<'a> Tree<'a> {
&self,
mut map_child: F,
) -> Result<Threshold<T, MAX>, E> {
let mut child_iter = self.children();
let kchild = match child_iter.next() {
Some(k) => k,
None => return Err(ParseThresholdError::NoChildren.into()),
};
// First, special case "no arguments" so we can index the first argument without panics.
if self.args.is_empty() {
return Err(ParseThresholdError::NoChildren.into());
}

if !self.args[0].args.is_empty() {
if kchild.n_children() > 0 {
return Err(ParseThresholdError::KNotTerminal.into());
}

let k = parse_num(self.args[0].name).map_err(ParseThresholdError::ParseK)? as usize;
Threshold::new(k, vec![(); self.args.len() - 1])
let k = parse_num(kchild.name()).map_err(ParseThresholdError::ParseK)? as usize;
Threshold::new(k, vec![(); self.n_children() - 1])
.map_err(ParseThresholdError::Threshold)
.map_err(From::from)
.and_then(|thresh| thresh.translate_by_index(|i| map_child(&self.args[1 + i])))
.and_then(|thresh| thresh.translate_by_index(|_| map_child(child_iter.next().unwrap())))
}

/// Check that a tree has no curly-brace children in it.
Expand Down
14 changes: 9 additions & 5 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,14 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Termina
top.verify_n_children("andor", 3..=3)
.map_err(From::from)
.map_err(Error::Parse)?;
let x = Arc::<Miniscript<Pk, Ctx>>::from_tree(&top.args[0])?;
let y = Arc::<Miniscript<Pk, Ctx>>::from_tree(&top.args[1])?;
let z = Arc::<Miniscript<Pk, Ctx>>::from_tree(&top.args[2])?;
Ok(Terminal::AndOr(x, y, z))
let mut child_iter = top
.children()
.map(|x| Arc::<Miniscript<Pk, Ctx>>::from_tree(x));
Ok(Terminal::AndOr(
child_iter.next().unwrap()?,
child_iter.next().unwrap()?,
child_iter.next().unwrap()?,
))
}
"or_b" => binary(top, "or_b", Terminal::OrB),
"or_d" => binary(top, "or_d", Terminal::OrD),
Expand All @@ -137,7 +141,7 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Termina

if frag_wrap == Some("") {
return Err(Error::Parse(crate::ParseError::Tree(
crate::ParseTreeError::UnknownName { name: top.name.to_owned() },
crate::ParseTreeError::UnknownName { name: top.name().to_owned() },
)));
}
let ms = super::wrap_into_miniscript(unwrapped, frag_wrap.unwrap_or(""))?;
Expand Down
8 changes: 3 additions & 5 deletions src/policy/concrete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ impl<Pk: FromStrKey> Policy<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?
} else {
(None, top.name)
(None, top.name())
};

let frag_prob = match frag_prob {
Expand Down Expand Up @@ -906,8 +906,7 @@ impl<Pk: FromStrKey> Policy<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?;
let subs = top
.args
.iter()
.children()
.map(|arg| Self::from_tree(arg).map(Arc::new))
.collect::<Result<_, Error>>()?;
Ok(Policy::And(subs))
Expand All @@ -917,8 +916,7 @@ impl<Pk: FromStrKey> Policy<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?;
let subs = top
.args
.iter()
.children()
.map(|arg| {
Self::from_tree_prob(arg, true).map(|(prob, sub)| (prob, Arc::new(sub)))
})
Expand Down
8 changes: 3 additions & 5 deletions src/policy/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ serde_string_impl_pk!(Policy, "a miniscript semantic policy");

impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> {
fn from_tree(top: &expression::Tree) -> Result<Policy<Pk>, Error> {
match top.name {
match top.name() {
"UNSATISFIABLE" => {
top.verify_n_children("UNSATISFIABLE", 0..=0)
.map_err(From::from)
Expand Down Expand Up @@ -325,8 +325,7 @@ impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?;
let subs = top
.args
.iter()
.children()
.map(|arg| Self::from_tree(arg).map(Arc::new))
.collect::<Result<Vec<_>, Error>>()?;
Ok(Policy::Thresh(Threshold::new(subs.len(), subs).map_err(Error::Threshold)?))
Expand All @@ -336,8 +335,7 @@ impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> {
.map_err(From::from)
.map_err(Error::Parse)?;
let subs = top
.args
.iter()
.children()
.map(|arg| Self::from_tree(arg).map(Arc::new))
.collect::<Result<Vec<_>, Error>>()?;
Ok(Policy::Thresh(Threshold::new(1, subs).map_err(Error::Threshold)?))
Expand Down

0 comments on commit e1349c6

Please sign in to comment.