mplusfonts-macros 0.3.4

Procedural macros re-exported in the mplusfonts crate
Documentation
use std::borrow::Cow;
use std::ops::Bound;

use syn::parse::{Parse, ParseStream};

use super::ExprPathExt;

pub enum CharSource {
    Strings(Vec<String>),
    Range(Bound<char>, Bound<char>),
    Kern(Bound<char>, Bound<char>, Vec<String>),
}

impl CharSource {
    pub fn strings(&self, is_code: bool) -> impl IntoIterator<Item = Cow<'_, [String]>> {
        match *self {
            Self::Strings(ref strings) => {
                let array = [strings.into()];

                Vec::from_iter(array)
            }
            Self::Range(start, end) => {
                let array = [single_char_strings(start, end).into()];

                Vec::from_iter(array)
            }
            Self::Kern(start, end, ref strings) if is_code => {
                let array = [single_char_strings(start, end).into(), strings.into()];

                Vec::from_iter(array)
            }
            Self::Kern(bound @ Bound::Excluded(start), end, ref strings) if start > '\u{24E}' => {
                let array = [single_char_strings(bound, end).into(), strings.into()];

                Vec::from_iter(array)
            }
            Self::Kern(bound @ Bound::Included(start), end, ref strings) if start > '\u{24F}' => {
                let array = [single_char_strings(bound, end).into(), strings.into()];

                Vec::from_iter(array)
            }
            Self::Kern(start, bound @ Bound::Included(end), ref strings) if end < '\u{250}' => {
                let array = [
                    single_char_strings(start, bound).into(),
                    strings.into(),
                    strings_in_cartesian_square(start, bound).into(),
                    single_char_affixed_strings(start, bound, strings).into(),
                ];

                Vec::from_iter(array)
            }
            Self::Kern(start, bound @ Bound::Excluded(end), ref strings) if end < '\u{251}' => {
                let array = [
                    single_char_strings(start, bound).into(),
                    strings.into(),
                    strings_in_cartesian_square(start, bound).into(),
                    single_char_affixed_strings(start, bound, strings).into(),
                ];

                Vec::from_iter(array)
            }
            Self::Kern(start, end, ref strings) => {
                let bound = Bound::Excluded('\u{250}');
                let array = [
                    single_char_strings(start, end).into(),
                    strings.into(),
                    strings_in_cartesian_square(start, bound).into(),
                    single_char_affixed_strings(start, bound, strings).into(),
                ];

                Vec::from_iter(array)
            }
        }
    }
}

impl Parse for CharSource {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let source = match input.parse()? {
            syn::Expr::Array(expr_array) => expr_array.try_into()?,
            syn::Expr::Range(expr_range) => expr_range.try_into()?,
            syn::Expr::Call(expr_call) => expr_call.try_into()?,
            expr => {
                let message = "expected slice literal, range expression, function call expression";
                return Err(syn::Error::new_spanned(expr, message));
            }
        };

        Ok(source)
    }
}

impl TryFrom<syn::ExprArray> for CharSource {
    type Error = syn::Error;

    fn try_from(expr_array: syn::ExprArray) -> Result<Self, Self::Error> {
        let exprs = expr_array.elems.into_iter();
        let strings = exprs.map(|expr| {
            let syn::Expr::Lit(expr_lit) = expr else {
                let message = "expected literal";
                return Err(syn::Error::new_spanned(expr, message));
            };

            let syn::Lit::Str(lit_str) = expr_lit.lit else {
                let message = "expected string literal";
                return Err(syn::Error::new_spanned(expr_lit.lit, message));
            };

            let value = lit_str.value();

            Ok(value)
        });
        let strings: Result<Vec<_>, _> = strings.collect();

        Ok(Self::Strings(strings?))
    }
}

impl TryFrom<syn::ExprRange> for CharSource {
    type Error = syn::Error;

    fn try_from(expr_range: syn::ExprRange) -> Result<Self, Self::Error> {
        use syn::RangeLimits::*;

        let exprs = [expr_range.start, expr_range.end];
        let [start, end] = exprs.map(|expr| {
            expr.map(|expr| {
                let syn::Expr::Lit(expr_lit) = *expr else {
                    let message = "expected literal";
                    return Err(syn::Error::new_spanned(expr, message));
                };

                let syn::Lit::Char(lit_char) = expr_lit.lit else {
                    let message = "expected character literal";
                    return Err(syn::Error::new_spanned(expr_lit.lit, message));
                };

                let value = lit_char.value();

                Ok(value)
            })
        });
        let start = start
            .transpose()
            .map(|c| c.map(Bound::Included).unwrap_or(Bound::Unbounded));

        let into_bound = match expr_range.limits {
            HalfOpen(_) => Bound::Excluded,
            Closed(_) => Bound::Included,
        };
        let end = end
            .transpose()
            .map(|c| c.map(into_bound).unwrap_or(Bound::Unbounded));

        Ok(Self::Range(start?, end?))
    }
}

impl TryFrom<syn::ExprCall> for CharSource {
    type Error = syn::Error;

