pub(crate) mod error;
pub(crate) mod lexer;
pub mod util;
use chumsky::{
error::{Rich, RichReason},
extra,
input::ValueInput,
prelude::*,
span::SimpleSpan,
Parser as ChumskyParser,
};
pub(crate) trait TokenSource<'src>:
ValueInput<'src, Token = lexer::Token<'src>, Span = SimpleSpan>
{
}
impl<'src, I> TokenSource<'src> for I where
I: ValueInput<'src, Token = lexer::Token<'src>, Span = SimpleSpan>
{
}
pub(crate) type TokenError<'src> = extra::Err<Rich<'src, lexer::Token<'src>>>;
pub(crate) trait InternalParser<'src>: Sized {
fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
where
I: TokenSource<'src>;
}
#[allow(private_bounds)]
pub trait Parser<'src>: InternalParser<'src> {
fn parse(
src: impl TokenSource<'src>,
) -> Result<Self, Vec<RichReason<'src, lexer::Token<'src>>>> {
let result = <Self as InternalParser<'src>>::parser::<_>().parse(src);
if result.has_errors() {
Err(result.errors().map(|e| e.reason().clone()).collect())
} else {
Ok(result.unwrap())
}
}
}
pub(crate) fn number<'a, T, I>() -> impl ChumskyParser<'a, I, T, TokenError<'a>>
where
T: std::str::FromStr,
T::Err: std::fmt::Debug,
I: TokenSource<'a>,
{
select! { lexer::Token::QuotedText(s) => s }.try_map(|s: &str, span| {
s.parse::<T>()
.map_err(|_| Rich::custom(span, "integer out of range"))
})
}
pub(crate) fn boolean<'a, I>() -> impl ChumskyParser<'a, I, bool, TokenError<'a>>
where
I: TokenSource<'a>,
{
quoted_string("1").or(quoted_string("0")).map(|v| match v {
"1" => true,
"0" => false,
_ => unreachable!(),
})
}
pub(crate) fn any_quoted_string<'src, I>(
) -> impl ChumskyParser<'src, I, &'src str, TokenError<'src>>
where
I: TokenSource<'src>,
{
select! { lexer::Token::QuotedText(s) => s }
}
pub(crate) fn quoted_string<'src, I>(
input: &'src str,
) -> impl ChumskyParser<'src, I, &'src str, TokenError<'src>>
where
I: TokenSource<'src>,
{
select! {
lexer::Token::QuotedText(s) if s == input => s
}
}
pub(crate) fn key_value<'src, I>(
key: &'src str,
) -> impl ChumskyParser<'src, I, &'src str, TokenError<'src>>
where
I: TokenSource<'src>,
{
quoted_string(key).ignore_then(any_quoted_string())
}
pub(crate) fn key_value_numeric<'src, T, I>(
key: &'src str,
) -> impl ChumskyParser<'src, I, T, TokenError<'src>>
where
T: std::str::FromStr,
T::Err: std::fmt::Debug,
I: TokenSource<'src>,
{
quoted_string(key).ignore_then(number::<T, I>())
}
pub(crate) fn key_value_boolean<'src, I>(
key: &'src str,
) -> impl ChumskyParser<'src, I, bool, TokenError<'src>>
where
I: TokenSource<'src>,
{
quoted_string(key).ignore_then(boolean())
}
pub(crate) fn open_block<'src, I>(
block: &'src str,
) -> impl ChumskyParser<'src, I, (), TokenError<'src>>
where
I: TokenSource<'src>,
{
just(lexer::Token::Ident(block))
.ignore_then(just(lexer::Token::LBracket))
.ignored()
}
pub(crate) fn close_block<'src, I>() -> impl ChumskyParser<'src, I, (), TokenError<'src>>
where
I: TokenSource<'src>,
{
just(lexer::Token::RBracket).ignored()
}
pub(crate) fn skip_unknown_block<'src, I>() -> impl ChumskyParser<'src, I, (), TokenError<'src>>
where
I: TokenSource<'src>,
{
recursive(|skip_block| {
any()
.filter(|tok| matches!(tok, lexer::Token::Ident(_)))
.ignore_then(just(lexer::Token::LBracket))
.ignore_then(
none_of([lexer::Token::LBracket, lexer::Token::RBracket])
.ignored()
.or(skip_block)
.repeated(),
)
.then_ignore(just(lexer::Token::RBracket))
.ignored()
})
}
#[cfg(test)]
mod tests {
use crate::util::lex;
use super::*;
use chumsky::Parser;
#[test]
fn test_number() {
let stream = lex("\"12345\"");
let result = number::<u32, _>().parse(stream);
for e in result.errors() {
println!("error: {:?}", e.reason());
}
assert!(!result.has_errors());
assert_eq!(result.unwrap(), 12345);
}
#[test]
fn test_boolean() {
let stream = lex(r#""1""#);
let result = boolean::<_>().parse(stream);
assert!(!result.has_errors());
assert!(result.unwrap());
}
#[test]
fn test_key_value_numeric() {
let stream = lex(r#""num" "42""#);
let result = key_value_numeric::<u32, _>("num").parse(stream);
assert!(!result.has_errors());
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_open_close_block() {
let stream = lex("blk {");
let r1 = open_block("blk").parse(stream);
for e in r1.errors() {
println!("error: {:?}", e.reason());
}
assert!(!r1.has_errors());
let stream = lex("}");
let r2 = close_block().parse(stream);
for e in r1.errors() {
println!("error: {:?}", e.reason());
}
assert!(!r2.has_errors());
}
}