Skip to content

Commit 853a01a

Browse files
committed
expression: rewrite parser to be non-recursive
This commit should yield no observable changes.
1 parent 20066bd commit 853a01a

File tree

2 files changed

+70
-136
lines changed

2 files changed

+70
-136
lines changed

src/descriptor/tr.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::policy::Liftable;
2323
use crate::prelude::*;
2424
use crate::util::{varint_len, witness_size};
2525
use crate::{
26-
errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold,
26+
Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold,
2727
ToPublicKey, TranslateErr, Translator,
2828
};
2929

@@ -504,10 +504,11 @@ impl<Pk: FromStrKey> Tr<Pk> {
504504
let right = Self::parse_tr_script_spend(&args[1])?;
505505
Ok(TapTree::combine(left, right))
506506
}
507-
_ => Err(Error::Unexpected(
507+
_ => {
508+
Err(Error::Unexpected(
508509
"unknown format for script spending paths while parsing taproot descriptor"
509510
.to_string(),
510-
)),
511+
))},
511512
}
512513
}
513514
}
@@ -608,12 +609,9 @@ fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
608609
return Err(Error::Unexpected("invalid taproot internal key".to_string()));
609610
}
610611
let internal_key = expression::Tree { name: key.name, args: vec![] };
611-
let (tree, rest) = expression::Tree::from_slice_delim(script, 1, '{')?;
612-
if rest.is_empty() {
613-
Ok(expression::Tree { name: "tr", args: vec![internal_key, tree] })
614-
} else {
615-
Err(errstr(rest))
616-
}
612+
let tree = expression::Tree::from_slice_delim(script, expression::Delimiter::Taproot)
613+
.map_err(Error::ParseTree)?;
614+
Ok(expression::Tree { name: "tr", args: vec![internal_key, tree] })
617615
} else {
618616
Err(Error::Unexpected("invalid taproot descriptor".to_string()))
619617
}

src/expression/mod.rs

Lines changed: 63 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -44,94 +44,34 @@ impl Eq for Tree<'_> {}
4444
// or_b()
4545
// pk(A), pk(B)
4646

47+
/// Whether to treat `{` and `}` as deliminators when parsing an expression.
48+
#[derive(Copy, Clone, PartialEq, Eq)]
49+
pub enum Delimiter {
50+
/// Use `(` and `)` as parentheses.
51+
NonTaproot,
52+
/// Use `{` and `}` as parentheses.
53+
Taproot,
54+
}
55+
4756
/// A trait for extracting a structure from a Tree representation in token form
4857
pub trait FromTree: Sized {
4958
/// Extract a structure from Tree representation
5059
fn from_tree(top: &Tree) -> Result<Self, Error>;
5160
}
5261

