|
5 | 5 |
|
6 | 6 | mod error; |
7 | 7 |
|
| 8 | +use core::ops; |
8 | 9 | use core::str::FromStr; |
9 | | -use core::{fmt, ops}; |
10 | 10 |
|
11 | | -pub use self::error::{ParseThresholdError, ParseTreeError}; |
| 11 | +pub use self::error::{ParseNumError, ParseThresholdError, ParseTreeError}; |
12 | 12 | use crate::blanket_traits::StaticDebugAndDisplay; |
13 | 13 | use crate::descriptor::checksum::verify_checksum; |
14 | 14 | use crate::iter::{self, TreeLike}; |
15 | 15 | use crate::prelude::*; |
16 | | -use crate::{errstr, Error, ParseError, Threshold, MAX_RECURSION_DEPTH}; |
| 16 | +use crate::{AbsLockTime, Error, ParseError, RelLockTime, Threshold, MAX_RECURSION_DEPTH}; |
17 | 17 |
|
18 | 18 | /// Allowed characters are descriptor strings. |
19 | 19 | pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; |
@@ -138,6 +138,38 @@ impl<'a> Tree<'a> { |
138 | 138 | } |
139 | 139 | } |
140 | 140 |
|
| 141 | + /// Check that a tree node has a single terminal child which is an absolute locktime. |
| 142 | + /// |
| 143 | + /// Returns an error assuming that the node is named "after". |
| 144 | + /// |
| 145 | + /// If so, parse the locktime from a string and return it. |
| 146 | + pub fn verify_after(&self) -> Result<AbsLockTime, ParseError> { |
| 147 | + self.verify_n_children("after", 1..=1) |
| 148 | + .map_err(ParseError::Tree)?; |
| 149 | + self.args[0] |
| 150 | + .verify_n_children("absolute locktime", 0..=0) |
| 151 | + .map_err(ParseError::Tree)?; |
| 152 | + parse_num(self.args[0].name) |
| 153 | + .map_err(ParseError::Num) |
| 154 | + .and_then(|n| AbsLockTime::from_consensus(n).map_err(ParseError::AbsoluteLockTime)) |
| 155 | + } |
| 156 | + |
| 157 | + /// Check that a tree node has a single terminal child which is a relative locktime. |
| 158 | + /// |
| 159 | + /// Returns an error assuming that the node is named "older". |
| 160 | + /// |
| 161 | + /// If so, parse the locktime from a string and return it. |
| 162 | + pub fn verify_older(&self) -> Result<RelLockTime, ParseError> { |
| 163 | + self.verify_n_children("older", 1..=1) |
| 164 | + .map_err(ParseError::Tree)?; |
| 165 | + self.args[0] |
| 166 | + .verify_n_children("relative locktime", 0..=0) |
| 167 | + .map_err(ParseError::Tree)?; |
| 168 | + parse_num(self.args[0].name) |
| 169 | + .map_err(ParseError::Num) |
| 170 | + .and_then(|n| RelLockTime::from_consensus(n).map_err(ParseError::RelativeLockTime)) |
| 171 | + } |
| 172 | + |
141 | 173 | /// Check that a tree node is a terminal (has no children). |
142 | 174 | /// |
143 | 175 | /// If so, parse the terminal from a string and return it. |
@@ -377,34 +409,23 @@ impl<'a> Tree<'a> { |
377 | 409 | return Err(ParseThresholdError::KNotTerminal); |
378 | 410 | } |
379 | 411 |
|
380 | | - let k = parse_num(self.args[0].name) |
381 | | - .map_err(|e| ParseThresholdError::ParseK(e.to_string()))? as usize; |
| 412 | + let k = parse_num(self.args[0].name).map_err(ParseThresholdError::ParseK)? as usize; |
382 | 413 | Threshold::new(k, vec![(); self.args.len() - 1]).map_err(ParseThresholdError::Threshold) |
383 | 414 | } |
384 | 415 | } |
385 | 416 |
|
386 | 417 | /// Parse a string as a u32, for timelocks or thresholds |
387 | | -pub fn parse_num(s: &str) -> Result<u32, Error> { |
388 | | - if s.len() > 1 { |
389 | | - let ch = s.chars().next().unwrap(); |
| 418 | +pub fn parse_num(s: &str) -> Result<u32, ParseNumError> { |
| 419 | + if s == "0" { |
| 420 | + // Special-case 0 since it is the only number which may start with a leading zero. |
| 421 | + return Ok(0); |
| 422 | + } |
| 423 | + if let Some(ch) = s.chars().next() { |
390 | 424 | if !('1'..='9').contains(&ch) { |
391 | | - return Err(Error::Unexpected("Number must start with a digit 1-9".to_string())); |
| 425 | + return Err(ParseNumError::InvalidLeadingDigit(ch)); |
392 | 426 | } |
393 | 427 | } |
394 | | - u32::from_str(s).map_err(|_| errstr(s)) |
395 | | -} |
396 | | - |
397 | | -/// Attempts to parse a terminal expression |
398 | | -pub fn terminal<T, F, Err>(term: &Tree, convert: F) -> Result<T, Error> |
399 | | -where |
400 | | - F: FnOnce(&str) -> Result<T, Err>, |
401 | | - Err: fmt::Display, |
402 | | -{ |
403 | | - if term.args.is_empty() { |
404 | | - convert(term.name).map_err(|e| Error::Unexpected(e.to_string())) |
405 | | - } else { |
406 | | - Err(errstr(term.name)) |
407 | | - } |
| 428 | + u32::from_str(s).map_err(ParseNumError::StdParse) |
408 | 429 | } |
409 | 430 |
|
410 | 431 | #[cfg(test)] |
|
0 commit comments