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