    fn try_from(expr_call: syn::ExprCall) -> Result<Self, Self::Error> {
        let syn::Expr::Path(expr_path) = *expr_call.func else {
            let message = "expected identifier";
            return Err(syn::Error::new_spanned(expr_call.func, message));
        };

        let ident = expr_path.try_into_ident()?;
        let name = ident.to_string();
        if name != "kern" {
            let message = format!("expected identifier `kern`, found `{name}`");
            return Err(syn::Error::new(ident.span(), message));
        }

        let mut exprs = expr_call.args.into_iter();
        let Some(first) = exprs.next() else {
            let message = "expected 2 arguments, found 0";
            return Err(syn::Error::new(expr_call.paren_token.span.join(), message));
        };
        let Some(second) = exprs.next() else {
            let message = "expected 2 arguments, found 1";
            return Err(syn::Error::new(expr_call.paren_token.span.join(), message));
        };
        let syn::Expr::Range(expr_range) = first else {
            let message = "expected range expression";
            return Err(syn::Error::new_spanned(first, message));
        };
        let syn::Expr::Array(expr_array) = second else {
            let message = "expected slice literal expression";
            return Err(syn::Error::new_spanned(second, message));
        };
        let [CharSource::Range(start, end), CharSource::Strings(strings)] =
            [expr_range.try_into()?, expr_array.try_into()?]
        else {
            panic!("expected character range and strings");
        };

        if let Some(third) = exprs.next() {
            let message = "remove the extra argument";
            return Err(syn::Error::new_spanned(third, message));
        }

        Ok(CharSource::Kern(start, end, strings))
    }
}

fn single_char_strings(start: Bound<char>, end: Bound<char>) -> Vec<String> {
    use Bound::*;

    match (start, end) {
        (Included(start), Included(end)) => (start..=end).map(|c| c.to_string()).collect(),
        (Included(start), Excluded(end)) => (start..end).map(|c| c.to_string()).collect(),
        (Included(start), Unbounded) => (start..=char::MAX).map(|c| c.to_string()).collect(),
        (Unbounded, Included(end)) => (char::MIN..=end).map(|c| c.to_string()).collect(),
        (Unbounded, Excluded(end)) => (char::MIN..end).map(|c| c.to_string()).collect(),
        (Unbounded, Unbounded) => (char::MIN..=char::MAX).map(|c| c.to_string()).collect(),
        (Excluded(_), _) => panic!("expected included or unbounded start index"),
    }
}

macro_rules! cartesian_square {
    ($iter:expr) => {
        $iter.flat_map(|a| $iter.map(move |b| String::from_iter([a, b])))
    };
}

fn strings_in_cartesian_square(start: Bound<char>, end: Bound<char>) -> Vec<String> {
    use Bound::*;

    match (start, end) {
        (Included(start), Included(end)) => cartesian_square!(start..=end).collect(),
        (Included(start), Excluded(end)) => cartesian_square!(start..end).collect(),
        (Included(start), Unbounded) => cartesian_square!(start..=char::MAX).collect(),
        (Unbounded, Included(end)) => cartesian_square!(char::MIN..=end).collect(),
        (Unbounded, Excluded(end)) => cartesian_square!(char::MIN..end).collect(),
        (Unbounded, Unbounded) => cartesian_square!(char::MIN..=char::MAX).collect(),
        (Excluded(_), _) => panic!("expected included or unbounded start index"),
    }
}

macro_rules! square_strings {
    ($strings:expr) => {
        $strings.into_iter().flat_map(|a| {
            $strings.into_iter().map(move |b| {
                let mut builder = String::with_capacity(a.len() + b.len());
                builder.push_str(a);
                builder.push_str(b);
                builder
            })
        })
    };
}

macro_rules! prefix_strings {
    ($iter:expr, $strings:expr) => {
        $iter.flat_map(|c| {
            $strings.into_iter().map(move |string| {
                let mut builder = String::with_capacity(c.len_utf8() + string.len());
                builder.push(c);
                builder.push_str(string);
                builder
            })
        })
    };
}

macro_rules! suffix_strings {
    ($iter:expr, $strings:expr) => {
        $iter.flat_map(|c| {
            $strings.into_iter().map(move |string| {
                let mut builder = String::with_capacity(c.len_utf8() + string.len());
                builder.push_str(string);
                builder.push(c);
                builder
            })
        })
    };
}

macro_rules! cartesian_products {
    ($iter:expr, $strings:expr) => {
        square_strings!($strings)
            .chain(prefix_strings!($iter, $strings))
            .chain(suffix_strings!($iter, $strings))
    };
}

fn single_char_affixed_strings<'a, T: IntoIterator<Item = &'a String> + Copy>(
    start: Bound<char>,
    end: Bound<char>,
    strings: T,
) -> Vec<String> {
    use Bound::*;

    match (start, end) {
        (Included(start), Included(end)) => cartesian_products!(start..=end, strings).collect(),
        (Included(start), Excluded(end)) => cartesian_products!(start..end, strings).collect(),
        (Included(start), Unbounded) => cartesian_products!(start..=char::MAX, strings).collect(),
        (Unbounded, Included(end)) => cartesian_products!(char::MIN..=end, strings).collect(),
        (Unbounded, Excluded(end)) => cartesian_products!(char::MIN..end, strings).collect(),
        (Unbounded, Unbounded) => cartesian_products!(char::MIN..=char::MAX, strings).collect(),
        (Excluded(_), _) => panic!("expected included or unbounded start index"),
    }
}