Skip to content

Commit 416c352

Browse files
committed
miniscript: factor out a couple parts of Terminal::from_tree
In a later commit we will rewrite Terminal::from_tree entirely. But the beginning and end (which parse out and then apply "wrappers", the modifiers added to miniscript fragments using `:`) will remain the same. Pull this logic out into a pair of helper functions. This also reduces the total amount of indendentation, reduces function size, and reduces the total number of variables in scope at once. So this is a useful refactor independent of any future work.
1 parent d2bbdf0 commit 416c352

File tree

2 files changed

+96
-66
lines changed

2 files changed

+96
-66
lines changed

src/miniscript/astelem.rs

Lines changed: 3 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -251,51 +251,8 @@ impl<Pk: crate::FromStrKey, Ctx: ScriptContext> crate::expression::FromTree
251251

252252
impl<Pk: crate::FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Terminal<Pk, Ctx> {
253253
fn from_tree(top: &expression::Tree) -> Result<Terminal<Pk, Ctx>, Error> {
254-
let mut aliased_wrap;
255-
let frag_name;
256-
let frag_wrap;
257-
let mut name_split = top.name.split(':');
258-
match (name_split.next(), name_split.next(), name_split.next()) {
259-
(None, _, _) => {
260-
frag_name = "";
261-
frag_wrap = "";
262-
}
263-
(Some(name), None, _) => {
264-
if name == "pk" {
265-
frag_name = "pk_k";
266-
frag_wrap = "c";
267-
} else if name == "pkh" {
268-
frag_name = "pk_h";
269-
frag_wrap = "c";
270-
} else {
271-
frag_name = name;
272-
frag_wrap = "";
273-
}
274-
}
275-
(Some(wrap), Some(name), None) => {
276-
if wrap.is_empty() {
277-
return Err(Error::Unexpected(top.name.to_owned()));
278-
}
279-
if name == "pk" {
280-
frag_name = "pk_k";
281-
aliased_wrap = wrap.to_owned();
282-
aliased_wrap.push('c');
283-
frag_wrap = &aliased_wrap;
284-
} else if name == "pkh" {
285-
frag_name = "pk_h";
286-
aliased_wrap = wrap.to_owned();
287-
aliased_wrap.push('c');
288-
frag_wrap = &aliased_wrap;
289-
} else {
290-
frag_name = name;
291-
frag_wrap = wrap;
292-
}
293-
}
294-
(Some(_), Some(_), Some(_)) => {
295-
return Err(Error::MultiColon(top.name.to_owned()));
296-
}
297-
}
298-
let mut unwrapped = match (frag_name, top.args.len()) {
254+
let (frag_name, frag_wrap) = super::split_expression_name(top.name)?;
255+
let unwrapped = match (frag_name, top.args.len()) {
299256
("expr_raw_pkh", 1) => expression::terminal(&top.args[0], |x| {
300257
hash160::Hash::from_str(x).map(Terminal::RawPkH)
301258
}),
@@ -389,27 +346,7 @@ impl<Pk: crate::FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for
389346
top.args.len(),
390347
))),
391348
}?;
392-
for ch in frag_wrap.chars().rev() {
393-
// Check whether the wrapper is valid under the current context
394-
let ms = Miniscript::from_ast(unwrapped)?;
395-
Ctx::check_global_validity(&ms)?;
396-
match ch {
397-
'a' => unwrapped = Terminal::Alt(Arc::new(ms)),
398-
's' => unwrapped = Terminal::Swap(Arc::new(ms)),
399-
'c' => unwrapped = Terminal::Check(Arc::new(ms)),
400-
'd' => unwrapped = Terminal::DupIf(Arc::new(ms)),
401-
'v' => unwrapped = Terminal::Verify(Arc::new(ms)),
402-
'j' => unwrapped = Terminal::NonZero(Arc::new(ms)),
403-
'n' => unwrapped = Terminal::ZeroNotEqual(Arc::new(ms)),
404-
't' => unwrapped = Terminal::AndV(Arc::new(ms), Arc::new(Miniscript::TRUE)),
405-
'u' => unwrapped = Terminal::OrI(Arc::new(ms), Arc::new(Miniscript::FALSE)),
406-
'l' => unwrapped = Terminal::OrI(Arc::new(Miniscript::FALSE), Arc::new(ms)),
407-
x => return Err(Error::UnknownWrapper(x)),
408-
}
409-
}
410-
// Check whether the unwrapped miniscript is valid under the current context
411-
let ms = Miniscript::from_ast(unwrapped)?;
412-
Ctx::check_global_validity(&ms)?;
349+
let ms = super::wrap_into_miniscript(unwrapped, frag_wrap)?;
413350
Ok(ms.node)
414351
}
415352
}

