#![doc = include_str!("../readme.md")]
#![cfg_attr(not(test), no_std)]
#![warn(missing_docs)]
#![deny(clippy::default_numeric_fallback)]
use core::{
fmt::{
Display,
Formatter,
},
marker::PhantomData,
};
use binator::{
Contexting,
Parse,
Parsed,
};
use nom::{
Err,
Needed,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NomAtom<Error> {
Incomplete {
needed: Needed,
},
Error {
error: Error,
},
}
impl<Error: Display> Display for NomAtom<Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::Incomplete { needed } => write!(f, "Nom: Incomplete {:?}", needed),
Self::Error { error } => write!(f, "Nom: Error {}", error),
}
}
}
#[derive(Clone)]
pub struct NomParser<Token, Parser, Error> {
error: PhantomData<Error>,
parser: Parser,
token: PhantomData<Token>,
}
pub trait Nom<Token, Stream, Parser, Error>: Sized
where
Parser: nom::Parser<Stream, Token, Error>,
{
fn nom(self) -> NomParser<Token, Self, Error>;
}
impl<Token, Stream, Context, Parser, Error> Parse<Stream, Context>
for NomParser<Token, Parser, Error>
where
Parser: nom::Parser<Stream, Token, Error>,
Context: Contexting<NomAtom<Error>>,
{
type Token = Token;
fn parse(&mut self, stream: Stream) -> Parsed<Token, Stream, Context> {
match self.parser.parse(stream) {
Ok((stream, token)) => Parsed::Success { token, stream },
Err(Err::Error(error)) => Parsed::Failure(Context::new(NomAtom::Error { error })),
Err(Err::Incomplete(needed)) => Parsed::Error(Context::new(NomAtom::Incomplete { needed })),
Err(Err::Failure(error)) => Parsed::Error(Context::new(NomAtom::Error { error })),
}
}
}
impl<Token, Stream, Parser, Error> Nom<Token, Stream, Parser, Error> for Parser
where
Parser: nom::Parser<Stream, Token, Error>,
{
fn nom(self) -> NomParser<Token, Self, Error> {
NomParser {
error: PhantomData::default(),
parser: self,
token: PhantomData::default(),
}
}
}
pub fn nom<Token, Stream, Context, Parser, Error>(
parser: Parser,
) -> impl Parse<Stream, Context, Token = Token>
where
Parser: nom::Parser<Stream, Token, Error>,
Context: Contexting<NomAtom<Error>>,
{
parser.nom()
}
#[cfg(test)]
mod tests {
use binator::{
context::{
First,
Keep,
},
Contexting,
Parse,
Parsed,
};
use derive_more::{
Display,
From,
};
use nom::{
bytes::complete::{
tag,
take_while_m_n,
},
combinator::map_res,
error::{
Error,
ErrorKind,
},
sequence::tuple,
IResult,
};
use crate::{
Nom,
NomAtom,
};
#[derive(Display, Debug, PartialEq, From)]
enum FromAtom {
Nom(NomAtom<Error<&'static str>>),
}
type Context = Keep<First, FromAtom>;
#[derive(Debug, PartialEq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
u8::from_str_radix(input, 16)
}
fn is_hex_digit(c: char) -> bool {
c.is_digit(16)
}
fn hex_primary(input: &str) -> IResult<&str, u8> {
map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(input)
}
fn hex_color(input: &str) -> IResult<&str, Color> {
let (input, _) = tag("#")(input)?;
let (input, (red, green, blue)) = tuple((hex_primary, hex_primary, hex_primary))(input)?;
Ok((input, Color { red, green, blue }))
}
#[test]
fn parse_color() {
assert_eq!(
super::nom::<_, _, Context, _, _>(hex_color).parse("#2F14DF"),
Parsed::Success {
stream: "",
token: Color {
red: 47,
green: 20,
blue: 223,
}
}
);
assert_eq!(
hex_color.nom().parse("#2F14D"),
Parsed::Failure(Context::new(NomAtom::Error {
error: Error {
input: "D",
code: ErrorKind::TakeWhileMN,
}
}))
);
}
}