use std::{cmp, fmt}; /// The representation of an s-expression parse error. pub struct Error { /// The error message. pub message: &'static str, /// Position in the source string where the error was detected pub pos: SourcePosition, } /// Position in the input string #[derive(Debug, PartialEq, Clone, Default)] pub struct SourcePosition { /// 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.pos.line, self.pos.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) -> SourcePosition { 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; } } SourcePosition { line, column: cmp::max(col, 0) as usize, index: pos, } } #[cold] fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err { Box::new(Error { message, pos: get_line_and_column(s, *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)) } /// Build a span pub(crate) fn spos(s: &str, pos: &usize) -> SourcePosition { if *pos >= s.len() { Default::default() } else { get_line_and_column(s, *pos) } }