Skip to content

Commit 33a60e2

Browse files
committed
expression: pull MultiColon error into parsing logic, drop AtOutsideOr
In miniscript and in policy we had near-identical logic dealing with : and @ separators on certain nodes. The differences were: * In Miniscript we had special handling for aliases, where we would synthetically munge the wrappers (characters before ':'). This was unnecessary since we can just handle the aliases directly. (Because of our munging code, we did some extra error-checking to ensure that a PkK fragment always fits into a Check. It does. This checking is completely unnecessary.) * In Policy we forbade the @ character if we were outside of an Or context. Also unnecessary. The @ character does not appear in any other fragment, so the "unknown fragment" error is already sufficient. Removes two variants from the giant Error enum.
1 parent f7cb701 commit 33a60e2

File tree

6 files changed

+75
-94
lines changed

6 files changed

+75
-94
lines changed

src/expression/error.rs

+15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ pub enum ParseTreeError {
7676
/// The position of the opening curly brace.
7777
pos: usize,
7878
},
79+
/// Multiple separators (':' or '@') appeared in a node name.
80+
MultipleSeparators {
81+
/// The separator in question.
82+
separator: char,
83+
/// The position of the second separator.
84+
pos: usize,
85+
},
7986
/// Data occurred after the final ).
8087
TrailingCharacter {
8188
/// The first trailing character.
@@ -149,6 +156,13 @@ impl fmt::Display for ParseTreeError {
149156
}?;
150157
write!(f, ", but found {}", n_children)
151158
}
159+
ParseTreeError::MultipleSeparators { separator, pos } => {
160+
write!(
161+
f,
162+
"separator '{}' occured multiple times (second time at position {})",
163+
separator, pos
164+
)
165+
}
152166
ParseTreeError::TrailingCharacter { ch, pos } => {
153167
write!(f, "trailing data `{}...` (position {})", ch, pos)
154168
}
@@ -169,6 +183,7 @@ impl std::error::Error for ParseTreeError {
169183
| ParseTreeError::IllegalCurlyBrace { .. }
170184
| ParseTreeError::IncorrectName { .. }
171185
| ParseTreeError::IncorrectNumberOfChildren { .. }
186+
| ParseTreeError::MultipleSeparators { .. }
172187
| ParseTreeError::TrailingCharacter { .. }
173188
| ParseTreeError::UnknownName { .. } => None,
174189
}

src/expression/mod.rs

+19
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@ pub trait FromTree: Sized {
7979
}
8080

