mintyml 0.1.19

A minialist alternative syntax to HTML
Documentation
mod ops;
use crate::{
    document::{Element, ElementDelimiter, ElementType, NodeType, Tag},
    utils::default,
};

use super::{
    DirectionPrecedence, Incomplete, InferencePredicate, InferencePredicateContext, TestResult,
};

pub struct InferWhen<P>(P);

impl<P: InferencePredicate> InferencePredicate for InferWhen<P> {
    fn test(&mut self, cx: &InferencePredicateContext) -> TestResult {
        self.0.test(cx)
    }
}

pub fn when<P: InferencePredicate>(pred: P) -> InferWhen<P> {
    InferWhen(pred)
}

struct PredImpl<Test, DirPrec, Data> {
    matches: Test,
    dir_prec: DirPrec,
    data: Data,
}

impl<Test, DirPrec, Data> InferWhen<PredImpl<Test, DirPrec, Data>> {
    pub fn direction_precedence<F: Fn(&Data) -> DirectionPrecedence>(
        self,
        dir_prec: F,
    ) -> InferWhen<PredImpl<Test, F, Data>> {
        InferWhen(PredImpl {
            matches: self.0.matches,
            dir_prec,
            data: self.0.data,
        })
    }
}

fn pred_impl<Test: FnMut(&InferencePredicateContext) -> TestResult>(
    mut m: Test,
) -> InferWhen<
    PredImpl<
        impl FnMut(&mut (), &InferencePredicateContext) -> TestResult,
        impl Fn(&()) -> DirectionPrecedence,
        (),
    >,
> {
    InferWhen(PredImpl {
        matches: move |_: &mut _, cx: &InferencePredicateContext| m(cx),
        dir_prec: |&_: &_| default(),
        data: (),
    })
}

fn pred_impl_with<Test: FnMut(&mut Data, &InferencePredicateContext) -> TestResult, Data>(
    data: Data,
    matches: Test,
) -> InferWhen<PredImpl<Test, impl Fn(&Data) -> DirectionPrecedence, Data>> {
    InferWhen(PredImpl {
        matches,
        dir_prec: |_: &_| default(),
        data,
    })
}

impl<
        Test: FnMut(&mut Data, &InferencePredicateContext) -> TestResult,
        DirPrec: Fn(&Data) -> DirectionPrecedence,
        Data,
    > InferencePredicate for PredImpl<Test, DirPrec, Data>
{
    fn test(&mut self, cx: &InferencePredicateContext) -> TestResult {
        (self.matches)(&mut self.data, cx)
    }
}

pub fn element_where<F>(mut f: F) -> InferWhen<impl InferencePredicate>
where
    F: FnMut(&Element) -> bool,
{
    pred_impl(move |cx| cx.match_this_element(|e| Ok(f(e))))
}

pub fn element() -> InferWhen<impl InferencePredicate> {
    pred_impl(|cx| cx.match_this_node(|n| Ok(matches!(n.node_type, NodeType::Element { .. }))))
}

pub fn paragraph() -> InferWhen<impl InferencePredicate> {
    element_where(|e| matches!(e.element_type, ElementType::Paragraph {}))
}

pub fn inline() -> InferWhen<impl InferencePredicate> {
    element_where(|e| matches!(e.element_type, ElementType::Inline { .. },))
}

pub fn line() -> InferWhen<impl InferencePredicate> {
    element_where(|e| {
        matches!(
            e.element_type,
            ElementType::Standard {
                delimiter: ElementDelimiter::Line { .. } | ElementDelimiter::LineBlock { .. }
            } | ElementType::Inline {
                delimiter: Some(ElementDelimiter::Line { .. } | ElementDelimiter::LineBlock { .. })
                    | None
            }
        )
    })
}

pub fn block() -> InferWhen<impl InferencePredicate> {
    element_where(|e| {
        matches!(
            e.element_type,
            ElementType::Standard {
                delimiter: ElementDelimiter::Block { .. }
            }
        )
    })
}

pub fn tag(tag: &str) -> InferWhen<impl InferencePredicate + '_> {
    tag_where(move |s| s == tag)
}

pub fn tag_in<'tag>(
    tags: impl IntoIterator<Item = &'tag str> + Clone + 'tag,
) -> InferWhen<impl InferencePredicate + 'tag> {
    tag_where(move |s| tags.clone().into_iter().any(|tag| s == tag))
}

pub fn tag_where<'tag>(
    mut pred: impl FnMut(&str) -> bool + 'tag,
) -> InferWhen<impl InferencePredicate + 'tag> {
    pred_impl(move |cx| {
        cx.match_this_element(|e| {
            Ok(e.selectors
                .first()
                .map(|s| match s.tag {
                    Tag::Explicit { ref value } => Ok(value.as_str(cx.src)),
                    _ => Err(Incomplete {}),
                })
                .transpose()?
                .map(&mut pred))
        })
    })
}

pub fn any() -> InferWhen<impl InferencePredicate> {
    pred_impl(|_| Ok(true))
}

pub fn child_of(pred: impl InferencePredicate) -> InferWhen<impl InferencePredicate> {
    pred_impl_with(pred, |pred, cx| {
        Ok(cx.parent.and_then(|cx| pred.test(cx).ok()).unwrap_or(false))
    })
}

pub fn first() -> InferWhen<impl InferencePredicate> {
    !after(any())
}

pub fn last() -> InferWhen<impl InferencePredicate> {
    !before(any())
}

pub fn descendant_of(pred: impl InferencePredicate) -> InferWhen<impl InferencePredicate> {
    pred_impl_with(pred, |pred, mut cx| {
        while let Some(parent) = cx.parent {
            cx = parent;
            if pred.test(cx).unwrap_or(false) {
                return Ok(true);
            }
        }
        Ok(false)
    })
}

mod relative {
    use super::*;
    macro_rules! impl_relative {
        ($func:ident, $Type:ident, |$idx:pat_param, $lo:pat_param, $hi:pat_param| $which:expr, $field:ident, $dir:expr) => {
            pub struct $Type<P>(P);

            impl<P: InferencePredicate> InferencePredicate for $Type<P> {
                fn test(&mut self, cx: &InferencePredicateContext) -> TestResult {
                    let mut out = Ok(false);
                    match (cx.index, 0, cx.nodes.len()) {
                        ($idx, $lo, $hi) => {
                            for index in $which {
                                out = out.and(
                                    match self.0.test(&InferencePredicateContext { index, ..*cx }) {
                                        Ok(true) => return Ok(true),
                                        res => res,
                                    },
                                );
                            }
                        }
                    }
                    out
                }

                fn direction_precedence(&self) -> DirectionPrecedence {
                    self.0.direction_precedence()
                        + DirectionPrecedence {
                            $field: $dir,
                            ..default()
                        }
                }
            }

            pub fn $func<P: InferencePredicate>(pred: P) -> InferWhen<$Type<P>> {
                when($Type(pred))
            }
        };
    }

    impl_relative!(before, Before, |i, _, hi| (i + 1)..hi, any, -1);
    impl_relative!(after, After, |i, lo, _| lo..i, any, 1);
    impl_relative!(
        just_before,
        JustBefore,
        |i, _, hi| (i + 1 < hi).then_some(i + 1).into_iter(),
        any,
        -1
    );
    impl_relative!(
        just_after,
        JustAfter,
        |i, lo, _| (lo + 1 <= i).then_some(i - 1).into_iter(),
        any,
        1
    );
}

pub use relative::*;