]> Witch of Git - ess/blob - src/lib.rs
Add tests for parsing number and symbol
[ess] / src / lib.rs
1 //! A lightweight S-expression parser intended for language implementation.
2
3 #![warn(missing_docs)]
4 #![deny(unsafe_code)]
5
6 #[macro_use]
7 extern crate nom;
8
9 use nom::{digit, multispace, IResult};
10
11 #[derive(Debug, PartialEq, Clone, PartialOrd)]
12 pub enum Atom {
13 /// A value representing a symbol. A symbol is an atomic unit
14 Sym(String),
15 /// A value representing a string literal.
16 Str(String),
17 /// A value representing an integer. Any number containing no decimal point
18 /// will be parsed as an `Int`.
19 Int(i64),
20 /// A value representing a float. Any number containing a decimal point will
21 /// be parsed as a `Float`.
22 Float(f64),
23 }
24
25 #[derive(Debug, PartialEq, Clone, PartialOrd)]
26 pub enum Sexp {
27 /// A wrapper around the atom type
28 Atom {
29 atom: Atom,
30 },
31 /// A list of subexpressions
32 List {
33 list: Vec<Sexp>,
34 }
35 }
36
37 pub fn parse(input: &str) -> Result<Sexp, ()> {
38 match sexp(input) {
39 IResult::Done(_, res) => Ok(res),
40 _ => Err(()),
41 }
42 }
43
44 named!(sexp<&str, Sexp>,
45 alt!(
46 list => { |list| Sexp::List { list: list } }
47 | atom => { |atom| Sexp::Atom { atom: atom } }
48 )
49 );
50
51 named!(list<&str, Vec<Sexp> >,
52 do_parse!(
53 opt!(multispace) >>
54 tag_s!("(") >>
55 entries: many0!(sexp) >>
56 opt!(multispace) >>
57 tag_s!(")") >>
58 (entries)
59 )
60 );
61
62 named!(atom<&str, Atom>, alt!(string | symbol | number));
63
64 named!(string<&str, Atom>,
65 do_parse!(
66 opt!(multispace) >>
67 tag_s!("\"") >>
68 contents: take_until_s!("\"") >>
69 tag_s!("\"") >>
70 opt!(multispace) >>
71 (Atom::Str(contents.into()))
72 )
73 );
74
75 named!(symbol<&str, Atom>,
76 do_parse!(
77 opt!(multispace) >>
78 peek!(valid_ident_prefix) >>
79 name: take_while1_s!(valid_ident_char) >>
80 (Atom::Sym(name.into()))
81 )
82 );
83
84 fn valid_ident_prefix(ident: &str) -> IResult<&str, ()> {
85 match ident.chars().next() {
86 Some(c) if !c.is_digit(10) && valid_ident_char(c) =>
87 IResult::Done(&ident[1..], ()),
88 None => IResult::Incomplete(nom::Needed::Unknown),
89 _ => IResult::Error(nom::ErrorKind::Custom(0)),
90 }
91 }
92
93 fn valid_ident_char(c: char) -> bool {
94 !c.is_whitespace() && c != '"' && c != '(' && c != ')'
95 }
96
97 named!(number<&str, Atom>,
98 do_parse!(
99 opt!(multispace) >>
100 integral: digit >>
101 peek!(not!(valid_ident_prefix)) >>
102 (Atom::Int(integral.chars().fold(0, |i, c| i * 10 + c as i64 - '0' as i64)))
103 )
104 );
105
106 #[cfg(test)]
107 #[test]
108 fn test_parse_number() {
109 assert_eq!(number("0"), IResult::Done("", Atom::Int(0)));
110 assert_eq!(number("123"), IResult::Done("", Atom::Int(123)));
111 assert_eq!(number("0123456789"), IResult::Done("", Atom::Int(123456789)));
112 assert_eq!(number(" 42"), IResult::Done("", Atom::Int(42)));
113
114 assert!(number(" 42a").is_err());
115 assert_eq!(number("13()"), IResult::Done("()", Atom::Int(13)));
116
117 assert!(number("abc").is_err());
118 assert!(number("()").is_err());
119 assert!(number("").is_incomplete());
120 }
121
122 #[cfg(test)]
123 #[test]
124 fn test_parse_ident() {
125 assert_eq!(symbol("+"), IResult::Done("", Atom::Sym("+".into())));
126 assert_eq!(symbol(" nil?"), IResult::Done("", Atom::Sym("nil?".into())));
127 assert_eq!(symbol(" ->socket"), IResult::Done("", Atom::Sym("->socket".into())));
128 assert_eq!(symbol("fib("), IResult::Done("(", Atom::Sym("fib".into())));
129
130 assert!(symbol("0").is_err());
131 assert!(symbol("()").is_err());
132 assert!(symbol("").is_incomplete());
133 }