use proc_macro::{Delimiter, Group, Punct, Spacing, TokenTree};
use crate::{Expected, Parser, ParserPos, Result};
pub trait Pattern {
type Output;
fn eat(self, cx: &mut Parser) -> Result<Self::Output>;
}
impl Pattern for Delimiter {
type Output = Group;
fn eat(self, cx: &mut Parser) -> Result<Self::Output> {
match cx.nibble() {
(Some(TokenTree::Group(contents)), _) if contents.delimiter() == self => Ok(contents),
(_, pos) => {
let delim_str = match self {
Delimiter::Parenthesis => "parentheses",
Delimiter::Brace => "braces",
Delimiter::Bracket => "square brackets",
Delimiter::None => "implicit delimiters",
};
Err(Expected::noun(pos, delim_str))
}
}
}
}
pub struct Eos;
impl Pattern for Eos {
type Output = ParserPos;
fn eat(self, cx: &mut Parser) -> Result<ParserPos> {
if cx.is_empty() {
Ok(cx.here())
} else {
Err(Expected::nothing(cx.here()))
}
}
}
#[derive(Clone, Copy)]
pub struct PunctPat<const N: usize> {
chars: [char; N],
name: &'static str,
}
impl<const N: usize> PunctPat<N> {
#[doc(hidden)]
pub fn new(chars: [char; N], name: &'static str) -> Self {
Self { chars, name }
}
}
impl<const N: usize> Pattern for PunctPat<N> {
type Output = [Punct; N];
fn eat(self, cx: &mut Parser) -> Result<[Punct; N]> {
let Some((&last, rest)) = self.chars.split_last() else {
unreachable!("`PunctPat`s should have at least one element");
};
let erf = |err| Expected::lit(err, self.name);
let mut puncts = std::array::from_fn(|_| Punct::new('#', Spacing::Alone));
for (i, &c) in rest.iter().enumerate() {
let p = cx.eat_punct_with_spacing(c, Spacing::Joint).map_err(erf)?;
puncts[i] = p;
}
let p = cx.eat_punct(last).map_err(erf)?;
puncts[puncts.len() - 1] = p;
Ok(puncts)
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! ඞ_punct_pat_def {
($p:tt $($char:literal)+) => {
$crate::PunctPat::new(
[ $($char),* ],
stringify!($p),
)
};
}
#[cfg_attr(docsrs, doc(cfg(feature = "parse")))]
#[macro_export]
macro_rules! punct_pat {
($($t:tt)+) => {{
#[allow(unused_imports)]
use $crate::ඞ_punct_pat_def;
$crate::punct_decompose!(
expand = ඞ_punct_pat_def,
fallback = { ::core::compile_error!("unrecognised token") },
$($t)+
)
}};
}