forked from MightyPork/crsn
commit
017ec53b14
@ -1,6 +1,59 @@ |
|||||||
pub use sexp_is_a::SexpIsA; |
use sexp::SourcePosition; |
||||||
pub use try_remove::TryRemove; |
|
||||||
|
|
||||||
mod try_remove; |
use crate::asm::error::CrsnError; |
||||||
mod sexp_is_a; |
|
||||||
|
|
||||||
|
pub trait TryRemove { |
||||||
|
type Item; |
||||||
|
fn try_remove(&mut self, index: usize) -> Option<Self::Item>; |
||||||
|
|
||||||
|
fn remove_or_err(&mut self, index: usize, pos: &SourcePosition, err: &'static str) -> Result<Self::Item, CrsnError>; |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> TryRemove for Vec<T> { |
||||||
|
type Item = T; |
||||||
|
|
||||||
|
fn try_remove(&mut self, index: usize) -> Option<T> { |
||||||
|
if self.is_empty() { |
||||||
|
None |
||||||
|
} else { |
||||||
|
Some(self.remove(index)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn remove_or_err(&mut self, index: usize, pos: &SourcePosition, err: &'static str) -> Result<Self::Item, CrsnError> { |
||||||
|
match self.try_remove(index) { |
||||||
|
None => { |
||||||
|
Err(CrsnError::Parse(err.into(), pos.clone())) |
||||||
|
} |
||||||
|
Some(removed) => Ok(removed) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub trait NextOrErr<T> { |
||||||
|
fn next_or_err(&mut self, pos: SourcePosition, err: &'static str) -> Result<T, CrsnError>; |
||||||
|
} |
||||||
|
|
||||||
|
impl<T, K: Iterator<Item=T>> NextOrErr<T> for K { |
||||||
|
fn next_or_err(&mut self, pos: SourcePosition, err: &'static str) -> Result<T, CrsnError> { |
||||||
|
match self.next() { |
||||||
|
None => { |
||||||
|
Err(CrsnError::Parse(err.into(), pos)) |
||||||
|
} |
||||||
|
Some(removed) => Ok(removed) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub trait ErrWithPos<T> { |
||||||
|
fn err_pos(self, pos: &SourcePosition) -> Result<T, CrsnError>; |
||||||
|
} |
||||||
|
|
||||||
|
impl<T, E: std::error::Error + Send + Sync + 'static> ErrWithPos<T> for Result<T, E> { |
||||||
|
fn err_pos(self, pos: &SourcePosition) -> Result<T, CrsnError> { |
||||||
|
match self { |
||||||
|
Ok(v) => Ok(v), |
||||||
|
Err(e) => Err(CrsnError::ParseOther(Box::new(e), pos.clone())) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
@ -1,23 +0,0 @@ |
|||||||
use sexp::Sexp; |
|
||||||
|
|
||||||
pub trait SexpIsA { |
|
||||||
fn is_atom(&self) -> bool; |
|
||||||
|
|
||||||
fn is_list(&self) -> bool; |
|
||||||
} |
|
||||||
|
|
||||||
impl SexpIsA for Sexp { |
|
||||||
fn is_atom(&self) -> bool { |
|
||||||
match self { |
|
||||||
Sexp::Atom(_) => true, |
|
||||||
_ => false, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn is_list(&self) -> bool { |
|
||||||
match self { |
|
||||||
Sexp::List(_) => true, |
|
||||||
_ => false, |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
pub trait TryRemove { |
|
||||||
type Item; |
|
||||||
fn try_remove(&mut self, index: usize) -> Option<Self::Item>; |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> TryRemove for Vec<T> { |
|
||||||
type Item = T; |
|
||||||
|
|
||||||
fn try_remove(&mut self, index: usize) -> Option<T> { |
|
||||||
if self.is_empty() { |
|
||||||
None |
|
||||||
} else { |
|
||||||
Some(self.remove(index)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -0,0 +1,2 @@ |
|||||||
|
target |
||||||
|
Cargo.lock |
@ -0,0 +1,14 @@ |
|||||||
|
[package] |
||||||
|
name = "sexp" |
||||||
|
version = "1.1.4" |
||||||
|
authors = ["Clark Gaebel <cg.wowus.cg@gmail.com>"] |
||||||
|
documentation = "https://cgaebel.github.io/sexp" |
||||||
|
homepage = "https://github.com/cgaebel/sexp" |
||||||
|
repository = "https://github.com/cgaebel/sexp" |
||||||
|
readme = "README.md" |
||||||
|
keywords = ["sexp", "parsing", "s-expression", "file-format"] |
||||||
|
description = "A small, simple, self-contained, s-expression parser and pretty-printer." |
||||||
|
license = "MIT" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
log = "0.4.11" |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright (c) 2015 Clark Gaebel <cg.wowus.cg@gmail.com> |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
@ -0,0 +1,9 @@ |
|||||||
|
Source-location tracking Sexp |
||||||
|
===== |
||||||
|
|
||||||
|
**This is a fork of "sexp", updated to the 2018 edition, where each parsed node tracks its |
||||||
|
source file position. This enables better error reporting in subsequent parsing and processing.** |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
Original version by Clark Gaebel: [https://github.com/cgaebel/sexp](https://github.com/cgaebel/sexp). |
@ -0,0 +1,85 @@ |
|||||||
|
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: u32, |
||||||
|
/// The column number on which the error occurred.
|
||||||
|
pub column: u32, |
||||||
|
/// The index in the given string which caused the error.
|
||||||
|
pub index: u32, |
||||||
|
} |
||||||
|
|
||||||
|
/// 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<Error>; |
||||||
|
|
||||||
|
/// Helps clean up type signatures, but shouldn't be exposed to the outside
|
||||||
|
/// world.
|
||||||
|
pub(crate) type ERes<T> = Result<T, Err>; |
||||||
|
|
||||||
|
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: line as u32, |
||||||
|
column: cmp::max(col, 0) as u32, |
||||||
|
index: pos as u32, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[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<T>(message: &'static str, s: &str, pos: &usize) -> ERes<T> { |
||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,324 @@ |
|||||||
|
//! A lightweight, self-contained s-expression parser and data format.
|
||||||
|
//! Use `parse` to get an s-expression from its string representation, and the
|
||||||
|
//! `Display` trait to serialize it, potentially by doing `sexp.to_string()`.
|
||||||
|
|
||||||
|
#![deny(unsafe_code)] |
||||||
|
|
||||||
|
#[macro_use] |
||||||
|
extern crate log; |
||||||
|
|
||||||
|
use std::borrow::Cow; |
||||||
|
use std::fmt; |
||||||
|
use std::str::{self, FromStr}; |
||||||
|
|
||||||
|
use error::{ERes, err, spos}; |
||||||
|
pub use error::Error; |
||||||
|
pub use error::SourcePosition; |
||||||
|
|
||||||
|
#[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.
|
||||||
|
///
|
||||||
|
/// All strings must be valid utf-8.
|
||||||
|
#[derive(PartialEq, Clone, PartialOrd)] |
||||||
|
#[allow(missing_docs)] |
||||||
|
pub enum Atom { |
||||||
|
S(String), |
||||||
|
I(i64), |
||||||
|
F(f64), |
||||||
|
} |
||||||
|
|
||||||
|
/// An s-expression is either an atom or a list of s-expressions. This is
|
||||||
|
/// similar to the data format used by lisp.
|
||||||
|
#[derive(Clone)] |
||||||
|
pub enum Sexp { |
||||||
|
/// Atom
|
||||||
|
Atom(Atom, SourcePosition), |
||||||
|
/// List of expressions
|
||||||
|
List(Vec<Sexp>, SourcePosition), |
||||||
|
} |
||||||
|
|
||||||
|
impl Sexp { |
||||||
|
pub fn pos(&self) -> &SourcePosition { |
||||||
|
match self { |
||||||
|
Sexp::List(_, pos) | Sexp::Atom(_, pos) => pos |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Check fi thsi Sexp is an atom
|
||||||
|
pub fn is_atom(&self) -> bool { |
||||||
|
match self { |
||||||
|
Sexp::Atom(_, _) => true, |
||||||
|
_ => false, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Check fi thsi Sexp is a list
|
||||||
|
pub fn is_list(&self) -> bool { |
||||||
|
match self { |
||||||
|
Sexp::List(_, _) => true, |
||||||
|
_ => false, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
fn atom_of_string(s: String) -> Atom { |
||||||
|
match FromStr::from_str(&s) { |
||||||
|
Ok(i) => return Atom::I(i), |
||||||
|
Err(_) => {} |
||||||
|
}; |
||||||
|
|
||||||
|
match FromStr::from_str(&s) { |
||||||
|
Ok(f) => return Atom::F(f), |
||||||
|
Err(_) => {} |
||||||
|
}; |
||||||
|
|
||||||
|
Atom::S(s) |
||||||
|
} |
||||||
|
|
||||||
|
// returns the char it found, and the new size if you wish to consume that char
|
||||||
|
fn peek(s: &str, pos: &usize) -> ERes<(char, usize)> { |
||||||
|
trace!("peek {}", pos); |
||||||
|
if *pos == s.len() { return err("unexpected eof", s, pos); } |
||||||
|
if s.is_char_boundary(*pos) { |
||||||
|
let ch = s[*pos..].chars().next().unwrap(); |
||||||
|
let next = *pos + ch.len_utf8(); |
||||||
|
Ok((ch, next)) |
||||||
|
} else { |
||||||
|
// strings must be composed of valid utf-8 chars.
|
||||||
|
unreachable!() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn expect(s: &str, pos: &mut usize, c: char) -> ERes<()> { |
||||||
|
trace!("expect {}", pos); |
||||||
|
let (ch, next) = peek(s, pos)?; |
||||||
|
*pos = next; |
||||||
|
if ch == c { Ok(()) } else { err("unexpected character", s, pos) } |
||||||
|
} |
||||||
|
|
||||||
|
fn consume_until_newline(s: &str, pos: &mut usize) -> ERes<()> { |
||||||
|
loop { |
||||||
|
if *pos == s.len() { return Ok(()); } |
||||||
|
let (ch, next) = peek(s, pos)?; |
||||||
|
*pos = next; |
||||||
|
if ch == '\n' { return Ok(()); } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// zero or more spaces
|
||||||
|
fn zspace(s: &str, pos: &mut usize) -> ERes<()> { |
||||||
|
trace!("zspace {}", pos); |
||||||
|
loop { |
||||||
|
if *pos == s.len() { return Ok(()); } |
||||||
|
let (ch, next) = peek(s, pos)?; |
||||||
|
|
||||||
|
if ch == ';' { consume_until_newline(s, pos)? } else if ch.is_whitespace() { *pos = next; } else { return Ok(()); } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_quoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> { |
||||||
|
trace!("parse_quoted_atom {}", pos); |
||||||
|
let mut cs: String = String::new(); |
||||||
|
|
||||||
|
expect(s, pos, '"')?; |
||||||
|
|
||||||
|
loop { |
||||||
|
let (ch, next) = peek(s, pos)?; |
||||||
|
if ch == '"' { |
||||||
|
*pos = next; |
||||||
|
break; |
||||||
|
} else if ch == '\\' { |
||||||
|
let (postslash, nextnext) = peek(s, &next)?; |
||||||
|
if postslash == '"' || postslash == '\\' { |
||||||
|
cs.push(postslash); |
||||||
|
} else { |
||||||
|
cs.push(ch); |
||||||
|
cs.push(postslash); |
||||||
|
} |
||||||
|
*pos = nextnext; |
||||||
|
} else { |
||||||
|
cs.push(ch); |
||||||
|
*pos = next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Do not try i64 conversion, since this atom was explicitly quoted.
|
||||||
|
Ok(Atom::S(cs)) |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_unquoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> { |
||||||
|
trace!("parse_unquoted_atom {}", pos); |
||||||
|
let mut cs: String = String::new(); |
||||||
|
|
||||||
|
loop { |
||||||
|
if *pos == s.len() { break; } |
||||||
|
let (c, next) = peek(s, pos)?; |
||||||
|
|
||||||
|
if c == ';' { |
||||||
|
consume_until_newline(s, pos)?; |
||||||
|
break; |
||||||
|
} |
||||||
|
if c.is_whitespace() || c == '(' || c == ')' { break; } |
||||||
|
cs.push(c); |
||||||
|
*pos = next; |
||||||
|
} |
||||||
|
|
||||||
|
Ok(atom_of_string(cs)) |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_atom(s: &str, pos: &mut usize) -> ERes<Atom> { |
||||||
|
trace!("parse_atom {}", pos); |
||||||
|
let (ch, _) = peek(s, pos)?; |
||||||
|
|
||||||
|
if ch == '"' { parse_quoted_atom(s, pos) } else { parse_unquoted_atom(s, pos) } |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_list(s: &str, pos: &mut usize) -> ERes<Vec<Sexp>> { |
||||||
|
trace!("parse_list {}", pos); |
||||||
|
zspace(s, pos)?; |
||||||
|
expect(s, pos, '(')?; |
||||||
|
|
||||||
|
let mut sexps: Vec<Sexp> = Vec::new(); |
||||||
|
|
||||||
|
loop { |
||||||
|
zspace(s, pos)?; |
||||||
|
let (c, next) = peek(s, pos)?; |
||||||
|
if c == ')' { |
||||||
|
*pos = next; |
||||||
|
break; |
||||||
|
} |
||||||
|
sexps.push(parse_sexp(s, pos)?); |
||||||
|
} |
||||||
|
|
||||||
|
zspace(s, pos)?; |
||||||
|
|
||||||
|
Ok(sexps) |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_sexp(s: &str, pos: &mut usize) -> ERes<Sexp> { |
||||||
|
trace!("parse_sexp {}", pos); |
||||||
|
zspace(s, pos)?; |
||||||
|
let (c, _) = peek(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()), Default::default()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Constructs an atomic s-expression from an int.
|
||||||
|
pub fn atom_i(i: i64) -> Sexp { |
||||||
|
Sexp::Atom(Atom::I(i), Default::default()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Constructs an atomic s-expression from a float.
|
||||||
|
pub fn atom_f(f: f64) -> Sexp { |
||||||
|
Sexp::Atom(Atom::F(f), Default::default()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Constructs a list s-expression given a slice of s-expressions.
|
||||||
|
pub fn list(xs: &[Sexp]) -> Sexp { |
||||||
|
Sexp::List(xs.to_owned(), Default::default()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Reads an s-expression out of a `&str`.
|
||||||
|
#[inline(never)] |
||||||
|
pub fn parse(s: &str) -> Result<Sexp, Box<Error>> { |
||||||
|
let mut pos = 0; |
||||||
|
let ret = parse_sexp(s, &mut pos)?; |
||||||
|
if pos == s.len() { Ok(ret) } else { err("unrecognized post-s-expression data", s, &pos) } |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Pretty print in lisp convention, instead of all on the same line,
|
||||||
|
// packed as tightly as possible. It's kinda ugly.
|
||||||
|
|
||||||
|
fn is_num_string(s: &str) -> bool { |
||||||
|
let x: Result<i64, _> = FromStr::from_str(&s); |
||||||
|
let y: Result<f64, _> = FromStr::from_str(&s); |
||||||
|
x.is_ok() || y.is_ok() |
||||||
|
} |
||||||
|
|
||||||
|
fn string_contains_whitespace(s: &str) -> bool { |
||||||
|
for c in s.chars() { |
||||||
|
if c.is_whitespace() { return true; } |
||||||
|
} |
||||||
|
false |
||||||
|
} |
||||||
|
|
||||||
|
fn quote(s: &str) -> Cow<str> { |
||||||
|
if !s.contains("\"") |
||||||
|
&& !string_contains_whitespace(s) |
||||||
|
&& !is_num_string(s) { |
||||||
|
Cow::Borrowed(s) |
||||||
|
} else { |
||||||
|
let mut r: String = "\"".to_string(); |
||||||
|
r.push_str(&s.replace("\\", "\\\\").replace("\"", "\\\"")); |
||||||
|
r.push_str("\""); |
||||||
|
Cow::Owned(r) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for Atom { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
||||||
|
match *self { |
||||||
|
Atom::S(ref s) => write!(f, "{}", quote(s)), |
||||||
|
Atom::I(i) => write!(f, "{}", i), |
||||||
|
Atom::F(d) => write!(f, "{}", d), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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, _) => { |
||||||
|
write!(f, "(")?; |
||||||
|
for (i, x) in xs.iter().enumerate() { |
||||||
|
let s = if i == 0 { "" } else { " " }; |
||||||
|
write!(f, "{}{}", s, x)?; |
||||||
|
} |
||||||
|
write!(f, ")") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Debug for Atom { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
||||||
|
write!(f, "{}", self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Debug for Sexp { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
||||||
|
write!(f, "{}", self) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
use super::*; |
||||||
|
use super::error::get_line_and_column; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_hello_world() { |
||||||
|
assert_eq!( |
||||||
|
parse("(hello -42\n\t -4.0 \"world\") ; comment").unwrap(), |
||||||
|
list(&[atom_s("hello"), atom_i(-42), atom_f(-4.0), atom_s("world")])); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_escaping() { |
||||||
|
assert_eq!( |
||||||
|
parse("(\"\\\"\\q\" \"1234\" 1234)").unwrap(), |
||||||
|
list(&[atom_s("\"\\q"), atom_s("1234"), atom_i(1234)])); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_pp() { |
||||||
|
let s = "(hello world (what is (up) (4 6.4 you \"123\\\\ \\\"\")))"; |
||||||
|
let sexp = parse(s).unwrap(); |
||||||
|
assert_eq!(s, sexp.to_string()); |
||||||
|
assert_eq!(s, format!("{:?}", sexp)); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
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()), Default::default()), |
||||||
|
Sexp::List(vec![Sexp::Atom(Atom::S("world".into()), Default::default())], Default::default())], Default::default())); |
||||||
|
let s = "(this (has)tight(parens))"; |
||||||
|
let s2 = "( this ( has ) tight ( parens ) )"; |
||||||
|
assert_eq!(parse(s).unwrap(), parse(s2).unwrap()); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_space_in_atom() { |
||||||
|
let sexp = list(&[atom_s("hello world")]); |
||||||
|
let sexp_as_string = sexp.to_string(); |
||||||
|
assert_eq!("(\"hello world\")", sexp_as_string); |
||||||
|
assert_eq!(sexp, parse(&sexp_as_string).unwrap()); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn show_an_error() { |
||||||
|
assert_eq!(format!("{:?}", parse("(aaaa").unwrap_err()), "1:4: unexpected eof"); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn line_and_col_test() { |
||||||
|
let s = "0123456789\n0123456789\n\n6"; |
||||||
|
assert_eq!(get_line_and_column(s, 4), SourcePosition { line: 1, column: 4, index: 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), 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] |
||||||
|
fn sexp_size() { |
||||||
|
// I just want to see when this changes, in the diff.
|
||||||
|
use std::mem; |
||||||
|
assert_eq!(mem::size_of::<Sexp>(), mem::size_of::<isize>() * 6); |
||||||
|
} |
Loading…
Reference in new issue