miniscript_qtum/
expression.rs

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