miniscript_debug/
expression.rs

1// Miniscript
2// Written in 2019 by
3//     Andrew Poelstra <apoelstra@wpsoftware.net>
4//
5// To the extent possible under law, the author(s) have dedicated all
6// copyright and related and neighboring rights to this software to
7// the public domain worldwide. This software is distributed without
8// any warranty.
9//
10// You should have received a copy of the CC0 Public Domain Dedication
11// along with this software.
12// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13//
14
15//! # Function-like Expression Language
16//!
17use core::str::FromStr;
18
19use crate::prelude::*;
20use crate::{errstr, Error, MAX_RECURSION_DEPTH};
21
22#[derive(Debug)]
23/// A token of the form `x(...)` or `x`
24pub struct Tree<'a> {
25    /// The name `x`
26    pub name: &'a str,
27    /// The comma-separated contents of the `(...)`, if any
28    pub args: Vec<Tree<'a>>,
29}
30// or_b(pk(A),pk(B))
31//
32// A = musig(musig(B,C),D,E)
33// or_b()
34// pk(A), pk(B)
35
36/// A trait for extracting a structure from a Tree representation in token form
37pub trait FromTree: Sized {
38    /// Extract a structure from Tree representation
39    fn from_tree(top: &Tree) -> Result<Self, Error>;
40}
41
42enum Found {
43    Nothing,
44    LBracket(usize), // Either a left ( or {
45    Comma(usize),
46    RBracket(usize), // Either a right ) or }
47}
48
49fn next_expr(sl: &str, delim: char) -> Found {
50    let mut found = Found::Nothing;
51    if delim == '(' {
52        for (n, ch) in sl.char_indices() {
53            match ch {
54                '(' => {
55                    found = Found::LBracket(n);
56                    break;
57                }
58                ',' => {
59                    found = Found::Comma(n);
60                    break;
61                }
62                ')' => {
63                    found = Found::RBracket(n);
64                    break;
65                }
66                _ => {}
67            }
68        }
69    } else if delim == '{' {
70        let mut new_count = 0;
71        for (n, ch) in sl.char_indices() {
72            match ch {
73                '{' => {
74                    found = Found::LBracket(n);
75                    break;
76                }
77                '(' => {
78                    new_count += 1;
79                }
80                ',' => {
81                    if new_count == 0 {
82                        found = Found::Comma(n);
83                        break;
84                    }
85                }
86                ')' => {
87                    new_count -= 1;
88                }
89                '}' => {
90                    found = Found::RBracket(n);
91                    break;
92                }
93                _ => {}
94            }
95        }
96    } else {
97        unreachable!("{}", "Internal: delimiters in parsing must be '(' or '{'");
98    }
99    found
100}
101
102// Get the corresponding delim
103fn closing_delim(delim: char) -> char {
104    match delim {
105        '(' => ')',
106        '{' => '}',
107        _ => unreachable!("Unknown delimiter"),
108    }
109}
110
111impl<'a> Tree<'a> {
112    /// Parse an expression with round brackets
113    pub fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> {
114        // Parsing TapTree or just miniscript
115        Self::from_slice_delim(sl, 0u32, '(')
116    }
117
118    pub(crate) fn from_slice_delim(
119        mut sl: &'a str,
120        depth: u32,
121        delim: char,
122    ) -> Result<(Tree<'a>, &'a str), Error> {
123        if depth >= MAX_RECURSION_DEPTH {
124            return Err(Error::MaxRecursiveDepthExceeded);
125        }
126
127        match next_expr(sl, delim) {
128            // String-ending terminal
129            Found::Nothing => Ok((
130                Tree {
131                    name: sl,
132                    args: vec![],
133                },
134                "",
135            )),
136            // Terminal
137            Found::Comma(n) | Found::RBracket(n) => Ok((
138                Tree {
139                    name: &sl[..n],
140                    args: vec![],
141                },
142                &sl[n..],
143            )),
144            // Function call
145            Found::LBracket(n) => {
146                let mut ret = Tree {
147                    name: &sl[..n],
148                    args: vec![],
149                };
150
151                sl = &sl[n + 1..];
152                loop {
153                    let (arg, new_sl) = Tree::from_slice_delim(sl, depth + 1, delim)?;
154                    ret.args.push(arg);
155
156                    if new_sl.is_empty() {
157                        return Err(Error::ExpectedChar(closing_delim(delim)));
158                    }
159
160                    sl = &new_sl[1..];
161                    match new_sl.as_bytes()[0] {
162                        b',' => {}
163                        last_byte => {
164                            if last_byte == closing_delim(delim) as u8 {
165                                break;
166                            } else {
167                                return Err(Error::ExpectedChar(closing_delim(delim)));
168                            }
169                        }
170                    }
171                }
172                Ok((ret, sl))
173            }
174        }
175    }
176
177    /// Parses a tree from a string
178    #[allow(clippy::should_implement_trait)] // Cannot use std::str::FromStr because of lifetimes.
179    pub fn from_str(s: &'a str) -> Result<Tree<'a>, Error> {
180        // Filter out non-ASCII because we byte-index strings all over the
181        // place and Rust gets very upset when you splinch a string.
182        for ch in s.bytes() {
183            if !ch.is_ascii() {
184                return Err(Error::Unprintable(ch));
185            }
186        }
187
188        let (top, rem) = Tree::from_slice(s)?;
189        if rem.is_empty() {
190            Ok(top)
191        } else {
192            Err(errstr(rem))
193        }
194    }
195}
196
197/// Parse a string as a u32, for timelocks or thresholds
198pub fn parse_num(s: &str) -> Result<u32, Error> {
199    if s.len() > 1 {
200        let ch = s.chars().next().unwrap();
201        if !('1'..='9').contains(&ch) {
202            return Err(Error::Unexpected(
203                "Number must start with a digit 1-9".to_string(),
204            ));
205        }
206    }
207    u32::from_str(s).map_err(|_| errstr(s))
208}
209
210/// Attempts to parse a terminal expression
211pub fn terminal<T, F, Err>(term: &Tree, convert: F) -> Result<T, Error>
212where
213    F: FnOnce(&str) -> Result<T, Err>,
214    Err: ToString,
215{
216    if term.args.is_empty() {
217        convert(term.name).map_err(|e| Error::Unexpected(e.to_string()))
218    } else {
219        Err(errstr(term.name))
220    }
221}
222
223/// Attempts to parse an expression with exactly one child
224pub fn unary<L, T, F>(term: &Tree, convert: F) -> Result<T, Error>
225where
226    L: FromTree,
227    F: FnOnce(L) -> T,
228{
229    if term.args.len() == 1 {
230        let left = FromTree::from_tree(&term.args[0])?;
231        Ok(convert(left))
232    } else {
233        Err(errstr(term.name))
234    }
235}
236
237/// Attempts to parse an expression with exactly two children
238pub fn binary<L, R, T, F>(term: &Tree, convert: F) -> Result<T, Error>
239where
240    L: FromTree,
241    R: FromTree,
242    F: FnOnce(L, R) -> T,
243{
244    if term.args.len() == 2 {
245        let left = FromTree::from_tree(&term.args[0])?;
246        let right = FromTree::from_tree(&term.args[1])?;
247        Ok(convert(left, right))
248    } else {
249        Err(errstr(term.name))
250    }
251}
252
253#[cfg(test)]
254mod tests {
255
256    use super::parse_num;
257
258    #[test]
259    fn test_parse_num() {
260        assert!(parse_num("0").is_ok());
261        assert!(parse_num("00").is_err());
262        assert!(parse_num("0000").is_err());
263        assert!(parse_num("06").is_err());
264        assert!(parse_num("+6").is_err());
265        assert!(parse_num("-6").is_err());
266    }
267}