Skip to content

Commit 70b989a

Browse files
committed
human_encoding: introduce Error and ErrorSet
1 parent 9d379ce commit 70b989a

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

src/human_encoding/error.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Simplicity "Human-Readable" Language
2+
//
3+
// To the extent possible under law, the author(s) have dedicated all
4+
// copyright and related and neighboring rights to this software to
5+
// the public domain worldwide. This software is distributed without
6+
// any warranty.
7+
//
8+
// You should have received a copy of the CC0 Public Domain Dedication
9+
// along with this software.
10+
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
11+
//
12+
13+
//! Parsing Errors
14+
15+
use std::collections::BTreeMap;
16+
use std::sync::{Arc, Mutex};
17+
use std::{error, fmt, iter};
18+
19+
use crate::types;
20+
21+
use super::Position;
22+
23+
/// A set of errors found in a human-readable encoding of a Simplicity program.
24+
#[derive(Clone, Debug, Default)]
25+
pub struct ErrorSet {
26+
context: Option<Arc<str>>,
27+
line_map: Arc<Mutex<Vec<usize>>>,
28+
errors: BTreeMap<Option<Position>, Vec<Error>>,
29+
}
30+
31+
impl ErrorSet {
32+
/// Constructs a new empty error set.
33+
pub fn new() -> Self {
34+
ErrorSet::default()
35+
}
36+
37+
/// Returns the first (and presumably most important) error in the set, if it
38+
/// is non-empty, along with its position.
39+
pub fn first_error(&self) -> Option<(Option<Position>, &Error)> {
40+
self.errors.iter().next().map(|(a, b)| (*a, &b[0]))
41+
}
42+
43+
/// Constructs a new error set with a single error in it.
44+
pub fn single<P: Into<Position>, E: Into<Error>>(position: P, err: E) -> Self {
45+
let mut errors = BTreeMap::default();
46+
errors.insert(Some(position.into()), vec![err.into()]);
47+
ErrorSet {
48+
context: None,
49+
line_map: Arc::new(Mutex::new(vec![])),
50+
errors,
51+
}
52+
}
53+
54+
/// Constructs a new error set with a single error in it.
55+
pub fn single_no_position<E: Into<Error>>(err: E) -> Self {
56+
let mut errors = BTreeMap::default();
57+
errors.insert(None, vec![err.into()]);
58+
ErrorSet {
59+
context: None,
60+
line_map: Arc::new(Mutex::new(vec![])),
61+
errors,
62+
}
63+
}
64+
65+
/// Adds an error to the error set.
66+
pub fn add<P: Into<Position>, E: Into<Error>>(&mut self, position: P, err: E) {
67+
self.errors
68+
.entry(Some(position.into()))
69+
.or_insert(vec![])
70+
.push(err.into());
71+
}
72+
73+
/// Merges another set of errors into the current set.
74+
///
75+
/// # Panics
76+
///
77+
/// Panics if the two sets have different contexts attached.
78+
pub fn merge(&mut self, other: &Self) {
79+
match (self.context.as_ref(), other.context.as_ref()) {
80+
(None, None) => {}
81+
(Some(_), None) => {}
82+
(None, Some(b)) => self.context = Some(Arc::clone(b)),
83+
(Some(a), Some(b)) => {
84+
assert_eq!(a, b, "cannot merge error sets for different source input");
85+
}
86+
};
87+
88+
for (pos, errs) in &other.errors {
89+
self.errors
90+
.entry(*pos)
91+
.or_insert(vec![])
92+
.extend(errs.iter().cloned());
93+
}
94+
}
95+
96+
/// Attaches the input code to the error set, so that error messages can include
97+
/// line numbers etc.
98+
///
99+
/// # Panics
100+
///
101+
/// Panics if it is called twice on the same error set. You should call this once
102+
/// with the complete input code.
103+
pub fn add_context(&mut self, s: Arc<str>) {
104+
if self.context.is_some() {
105+
panic!("tried to add context to the same error context twice");
106+
}
107+
self.context = Some(s);
108+
}
109+
110+
/// Returns a boolean indicating whether the set is empty.
111+
pub fn is_empty(&self) -> bool {
112+
self.errors.is_empty()
113+
}
114+
115+
/// Returns the number of errors currently in the set.
116+
pub fn len(&self) -> usize {
117+
self.errors.len()
118+
}
119+
120+
/// Converts the error set into a result.
121+
///
122+
/// If the set is empty, returns Ok with the given value. Otherwise
123+
/// returns Err with itself.
124+
pub fn into_result<T>(self, ok: T) -> Result<T, Self> {
125+
if self.is_empty() {
126+
Ok(ok)
127+
} else {
128+
Err(self)
129+
}
130+
}
131+
132+
/// Converts the error set into a result.
133+
///
134+
/// If the set is empty, returns Ok with the result of calling the given closure.
135+
/// Otherwise returns Err with itself.
136+
pub fn into_result_with<T, F: FnOnce() -> T>(self, okfn: F) -> Result<T, Self> {
137+
if self.is_empty() {
138+
Ok(okfn())
139+
} else {
140+
Err(self)
141+
}
142+
}
143+
}
144+
145+
impl error::Error for ErrorSet {
146+
fn cause(&self) -> Option<&(dyn error::Error + 'static)> {
147+
match self.first_error()?.1 {
148+
Error::TypeCheck(ref e) => Some(e),
149+
}
150+
}
151+
}
152+
153+
impl fmt::Display for ErrorSet {
154+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155+
let mut line_map = self.line_map.lock().unwrap();
156+
if line_map.is_empty() {
157+
if let Some(ref s) = self.context {
158+
*line_map = iter::repeat(0)
159+
.take(2)
160+
.chain(
161+
s.char_indices()
162+
.filter_map(|(n, ch)| if ch == '\n' { Some(n) } else { None }),
163+
)
164+
.collect();
165+
}
166+
}
167+
168+
for (pos, errs) in &self.errors {
169+
if let Some(pos) = pos {
170+
for err in errs {
171+
if let Some(ref s) = self.context {
172+
let end = line_map.get(pos.line + 1).copied().unwrap_or(s.len());
173+
let line = &s[line_map[pos.line] + 1..end];
174+
writeln!(f, "{:5} | {}", pos.line, line)?;
175+
writeln!(f, " | {:>width$}", "^", width = pos.column)?;
176+
writeln!(f, " \\-- {}", err)?;
177+
writeln!(f)?;
178+
} else {
179+
writeln!(f, "{:4}:{:2}: {}", pos.line, pos.column, err,)?;
180+
writeln!(f)?;
181+
}
182+
}
183+
} else {
184+
for err in errs {
185+
writeln!(f, "Error: {}", err)?;
186+
}
187+
}
188+
}
189+
Ok(())
190+
}
191+
}
192+
193+
/// An individual error.
194+
///
195+
/// Generally this structure should not be used on its own, but only wrapped in an
196+
/// [`ErrorSet`]. This is because in the human-readable encoding errors it is usually
197+
/// possible to continue past individual errors, and the user would prefer to see as
198+
/// many as possible at once.
199+
#[derive(Clone, Debug)]
200+
pub enum Error {
201+
/// Simplicity type-checking error
202+
TypeCheck(types::Error),
203+
}
204+
205+
impl From<types::Error> for Error {
206+
fn from(e: types::Error) -> Self {
207+
Error::TypeCheck(e)
208+
}
209+
}
210+
211+
impl fmt::Display for Error {
212+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213+
match *self {
214+
Error::TypeCheck(ref e) => fmt::Display::fmt(e, f),
215+
}
216+
}
217+
}

src/human_encoding/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
//! in a human-readable format.
2121
//!
2222
23+
mod error;
2324
mod named_node;
2425
mod serialize;
2526

@@ -31,6 +32,7 @@ use std::collections::HashMap;
3132
use std::str;
3233
use std::sync::Arc;
3334

35+
pub use self::error::{Error, ErrorSet};
3436
pub use self::named_node::NamedCommitNode;
3537

3638
/// Line/column pair

0 commit comments

Comments
 (0)