]>
Witch of Git - ess/blob - src/lib.rs
1 //! A lightweight S-expression parser intended for language implementation.
3 // #![warn(missing_docs)]
9 use nom
::{digit
, multispace
, IResult
};
11 /// Indicates how parsing failed.
12 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
14 /// We can't explain how the parsing failed.
18 #[derive(Debug, PartialEq, Clone, PartialOrd)]
19 /// An `Atom` is the representation of a non-composite object
21 /// A value representing a symbol. A symbol is an atomic unit
23 /// A value representing a string literal.
25 /// A value representing a single character.
27 /// A value representing an integer. Any number containing no decimal point
28 /// will be parsed as an `Int`.
30 /// A value representing a float. Any number containing a decimal point will
31 /// be parsed as a `Float`.
35 #[derive(Debug, PartialEq, Clone, PartialOrd)]
36 /// A `Sexp` represents either an `Atom` or a `List`. It encompasses all
37 /// possible lisp expressions.
39 /// A wrapper around the atom type
43 /// A list of subexpressions
49 pub fn parse(input
: &str) -> Result
<Sexp
, ParseError
> {
50 match do_parse
!(input
, exp
: sexp
>> opt
!(multispace
) >> eof
!() >> (exp
)) {
51 IResult
::Done(_
, res
) => Ok(res
),
52 _
=> Err(ParseError
::Unspecified
),
56 named
!(sexp
<&str, Sexp
>,
58 list
=> { |list
| Sexp
::List
{ list
: list
} }
59 | atom
=> { |atom
| Sexp
::Atom
{ atom
: atom
} }
63 named
!(list
<&str, Vec
<Sexp
> >,
67 entries
: many0
!(sexp
) >>
74 named
!(atom
<&str, Atom
>, alt
!(string
| symbol
| number
| character
));
76 named
!(string
<&str, Atom
>,
80 contents: take_until_s!("\"") >>
82 (Atom::Str(contents.into()))
86 named!(symbol<&str, Atom>,
89 peek!(valid_ident_prefix) >>
90 name: take_while1_s!(valid_ident_char) >>
91 (Atom::Sym(name.into()))
95 fn valid_ident_prefix(ident: &str) -> IResult<&str, ()> {
96 match ident.chars().next() {
97 Some(c) if c != '#' && !c.is_digit(10) && valid_ident_char(c) =>
98 IResult::Done(&ident[1..], ()),
99 None => IResult::Incomplete(nom::Needed::Unknown),
100 _ => IResult::Error(nom::ErrorKind::Custom(0)),
104 fn valid_ident_char(c: char) -> bool {
105 !c.is_whitespace() && c != '"'
&& c
!= '
('
&& c
!= '
)'
108 named
!(number
<&str, Atom
>,
112 peek
!(not
!(valid_ident_prefix
)) >>
113 (Atom
::Int(integral
.chars().fold
(0, |i
, c
| i
* 10 + c
as i64 - '
0'
as i64)))
117 named
!(character
<&str, Atom
>,
121 character
: take_s
!(1) >>
122 (Atom
::Char(character
.chars().next().unwrap
()))
128 fn test_parse_number() {
129 assert_eq
!(number("0"), IResult
::Done("", Atom
::Int(0)));
130 assert_eq
!(number("123"), IResult
::Done("", Atom
::Int(123)));
131 assert_eq
!(number("0123456789"), IResult
::Done("", Atom
::Int(123456789)));
132 assert_eq
!(number(" 42"), IResult
::Done("", Atom
::Int(42)));
134 assert
!(number(" 42a").is
_err
());
135 assert_eq
!(number("13()"), IResult
::Done("()", Atom
::Int(13)));
137 assert
!(number("abc").is
_err
());
138 assert
!(number("()").is
_err
());
139 assert
!(number("").is
_incomplete
());
144 fn test_parse_ident() {
145 assert_eq
!(symbol("+"), IResult
::Done("", Atom
::Sym("+".into
())));
146 assert_eq
!(symbol(" nil?"), IResult
::Done("", Atom
::Sym("nil?".into
())));
147 assert_eq
!(symbol(" ->socket"), IResult
::Done("", Atom
::Sym("->socket".into
())));
148 assert_eq
!(symbol("fib("), IResult
::Done("(", Atom
::Sym("fib".into
())));
150 // We reserve #foo for the implementation to do as it wishes
151 assert
!(symbol("#hi").is
_err
());
153 assert
!(symbol("0").is
_err
());
154 assert
!(symbol("()").is
_err
());
155 assert
!(symbol("").is
_incomplete
());
160 fn test_parse_string() {
161 assert_eq
!(string(r
#""hello""#), IResult::Done("", Atom::Str("hello".into())));
162 assert_eq
!(string(r
#" "this is a nice string
163 with
0123 things
in it
""#),
164 IResult
::Done("", Atom
::Str("this is a nice string\nwith 0123 things in it".into
())));
166 assert
!(string(r
#""hi"#).is_err());
171 fn test_parse_char() {
172 assert_eq!(character("#\\\""), IResult::Done("", Atom::Char('"')));
173 assert_eq!(character("#\\ "), IResult::Done("", Atom::Char(' ')));
174 assert_eq!(character(" #\\\\"), IResult::Done("", Atom::Char('\\')));
176 assert!(character("#").is_incomplete());
177 assert!(character("a").is_err());
182 fn test_parse_list() {
183 assert_eq!(list("()"), IResult::Done("", vec![]));
184 assert_eq!(list("(1)"), IResult::Done("", vec![Sexp::Atom { atom: Atom::Int(1) }]));
185 assert_eq!(list(" ( 1 2 3 a )"), IResult::Done("", vec![
186 Sexp::Atom { atom: Atom::Int(1) },
187 Sexp::Atom { atom: Atom::Int(2) },
188 Sexp::Atom { atom: Atom::Int(3) },
189 Sexp::Atom { atom: Atom::Sym("a".into()) },
195 fn test_cant_parse() {
196 assert!(parse("1 2").is_err());
201 fn test_parse_expression() {
204 (print (str "say " #\" "Hello, World" #\" " today!")))
208 Sexp::Atom { atom: Atom::Sym("def
".into()) },
211 Sexp::Atom { atom: Atom::Sym("main
".into()) }
216 Sexp::Atom { atom: Atom::Sym("print
".into()) },
220 atom: Atom::Sym("str".into())
223 atom: Atom::Str("say
".into())
225 Sexp::Atom { atom: Atom::Char('"'
) },
227 atom
: Atom
::Str("Hello, World".into
())
229 Sexp
::Atom
{ atom
: Atom
::Char('
"') },
231 atom: Atom::Str(" today
!".into())