Compare commits
3 Commits
918e3d042b
...
bb06350404
Author | SHA1 | Date | |
---|---|---|---|
|
bb06350404 | ||
|
56042dbbe2 | ||
|
3669d5d2cc |
@ -1,19 +1,49 @@
|
||||
use crate::Parser;
|
||||
use crate::{ParseResult, Parser};
|
||||
|
||||
pub fn choice<'a, I, O, E>(parsers: &'a [&'a dyn Parser<I, O, E>]) -> impl Parser<I, O, E> + 'a {
|
||||
move |mut input: I| {
|
||||
//TODO need a more principled way to return an error when no choices work
|
||||
let mut err = None;
|
||||
pub trait Choice<I, O, E> {
|
||||
fn parse_choice(&self, input: I) -> Result<(O, I), (E, I)>;
|
||||
}
|
||||
|
||||
for parser in parsers.iter() {
|
||||
match parser.parse(input) {
|
||||
Ok(res) => return Ok(res),
|
||||
Err((e, rest)) => {
|
||||
err = Some(e);
|
||||
input = rest;
|
||||
}
|
||||
pub fn choice<C: Choice<I, O, E>, I, O, E>(choices: C) -> impl Parser<I, O, E> {
|
||||
move |input| choices.parse_choice(input)
|
||||
}
|
||||
|
||||
fn choice_loop<'a, I, O, E>(
|
||||
mut input: I,
|
||||
parsers: &'a [&'a dyn Parser<I, O, E>],
|
||||
) -> ParseResult<I, O, E> {
|
||||
//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) {
|
||||
Ok(res) => return Ok(res),
|
||||
Err((e, rest)) => {
|
||||
err = Some(e);
|
||||
input = rest;
|
||||
}
|
||||
}
|
||||
Err((err.unwrap(), input))
|
||||
}
|
||||
Err((err.unwrap(), input))
|
||||
}
|
||||
|
||||
impl<P1, P2, I, O, E> Choice<I, O, E> for (P1, P2)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
{
|
||||
fn parse_choice(&self, input: I) -> Result<(O, I), (E, I)> {
|
||||
choice_loop(input, &[&self.0, &self.1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<P1, P2, P3, I, O, E> Choice<I, O, E> for (P1, P2, P3)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
{
|
||||
fn parse_choice(&self, input: I) -> Result<(O, I), (E, I)> {
|
||||
choice_loop(input, &[&self.0, &self.1, &self.2])
|
||||
}
|
||||
}
|
||||
|
79
src/lib.rs
79
src/lib.rs
@ -6,85 +6,12 @@ mod parser;
|
||||
mod primitives;
|
||||
mod sequence;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
pub use choice::*;
|
||||
pub use combinators::*;
|
||||
pub use map::*;
|
||||
pub use parser::{ParseResult, Parser, ParserExtension};
|
||||
pub use primitives::*;
|
||||
pub use sequence::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parsing() {
|
||||
let (parsed, rest) = literal("a")("a yolo").unwrap();
|
||||
assert_eq!(parsed, "a");
|
||||
assert_eq!(rest, " yolo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequence() {
|
||||
let parser = seq2(literal("bongo"), seq2(literal(" "), literal("jonzzz")));
|
||||
let output = parser.parse("bongo jonzzz").unwrap();
|
||||
assert_eq!(output.0 .0, "bongo");
|
||||
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, ()>, &b, &c];
|
||||
let parser = choice(&inputs);
|
||||
|
||||
let output = parser.parse("ara hajimete").unwrap();
|
||||
assert_eq!(("ara", " hajimete"), output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let parser =
|
||||
seq2(literal("a"), literal("b")).map(|(a, _b): (&str, &str)| (a.to_uppercase(), 59));
|
||||
let output = parser.parse("abcd").unwrap();
|
||||
assert_eq!((("A".to_owned(), 59), "cd"), output);
|
||||
|
||||
let spaces = repeated(literal_char(' ')).at_least(1);
|
||||
let parser = seq2(literal("lute"), spaces).to(500);
|
||||
assert_eq!(parser.parse("lute "), Ok((500, "")));
|
||||
assert_eq!(parser.parse("lute"), Err(((), "")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combinators() {
|
||||
let parser = repeated(literal_char('a')).to(10).then(literal_char('b'));
|
||||
let output = parser.parse("aaaaaaaabcd").unwrap();
|
||||
assert_eq! {((10, 'b'), "cd"), output};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional() {
|
||||
let parser = seq2(
|
||||
optional(literal("alpha")),
|
||||
seq2(repeated(literal(" ")), literal("beta")),
|
||||
);
|
||||
|
||||
let output1 = parser.parse(" beta").unwrap();
|
||||
assert_eq!(output1.0 .0, None);
|
||||
let output2 = parser.parse("alpha beta").unwrap();
|
||||
assert_eq!(output2.0 .0, Some("alpha"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repeated() {
|
||||
let spaces = repeated(literal_char(' ')).at_least(1);
|
||||
let bongo = literal("bongo");
|
||||
let parser = repeated(bongo).separated_by(map(spaces, |_| ()));
|
||||
let output = parser.parse("bongo bongo bongo bongo");
|
||||
let output = output.unwrap();
|
||||
assert_eq!(output.0, vec!["bongo", "bongo", "bongo", "bongo"]);
|
||||
}
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
70
src/test/mod.rs
Normal file
70
src/test/mod.rs
Normal file
@ -0,0 +1,70 @@
|
||||
mod sexp;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parsing() {
|
||||
let (parsed, rest) = literal("a")("a yolo").unwrap();
|
||||
assert_eq!(parsed, "a");
|
||||
assert_eq!(rest, " yolo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequence() {
|
||||
let parser = seq2(literal("bongo"), seq2(literal(" "), literal("jonzzz")));
|
||||
let output = parser.parse("bongo jonzzz").unwrap();
|
||||
assert_eq!(output.0 .0, "bongo");
|
||||
assert_eq!(output.0 .1, (" ", "jonzzz"));
|
||||
assert_eq!(output.1, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_choice() {
|
||||
let parser = choice((literal("bongo"), literal("sucy"), literal("ara")));
|
||||
|
||||
let output = parser.parse("ara hajimete").unwrap();
|
||||
assert_eq!(("ara", " hajimete"), output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let parser =
|
||||
seq2(literal("a"), literal("b")).map(|(a, _b): (&str, &str)| (a.to_uppercase(), 59));
|
||||
let output = parser.parse("abcd").unwrap();
|
||||
assert_eq!((("A".to_owned(), 59), "cd"), output);
|
||||
|
||||
let spaces = repeated(literal_char(' ')).at_least(1);
|
||||
let parser = seq2(literal("lute"), spaces).to(500);
|
||||
assert_eq!(parser.parse("lute "), Ok((500, "")));
|
||||
assert_eq!(parser.parse("lute"), Err(((), "")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combinators() {
|
||||
let parser = repeated(literal_char('a')).to(10).then(literal_char('b'));
|
||||
let output = parser.parse("aaaaaaaabcd").unwrap();
|
||||
assert_eq! {((10, 'b'), "cd"), output};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional() {
|
||||
let parser = seq2(
|
||||
optional(literal("alpha")),
|
||||
seq2(repeated(literal(" ")), literal("beta")),
|
||||
);
|
||||
|
||||
let output1 = parser.parse(" beta").unwrap();
|
||||
assert_eq!(output1.0 .0, None);
|
||||
let output2 = parser.parse("alpha beta").unwrap();
|
||||
assert_eq!(output2.0 .0, Some("alpha"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repeated() {
|
||||
let spaces = repeated(literal_char(' ')).at_least(1);
|
||||
let bongo = literal("bongo");
|
||||
let parser = repeated(bongo).separated_by(map(spaces, |_| ()));
|
||||
let output = parser.parse("bongo bongo bongo bongo");
|
||||
let output = output.unwrap();
|
||||
assert_eq!(output.0, vec!["bongo", "bongo", "bongo", "bongo"]);
|
||||
}
|
34
src/test/sexp.rs
Normal file
34
src/test/sexp.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Expr {
|
||||
Atom(Atom),
|
||||
List(Vec<Expr>),
|
||||
Quote(Vec<Expr>),
|
||||
}
|
||||
|
||||
#[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::<String>().parse::<i64>().unwrap()));
|
||||
|
||||
let parser = choice((parse_bool, parse_number));
|
||||
|
||||
let output = parser.parse("#t").unwrap();
|
||||
assert_eq!(output.0, Atom::Bool(true));
|
||||
}
|
Loading…
Reference in New Issue
Block a user