8181
impl<'a> Tree<'a> {
82+
/// Split the name by a separating character.
83+
///
84+
/// If the separator is present, returns the prefix before the separator and
85+
/// the suffix after the separator. Otherwise returns the whole name.
86+
///
87+
/// If the separator occurs multiple times, returns an error.
88+
pub fn name_separated(&self, separator: char) -> Result<(Option<&str>, &str), ParseTreeError> {
89+
let mut name_split = self.name.splitn(3, separator);
90+
match (name_split.next(), name_split.next(), name_split.next()) {
91+
(None, _, _) => unreachable!("'split' always yields at least one element"),
92+
(Some(_), None, _) => Ok((None, self.name)),
93+
(Some(prefix), Some(name), None) => Ok((Some(prefix), name)),
94+
(Some(_), Some(_), Some(suffix)) => Err(ParseTreeError::MultipleSeparators {
95+
separator,
96+
pos: self.children_pos - suffix.len() - 1,
97+
}),
98+
}
99+
}
100+
82101
/// Check that a tree node has the given number of children.
83102
///
84103
/// The `description` argument is only used to populate the error return,

src/lib.rs

-8
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,6 @@ pub enum Error {
427427
UnexpectedStart,
428428
/// Got something we were not expecting
429429
Unexpected(String),
430-
/// Name of a fragment contained `:` multiple times
431-
MultiColon(String),
432-
/// Name of a fragment contained `@` but we were not parsing an OR
433-
AtOutsideOr(String),
434430
/// Encountered a wrapping character that we don't recognize
435431
UnknownWrapper(char),
436432
/// Parsed a miniscript and the result was not of type T
@@ -500,8 +496,6 @@ impl fmt::Display for Error {
500496
Error::AddrP2shError(ref e) => fmt::Display::fmt(e, f),
501497
Error::UnexpectedStart => f.write_str("unexpected start of script"),
502498
Error::Unexpected(ref s) => write!(f, "unexpected «{}»", s),
503-
Error::MultiColon(ref s) => write!(f, "«{}» has multiple instances of «:»", s),
504-
Error::AtOutsideOr(ref s) => write!(f, "«{}» contains «@» in non-or() context", s),
505499
Error::UnknownWrapper(ch) => write!(f, "unknown wrapper «{}:»", ch),
506500
Error::NonTopLevel(ref s) => write!(f, "non-T miniscript: {}", s),
507501
Error::Trailing(ref s) => write!(f, "trailing tokens: {}", s),
@@ -553,8 +547,6 @@ impl std::error::Error for Error {
553547
| InvalidPush(_)
554548
| UnexpectedStart
555549
| Unexpected(_)
556-
| MultiColon(_)
557-
| AtOutsideOr(_)
558550
| UnknownWrapper(_)
559551
| NonTopLevel(_)
560552
| Trailing(_)

src/miniscript/astelem.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,28 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Termina
3737
})
3838
};
3939

40-
let (frag_name, frag_wrap) = super::split_expression_name(top.name)?;
40+
let (frag_wrap, frag_name) = top
41+
.name_separated(':')
42+
.map_err(From::from)
43+
.map_err(Error::Parse)?;
44+
// "pk" and "pkh" are aliases for "c:pk_k" and "c:pk_h" respectively.
4145
let unwrapped = match frag_name {
4246
"expr_raw_pkh" => top
4347
.verify_terminal_parent("expr_raw_pkh", "public key hash")
4448
.map(Terminal::RawPkH)
4549
.map_err(Error::Parse),
50+
"pk" => top
51+
.verify_terminal_parent("pk", "public key")
52+
.map(Terminal::PkK)
53+
.map_err(Error::Parse)
54+
.and_then(|term| Miniscript::from_ast(term))
55+
.map(|ms| Terminal::Check(Arc::new(ms))),
56+
"pkh" => top
57+
.verify_terminal_parent("pkh", "public key")
58+
.map(Terminal::PkH)
59+
.map_err(Error::Parse)
60+
.and_then(|term| Miniscript::from_ast(term))
61+
.map(|ms| Terminal::Check(Arc::new(ms))),
4662
"pk_k" => top
4763
.verify_terminal_parent("pk_k", "public key")
4864
.map(Terminal::PkK)
@@ -132,7 +148,13 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Termina
132148
name: x.to_owned(),
133149
}))),
134150
}?;
135-
let ms = super::wrap_into_miniscript(unwrapped, frag_wrap)?;
151+
152+
if frag_wrap == Some("") {
153+
return Err(Error::Parse(crate::ParseError::Tree(
154+
crate::ParseTreeError::UnknownName { name: top.name.to_owned() },
155+
)));
156+
}
157+
let ms = super::wrap_into_miniscript(unwrapped, frag_wrap.unwrap_or(""))?;
136158
Ok(ms.node)
137159
}
138160
}

src/miniscript/mod.rs

+1-59
Original file line numberDiff line numberDiff line change
@@ -670,71 +670,13 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
670670
}
671671
}
672672