src/miniscript/mod.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,99 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
525525
}
526526
}
527527

528+
/// Utility function used when parsing a script from an expression tree.
529+
///
530+
/// Checks that the name of each fragment has at most one `:`, splits
531+
/// the name at the `:`, and implements aliases for the old `pk`/`pk_h`
532+
/// fragments.
533+
///
534+
/// Returns the fragment name (right of the `:`) and a list of wrappers
535+
/// (left of the `:`).
536+
fn split_expression_name(name: &str) -> Result<(&str, Cow<str>), Error> {
537+
let mut aliased_wrap;
538+
let frag_name;
539+
let frag_wrap;
540+
let mut name_split = name.split(':');
541+
match (name_split.next(), name_split.next(), name_split.next()) {
542+
(None, _, _) => {
543+
frag_name = "";
544+
frag_wrap = "".into();
545+
}
546+
(Some(name), None, _) => {
547+
if name == "pk" {
548+
frag_name = "pk_k";
549+
frag_wrap = "c".into();
550+
} else if name == "pkh" {
551+
frag_name = "pk_h";
552+
frag_wrap = "c".into();
553+
} else {
554+
frag_name = name;
555+
frag_wrap = "".into();
556+
}
557+
}
558+
(Some(wrap), Some(name), None) => {
559+
if wrap.is_empty() {
560+
return Err(Error::Unexpected(name.to_owned()));
561+
}
562+
if name == "pk" {
563+
frag_name = "pk_k";
564+
aliased_wrap = wrap.to_owned();
565+
aliased_wrap.push('c');
566+
frag_wrap = aliased_wrap.into();
567+
} else if name == "pkh" {
568+
frag_name = "pk_h";
569+
aliased_wrap = wrap.to_owned();
570+
aliased_wrap.push('c');
571+
frag_wrap = aliased_wrap.into();
572+
} else {
573+
frag_name = name;
574+
frag_wrap = wrap.into();
575+
}
576+
}
577+
(Some(_), Some(_), Some(_)) => {
578+
return Err(Error::MultiColon(name.to_owned()));
579+
}
580+
}
581+
Ok((frag_name, frag_wrap))
582+
}
583+
584+
/// Utility function used when parsing a script from an expression tree.
585+
///
586+
/// Once a Miniscript fragment has been parsed into a terminal, apply any
587+
/// wrappers that were included in its name.
588+
fn wrap_into_miniscript<Pk, Ctx>(
589+
term: Terminal<Pk, Ctx>,
590+
frag_wrap: Cow<str>,
591+
) -> Result<Miniscript<Pk, Ctx>, Error>
592+
where
593+
Pk: MiniscriptKey,
594+
Ctx: ScriptContext,
595+
{
596+
let mut unwrapped = term;
597+
for ch in frag_wrap.chars().rev() {
598+
// Check whether the wrapper is valid under the current context
599+
let ms = Miniscript::from_ast(unwrapped)?;
600+
Ctx::check_global_validity(&ms)?;
601+
match ch {
602+
'a' => unwrapped = Terminal::Alt(Arc::new(ms)),
603+
's' => unwrapped = Terminal::Swap(Arc::new(ms)),
604+
'c' => unwrapped = Terminal::Check(Arc::new(ms)),
605+
'd' => unwrapped = Terminal::DupIf(Arc::new(ms)),
606+
'v' => unwrapped = Terminal::Verify(Arc::new(ms)),
607+
'j' => unwrapped = Terminal::NonZero(Arc::new(ms)),
608+
'n' => unwrapped = Terminal::ZeroNotEqual(Arc::new(ms)),
609+
't' => unwrapped = Terminal::AndV(Arc::new(ms), Arc::new(Miniscript::TRUE)),
610+
'u' => unwrapped = Terminal::OrI(Arc::new(ms), Arc::new(Miniscript::FALSE)),
611+
'l' => unwrapped = Terminal::OrI(Arc::new(Miniscript::FALSE), Arc::new(ms)),
612+
x => return Err(Error::UnknownWrapper(x)),
613+
}
614+
}
615+
// Check whether the unwrapped miniscript is valid under the current context
616+
let ms = Miniscript::from_ast(unwrapped)?;
617+
Ctx::check_global_validity(&ms)?;
618+
Ok(ms)
619+
}
620+
528621
impl<Pk: crate::FromStrKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
529622
/// Attempt to parse an insane(scripts don't clear sanity checks)
530623
/// from string into a Miniscript representation.

0 commit comments

Comments
 (0)