use crate::{BoxedZngParser, ParseContext, Span, Spanned, Token, ZngParser, spanned};
use chumsky::prelude::*;
pub trait Matchable: core::fmt::Debug + Clone + PartialEq + Eq {
type Pattern: MatchPattern;
fn eval(&self, pattern: &Self::Pattern, ctx: &mut ParseContext) -> bool;
}
pub trait MatchableParse<'src>: Matchable {
fn parser() -> impl ZngParser<'src, Self>;
#[allow(clippy::complexity)]
fn combined()
-> Option<BoxedZngParser<'src, (Spanned<Self>, Spanned<<Self as Matchable>::Pattern>)>> {
None
}
}
pub trait MatchPattern: core::fmt::Debug + Clone + PartialEq + Eq {
fn default_some(span: crate::Span) -> Self;
}
pub trait MatchPatternParse<'src>: MatchPattern {
fn parser() -> impl ZngParser<'src, Self>;
}
pub trait BodyItem: core::fmt::Debug + Clone + PartialEq + Eq {
type Processed;
fn process(self, ctx: &mut ParseContext) -> Self::Processed;
}
pub trait ConditionBody<Pattern: MatchPattern, Item: BodyItem>: core::fmt::Debug {
fn pattern(&self) -> &Pattern;
}
pub trait ConditionBodyCardinality<Item: BodyItem>:
core::fmt::Debug + Clone + PartialEq + Eq
{
type Body<Pattern: MatchPattern>: ConditionBody<Pattern, Item> + Clone + PartialEq + Eq;
type Block: core::fmt::Debug + Clone + PartialEq + Eq + IntoIterator<Item = Spanned<Item>>;
type EvalResult: IntoIterator<Item = Spanned<<Item as BodyItem>::Processed>>;
fn single_to_block(item: Spanned<Item>) -> Self::Block;
fn empty_block() -> Self::Block;
fn new_body<Pattern: MatchPattern>(
pattern: Spanned<Pattern>,
block: Self::Block,
) -> Self::Body<Pattern>;
fn pass_block(block: &Self::Block, ctx: &mut ParseContext) -> Self::EvalResult;
fn pass_body<Pattern: MatchPattern>(
body: &Self::Body<Pattern>,
ctx: &mut ParseContext,
) -> Self::EvalResult;
}
pub trait ConditionalItem<Item: BodyItem, Cardinality: ConditionBodyCardinality<Item>> {
fn eval(&self, ctx: &mut ParseContext) -> Option<Cardinality::EvalResult>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConditionBodyMany<Pattern: MatchPattern, Item: BodyItem> {
pub pattern: Spanned<Pattern>,
pub block: Vec<Spanned<Item>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConditionBodySingle<Pattern: MatchPattern, Item: BodyItem> {
pub pattern: Spanned<Pattern>,
pub block: Option<Spanned<Item>>,
}
impl<Pattern: MatchPattern, Item: BodyItem> ConditionBody<Pattern, Item>
for ConditionBodyMany<Pattern, Item>
{
fn pattern(&self) -> &Pattern {
&self.pattern.inner
}
}
impl<Pattern: MatchPattern, Item: BodyItem> ConditionBody<Pattern, Item>
for ConditionBodySingle<Pattern, Item>
{
fn pattern(&self) -> &Pattern {
&self.pattern.inner
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SingleItem;
impl<Item: BodyItem> ConditionBodyCardinality<Item> for SingleItem {
type Body<Pattern: MatchPattern> = ConditionBodySingle<Pattern, Item>;
type Block = Option<Spanned<Item>>;
type EvalResult = Option<Spanned<<Item as BodyItem>::Processed>>;
fn single_to_block(item: Spanned<Item>) -> Self::Block {
Some(item)
}
fn empty_block() -> Self::Block {
None
}
fn new_body<Pattern: MatchPattern>(
pattern: Spanned<Pattern>,
block: Self::Block,
) -> Self::Body<Pattern> {
ConditionBodySingle { pattern, block }
}
fn pass_block(block: &Self::Block, ctx: &mut ParseContext) -> Self::EvalResult {
block.clone().map(|item| {
let span = item.span;
Spanned {
span,
inner: item.inner.process(ctx),
}
})
}
fn pass_body<Pattern: MatchPattern>(
body: &Self::Body<Pattern>,
ctx: &mut ParseContext,
) -> Self::EvalResult {
Self::pass_block(&body.block, ctx)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NItems;
impl<Item: BodyItem> ConditionBodyCardinality<Item> for NItems {
type Body<Pattern: MatchPattern> = ConditionBodyMany<Pattern, Item>;
type Block = Vec<Spanned<Item>>;
type EvalResult = Vec<Spanned<<Item as BodyItem>::Processed>>;
fn single_to_block(item: Spanned<Item>) -> Self::Block {
vec![item]
}
fn empty_block() -> Self::Block {
Vec::new()
}
fn new_body<Pattern: MatchPattern>(
pattern: Spanned<Pattern>,
block: Self::Block,
) -> Self::Body<Pattern> {
ConditionBodyMany { pattern, block }
}
fn pass_block(block: &Self::Block, ctx: &mut ParseContext) -> Self::EvalResult {
block
.iter()
.cloned()
.map(|item| {
let span = item.span;
Spanned {
span,
inner: item.inner.process(ctx),
}
})
.collect()
}
fn pass_body<Pattern: MatchPattern>(
body: &Self::Body<Pattern>,
ctx: &mut ParseContext,
) -> Self::EvalResult {
Self::pass_block(&body.block, ctx)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConditionGuard<Scrutinee: Matchable> {
Single {
scrutinee: Spanned<Scrutinee>,
pattern: Spanned<<Scrutinee as Matchable>::Pattern>,
span: Span,
},
And(Vec<ConditionGuard<Scrutinee>>, Span),
Or(Vec<ConditionGuard<Scrutinee>>, Span),
Not(Box<ConditionGuard<Scrutinee>>, Span),
Grouped(Box<ConditionGuard<Scrutinee>>, Span),
}
impl<Scrutinee: Matchable> ConditionGuard<Scrutinee> {
fn eval(&self, ctx: &mut ParseContext) -> bool {
match self {
Self::Single {
scrutinee, pattern, ..
} => scrutinee.inner.eval(&pattern.inner, ctx),
Self::And(items, _) => items.iter().all(|item| item.eval(ctx)),
Self::Or(items, _) => items.iter().any(|item| item.eval(ctx)),
Self::Not(item, _) => !item.eval(ctx),
Self::Grouped(item, _) => item.eval(ctx),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConditionBranch<
Scrutinee: Matchable,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
> {
pub guard: ConditionGuard<Scrutinee>,
pub block: <Cardinality as ConditionBodyCardinality<Item>>::Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConditionIf<
Scrutinee: Matchable,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
> {
pub arms: Vec<ConditionBranch<Scrutinee, Item, Cardinality>>,
pub fallback: Option<Cardinality::Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConditionMatch<
Scrutinee: Matchable,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
> {
pub scrutinee: Spanned<Scrutinee>,
pub arms: Vec<
Spanned<
<Cardinality as ConditionBodyCardinality<Item>>::Body<
<Scrutinee as Matchable>::Pattern,
>,
>,
>,
}
impl<Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality<Item>>
ConditionalItem<Item, Cardinality> for ConditionBranch<Scrutinee, Item, Cardinality>
{
fn eval(
&self,
ctx: &mut ParseContext,
) -> Option<<Cardinality as ConditionBodyCardinality<Item>>::EvalResult> {
if self.guard.eval(ctx) {
return Some(<Cardinality as ConditionBodyCardinality<Item>>::pass_block(
&self.block,
ctx,
));
}
None
}
}
impl<Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality<Item>>
ConditionalItem<Item, Cardinality> for ConditionMatch<Scrutinee, Item, Cardinality>
{
fn eval(
&self,
ctx: &mut ParseContext,
) -> Option<<Cardinality as ConditionBodyCardinality<Item>>::EvalResult> {
for arm in &self.arms {
let pattern = arm.inner.pattern();
if self.scrutinee.inner.eval(pattern, ctx) {
return Some(<Cardinality as ConditionBodyCardinality<Item>>::pass_body(
&arm.inner, ctx,
));
}
}
None
}
}
impl<Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality<Item>>
ConditionalItem<Item, Cardinality> for ConditionIf<Scrutinee, Item, Cardinality>
{
fn eval(
&self,
ctx: &mut ParseContext,
) -> Option<<Cardinality as ConditionBodyCardinality<Item>>::EvalResult> {
for arm in &self.arms {
if let Some(result) = arm.eval(ctx) {
return Some(result);
}
}
if let Some(fallback) = &self.fallback {
return Some(<Cardinality as ConditionBodyCardinality<Item>>::pass_block(
fallback, ctx,
));
}
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Condition<
Scrutinee: Matchable,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
> {
If(ConditionIf<Scrutinee, Item, Cardinality>),
Match(ConditionMatch<Scrutinee, Item, Cardinality>),
}
impl<Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality<Item>>
ConditionalItem<Item, Cardinality> for Condition<Scrutinee, Item, Cardinality>
{
fn eval(
&self,
ctx: &mut ParseContext,
) -> Option<<Cardinality as ConditionBodyCardinality<Item>>::EvalResult> {
match self {
Self::If(item) => item.eval(ctx),
Self::Match(item) => item.eval(ctx),
}
}
}
pub trait Conditional<'src, Item: BodyItem, Cardinality: ConditionBodyCardinality<Item>> {
type Scrutinee: MatchableParse<'src>;
fn if_parser(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> BoxedZngParser<'src, Condition<Self::Scrutinee, Item, Cardinality>>;
fn match_parser(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> BoxedZngParser<'src, Condition<Self::Scrutinee, Item, Cardinality>>;
}
pub fn block_for_single<'src, Item: BodyItem>(
item_parser: impl ZngParser<'src, Item>,
) -> impl ZngParser<'src, Option<Spanned<Item>>> {
spanned(item_parser)
.repeated()
.at_most(1)
.collect::<Vec<_>>()
.map(|items| {
items.into_iter().next()
})
}
pub fn block_for_many<'src, Item: BodyItem>(
item_parser: impl ZngParser<'src, Item>,
) -> impl ZngParser<'src, Vec<Spanned<Item>>> {
spanned(item_parser).repeated().collect::<Vec<_>>()
}
pub fn guarded_block<
'src,
Scrutinee: MatchableParse<'src> + 'src,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
>(
block: impl ZngParser<'src, Cardinality::Block>,
) -> impl ZngParser<'src, ConditionBranch<Scrutinee, Item, Cardinality>>
where
<Scrutinee as Matchable>::Pattern: MatchPatternParse<'src>,
{
let guard = recursive(|guard| {
let single = if let Some(combined) = <Scrutinee as MatchableParse<'src>>::combined() {
combined
} else {
spanned(<Scrutinee as MatchableParse<'src>>::parser())
.then(
just(Token::Eq)
.ignore_then(spanned(
<<Scrutinee as Matchable>::Pattern as MatchPatternParse<'src>>::parser(
),
))
.or_not()
.map_with(|pat, e| {
pat.unwrap_or_else(|| Spanned {
inner: <Scrutinee as Matchable>::Pattern::default_some(e.span()),
span: e.span(),
})
}),
)
.boxed()
}
.map_with(|(scrutinee, pattern), e| ConditionGuard::Single {
scrutinee,
pattern,
span: e.span(),
})
.or(
spanned(guard.delimited_by(just(Token::ParenOpen), just(Token::ParenClose)))
.map(|item| ConditionGuard::Grouped(Box::new(item.inner), item.span)),
);
let not_pat = just(Token::Bang)
.repeated()
.foldr_with(single, |_op, rhs, e| {
ConditionGuard::Not(Box::new(rhs), e.span())
});
let and_pat = not_pat.clone().foldl_with(
just([Token::And, Token::And])
.ignore_then(not_pat)
.repeated(),
|lhs, rhs, e| match lhs {
ConditionGuard::And(mut items, _span) => {
items.push(rhs);
ConditionGuard::And(items, e.span())
}
_ => ConditionGuard::And(vec![lhs, rhs], e.span()),
},
);
and_pat.clone().foldl_with(
just([Token::Pipe, Token::Pipe])
.ignore_then(and_pat)
.repeated(),
|lhs, rhs, e| match lhs {
ConditionGuard::Or(mut items, _span) => {
items.push(rhs);
ConditionGuard::Or(items, e.span())
}
_ => ConditionGuard::Or(vec![lhs, rhs], e.span()),
},
)
});
guard
.then(block.delimited_by(just(Token::BraceOpen), just(Token::BraceClose)))
.map(move |(guard, block)| ConditionBranch { guard, block })
}
pub fn if_stmnt<
'src,
Scrutinee: MatchableParse<'src>,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
>(
guard: impl ZngParser<'src, ConditionBranch<Scrutinee, Item, Cardinality>>,
fallback: impl ZngParser<'src, Cardinality::Block>,
) -> impl ZngParser<'src, ConditionIf<Scrutinee, Item, Cardinality>>
where
<Scrutinee as Matchable>::Pattern: MatchPatternParse<'src>,
{
just([Token::Sharp, Token::KwIf])
.ignore_then(guard.clone())
.then(
just([Token::Sharp, Token::KwElse, Token::KwIf])
.ignore_then(guard)
.repeated()
.collect::<Vec<_>>()
.or_not(),
)
.then(
just([Token::Sharp, Token::KwElse])
.ignore_then(fallback.delimited_by(just(Token::BraceOpen), just(Token::BraceClose)))
.or_not(),
)
.map(|((if_block, else_if_blocks), else_block)| {
let mut arms: Vec<ConditionBranch<Scrutinee, Item, Cardinality>> = vec![if_block];
arms.extend(else_if_blocks.unwrap_or_default());
ConditionIf {
arms,
fallback: else_block,
}
})
}
fn match_arm<
'src,
Scrutinee: MatchableParse<'src>,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
>(
block: impl ZngParser<'src, Cardinality::Block>,
item_parser: impl ZngParser<'src, Item>,
) -> impl ZngParser<
'src,
<Cardinality as ConditionBodyCardinality<Item>>::Body<<Scrutinee as Matchable>::Pattern>,
>
where
<Scrutinee as Matchable>::Pattern: MatchPatternParse<'src>,
{
let arm_choices = (
block.delimited_by(just(Token::BraceOpen), just(Token::BraceClose)),
spanned(item_parser).map(<Cardinality as ConditionBodyCardinality<Item>>::single_to_block),
just([Token::BraceOpen, Token::BraceClose])
.map(|_| <Cardinality as ConditionBodyCardinality<Item>>::empty_block()),
);
spanned(<<Scrutinee as Matchable>::Pattern as MatchPatternParse<
'src,
>>::parser())
.then(just(Token::ArrowArm).ignore_then(choice(arm_choices)))
.map(|(pattern, block)| {
<Cardinality as ConditionBodyCardinality<Item>>::new_body(pattern, block)
})
}
fn match_stmt<
'src,
Scrutinee: MatchableParse<'src>,
Item: BodyItem,
Cardinality: ConditionBodyCardinality<Item>,
>(
block: impl ZngParser<'src, Cardinality::Block>,
item_parser: impl ZngParser<'src, Item>,
) -> impl ZngParser<'src, ConditionMatch<Scrutinee, Item, Cardinality>>
where
<Scrutinee as Matchable>::Pattern: MatchPatternParse<'src>,
{
let arm = match_arm::<Scrutinee, Item, Cardinality>(block, item_parser);
just([Token::Sharp, Token::KwMatch])
.ignore_then(spanned(<Scrutinee as MatchableParse>::parser()))
.then(
spanned(arm)
.separated_by(just(Token::Comma).or_not())
.allow_trailing()
.collect::<Vec<_>>()
.delimited_by(just(Token::BraceOpen), just(Token::BraceClose)),
)
.map(|(scrutinee, arms)| ConditionMatch { scrutinee, arms })
}
pub fn conditional_item<
'src,
Item: BodyItem,
Cond: Conditional<'src, Item, Cardinality>,
Cardinality: ConditionBodyCardinality<Item>,
>(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> impl ZngParser<
'src,
Condition<<Cond as Conditional<'src, Item, Cardinality>>::Scrutinee, Item, Cardinality>,
> {
let if_parser = <Cond as Conditional<'src, Item, Cardinality>>::if_parser(item_parser.clone()).try_map_with(|match_, e| {
if !e.state().unstable_features.cfg_if {
Err(Rich::custom(e.span(), "`#if` statements are unstable. Enable them by using `#unstable(cfg_if)` at the top of the file."))
} else {
Ok(match_)
}
});
let match_parser = <Cond as Conditional<'src, Item, Cardinality>>::match_parser(item_parser).try_map_with(|match_, e| {
if !e.state().unstable_features.cfg_match {
Err(Rich::custom(e.span(), "`#match` statements are unstable. Enable them by using `#unstable(cfg_match)` at the top of the file."))
} else {
Ok(match_)
}
});
choice((if_parser, match_parser))
}
impl<'src, Scrutinee: MatchableParse<'src> + 'src, Item: BodyItem + 'src>
Conditional<'src, Item, SingleItem> for Scrutinee
where
<Scrutinee as Matchable>::Pattern: MatchPatternParse<'src>,
{
type Scrutinee = Scrutinee;
fn if_parser(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> BoxedZngParser<'src, Condition<Scrutinee, Item, SingleItem>> {
let block = block_for_single::<Item>(item_parser);
let guard = guarded_block::<Scrutinee, Item, SingleItem>(block.clone());
if_stmnt::<Scrutinee, Item, SingleItem>(guard, block)
.map(|item| Condition::<Scrutinee, Item, SingleItem>::If(item))
.boxed()
}
fn match_parser(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> BoxedZngParser<'src, Condition<Scrutinee, Item, SingleItem>> {
let block = block_for_single::<Item>(item_parser.clone());
match_stmt::<Scrutinee, Item, SingleItem>(block, item_parser)
.map(|item| Condition::<Scrutinee, Item, SingleItem>::Match(item))
.boxed()
}
}
impl<'src, Scrutinee: MatchableParse<'src> + 'src, Item: BodyItem + 'src>
Conditional<'src, Item, NItems> for Scrutinee
where
<Scrutinee as Matchable>::Pattern: MatchPatternParse<'src>,
{
type Scrutinee = Scrutinee;
fn if_parser(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> BoxedZngParser<'src, Condition<Scrutinee, Item, NItems>> {
let block = block_for_many::<Item>(item_parser);
let guard = guarded_block::<Scrutinee, Item, NItems>(block.clone());
if_stmnt::<Scrutinee, Item, NItems>(guard, block)
.map(|item| Condition::<Scrutinee, Item, NItems>::If(item))
.boxed()
}
fn match_parser(
item_parser: impl ZngParser<'src, Item> + 'src,
) -> BoxedZngParser<'src, Condition<Scrutinee, Item, NItems>> {
let block = block_for_many::<Item>(item_parser.clone());
match_stmt::<Scrutinee, Item, NItems>(block, item_parser)
.map(|item| Condition::<Scrutinee, Item, NItems>::Match(item))
.boxed()
}
}