|
| 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 | +} |
0 commit comments