parent
							
								
									548addc78c
								
							
						
					
					
						commit
						1f2dbaa81d
					
				| @ -1,4 +1,5 @@ | |||||||
| Copyright (c) 2015 Clark Gaebel <cg.wowus.cg@gmail.com> | Copyright (c) 2015 Clark Gaebel <cg.wowus.cg@gmail.com> | ||||||
|  | Copyright (c) 2020 Ondřej Hruška <ondra@ondrovo.com> | ||||||
| 
 | 
 | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | CRSN Sexp | ||||||
|  | ========= | ||||||
|  | 
 | ||||||
|  | This is an updated and extended version of the "sexp" crate by Clark Gaebel: [https://github.com/cgaebel/sexp](https://github.com/cgaebel/sexp). | ||||||
|  | 
 | ||||||
|  | ## Changes from "cgaebel/sexp" | ||||||
|  | 
 | ||||||
|  | - Updated to the 2018 Rust edition (that is, removed `try!()` and such) | ||||||
|  | - All parsed atoms now track their source location. This enables better error reporting in subsequent parsing and processing. | ||||||
|  | - Quoted strings now support C-style escapes other than `\"`, such as `\n`, `\t` and `\\`.  | ||||||
|  |   - Unrecognized escapes result in the slash being removed and the next character being taken literally. Use `\\` to enter a backslash. | ||||||
|  | - Added special parsing of "character literals" that use single quotes (`'A'`) and may contain backslash escapes (`'\n'`). | ||||||
|  |   - This does not interfere with "apostrophe tokens" like `'foo`, that becomes an unquoted string atom. | ||||||
|  | - Added "quoted string", "unsigned" and "character" atom types | ||||||
|  | - Added parsing for `0x123`, `#ff00ff` and `0b123456` | ||||||
|  | - Numeric literals may now contain underscores to separate digit groups | ||||||
|  | - Numbers are preferably parsed as the unsigned atom (u64). The signed atom (i64) is only used for negative numbers. | ||||||
|  | 
 | ||||||
|  | . | ||||||
| @ -0,0 +1,444 @@ | |||||||
|  | //! 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(Debug, PartialEq, Clone, PartialOrd)] | ||||||
|  | pub enum Atom { | ||||||
|  |     /// Simple string atom
 | ||||||
|  |     S(String), | ||||||
|  |     /// Quoted string
 | ||||||
|  |     QS(String), | ||||||
|  |     /// Character literal (with single quotes)
 | ||||||
|  |     C(char), | ||||||
|  |     /// Signed integer (normally only used for negative values)
 | ||||||
|  |     I(i64), | ||||||
|  |     /// Unsigned integer
 | ||||||
|  |     U(u64), | ||||||
|  |     /// Float
 | ||||||
|  |     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(Debug, 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 without_underscores(s: &str) -> Cow<str> { | ||||||
|  |     if s.contains('_') { | ||||||
|  |         s.chars() | ||||||
|  |             .filter(|c| *c != '_') | ||||||
|  |             .collect::<String>().into() | ||||||
|  |     } else { | ||||||
|  |         s.into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn atom_of_string(s: String) -> Atom { | ||||||
|  |     if s.starts_with('#') { | ||||||
|  |         match u64::from_str_radix(&without_underscores(&s[1..]), 16) { | ||||||
|  |             Ok(u) => return Atom::U(u), | ||||||
|  |             Err(_) => {} | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if s.starts_with("0x") { | ||||||
|  |         match u64::from_str_radix(&without_underscores(&s[2..]), 16) { | ||||||
|  |             Ok(u) => return Atom::U(u), | ||||||
|  |             Err(_) => {} | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if s.starts_with("0b") { | ||||||
|  |         match u64::from_str_radix(&without_underscores(&s[2..]), 2) { | ||||||
|  |             Ok(u) => return Atom::U(u), | ||||||
|  |             Err(_) => {} | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if !s.starts_with('_') { | ||||||
|  |         let filtered = without_underscores(&s); | ||||||
|  | 
 | ||||||
|  |         match FromStr::from_str(&filtered) { | ||||||
|  |             Ok(u) => return Atom::U(u), | ||||||
|  |             Err(_) => {} | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         match FromStr::from_str(&filtered) { | ||||||
|  |             Ok(i) => return Atom::I(i), | ||||||
|  |             Err(_) => {} | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         match FromStr::from_str(&filtered) { | ||||||
|  |             Ok(f) => return Atom::F(f), | ||||||
|  |             Err(_) => {} | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Atom::S(s) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // returns the char it found, and the new pos 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!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // returns the char it found, and the new pos if you wish to consume that char
 | ||||||
|  | fn peekn(s: &str, nth: usize, pos: usize) -> Option<(char, usize)> { | ||||||
|  |     trace!("peekn {}", pos); | ||||||
|  |     if nth == 0 { | ||||||
|  |         panic!("peekn with nth=0"); | ||||||
|  |     } | ||||||
|  |     if s.is_char_boundary(pos) { | ||||||
|  |         let mut iter = s[pos..].chars(); | ||||||
|  |         let mut bytelen = 0; | ||||||
|  |         for n in 0..nth { | ||||||
|  |             if let Some(ch) = iter.next() { | ||||||
|  |                 bytelen += ch.len_utf8(); | ||||||
|  |                 if n == (nth - 1) { | ||||||
|  |                     return Some((ch, pos + bytelen)); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 return None; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         unreachable!() | ||||||
|  |     } 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, quote: char, pos: &mut usize) -> ERes<Atom> { | ||||||
|  |     trace!("parse_quoted_atom {}", pos); | ||||||
|  |     let pos0 = *pos; | ||||||
|  |     let mut cs: String = String::new(); | ||||||
|  | 
 | ||||||
|  |     expect(s, pos, quote)?; | ||||||
|  | 
 | ||||||
|  |     loop { | ||||||
|  |         let (ch, next) = peek(s, *pos)?; | ||||||
|  |         if ch == quote { | ||||||
|  |             *pos = next; | ||||||
|  |             break; | ||||||
|  |         } else if ch == '\\' { | ||||||
|  |             let (postslash, nextnext) = peek(s, next)?; | ||||||
|  |             match postslash { | ||||||
|  |                 'r' => cs.push('\r'), | ||||||
|  |                 'n' => cs.push('\n'), | ||||||
|  |                 't' => cs.push('\t'), | ||||||
|  |                 other => cs.push(other) | ||||||
|  |             } | ||||||
|  |             *pos = nextnext; | ||||||
|  |         } else { | ||||||
|  |             cs.push(ch); | ||||||
|  |             *pos = next; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if quote == '\'' { | ||||||
|  |         // This is a character literal
 | ||||||
|  |         if cs.chars().count() == 1 { | ||||||
|  |             return Ok(Atom::C(cs.chars().next().unwrap())); | ||||||
|  |         } else { | ||||||
|  |             return err("Too long character literal!", s, pos0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Do not try i64 conversion, since this atom was explicitly quoted.
 | ||||||
|  |     Ok(Atom::QS(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 == '"' { | ||||||
|  |         return parse_quoted_atom(s, ch, pos); | ||||||
|  |     } else if ch == '\'' { | ||||||
|  |         if let Some(('\\', _)) = peekn(s, 2, *pos) { | ||||||
|  |             if let Some(('\'', _)) = peekn(s, 4, *pos) { | ||||||
|  |                 // Character literal with an escape sequence
 | ||||||
|  |                 return parse_quoted_atom(s, '\'', pos); | ||||||
|  |             } | ||||||
|  |         } else if let Some(('\'', _)) = peekn(s, 3, *pos) { | ||||||
|  |             // Simple character literal
 | ||||||
|  |             return parse_quoted_atom(s, '\'', pos); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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 a string.
 | ||||||
|  | pub fn atom_qs(s: &str) -> Sexp { | ||||||
|  |     Sexp::Atom(Atom::QS(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 an unsigned int.
 | ||||||
|  | pub fn atom_u(u: u64) -> Sexp { | ||||||
|  |     Sexp::Atom(Atom::U(u), Default::default()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Constructs an atomic s-expression from a char
 | ||||||
|  | pub fn atom_c(c: char) -> Sexp { | ||||||
|  |     Sexp::Atom(Atom::C(c), 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn quote(s: &str) -> String { | ||||||
|  |     s.chars().fold(String::new(), |mut s, ch| { | ||||||
|  |         match ch { | ||||||
|  |             '\'' | '\\' | '"' => { | ||||||
|  |                 s.push('\\'); | ||||||
|  |                 s.push(ch); | ||||||
|  |             } | ||||||
|  |             '\n' => { | ||||||
|  |                 s.push_str("\\n"); | ||||||
|  |             } | ||||||
|  |             '\r' => { | ||||||
|  |                 s.push_str("\\n"); | ||||||
|  |             } | ||||||
|  |             '\t' => { | ||||||
|  |                 s.push_str("\\t"); | ||||||
|  |             } | ||||||
|  |             other => { | ||||||
|  |                 s.push(other); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         s | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl fmt::Display for Atom { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | ||||||
|  |         match *self { | ||||||
|  |             Atom::QS(ref s) => write!(f, "\"{}\"", quote(s)), | ||||||
|  |             Atom::S(ref s) => write!(f, "{}", s), | ||||||
|  |             Atom::C(c) => write!(f, "'{}'", quote(&c.to_string())), | ||||||
|  |             Atom::I(i) => write!(f, "{}", i), | ||||||
|  |             Atom::U(u) => write!(f, "{}", u), | ||||||
|  |             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, ")") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,9 +0,0 @@ | |||||||
| 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). |  | ||||||
| @ -1,324 +0,0 @@ | |||||||
| //! 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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
					Loading…
					
					
				
		Reference in new issue