diff --git a/lib/spanned_sexp/src/error.rs b/lib/spanned_sexp/src/error.rs index f4cdca5..f3d87ad 100644 --- a/lib/spanned_sexp/src/error.rs +++ b/lib/spanned_sexp/src/error.rs @@ -4,6 +4,13 @@ use std::{cmp, fmt}; 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)] +pub struct SourcePosition { /// The line number on which the error occurred. pub line: usize, /// The column number on which the error occurred. @@ -25,7 +32,7 @@ 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) + write!(f, "{}:{}: {}", self.pos.line, self.pos.column, self.message) } } @@ -37,7 +44,7 @@ impl fmt::Debug for Error { impl std::error::Error for Error {} -pub(crate) fn get_line_and_column(s: &str, pos: usize) -> (usize, usize) { +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) { @@ -48,17 +55,18 @@ pub(crate) fn get_line_and_column(s: &str, pos: usize) -> (usize, usize) { col += 1; } } - (line, cmp::max(col, 0) as usize) + SourcePosition { + line, + column: cmp::max(col, 0) as usize, + index: pos, + } } #[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, + message, + pos: get_line_and_column(s, *pos) }) } @@ -66,3 +74,12 @@ fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err { 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) -> Option> { + if *pos >= s.len() { + None + } else { + Some(Box::new(get_line_and_column(s, *pos))) + } +} diff --git a/lib/spanned_sexp/src/lib.rs b/lib/spanned_sexp/src/lib.rs index 35dee98..d3b8c91 100644 --- a/lib/spanned_sexp/src/lib.rs +++ b/lib/spanned_sexp/src/lib.rs @@ -13,7 +13,8 @@ use std::fmt; use std::str::{self, FromStr}; pub use error::Error; -use error::{ERes, err}; +pub use error::SourcePosition; +use error::{ERes, err, spos}; #[cfg(test)] mod test; @@ -34,11 +35,26 @@ pub enum Atom { /// An s-expression is either an atom or a list of s-expressions. This is /// similar to the data format used by lisp. -#[derive(PartialEq, Clone, PartialOrd)] -#[allow(missing_docs)] +#[derive(Clone)] pub enum Sexp { - Atom(Atom), - List(Vec), + /// Atom + Atom(Atom, Option>), + /// List of expressions + List(Vec, Option>), +} + +impl PartialEq for Sexp { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Sexp::Atom(a, _), Sexp::Atom(b, _)) => { + a == b + } + (Sexp::List(a, _), Sexp::List(b, _)) => { + a == b + } + _ => false + } + } } @@ -180,30 +196,33 @@ fn parse_sexp(s: &str, pos: &mut usize) -> ERes { trace!("parse_sexp {}", pos); zspace(s, pos)?; let (c, _) = peek(s, pos)?; - let r = - if c == '(' { Ok(Sexp::List(parse_list(s, pos)?)) } else { Ok(Sexp::Atom(parse_atom(s, pos)?)) }; + let r = if c == '(' { + Ok(Sexp::List(parse_list(s, pos)?, spos(s, pos))) + } else { + Ok(Sexp::Atom(parse_atom(s, pos)?, spos(s, pos))) + }; zspace(s, pos)?; r } /// Constructs an atomic s-expression from a string. pub fn atom_s(s: &str) -> Sexp { - Sexp::Atom(Atom::S(s.to_owned())) + Sexp::Atom(Atom::S(s.to_owned()), None) } /// Constructs an atomic s-expression from an int. pub fn atom_i(i: i64) -> Sexp { - Sexp::Atom(Atom::I(i)) + Sexp::Atom(Atom::I(i), None) } /// Constructs an atomic s-expression from a float. pub fn atom_f(f: f64) -> Sexp { - Sexp::Atom(Atom::F(f)) + Sexp::Atom(Atom::F(f), None) } /// Constructs a list s-expression given a slice of s-expressions. pub fn list(xs: &[Sexp]) -> Sexp { - Sexp::List(xs.to_owned()) + Sexp::List(xs.to_owned(), None) } /// Reads an s-expression out of a `&str`. @@ -256,8 +275,8 @@ impl fmt::Display for Atom { impl fmt::Display for Sexp { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Sexp::Atom(ref a) => write!(f, "{}", a), - Sexp::List(ref xs) => { + Sexp::Atom(ref a, _) => write!(f, "{}", a), + Sexp::List(ref xs, _) => { write!(f, "(")?; for (i, x) in xs.iter().enumerate() { let s = if i == 0 { "" } else { " " }; diff --git a/lib/spanned_sexp/src/test.rs b/lib/spanned_sexp/src/test.rs index ec6c23c..f96beca 100644 --- a/lib/spanned_sexp/src/test.rs +++ b/lib/spanned_sexp/src/test.rs @@ -27,8 +27,8 @@ fn test_pp() { fn test_tight_parens() { let s = "(hello(world))"; let sexp = parse(s).unwrap(); - assert_eq!(sexp, Sexp::List(vec![Sexp::Atom(Atom::S("hello".into())), - Sexp::List(vec![Sexp::Atom(Atom::S("world".into()))])])); + assert_eq!(sexp, Sexp::List(vec![Sexp::Atom(Atom::S("hello".into()), None), + Sexp::List(vec![Sexp::Atom(Atom::S("world".into()), None)], None)], None)); let s = "(this (has)tight(parens))"; let s2 = "( this ( has ) tight ( parens ) )"; assert_eq!(parse(s).unwrap(), parse(s2).unwrap()); @@ -50,16 +50,16 @@ fn show_an_error() { #[test] fn line_and_col_test() { let s = "0123456789\n0123456789\n\n6"; - assert_eq!(get_line_and_column(s, 4), (1, 4)); + assert_eq!(get_line_and_column(s, 4), SourcePosition { line: 1, column: 4, index: 4 }); - assert_eq!(get_line_and_column(s, 10), (2, 0)); - assert_eq!(get_line_and_column(s, 11), (2, 0)); - assert_eq!(get_line_and_column(s, 15), (2, 4)); + assert_eq!(get_line_and_column(s, 10), SourcePosition { line: 2, column: 0, index: 10 }); + assert_eq!(get_line_and_column(s, 11), SourcePosition { line: 2, column: 0, index: 11 }); + assert_eq!(get_line_and_column(s, 15), SourcePosition { line: 2, column: 4, index: 15 }); - assert_eq!(get_line_and_column(s, 21), (3, 0)); - assert_eq!(get_line_and_column(s, 22), (4, 0)); - assert_eq!(get_line_and_column(s, 23), (4, 0)); - assert_eq!(get_line_and_column(s, 500), (4, 0)); + assert_eq!(get_line_and_column(s, 21), SourcePosition { line: 3, column: 0, index: 21 }); + assert_eq!(get_line_and_column(s, 22), SourcePosition { line: 4, column: 0, index: 22 }); + assert_eq!(get_line_and_column(s, 23), SourcePosition { line: 4, column: 0, index: 23 }); + assert_eq!(get_line_and_column(s, 500), SourcePosition { line: 4, column: 0, index: 500 }); } #[test]