53-
enum Found {
54-
Nothing,
55-
LBracket(usize), // Either a left ( or {
56-
Comma(usize),
57-
RBracket(usize), // Either a right ) or }
58-
}
59-
60-
fn next_expr(sl: &str, delim: char) -> Found {
61-
let mut found = Found::Nothing;
62-
if delim == '(' {
63-
for (n, ch) in sl.char_indices() {
64-
match ch {
65-
'(' => {
66-
found = Found::LBracket(n);
67-
break;
68-
}
69-
',' => {
70-
found = Found::Comma(n);
71-
break;
72-
}
73-
')' => {
74-
found = Found::RBracket(n);
75-
break;
76-
}
77-
_ => {}
78-
}
79-
}
80-
} else if delim == '{' {
81-
let mut new_count = 0;
82-
for (n, ch) in sl.char_indices() {
83-
match ch {
84-
'{' => {
85-
found = Found::LBracket(n);
86-
break;
87-
}
88-
'(' => {
89-
new_count += 1;
90-
}
91-
',' => {
92-
if new_count == 0 {
93-
found = Found::Comma(n);
94-
break;
95-
}
96-
}
97-
')' => {
98-
new_count -= 1;
99-
}
100-
'}' => {
101-
found = Found::RBracket(n);
102-
break;
103-
}
104-
_ => {}
105-
}
106-
}
107-
} else {
108-
unreachable!("{}", "Internal: delimiters in parsing must be '(' or '{'");
109-
}
110-
found
111-
}
112-
113-
// Get the corresponding delim
114-
fn closing_delim(delim: char) -> char {
115-
match delim {
116-
'(' => ')',
117-
'{' => '}',
118-
_ => unreachable!("Unknown delimiter"),
119-
}
120-
}
121-
12262
impl<'a> Tree<'a> {
12363
/// Parse an expression with round brackets
124-
pub fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> {
125-
// Parsing TapTree or just miniscript
126-
Self::from_slice_delim(sl, 0u32, '(')
64+
pub fn from_slice(sl: &'a str) -> Result<Tree<'a>, ParseTreeError> {
65+
Self::from_slice_delim(sl, Delimiter::NonTaproot)
12766
}
12867

12968
/// Check that a string is a well-formed expression string, with optional
13069
/// checksum.
13170
///
132-
/// Returns the string with the checksum removed.
133-
fn parse_pre_check(s: &str, open: u8, close: u8) -> Result<&str, ParseTreeError> {
134-
// Do ASCII check first; after this we can use .bytes().enumerate() rather
71+
/// Returns the string with the checksum removed and its tree depth.
72+
fn parse_pre_check(s: &str, open: u8, close: u8) -> Result<(&str, usize), ParseTreeError> {
73+
// First, scan through string to make sure it is well-formed.
74+
// Do ASCII/checksum check first; after this we can use .bytes().enumerate() rather
13575
// than .char_indices(), which is *significantly* faster.
13676
let s = verify_checksum(s)?;
13777

@@ -211,68 +151,64 @@ impl<'a> Tree<'a> {
211151
});
212152
}
213153

214-
Ok(s)
154+
Ok((s, max_depth))
215155
}
216156

217-
pub(crate) fn from_slice_delim(
218-
mut sl: &'a str,
219-
depth: u32,
220-
delim: char,
221-
) -> Result<(Tree<'a>, &'a str), Error> {
222-
if depth == 0 {
223-
if delim == '{' {
224-
sl = Self::parse_pre_check(sl, b'{', b'}').map_err(Error::ParseTree)?;
225-
} else {
226-
sl = Self::parse_pre_check(sl, b'(', b')').map_err(Error::ParseTree)?;
157+
pub(crate) fn from_slice_delim(s: &'a str, delim: Delimiter) -> Result<Self, ParseTreeError> {
158+
let (oparen, cparen) = match delim {
159+
Delimiter::NonTaproot => (b'(', b')'),
160+
Delimiter::Taproot => (b'{', b'}'),
161+
};
162+
163+
// First, scan through string to make sure it is well-formed.
164+
let (s, max_depth) = Self::parse_pre_check(s, oparen, cparen)?;
165+
166+
// Now, knowing it is sane and well-formed, we can easily parse it backward,
167+
// which will yield a post-order right-to-left iterator of its nodes.
168+
let mut stack = Vec::with_capacity(max_depth);
169+
let mut children = None;
170+
let mut node_name_end = s.len();
171+
let mut tapleaf_depth = 0;
172+
for (pos, ch) in s.bytes().enumerate().rev() {
173+
if ch == cparen {
174+
stack.push(vec![]);
175+
node_name_end = pos;
176+
} else if tapleaf_depth == 0 && ch == b',' {
177+
let top = stack.last_mut().unwrap();
178+
let mut new_tree = Tree {
179+
name: &s[pos + 1..node_name_end],
180+
args: children.take().unwrap_or(vec![]),
181+
};
182+
new_tree.args.reverse();
183+
top.push(new_tree);
184+
node_name_end = pos;
185+
} else if ch == oparen {
186+
let mut top = stack.pop().unwrap();
187+
let mut new_tree = Tree {
188+
name: &s[pos + 1..node_name_end],
189+
args: children.take().unwrap_or(vec![]),
190+
};
191+
new_tree.args.reverse();
192+
top.push(new_tree);
193+
children = Some(top);
194+
node_name_end = pos;
195+
} else if delim == Delimiter::Taproot && ch == b'(' {
196+
tapleaf_depth += 1;
197+
} else if delim == Delimiter::Taproot && ch == b')' {
198+
tapleaf_depth -= 1;
227199
}
228200
}
229201

230-
match next_expr(sl, delim) {
231-
// String-ending terminal
232-
Found::Nothing => Ok((Tree { name: sl, args: vec![] }, "")),
233-
// Terminal
234-
Found::Comma(n) | Found::RBracket(n) => {
235-
Ok((Tree { name: &sl[..n], args: vec![] }, &sl[n..]))
236-
}
237-
// Function call
238-
Found::LBracket(n) => {
239-
let mut ret = Tree { name: &sl[..n], args: vec![] };
240-
241-
sl = &sl[n + 1..];
242-
loop {
243-
let (arg, new_sl) = Tree::from_slice_delim(sl, depth + 1, delim)?;
244-
ret.args.push(arg);
245-
246-
if new_sl.is_empty() {
247-
unreachable!()
248-
}
249-
250-
sl = &new_sl[1..];
251-
match new_sl.as_bytes()[0] {
252-
b',' => {}
253-
last_byte => {
254-
if last_byte == closing_delim(delim) as u8 {
255-
break;
256-
} else {
257-
unreachable!()
258-
}
259-
}
260-
}
261-
}
262-
Ok((ret, sl))
263-
}
264-
}
202+
assert_eq!(stack.len(), 0);
203+
let mut children = children.take().unwrap_or(vec![]);
204+
children.reverse();
205+
Ok(Tree { name: &s[..node_name_end], args: children })
265206
}
266207

267208
/// Parses a tree from a string
268209
#[allow(clippy::should_implement_trait)] // Cannot use std::str::FromStr because of lifetimes.
269210
pub fn from_str(s: &'a str) -> Result<Tree<'a>, Error> {
270-
let (top, rem) = Tree::from_slice(s)?;
271-
if rem.is_empty() {
272-
Ok(top)
273-
} else {
274-
unreachable!()
275-
}
211+
Self::from_slice_delim(s, Delimiter::NonTaproot).map_err(Error::ParseTree)
276212
}
277213

278214
/// Parses an expression tree as a threshold (a term with at least one child,

0 commit comments

Comments
 (0)