673-
/// Utility function used when parsing a script from an expression tree.
674-
///
675-
/// Checks that the name of each fragment has at most one `:`, splits
676-
/// the name at the `:`, and implements aliases for the old `pk`/`pk_h`
677-
/// fragments.
678-
///
679-
/// Returns the fragment name (right of the `:`) and a list of wrappers
680-
/// (left of the `:`).
681-
fn split_expression_name(full_name: &str) -> Result<(&str, Cow<str>), Error> {
682-
let mut aliased_wrap;
683-
let frag_name;
684-
let frag_wrap;
685-
let mut name_split = full_name.split(':');
686-
match (name_split.next(), name_split.next(), name_split.next()) {
687-
(None, _, _) => {
688-
frag_name = "";
689-
frag_wrap = "".into();
690-
}
691-
(Some(name), None, _) => {
692-
if name == "pk" {
693-
frag_name = "pk_k";
694-
frag_wrap = "c".into();
695-
} else if name == "pkh" {
696-
frag_name = "pk_h";
697-
frag_wrap = "c".into();
698-
} else {
699-
frag_name = name;
700-
frag_wrap = "".into();
701-
}
702-
}
703-
(Some(wrap), Some(name), None) => {
704-
if wrap.is_empty() {
705-
return Err(Error::Parse(crate::ParseError::Tree(
706-
crate::ParseTreeError::UnknownName { name: full_name.to_owned() },
707-
)));
708-
}
709-
if name == "pk" {
710-
frag_name = "pk_k";
711-
aliased_wrap = wrap.to_owned();
712-
aliased_wrap.push('c');
713-
frag_wrap = aliased_wrap.into();
714-
} else if name == "pkh" {
715-
frag_name = "pk_h";
716-
aliased_wrap = wrap.to_owned();
717-
aliased_wrap.push('c');
718-
frag_wrap = aliased_wrap.into();
719-
} else {
720-
frag_name = name;
721-
frag_wrap = wrap.into();
722-
}
723-
}
724-
(Some(_), Some(_), Some(_)) => {
725-
return Err(Error::MultiColon(full_name.to_owned()));
726-
}
727-
}
728-
Ok((frag_name, frag_wrap))
729-
}
730-
731673
/// Utility function used when parsing a script from an expression tree.
732674
///
733675
/// Once a Miniscript fragment has been parsed into a terminal, apply any
734676
/// wrappers that were included in its name.
735677
fn wrap_into_miniscript<Pk, Ctx>(
736678
term: Terminal<Pk, Ctx>,
737-
frag_wrap: Cow<str>,
679+
frag_wrap: &str,
738680
) -> Result<Miniscript<Pk, Ctx>, Error>
739681
where
740682
Pk: MiniscriptKey,

src/policy/concrete.rs

+16-25
Original file line numberDiff line numberDiff line change
@@ -850,31 +850,22 @@ impl<Pk: FromStrKey> Policy<Pk> {
850850
top: &expression::Tree,
851851
allow_prob: bool,
852852
) -> Result<(usize, Policy<Pk>), Error> {
853-
let frag_prob;
854-
let frag_name;
855-
let mut name_split = top.name.split('@');
856-
match (name_split.next(), name_split.next(), name_split.next()) {
857-
(None, _, _) => {
858-
frag_prob = 1;
859-
frag_name = "";
860-
}
861-
(Some(name), None, _) => {
862-
frag_prob = 1;
863-
frag_name = name;
864-
}
865-
(Some(prob), Some(name), None) => {
866-
if !allow_prob {
867-
return Err(Error::AtOutsideOr(top.name.to_owned()));
868-
}
869-
frag_prob = expression::parse_num(prob)
870-
.map_err(crate::ParseError::Num)
871-
.map_err(Error::Parse)? as usize;
872-
frag_name = name;
873-
}
874-
(Some(_), Some(_), Some(_)) => {
875-
return Err(Error::MultiColon(top.name.to_owned()));
876-
}
877-
}
853+
// When 'allow_prob' is true we parse '@' signs out of node names.
854+
let (frag_prob, frag_name) = if allow_prob {
855+
top.name_separated('@')
856+
.map_err(From::from)
857+
.map_err(Error::Parse)?
858+
} else {
859+
(None, top.name)
860+
};
861+
862+
let frag_prob = match frag_prob {
863+
None => 1,
864+
Some(s) => expression::parse_num(s)
865+
.map_err(From::from)
866+
.map_err(Error::Parse)? as usize,
867+
};
868+
878869
match frag_name {
879870
"UNSATISFIABLE" => {
880871
top.verify_n_children("UNSATISFIABLE", 0..=0)

0 commit comments

Comments
 (0)