]>
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)]));
This page took 0.074822 seconds and 5 git commands to generate.