diff --git a/src/human_encoding/error.rs b/src/human_encoding/error.rs
new file mode 100644
index 00000000..3cbcbef3
--- /dev/null
+++ b/src/human_encoding/error.rs
@@ -0,0 +1,217 @@
+// Simplicity "Human-Readable" Language
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this software to
+// the public domain worldwide. This software is distributed without
+// any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication
+// along with this software.
+// If not, see .
+//
+
+//! Parsing Errors
+
+use std::collections::BTreeMap;
+use std::sync::{Arc, Mutex};
+use std::{error, fmt, iter};
+
+use crate::types;
+
+use super::Position;
+
+/// A set of errors found in a human-readable encoding of a Simplicity program.
+#[derive(Clone, Debug, Default)]
+pub struct ErrorSet {
+ context: Option>,
+ line_map: Arc>>,
+ errors: BTreeMap, Vec>,
+}
+
+impl ErrorSet {
+ /// Constructs a new empty error set.
+ pub fn new() -> Self {
+ ErrorSet::default()
+ }
+
+ /// Returns the first (and presumably most important) error in the set, if it
+ /// is non-empty, along with its position.
+ pub fn first_error(&self) -> Option<(Option, &Error)> {
+ self.errors.iter().next().map(|(a, b)| (*a, &b[0]))
+ }
+
+ /// Constructs a new error set with a single error in it.
+ pub fn single, E: Into>(position: P, err: E) -> Self {
+ let mut errors = BTreeMap::default();
+ errors.insert(Some(position.into()), vec![err.into()]);
+ ErrorSet {
+ context: None,
+ line_map: Arc::new(Mutex::new(vec![])),
+ errors,
+ }
+ }
+
+ /// Constructs a new error set with a single error in it.
+ pub fn single_no_position>(err: E) -> Self {
+ let mut errors = BTreeMap::default();
+ errors.insert(None, vec![err.into()]);
+ ErrorSet {
+ context: None,
+ line_map: Arc::new(Mutex::new(vec![])),
+ errors,
+ }
+ }
+
+ /// Adds an error to the error set.
+ pub fn add, E: Into>(&mut self, position: P, err: E) {
+ self.errors
+ .entry(Some(position.into()))
+ .or_insert(vec![])
+ .push(err.into());
+ }
+
+ /// Merges another set of errors into the current set.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the two sets have different contexts attached.
+ pub fn merge(&mut self, other: &Self) {
+ match (self.context.as_ref(), other.context.as_ref()) {
+ (None, None) => {}
+ (Some(_), None) => {}
+ (None, Some(b)) => self.context = Some(Arc::clone(b)),
+ (Some(a), Some(b)) => {
+ assert_eq!(a, b, "cannot merge error sets for different source input");
+ }
+ };
+
+ for (pos, errs) in &other.errors {
+ self.errors
+ .entry(*pos)
+ .or_insert(vec![])
+ .extend(errs.iter().cloned());
+ }
+ }
+
+ /// Attaches the input code to the error set, so that error messages can include
+ /// line numbers etc.
+ ///
+ /// # Panics
+ ///
+ /// Panics if it is called twice on the same error set. You should call this once
+ /// with the complete input code.
+ pub fn add_context(&mut self, s: Arc) {
+ if self.context.is_some() {
+ panic!("tried to add context to the same error context twice");
+ }
+ self.context = Some(s);
+ }
+
+ /// Returns a boolean indicating whether the set is empty.
+ pub fn is_empty(&self) -> bool {
+ self.errors.is_empty()
+ }
+
+ /// Returns the number of errors currently in the set.
+ pub fn len(&self) -> usize {
+ self.errors.len()
+ }
+
+ /// Converts the error set into a result.
+ ///
+ /// If the set is empty, returns Ok with the given value. Otherwise
+ /// returns Err with itself.
+ pub fn into_result(self, ok: T) -> Result {
+ if self.is_empty() {
+ Ok(ok)
+ } else {
+ Err(self)
+ }
+ }
+
+ /// Converts the error set into a result.
+ ///
+ /// If the set is empty, returns Ok with the result of calling the given closure.
+ /// Otherwise returns Err with itself.
+ pub fn into_result_with T>(self, okfn: F) -> Result {
+ if self.is_empty() {
+ Ok(okfn())
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl error::Error for ErrorSet {
+ fn cause(&self) -> Option<&(dyn error::Error + 'static)> {
+ match self.first_error()?.1 {
+ Error::TypeCheck(ref e) => Some(e),
+ }
+ }
+}
+
+impl fmt::Display for ErrorSet {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut line_map = self.line_map.lock().unwrap();
+ if line_map.is_empty() {
+ if let Some(ref s) = self.context {
+ *line_map = iter::repeat(0)
+ .take(2)
+ .chain(
+ s.char_indices()
+ .filter_map(|(n, ch)| if ch == '\n' { Some(n) } else { None }),
+ )
+ .collect();
+ }
+ }
+
+ for (pos, errs) in &self.errors {
+ if let Some(pos) = pos {
+ for err in errs {
+ if let Some(ref s) = self.context {
+ let end = line_map.get(pos.line + 1).copied().unwrap_or(s.len());
+ let line = &s[line_map[pos.line] + 1..end];
+ writeln!(f, "{:5} | {}", pos.line, line)?;
+ writeln!(f, " | {:>width$}", "^", width = pos.column)?;
+ writeln!(f, " \\-- {}", err)?;
+ writeln!(f)?;
+ } else {
+ writeln!(f, "{:4}:{:2}: {}", pos.line, pos.column, err,)?;
+ writeln!(f)?;
+ }
+ }
+ } else {
+ for err in errs {
+ writeln!(f, "Error: {}", err)?;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+/// An individual error.
+///
+/// Generally this structure should not be used on its own, but only wrapped in an
+/// [`ErrorSet`]. This is because in the human-readable encoding errors it is usually
+/// possible to continue past individual errors, and the user would prefer to see as
+/// many as possible at once.
+#[derive(Clone, Debug)]
+pub enum Error {
+ /// Simplicity type-checking error
+ TypeCheck(types::Error),
+}
+
+impl From for Error {
+ fn from(e: types::Error) -> Self {
+ Error::TypeCheck(e)
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Error::TypeCheck(ref e) => fmt::Display::fmt(e, f),
+ }
+ }
+}
diff --git a/src/human_encoding/mod.rs b/src/human_encoding/mod.rs
index 855d4f95..39ccc1ba 100644
--- a/src/human_encoding/mod.rs
+++ b/src/human_encoding/mod.rs
@@ -20,6 +20,7 @@
//! in a human-readable format.
//!
+mod error;
mod named_node;
mod serialize;
@@ -31,6 +32,7 @@ use std::collections::HashMap;
use std::str;
use std::sync::Arc;
+pub use self::error::{Error, ErrorSet};
pub use self::named_node::NamedCommitNode;
/// Line/column pair