diff --git a/src/primitives.rs b/src/primitives.rs index 8101175..62db1cd 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -14,6 +14,40 @@ pub fn literal_char<'a>(expected: char) -> impl Parser<&'a str, char, ()> { } } +pub fn one_of<'a>(items: &'static str) -> impl Parser<&'a str, char, ()> { + move |input: &'a str| { + if let Some(ch) = input.chars().next() { + if items.contains(ch) { + let (_first, rest) = input.split_at(1); + return Ok((ch, rest)); + } + } + Err(((), input)) + } +} + +/// Parses a standard identifier in a programming language +pub fn identifier(input: &str) -> ParseResult<&str, String, &str> { + let mut chars = input.chars(); + let mut buf = String::new(); + + match chars.next() { + Some(ch) if ch.is_alphabetic() => buf.push(ch), + _ => return Err((input, input)), + } + + for next in chars { + if next.is_alphanumeric() { + buf.push(next); + } else { + break; + } + } + + let next_index = buf.len(); + Ok((buf, &input[next_index..])) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/test/mod.rs b/src/test/mod.rs index a4211b5..94e1aa1 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -1,3 +1,5 @@ +mod sexp; + use super::*; #[test] diff --git a/src/test/sexp.rs b/src/test/sexp.rs new file mode 100644 index 0000000..be6c1aa --- /dev/null +++ b/src/test/sexp.rs @@ -0,0 +1,34 @@ +use crate::*; + +#[derive(Debug, PartialEq)] +enum Expr { + Atom(Atom), + List(Vec), + Quote(Vec), +} + +#[derive(Clone, Debug, PartialEq)] +enum Atom { + Num(i64), + Str(String), + Bool(bool), + Symbol(String), +} + +#[test] +fn parse_sexp() { + let parse_bool = choice(( + literal("#t").to(Atom::Bool(true)), + literal("#f").to(Atom::Bool(false)), + )); + + let parse_symbol = identifier; + let parse_number = repeated(one_of("1234567890")) + .at_least(1) + .map(|n| Atom::Num(n.iter().collect::().parse::().unwrap())); + + let parser = choice((parse_bool, parse_number)); + + let output = parser.parse("#t").unwrap(); + assert_eq!(output.0, Atom::Bool(true)); +}