gramma 0.2.24

Generate a scannerless parser by declaring types.
Documentation
use super::{
    super::{MatchString, StringPattern},
    word, MatchChar,
};

use crate::{
    string_matcher::{traits::IntoMatchString, Link, Links, StringMatcherContext},
    utils::default,
};

use core::fmt;

pub trait BoundaryDef {
    fn is_boundary(&self, pre: &str, post: &str) -> bool;
    fn fmt_boundary(&self, f: &mut fmt::Formatter) -> fmt::Result;
}

pub(crate) struct FnBoundary<M, F1, F2>
where
    F1: Fn(&M, &str, &str) -> bool,
{
    pub(crate) data: M,
    pub(crate) is_boundary: F1,
    pub(crate) fmt: F2,
}

impl<M, F1, F2> FnBoundary<M, F1, F2>
where
    M: MatchChar,
    F1: Fn(&M, &str, &str) -> bool,
{
    pub(crate) fn fmt(
        self,
        name_field: impl Fn() -> (&'static str, bool) + 'static,
    ) -> FnBoundary<M, F1, impl Fn(&M, &mut fmt::Formatter) -> fmt::Result> {
        let Self {
            data, is_boundary, ..
        } = self;
        FnBoundary {
            data,
            is_boundary,
            fmt: move |m: &M, f: &mut fmt::Formatter| {
                let (name, include_field) = name_field();
                let mut d = f.debug_tuple(name);
                if include_field {
                    d.field(&m.as_debug());
                }
                d.finish()
            },
        }
    }
}

pub(crate) fn boundary_def<M: MatchChar>(
    matcher: M,
) -> FnBoundary<M, impl Fn(&M, &str, &str) -> bool, ()> {
    FnBoundary {
        data: matcher,
        is_boundary: |matcher, pre, post| {
            pre.is_empty()
                || post.is_empty()
                || (matcher.match_end(pre) ^ matcher.match_start(post))
        },
        fmt: (),
    }
}

pub(crate) fn boundary_start_def<M: MatchChar>(
    matcher: M,
) -> FnBoundary<M, impl Fn(&M, &str, &str) -> bool, ()> {
    FnBoundary {
        data: matcher,
        is_boundary: |matcher, pre, post| {
            pre.is_empty() || (!matcher.match_end(pre) && matcher.match_start(post))
        },
        fmt: (),
    }
}

pub(crate) fn boundary_end_def<M: MatchChar>(
    matcher: M,
) -> FnBoundary<M, impl Fn(&M, &str, &str) -> bool, ()> {
    FnBoundary {
        data: matcher,
        is_boundary: |matcher, pre, post| {
            post.is_empty() || (matcher.match_end(pre) && !matcher.match_start(post))
        },
        fmt: (),
    }
}

impl<M, F1, F2> BoundaryDef for FnBoundary<M, F1, F2>
where
    F1: Fn(&M, &str, &str) -> bool,
    F2: Fn(&M, &mut fmt::Formatter) -> fmt::Result,
{
    fn is_boundary(&self, pre: &str, post: &str) -> bool {
        (self.is_boundary)(&self.data, pre, post)
    }

    fn fmt_boundary(&self, f: &mut fmt::Formatter) -> fmt::Result {
        (self.fmt)(&self.data, f)
    }
}

pub struct Boundary<'m, D> {
    pub(crate) def: D,
    pub(crate) links: (Link<'m>, Link<'m>),
}

impl<'m, D> Boundary<'m, D> {
    pub(crate) fn new(def: D) -> Self {
        Self {
            def,
            links: default(),
        }
    }
}

impl<D: BoundaryDef> IntoMatchString for Boundary<'_, D> {
    type Matcher<'m> = Boundary<'m, D>
    where
        Self: 'm;

    fn into_match_string<'m>(self) -> Self::Matcher<'m>
    where
        Self: 'm,
    {
        Self::Matcher {
            def: self.def,
            links: default(),
        }
    }
}

impl<'m, D: BoundaryDef> MatchString<'m> for Boundary<'m, D> {
    fn match_string(&'m self, cx: &mut StringMatcherContext<'m, '_>) -> Option<bool> {
        if !self.def.is_boundary(cx.pre(), cx.post()) {
            return Some(false);
        }
        cx.run_next(self)
    }

    fn links(&'m self) -> Option<Links<'m>> {
        Some((&self.links).into())
    }

    fn fmt_matcher(&self, f: &mut fmt::Formatter, _: crate::string_matcher::DebugPrecedence) -> fmt::Result {
        self.def.fmt_boundary(f)
    }
}

pub fn boundary<'m>(matcher: impl MatchChar + 'm) -> StringPattern<impl IntoMatchString> {
    Boundary::new(boundary_def(matcher).fmt(|| ("boundary", true))).into()
}

pub fn boundary_start<'m>(matcher: impl MatchChar + 'm) -> StringPattern<impl IntoMatchString> {
    Boundary::new(boundary_start_def(matcher).fmt(|| ("boundary_start", true))).into()
}

pub fn boundary_end<'m>(matcher: impl MatchChar + 'm) -> StringPattern<impl IntoMatchString> {
    Boundary::new(boundary_end_def(matcher).fmt(|| ("boundary_end", true))).into()
}

pub fn word_boundary<'m>() -> StringPattern<impl IntoMatchString> {
    Boundary::new(boundary_def(word()).fmt(|| ("word_boundary", false))).into()
}