mplusfonts-macros 0.3.4

Procedural macros re-exported in the mplusfonts crate
Documentation
use syn::parse::{Parse, ParseStream};
use syn::punctuated::{Pair, Punctuated};
use syn::{Token, parse};

use super::CharSource;
use super::font::{Font, FontSize, FontWeight};

pub struct Arguments {
    pub font: Pair<Font, Token![,]>,
    pub weight: Pair<u16, Token![,]>,
    pub size: Pair<f32, Token![,]>,
    pub hint: Pair<bool, Token![,]>,
    pub positions: Pair<u8, Token![,]>,
    pub bit_depth: Pair<u8, Token![,]>,
    pub sources: Punctuated<CharSource, Token![,]>,
}

impl Parse for Arguments {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let font = Pair::new(input.parse()?, input.parse()?);
        let weight = Pair::new(input.call(parse_weight(font.value()))?, input.parse()?);
        let size = Pair::new(input.call(parse_size)?, input.parse()?);
        let hint = Pair::new(input.call(parse_bool)?, input.parse()?);
        let positions = Pair::new(input.call(parse_u8_in_range::<1, 16>)?, input.parse()?);
        let bit_depth = input.call(parse_u8_in_set::<1, 2, 4, 8>)?;
        let lookahead = input.lookahead1();
        if lookahead.peek(Token![,]) || lookahead.peek(parse::End) {
            let bit_depth = Pair::new(bit_depth, input.parse()?);
            let sources = Punctuated::parse_terminated(input)?;
            let arguments = Self {
                font,
                weight,
                size,
                hint,
                positions,
                bit_depth,
                sources,
            };

            Ok(arguments)
        } else {
            Err(lookahead.error())
        }
    }
}

fn parse_weight(font: &Font) -> fn(ParseStream) -> syn::Result<u16> {
    use Font::*;

    match font {
        MPLUS1(..) | MPLUS2(..) => |input| {
            let FontWeight::<900>(value) = input.parse()?;

            Ok(value)
        },
        MPLUSCode { .. } => |input| {
            let FontWeight::<700>(value) = input.parse()?;

            Ok(value)
        },
    }
}

fn parse_size(input: ParseStream) -> syn::Result<f32> {
    let FontSize(value) = input.parse()?;

    Ok(value)
}

fn parse_bool(input: ParseStream) -> syn::Result<bool> {
    let syn::LitBool { value, .. } = input.parse()?;

    Ok(value)
}

fn parse_u8_in_range<const MIN: u8, const MAX: u8>(input: ParseStream) -> syn::Result<u8> {
    let lit_int: syn::LitInt = input.parse()?;
    let value = lit_int.base10_parse()?;
    if value < MIN || value > MAX {
        let message = format!("expected number between `{MIN}` and `{MAX}`, found `{value}`");
        return Err(syn::Error::new(lit_int.span(), message));
    }

    Ok(value)
}

fn parse_u8_in_set<const A: u8, const B: u8, const C: u8, const D: u8>(
    input: ParseStream,
) -> syn::Result<u8> {
    let lit_int: syn::LitInt = input.parse()?;
    let value = lit_int.base10_parse()?;
    if ![A, B, C, D].contains(&value) {
        let message = format!("expected one of: `{A}`, `{B}`, `{C}`, `{D}`; found `{value}`");
        return Err(syn::Error::new(lit_int.span(), message));
    }

    Ok(value)
}