]>
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
};
10 use std
::str::FromStr
;
12 /// Indicates how parsing failed.
13 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
15 /// We can't explain how the parsing failed.
19 #[derive(Debug, PartialEq, Clone, PartialOrd)]
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`.
33 /// A list of subexpressions
37 pub fn parse_one(input
: &str) -> Result
<Sexp
, ParseError
> {
38 match do_parse
!(input
,
40 opt
!(complete
!(multispace
)) >>
43 IResult
::Done(_
, res
) => Ok(res
),
44 _
=> Err(ParseError
::Unspecified
),
48 pub fn parse(input
: &str) -> Result
<Vec
<Sexp
>, ParseError
> {
49 let parse_res
: IResult
<&str, Vec
<Sexp
>> =
51 exps
: many1
!(complete
!(sexp
)) >>
52 opt
!(complete
!(multispace
)) >>
56 IResult
::Done(_
, res
) => Ok(res
),
59 Err(ParseError
::Unspecified
)
64 named
!(sexp
<&str, Sexp
>,
66 list
=> { |list
| Sexp
::List(list
) }
71 named
!(list
<&str, Vec
<Sexp
> >,
75 entries
: many0
!(sexp
) >>
82 named
!(atom
<&str, Sexp
>, alt_complete
!(string
| symbol
| number
| character
));
84 named
!(string
<&str, Sexp
>,
88 contents: take_until_s!(r#"""#) >>
90 (Sexp::Str(contents.into()))
94 named!(symbol<&str, Sexp>,
97 peek!(valid_ident_prefix) >>
98 name: take_while1_s!(valid_ident_char) >>
99 (Sexp::Sym(name.into()))
103 fn valid_ident_prefix(ident: &str) -> IResult<&str, ()> {
104 match ident.chars().next() {
105 Some(c) if c != '#' && !c.is_digit(10) && valid_ident_char(c) =>
106 IResult::Done(&ident[1..], ()),
107 None => IResult::Incomplete(nom::Needed::Unknown),
108 _ => IResult::Error(nom::ErrorKind::Custom(0)),
112 fn valid_ident_char(c: char) -> bool {
113 !c.is_whitespace() && c != '"' && c != '(' && c != ')'
116 named
!(number
<&str, Sexp
>,
117 preceded
!(opt
!(multispace
),
119 recognize
!(do_parse
!(
121 is_float
: opt
!(complete
!(tag_s
!("."))) >>
122 opt
!(complete
!(digit
)) >>
123 peek
!(not
!(valid_ident_prefix
)) >>
127 if text
.contains(".") {
128 f64::from_str(text
).map(Sexp
::Float
).or(Err(()))
130 i64::from_str(text
).map(Sexp
::Int
).or(Err(()))
137 named
!(character
<&str, Sexp
>,
141 character
: take_s
!(1) >>
142 (Sexp
::Char(character
.chars().next().unwrap
()))
148 fn test_parse_number() {
149 assert_eq
!(number("0"), IResult
::Done("", Sexp
::Int(0)));
150 assert_eq
!(number("123"), IResult
::Done("", Sexp
::Int(123)));
151 assert_eq
!(number("0123456789"), IResult
::Done("", Sexp
::Int(123456789)));
152 assert_eq
!(number(" 42"), IResult
::Done("", Sexp
::Int(42)));
154 assert_eq
!(number("4."), IResult
::Done("", Sexp
::Float(4.)));
155 assert_eq
!(number("4.2"), IResult
::Done("", Sexp
::Float(4.2)));
156 assert_eq
!(number("1.00000000001"),
157 IResult
::Done("", Sexp
::Float(1.00000000001)));
159 assert
!(number(" 42a").is
_err
());
160 assert_eq
!(number("13()"), IResult
::Done("()", Sexp
::Int(13)));
162 assert
!(number("abc").is
_err
());
163 assert
!(number("()").is
_err
());
164 assert
!(number("").is
_incomplete
());
169 fn test_parse_ident() {
170 assert_eq
!(symbol("+"), IResult
::Done("", Sexp
::Sym("+".into
())));
171 assert_eq
!(symbol(" nil?"), IResult
::Done("", Sexp
::Sym("nil?".into
())));
172 assert_eq
!(symbol(" ->socket"), IResult
::Done("", Sexp
::Sym("->socket".into
())));
173 assert_eq
!(symbol("fib("), IResult
::Done("(", Sexp
::Sym("fib".into
())));
175 // We reserve #foo for the implementation to do as it wishes
176 assert
!(symbol("#hi").is
_err
());
178 assert
!(symbol("0").is
_err
());
179 assert
!(symbol("()").is
_err
());
180 assert
!(symbol("").is
_incomplete
());
185 fn test_parse_string() {
186 assert_eq
!(string(r
#""hello""#), IResult::Done("", Sexp::Str("hello".into())));
187 assert_eq
!(string(r
#" "this is a nice string
188 with
0123 things
in it
""#),
189 IResult
::Done("", Sexp
::Str("this is a nice string\nwith 0123 things in it".into
())));
191 assert
!(string(r
#""hi"#).is_err());
196 fn test_parse_char() {
197 assert_eq!(character(r#"#\""#), IResult::Done("", Sexp::Char('"')));
198 assert_eq!(character(r#"#\ "#), IResult::Done("", Sexp::Char(' ')));
199 assert_eq!(character(r#" #\\"#), IResult::Done("", Sexp::Char('\\')));
201 assert!(character("#").is_incomplete());
202 assert!(character("a").is_err());
207 fn test_parse_list() {
208 assert_eq!(list("()"), IResult::Done("", vec![]));
209 assert_eq!(list("(1)"), IResult::Done("", vec![Sexp::Int(1)]));
210 assert_eq!(list(" ( 1 2 3 a )"), IResult::Done("", vec![
214 Sexp::Sym("a".into()),
220 fn test_parse_only_one() {
221 assert!(parse_one("1 2").is_err());
226 fn test_parse_expression() {
227 assert_eq!(parse_one(r#"
229 (print (str "say " #\" "Hello, World" #\" " today!")))
232 Sexp::Sym("def
".into()),
234 vec![Sexp::Sym("main
".into())]
237 Sexp::Sym("print
".into()),
239 Sexp::Sym("str".into()),
240 Sexp::Str("say
".into()),
242 Sexp
::Str("Hello, World".into
()),
244 Sexp::Str(" today
!".into()),
252 fn test_parse_multi() {
253 assert_eq!(parse(" 1 2 3 "),
254 Ok(vec![Sexp::Int(1), Sexp::Int(2), Sexp::Int(3)]));