diff --git a/src/choice.rs b/src/choice.rs new file mode 100644 index 0000000..f037cfb --- /dev/null +++ b/src/choice.rs @@ -0,0 +1,21 @@ +use crate::Parser; + +pub fn choice<'a, I, O, E>(parsers: &'a [&'a dyn Parser]) -> impl Parser + 'a +where + I: Clone, +{ + move |input: I| { + //TODO need a more principled way to return an error when no choices work + let mut err = None; + + for parser in parsers.iter() { + match parser.parse(input.clone()) { + Ok(res) => return Ok(res), + Err(e) => { + err = Some(e); + } + } + } + Err(err.unwrap()) + } +} diff --git a/src/lib.rs b/src/lib.rs index b955260..245f314 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,15 @@ #![allow(dead_code)] //TODO eventually turn this off +mod choice; +mod primitives; +mod sequence; + +pub use choice::*; +pub use primitives::*; +pub use sequence::*; type ParseResult = Result<(O, I), E>; -trait Parser { +pub trait Parser { fn parse(&self, input: I) -> ParseResult; } @@ -15,46 +22,6 @@ where } } -fn literal(expected: &'static str) -> impl Fn(&str) -> ParseResult<&str, &str, &str> { - move |input| match input.get(0..expected.len()) { - Some(next) if next == expected => Ok((next, &input[expected.len()..])), - _ => Err(input), - } -} - -fn sequence( - first: impl Parser, - second: impl Parser, -) -> impl Parser { - move |input| -> ParseResult { - first.parse(input).and_then(|(result1, rest)| { - second - .parse(rest) - .map(|(result2, rest2)| ((result1, result2), rest2)) - }) - } -} - -fn choice<'a, I, O, E>(parsers: &'a [&'a dyn Parser]) -> impl Parser + 'a -where - I: Clone, -{ - move |input: I| { - //TODO need a more principled way to return an error when no choices work - let mut err = None; - - for parser in parsers.iter() { - match parser.parse(input.clone()) { - Ok(res) => return Ok(res), - Err(e) => { - err = Some(e); - } - } - } - Err(err.unwrap()) - } -} - #[cfg(test)] mod tests { use super::*; @@ -74,4 +41,16 @@ mod tests { assert_eq!(output.0 .1, (" ", "jonzzz")); assert_eq!(output.1, ""); } + + #[test] + fn test_choice() { + let a = literal("bongo"); + let b = literal("sucy"); + let c = literal("ara"); + let inputs = [&a as &dyn Parser<&str, &str, &str>, &b, &c]; + let parser = choice(&inputs); + + let output = parser.parse("ara hajimete").unwrap(); + assert_eq!(("ara", " hajimete"), output); + } } diff --git a/src/primitives.rs b/src/primitives.rs new file mode 100644 index 0000000..c5e1622 --- /dev/null +++ b/src/primitives.rs @@ -0,0 +1,26 @@ +use crate::{ParseResult, Parser}; + +pub fn literal(expected: &'static str) -> impl Fn(&str) -> ParseResult<&str, &str, &str> { + move |input| match input.get(0..expected.len()) { + Some(next) if next == expected => Ok((next, &input[expected.len()..])), + _ => Err(input), + } +} + +pub fn literal_char<'a>(expected: char) -> impl Parser<&'a str, char, &'a str> { + move |input: &'a str| match input.chars().next() { + Some(ch) if ch == expected => Ok((expected, &input[ch.len_utf8()..])), + _ => Err(input), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn literals() { + let parser = literal_char('f'); + assert_eq!(Ok(('f', "unky")), parser.parse("funky")); + } +} diff --git a/src/sequence.rs b/src/sequence.rs new file mode 100644 index 0000000..87229e1 --- /dev/null +++ b/src/sequence.rs @@ -0,0 +1,14 @@ +use crate::{ParseResult, Parser}; + +pub fn sequence( + first: impl Parser, + second: impl Parser, +) -> impl Parser { + move |input| -> ParseResult { + first.parse(input).and_then(|(result1, rest)| { + second + .parse(rest) + .map(|(result2, rest2)| ((result1, result2), rest2)) + }) + } +}