sexp: add span info to Sexps

pull/21/head
Ondřej Hruška 4 years ago
parent 0fd1c9980b
commit d4534c9c66
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 33
      lib/spanned_sexp/src/error.rs
  2. 45
      lib/spanned_sexp/src/lib.rs
  3. 20
      lib/spanned_sexp/src/test.rs

@ -4,6 +4,13 @@ use std::{cmp, fmt};
pub struct Error { pub struct Error {
/// The error message. /// The error message.
pub message: &'static str, 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. /// The line number on which the error occurred.
pub line: usize, pub line: usize,
/// The column number on which the error occurred. /// The column number on which the error occurred.
@ -25,7 +32,7 @@ pub(crate) type ERes<T> = Result<T, Err>;
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::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 {} 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 line: usize = 1;
let mut col: isize = -1; let mut col: isize = -1;
for c in s.chars().take(pos + 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; col += 1;
} }
} }
(line, cmp::max(col, 0) as usize) SourcePosition {
line,
column: cmp::max(col, 0) as usize,
index: pos,
}
} }
#[cold] #[cold]
fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err { fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err {
let (line, column) = get_line_and_column(s, *pos);
Box::new(Error { Box::new(Error {
message: message, message,
line: line, pos: get_line_and_column(s, *pos)
column: column,
index: *pos,
}) })
} }
@ -66,3 +74,12 @@ fn err_impl(message: &'static str, s: &str, pos: &usize) -> Err {
pub(crate) fn err<T>(message: &'static str, s: &str, pos: &usize) -> ERes<T> { pub(crate) fn err<T>(message: &'static str, s: &str, pos: &usize) -> ERes<T> {
Err(err_impl(message, s, pos)) Err(err_impl(message, s, pos))
} }
/// Build a span
pub(crate) fn spos(s: &str, pos: &usize) -> Option<Box<SourcePosition>> {
if *pos >= s.len() {
None
} else {
Some(Box::new(get_line_and_column(s, *pos)))
}
}

@ -13,7 +13,8 @@ use std::fmt;
use std::str::{self, FromStr}; use std::str::{self, FromStr};
pub use error::Error; pub use error::Error;
use error::{ERes, err}; pub use error::SourcePosition;
use error::{ERes, err, spos};
#[cfg(test)] #[cfg(test)]
mod 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 /// An s-expression is either an atom or a list of s-expressions. This is
/// similar to the data format used by lisp. /// similar to the data format used by lisp.
#[derive(PartialEq, Clone, PartialOrd)] #[derive(Clone)]
#[allow(missing_docs)]
pub enum Sexp { pub enum Sexp {
Atom(Atom), /// Atom
List(Vec<Sexp>), Atom(Atom, Option<Box<SourcePosition>>),
/// List of expressions
List(Vec<Sexp>, Option<Box<SourcePosition>>),
}
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<Sexp> {
trace!("parse_sexp {}", pos); trace!("parse_sexp {}", pos);
zspace(s, pos)?; zspace(s, pos)?;
let (c, _) = peek(s, pos)?; let (c, _) = peek(s, pos)?;
let r = let r = if c == '(' {
if c == '(' { Ok(Sexp::List(parse_list(s, pos)?)) } else { Ok(Sexp::Atom(parse_atom(s, pos)?)) }; Ok(Sexp::List(parse_list(s, pos)?, spos(s, pos)))
} else {
Ok(Sexp::Atom(parse_atom(s, pos)?, spos(s, pos)))
};
zspace(s, pos)?; zspace(s, pos)?;
r r
} }
/// Constructs an atomic s-expression from a string. /// Constructs an atomic s-expression from a string.
pub fn atom_s(s: &str) -> Sexp { 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. /// Constructs an atomic s-expression from an int.
pub fn atom_i(i: i64) -> Sexp { 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. /// Constructs an atomic s-expression from a float.
pub fn atom_f(f: f64) -> Sexp { 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. /// Constructs a list s-expression given a slice of s-expressions.
pub fn list(xs: &[Sexp]) -> Sexp { 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`. /// Reads an s-expression out of a `&str`.
@ -256,8 +275,8 @@ impl fmt::Display for Atom {
impl fmt::Display for Sexp { impl fmt::Display for Sexp {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self { match *self {
Sexp::Atom(ref a) => write!(f, "{}", a), Sexp::Atom(ref a, _) => write!(f, "{}", a),
Sexp::List(ref xs) => { Sexp::List(ref xs, _) => {
write!(f, "(")?; write!(f, "(")?;
for (i, x) in xs.iter().enumerate() { for (i, x) in xs.iter().enumerate() {
let s = if i == 0 { "" } else { " " }; let s = if i == 0 { "" } else { " " };

@ -27,8 +27,8 @@ fn test_pp() {
fn test_tight_parens() { fn test_tight_parens() {
let s = "(hello(world))"; let s = "(hello(world))";
let sexp = parse(s).unwrap(); let sexp = parse(s).unwrap();
assert_eq!(sexp, Sexp::List(vec![Sexp::Atom(Atom::S("hello".into())), assert_eq!(sexp, Sexp::List(vec![Sexp::Atom(Atom::S("hello".into()), None),
Sexp::List(vec![Sexp::Atom(Atom::S("world".into()))])])); Sexp::List(vec![Sexp::Atom(Atom::S("world".into()), None)], None)], None));
let s = "(this (has)tight(parens))"; let s = "(this (has)tight(parens))";
let s2 = "( this ( has ) tight ( parens ) )"; let s2 = "( this ( has ) tight ( parens ) )";
assert_eq!(parse(s).unwrap(), parse(s2).unwrap()); assert_eq!(parse(s).unwrap(), parse(s2).unwrap());
@ -50,16 +50,16 @@ fn show_an_error() {
#[test] #[test]
fn line_and_col_test() { fn line_and_col_test() {
let s = "0123456789\n0123456789\n\n6"; 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, 10), SourcePosition { line: 2, column: 0, index: 10 });
assert_eq!(get_line_and_column(s, 11), (2, 0)); assert_eq!(get_line_and_column(s, 11), SourcePosition { line: 2, column: 0, index: 11 });
assert_eq!(get_line_and_column(s, 15), (2, 4)); 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, 21), SourcePosition { line: 3, column: 0, index: 21 });
assert_eq!(get_line_and_column(s, 22), (4, 0)); assert_eq!(get_line_and_column(s, 22), SourcePosition { line: 4, column: 0, index: 22 });
assert_eq!(get_line_and_column(s, 23), (4, 0)); assert_eq!(get_line_and_column(s, 23), SourcePosition { line: 4, column: 0, index: 23 });
assert_eq!(get_line_and_column(s, 500), (4, 0)); assert_eq!(get_line_and_column(s, 500), SourcePosition { line: 4, column: 0, index: 500 });
} }
#[test] #[test]

Loading…
Cancel
Save