diff --git a/lib/spanned_sexp/src/error.rs b/lib/spanned_sexp/src/error.rs new file mode 100644 index 0000000..f4cdca5 --- /dev/null +++ b/lib/spanned_sexp/src/error.rs @@ -0,0 +1,68 @@ +use std::{cmp, fmt}; + +/// The representation of an s-expression parse error. +pub struct Error { + /// The error message. + pub message: &'static str, + /// The line number on which the error occurred. + pub line: usize, + /// The column number on which the error occurred. + pub column: usize, + /// The index in the given string which caused the error. + pub index: usize, +} + +/// Since errors are the uncommon case, they're boxed. This keeps the size of +/// structs down, which helps performance in the common case. +/// +/// For example, an `ERes<()>` becomes 8 bytes, instead of the 24 bytes it would +/// be if `Err` were unboxed. +type Err = Box; + +/// Helps clean up type signatures, but shouldn't be exposed to the outside +/// world. +pub(crate) type ERes = Result; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}:{}: {}", self.line, self.column, self.message) + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self) + } +} + +impl std::error::Error for Error {} + +pub(crate) fn get_line_and_column(s: &str, pos: usize) -> (usize, usize) { + let mut line: usize = 1; + let mut col: isize = -1; + for c in s.chars().take(pos + 1) { + if c == '\n' { + line += 1; + col = -1; + } else { + col += 1; + } + } + (line, cmp::max(col, 0) as usize) +} + +#[cold] +fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err { + let (line, column) = get_line_and_column(s, *pos); + Box::new(Error { + message: message, + line: line, + column: column, + index: *pos, + }) +} + +/// Build an error with span information +pub(crate) fn err(message: &'static str, s: &str, pos: &usize) -> ERes { + Err(err_impl(message, s, pos)) +} diff --git a/lib/spanned_sexp/src/lib.rs b/lib/spanned_sexp/src/lib.rs index 23b0789..35dee98 100644 --- a/lib/spanned_sexp/src/lib.rs +++ b/lib/spanned_sexp/src/lib.rs @@ -9,14 +9,17 @@ extern crate log; use std::borrow::Cow; -use std::cmp; -use std::error; use std::fmt; use std::str::{self, FromStr}; +pub use error::Error; +use error::{ERes, err}; + #[cfg(test)] mod test; +mod error; + /// A single data element in an s-expression. Floats are excluded to ensure /// atoms may be used as keys in ordered and hashed data structures. /// @@ -38,75 +41,6 @@ pub enum Sexp { List(Vec), } -/// The representation of an s-expression parse error. -pub struct Error { - /// The error message. - pub message: &'static str, - /// The line number on which the error occurred. - pub line: usize, - /// The column number on which the error occurred. - pub column: usize, - /// The index in the given string which caused the error. - pub index: usize, -} - -impl error::Error for Error { - fn description(&self) -> &str { self.message } - fn cause(&self) -> Option<&dyn error::Error> { None } -} - -/// Since errors are the uncommon case, they're boxed. This keeps the size of -/// structs down, which helps performance in the common case. -/// -/// For example, an `ERes<()>` becomes 8 bytes, instead of the 24 bytes it would -/// be if `Err` were unboxed. -type Err = Box; - -/// Helps clean up type signatures, but shouldn't be exposed to the outside -/// world. -type ERes = Result; - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "{}:{}: {}", self.line, self.column, self.message) - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "{}", self) - } -} - -fn get_line_and_column(s: &str, pos: usize) -> (usize, usize) { - let mut line: usize = 1; - let mut col: isize = -1; - for c in s.chars().take(pos + 1) { - if c == '\n' { - line += 1; - col = -1; - } else { - col += 1; - } - } - (line, cmp::max(col, 0) as usize) -} - - -#[cold] -fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err { - let (line, column) = get_line_and_column(s, *pos); - Box::new(Error { - message: message, - line: line, - column: column, - index: *pos, - }) -} - -fn err(message: &'static str, s: &str, pos: &usize) -> ERes { - Err(err_impl(message, s, pos)) -} fn atom_of_string(s: String) -> Atom { match FromStr::from_str(&s) { @@ -281,7 +215,7 @@ pub fn parse(s: &str) -> Result> { } // TODO: Pretty print in lisp convention, instead of all on the same line, -// packed as tightly as possible. It's kinda ugly. +// packed as tightly as possible. It's kinda ugly. fn is_num_string(s: &str) -> bool { let x: Result = FromStr::from_str(&s); diff --git a/lib/spanned_sexp/src/test.rs b/lib/spanned_sexp/src/test.rs index 2ed1e98..ec6c23c 100644 --- a/lib/spanned_sexp/src/test.rs +++ b/lib/spanned_sexp/src/test.rs @@ -1,4 +1,5 @@ use super::*; +use super::error::get_line_and_column; #[test] fn test_hello_world() {