commit ffe288ab613fdca129146aca7c58fde0dc3e6f14 Author: jackfiled <xcrenchangjun@outlook.com> Date: Fri Nov 15 22:54:54 2024 +0800 init: repo. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c403c34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..28954a6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "zero-parser" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2c6120c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "zero-parser" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..10f2d36 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +extern crate core; + +mod parser; +mod text; diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..c946190 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,229 @@ +mod modified_parsers; +mod primitive_parsers; + +use crate::parser::modified_parsers::{BindParser, ConvertParser, MapParser, NextParser}; +use crate::parser::primitive_parsers::{ + AnyParser, FailParser, FailWithMessageParser, SatisfyParser, SkipParser, SucceedParser, + TakeParser, +}; +use std::cell::RefCell; +use std::fmt::{Debug, Display, Formatter}; +use std::marker::PhantomData; +use std::rc::Rc; + +#[derive(Debug)] +pub struct FailedParserResult { + message: String, +} + +impl FailedParserResult { + pub fn new(message: String) -> Self { + Self { message } + } +} + +impl Display for FailedParserResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Parse failed: {}.", self.message) + } +} + +pub type ParserResult<'a, TToken, T> = + Result<(&'a [TToken], T), (&'a [TToken], FailedParserResult)>; + +pub struct ParserContext<TToken, TContext> +where + TToken: Debug + Clone, +{ + pub context: TContext, + input_array: Vec<TToken>, +} + +impl<TToken, TContext> ParserContext<TToken, TContext> +where + TToken: Debug + Clone, +{ + pub fn new(input: &[TToken], context: TContext) -> Rc<RefCell<Self>> { + Rc::new(RefCell::new(Self { + input_array: input.iter().map(|x| x.clone()).collect(), + context, + })) + } + + pub fn input_slice(&self) -> &[TToken] { + self.input_array.as_slice() + } +} + +impl<TContext> ParserContext<char, TContext> { + pub fn new_with_str(input: &str, context: TContext) -> Rc<RefCell<Self>> { + Rc::new(RefCell::new(Self { + input_array: input.chars().collect(), + context, + })) + } +} + +pub trait Parser<TToken, T, TContext> +where + TToken: Debug + Clone, +{ + fn parse<'a>( + &self, + context: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T>; + + fn map<TResult, F>(self, f: F) -> MapParser<Self, F, T> + where + Self: Sized, + F: Fn(&T) -> TResult, + { + MapParser { + parser: self, + mapper: f, + phantom: PhantomData, + } + } + + fn bind<TResult, F, P>(self, f: F) -> BindParser<Self, F, T> + where + Self: Sized, + F: Fn(&T) -> P, + P: Parser<TToken, TResult, TContext>, + { + BindParser { + parser: self, + binder: f, + phantom_data: PhantomData, + } + } + + fn convert<TResult>(self) -> ConvertParser<Self, T, TResult> + where + Self: Sized, + { + ConvertParser { + parser: self, + phantom_data1: PhantomData, + phantom_data2: PhantomData, + } + } + + fn next<TResult, H>(self, handler: H) -> NextParser<Self, H, T> + where + Self: Sized, + H: for<'f> Fn(ParserResult<'f, TToken, T>) -> ParserResult<'f, TToken, TResult>, + { + NextParser { + parser: self, + handler, + phantom_data: PhantomData, + } + } +} + +impl<TToken, T, TContext, F> Parser<TToken, T, TContext> for F +where + TToken: Debug + Clone, + F: for<'f> Fn( + Rc<RefCell<ParserContext<TToken, TContext>>>, + &'f [TToken], + ) -> ParserResult<'f, TToken, T>, +{ + fn parse<'a>( + &self, + context: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T> { + (*self)(context, input) + } +} + +pub fn succeed<TToken, T, TContext>(value: T) -> impl Parser<TToken, T, TContext> +where + TToken: Debug + Clone, + T: Debug + Clone, +{ + SucceedParser { value } +} + +pub fn fail<TToken, T, TContext>() -> impl Parser<TToken, T, TContext> +where + TToken: Debug + Clone, +{ + FailParser { + phantom_data: PhantomData, + } +} + +pub fn fail_with_message<TToken, T, TContext>(message: &str) -> impl Parser<TToken, T, TContext> +where + TToken: Debug + Clone, +{ + FailWithMessageParser { + message: message.to_owned(), + phantom_data: PhantomData, + } +} + +pub fn satisfy<TToken, TContext, TP>(predicate: TP) -> impl Parser<TToken, TToken, TContext> +where + TToken: Debug + Clone, + TP: Fn(&TToken) -> bool, +{ + SatisfyParser { predicate } +} + +pub fn any<TToken, TContext>() -> impl Parser<TToken, TToken, TContext> +where + TToken: Debug + Clone, +{ + AnyParser {} +} + +pub fn take<TToken, TContext>(count: usize) -> impl Parser<TToken, Vec<TToken>, TContext> +where + TToken: Debug + Clone, +{ + TakeParser { count } +} + +pub fn skip<TToken, TContext>(count: usize) -> impl Parser<TToken, (), TContext> +where + TToken: Debug + Clone, +{ + SkipParser { count } +} + +#[cfg(test)] +pub fn parser_test_helper<T, P>( + context: Rc<RefCell<ParserContext<char, ()>>>, + excepted_node: &T, + test_parser: P, +) where + T: PartialEq + Debug, + P: Parser<char, T, ()>, +{ + let borrowed_context = context.borrow(); + let input = borrowed_context.input_slice(); + let (_, actual_result) = test_parser.parse(context.clone(), input).unwrap(); + + assert_eq!(excepted_node, &actual_result); +} + +#[cfg(test)] +pub fn failed_parser_test_helper<T, P>( + context: Rc<RefCell<ParserContext<char, ()>>>, + test_parser: P, +) where + T: Debug, + P: Parser<char, T, ()>, +{ + let borrowed_context = context.borrow(); + let input = borrowed_context.input_slice(); + let result = test_parser.parse(context.clone(), input); + + assert!(result.is_err()); +} + diff --git a/src/parser/modified_parsers.rs b/src/parser/modified_parsers.rs new file mode 100644 index 0000000..b849d06 --- /dev/null +++ b/src/parser/modified_parsers.rs @@ -0,0 +1,131 @@ +use crate::parser::{Parser, ParserContext, ParserResult}; +use std::cell::RefCell; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::rc::Rc; + +pub struct MapParser<P, F, T1> { + pub(crate) parser: P, + pub(crate) mapper: F, + pub(crate) phantom: PhantomData<T1>, +} + +impl<TToken, T1, T2, TContext, P, F> Parser<TToken, T2, TContext> for MapParser<P, F, T1> +where + TToken: Debug + Clone, + P: Parser<TToken, T1, TContext>, + F: Fn(&T1) -> T2, +{ + fn parse<'a>( + &self, + context: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T2> { + self.parser + .parse(context, input) + .map(|(remainder, result)| (remainder, (self.mapper)(&result))) + } +} + +pub struct BindParser<P, F, T1> { + pub(crate) parser: P, + pub(crate) binder: F, + pub(crate) phantom_data: PhantomData<T1>, +} + +impl<TToken, T1, T2, TContext, P, P2, F> Parser<TToken, T2, TContext> for BindParser<P, F, T1> +where + TToken: Debug + Clone, + P: Parser<TToken, T1, TContext>, + F: Fn(&T1) -> P2, + P2: Parser<TToken, T2, TContext>, +{ + fn parse<'a>( + &self, + context: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T2> { + let (input, middle) = self.parser.parse(context.clone(), input)?; + + (self.binder)(&middle).parse(context, input) + } +} + +pub struct ConvertParser<P, T1, T2> { + pub(crate) parser: P, + pub(crate) phantom_data1: PhantomData<T1>, + pub(crate) phantom_data2: PhantomData<T2>, +} + +impl<TToken, T1, T2, TContext, P> Parser<TToken, T2, TContext> for ConvertParser<P, T1, T2> +where + TToken: Debug + Clone, + T2: From<T1>, + P: Parser<TToken, T1, TContext>, +{ + fn parse<'a>( + &self, + context: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T2> { + self.parser + .parse(context, input) + .map(|(input, result)| (input, result.into())) + } +} + +pub struct NextParser<P, H, T> { + pub(crate) parser: P, + pub(crate) handler: H, + pub(crate) phantom_data: PhantomData<T>, +} + +impl<TToken, T, TResult, TContext, P, H> Parser<TToken, TResult, TContext> for NextParser<P, H, T> +where + TToken: Debug + Clone, + P: Parser<TToken, T, TContext>, + H: for<'f> Fn(ParserResult<'f, TToken, T>) -> ParserResult<'f, TToken, TResult>, +{ + fn parse<'a>( + &self, + context: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, TResult> { + let result = self.parser.parse(context, input); + + (self.handler)(result) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::{parser_test_helper, take}; + use crate::text::char_parser; + + #[test] + fn map_test() { + parser_test_helper( + ParserContext::new_with_str("hello, world!", ()), + &(), + take(5).map(|_| ()), + ) + } + + #[test] + fn bind_test() { + parser_test_helper( + ParserContext::new_with_str("abc", ()), + &'b', + char_parser('a').bind(|_| char_parser('b')), + ); + + parser_test_helper( + ParserContext::new_with_str("abc", ()), + &'c', + char_parser('a') + .bind(|_| char_parser('b')) + .bind(|_| char_parser('c')), + ); + } +} diff --git a/src/parser/primitive_parsers.rs b/src/parser/primitive_parsers.rs new file mode 100644 index 0000000..82e1442 --- /dev/null +++ b/src/parser/primitive_parsers.rs @@ -0,0 +1,236 @@ +use crate::parser::{FailedParserResult, Parser, ParserContext, ParserResult}; +use std::cell::RefCell; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::rc::Rc; + +pub struct SucceedParser<T> { + pub(crate) value: T, +} + +impl<TToken, T, TContext> Parser<TToken, T, TContext> for SucceedParser<T> +where + TToken: Debug + Clone, + T: Debug + Clone, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T> { + Ok((input, self.value.clone())) + } +} + +pub struct FailParser<T> { + pub(crate) phantom_data: PhantomData<T>, +} + +impl<TToken, T, TContext> Parser<TToken, T, TContext> for FailParser<T> +where + TToken: Debug + Clone, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T> { + Err(( + input, + FailedParserResult::new("Default failed parser.".to_owned()), + )) + } +} + +pub struct FailWithMessageParser<T> { + pub(crate) message: String, + pub(crate) phantom_data: PhantomData<T>, +} + +impl<TToken, T, TContext> Parser<TToken, T, TContext> for FailWithMessageParser<T> +where + TToken: Debug + Clone, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, T> { + Err((input, FailedParserResult::new(self.message.clone()))) + } +} + +pub struct SatisfyParser<F> { + pub(crate) predicate: F, +} + +impl<TToken, TContext, F> Parser<TToken, TToken, TContext> for SatisfyParser<F> +where + TToken: Debug + Clone, + F: Fn(&TToken) -> bool, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, TToken> { + if input.is_empty() { + return Err((input, FailedParserResult::new("Input is empty.".to_owned()))); + } + + if (self.predicate)(&input[0]) { + Ok((&input[1..], input[0].clone())) + } else { + Err(( + input, + FailedParserResult::new("Predicate failed.".to_owned()), + )) + } + } +} + +pub struct AnyParser {} + +impl<TToken, TContext> Parser<TToken, TToken, TContext> for AnyParser +where + TToken: Debug + Clone, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, TToken> { + if input.is_empty() { + Err((input, FailedParserResult::new("Input is empty.".to_owned()))) + } else { + Ok((&input[1..], input[0].clone())) + } + } +} + +pub struct TakeParser { + pub(crate) count: usize, +} + +impl<TToken, TContext> Parser<TToken, Vec<TToken>, TContext> for TakeParser +where + TToken: Debug + Clone, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, Vec<TToken>> { + if input.len() < self.count { + Err(( + input, + FailedParserResult::new(format!( + "The input doesn't contain enough {} elemements.", + self.count + )), + )) + } else { + Ok(( + &input[self.count..], + (&input[..self.count]).iter().map(|x| x.clone()).collect(), + )) + } + } +} + +pub struct SkipParser { + pub(crate) count: usize, +} + +impl<TToken, TContext> Parser<TToken, (), TContext> for SkipParser +where + TToken: Debug + Clone, +{ + fn parse<'a>( + &self, + _: Rc<RefCell<ParserContext<TToken, TContext>>>, + input: &'a [TToken], + ) -> ParserResult<'a, TToken, ()> { + if input.len() < self.count { + Err(( + input, + FailedParserResult::new(format!( + "The input doesn't contain enough {} elemements.", + self.count + )), + )) + } else { + Ok((&input[self.count..], ())) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::{ + any, fail, fail_with_message, failed_parser_test_helper, parser_test_helper, satisfy, skip, + succeed, take, + }; + + #[test] + fn succeed_test() { + parser_test_helper(ParserContext::new_with_str("abc", ()), &(), succeed(())); + } + + #[test] + fn fail_test() { + failed_parser_test_helper::<(), _>(ParserContext::new_with_str("abc", ()), fail()); + } + + #[test] + fn fail_with_message_test() { + failed_parser_test_helper::<(), _>( + ParserContext::new_with_str("abc", ()), + fail_with_message("Failed!"), + ); + } + + #[test] + fn satisfy_test() { + parser_test_helper( + ParserContext::new_with_str("abc", ()), + &'a', + satisfy(|x| x == &'a'), + ); + + failed_parser_test_helper( + ParserContext::new_with_str("abc", ()), + satisfy(|x| x == &'b'), + ); + } + + #[test] + fn any_test() { + parser_test_helper(ParserContext::new_with_str("abc", ()), &'a', any()); + + parser_test_helper(ParserContext::new_with_str("cde", ()), &'c', any()); + } + + #[test] + fn take_test() { + parser_test_helper( + ParserContext::new_with_str("hello, world!", ()), + &("hello".chars().collect()), + take(5), + ); + + failed_parser_test_helper(ParserContext::new_with_str("abcd", ()), take(5)); + } + + #[test] + fn skip_test() { + parser_test_helper( + ParserContext::new_with_str("hello, world!", ()), + &(), + skip(5), + ); + + failed_parser_test_helper(ParserContext::new_with_str("abcd", ()), skip(5)); + } +} diff --git a/src/text.rs b/src/text.rs new file mode 100644 index 0000000..a6f9b8a --- /dev/null +++ b/src/text.rs @@ -0,0 +1,62 @@ +use crate::parser::{satisfy, take, FailedParserResult, Parser}; + +pub fn char_parser<TContext>(c: char) -> impl Parser<char, char, TContext> { + satisfy(move |x| *x == c) +} + +pub fn string_parser<TContext>(str: String) -> impl Parser<char, String, TContext> { + take::<char, TContext>(str.len()).next(move |result| { + result.and_then(|(input, chars)| { + let chars: String = chars.into_iter().collect(); + + if chars == str { + Ok((input, chars)) + } else { + Err(( + input, + FailedParserResult::new(format!("Failed to parse '{}'.", str)), + )) + } + }) + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::{parser_test_helper, ParserContext, ParserResult}; + use std::cell::RefCell; + use std::rc::Rc; + + #[test] + fn string_parser_test() { + parser_test_helper( + ParserContext::new_with_str("Hello, world!", ()), + &"Hello".to_owned(), + string_parser("Hello".to_owned()), + ); + + fn test_parser( + context: Rc<RefCell<ParserContext<char, ()>>>, + input: &[char], + ) -> ParserResult<char, String> { + let (input, first) = + string_parser("hello, ".to_string()).parse(context.clone(), input)?; + let (input, second) = + string_parser("world!".to_owned()).parse(context.clone(), input)?; + + Ok((input, first + second.as_str())) + } + + parser_test_helper( + ParserContext::new_with_str("hello, world!", ()), + &"hello, world!".to_owned(), + test_parser, + ); + parser_test_helper( + ParserContext::new_with_str("hello, world!", ()), + &(), + test_parser.map(|_| ()), + ) + } +}