include-pnm 1.0.0

Include PNM images directly in Rust code.
Documentation
//! Parsing of macro arguments.
use proc_macro::{Delimiter, Span, TokenTree, token_stream};

use crate::{
    cold,
    metadata::{Channels, Depth, DepthChannels},
};

/// Parse out type from tokens to macro.
pub(crate) fn get_depth_channels(
    caller: &'static str,
    input: &mut token_stream::IntoIter,
) -> DepthChannels {
    match input.next() {
        Some(TokenTree::Group(grp)) if grp.delimiter() == Delimiter::Bracket => {
            let mut iter = grp.stream().into_iter();
            let depth = match iter.next() {
                Some(TokenTree::Ident(ident)) => match &*ident.to_string() {
                    "bool" => Depth::One,
                    "u8" => Depth::Eight,
                    "u16" => Depth::Sixteen,
                    _ => cold::expected_token(caller, "bit depth type", ident.span()),
                },
                Some(other) => cold::expected_token(caller, "bit depth type", other.span()),
                None => cold::expected_token(caller, "bit depth type", grp.span_close().start()),
            };
            match iter.next() {
                Some(TokenTree::Punct(punct)) if punct.as_char() == ';' => (),
                Some(other) => cold::expected_token(caller, "semicolon", other.span()),
                None => cold::expected_token(caller, "semicolon", Span::call_site().end()),
            }
            let channels = match iter.next() {
                Some(TokenTree::Literal(lit)) => match lit.to_string().parse::<u8>() {
                    Ok(1) => Channels::One,
                    Ok(2) => Channels::Two,
                    Ok(3) => Channels::Three,
                    Ok(4) => Channels::Four,
                    _ => cold::expected_token(caller, "channel count", lit.span()),
                },
                Some(other) => cold::expected_token(caller, "channel count", other.span()),
                None => cold::expected_token(caller, "channel count", Span::call_site().end()),
            };
            if let Some(token) = iter.next() {
                cold::extraneous_token(caller, token.span());
            }
            DepthChannels::new(depth, channels)
                .expect("cannot have more than two channels for black-and-white image")
        }
        Some(other) => cold::expected_token(caller, "pixel format", other.span()),
        None => cold::expected_token(caller, "pixel format", Span::call_site().end()),
    }
}

/// Sentinel indicating leading comma.
pub(crate) struct LeadingComma;

/// Parse out filename from remaining tokens to macro.
pub(crate) fn get_file(
    caller: &'static str,
    input: &mut token_stream::IntoIter,
    mut leading_comma: Option<LeadingComma>,
) -> String {
    let lit = loop {
        match input.next() {
            Some(TokenTree::Literal(lit)) => break lit,
            Some(TokenTree::Punct(punct))
                if punct.as_char() == ',' && leading_comma.take().is_some() =>
            {
                continue;
            }
            Some(other) => cold::expected_token(caller, "filename", other.span()),
            None => cold::expected_token(caller, "filename", Span::call_site()),
        }
    };
    match input.next() {
        // allow trailing comma, but only one
        Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => {
            if let Some(other) = input.next() {
                cold::extraneous_token(caller, other.span());
            }
        }
        Some(other) => cold::extraneous_token(caller, other.span()),
        None => (),
    }
    let span = lit.span();
    let mut lit = lit.to_string();
    assert!(
        lit.starts_with("\"") && lit.ends_with("\"") && !lit.contains("\\"),
        "unsupported string literal type at {span:?}"
    );
    lit.remove(0);
    lit.pop();
    lit
}