sim-macros 0.2.1

Proc-macro surface for authored SIM libraries.
Documentation
use super::{ParsedCodec, ParsedMacro, ParsedNumberDomain, ParsedSite};
use crate::attrs::strip_attrs;
use syn::{Ident, ItemFn, LitStr};

pub(super) fn parse_codec(item: &mut ItemFn) -> syn::Result<ParsedCodec> {
    let matching_attrs = item
        .attrs
        .iter()
        .filter(|attr| attr.path().is_ident("sim_codec"))
        .collect::<Vec<_>>();
    let attr = matching_attrs
        .first()
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "missing #[sim_codec]"))?;
    if matching_attrs.len() > 1 {
        return Err(syn::Error::new_spanned(
            attr,
            "duplicate #[sim_codec(...)] attribute",
        ));
    }

    let mut symbol = None;
    let mut decode = None;
    let mut encode = None;
    attr.parse_nested_meta(|meta| {
        if meta.path.is_ident("symbol") {
            symbol = Some(parse_unique_lit_str(&meta, symbol.as_ref(), "symbol")?);
            Ok(())
        } else if meta.path.is_ident("decode") {
            decode = Some(parse_unique_lit_str(&meta, decode.as_ref(), "decode")?);
            Ok(())
        } else if meta.path.is_ident("encode") {
            encode = Some(parse_unique_lit_str(&meta, encode.as_ref(), "encode")?);
            Ok(())
        } else {
            Err(meta.error("unsupported #[sim_codec(...)] entry"))
        }
    })?;

    let symbol = symbol
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "#[sim_codec] requires symbol"))?
        .value();
    let decode_ident = codec_ident_attr(&item.sig.ident, "decode", decode)?;
    let encode_ident = codec_ident_attr(&item.sig.ident, "encode", encode)?;
    strip_attrs(&mut item.attrs, &["sim_codec"]);
    Ok(ParsedCodec {
        symbol,
        decode_ident,
        encode_ident,
    })
}

pub(super) fn parse_macro(item: &mut ItemFn) -> syn::Result<ParsedMacro> {
    let matching_attrs = item
        .attrs
        .iter()
        .filter(|attr| attr.path().is_ident("sim_macro"))
        .collect::<Vec<_>>();
    let attr = matching_attrs
        .first()
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "missing #[sim_macro]"))?;
    if matching_attrs.len() > 1 {
        return Err(syn::Error::new_spanned(
            attr,
            "duplicate #[sim_macro(...)] attribute",
        ));
    }

    let mut symbol = None;
    let mut expand = None;
    attr.parse_nested_meta(|meta| {
        if meta.path.is_ident("symbol") {
            symbol = Some(parse_unique_lit_str(&meta, symbol.as_ref(), "symbol")?);
            Ok(())
        } else if meta.path.is_ident("expand") {
            expand = Some(parse_unique_lit_str(&meta, expand.as_ref(), "expand")?);
            Ok(())
        } else {
            Err(meta.error("unsupported #[sim_macro(...)] entry"))
        }
    })?;

    let symbol = symbol
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "#[sim_macro] requires symbol"))?
        .value();
    let expand_ident = marker_ident_attr(&item.sig.ident, "sim_macro", "expand", expand)?;
    strip_attrs(&mut item.attrs, &["sim_macro"]);
    Ok(ParsedMacro {
        symbol,
        expand_ident,
    })
}

pub(super) fn parse_number_domain(item: &mut ItemFn) -> syn::Result<ParsedNumberDomain> {
    let matching_attrs = item
        .attrs
        .iter()
        .filter(|attr| attr.path().is_ident("sim_number_domain"))
        .collect::<Vec<_>>();
    let attr = matching_attrs
        .first()
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "missing #[sim_number_domain]"))?;
    if matching_attrs.len() > 1 {
        return Err(syn::Error::new_spanned(
            attr,
            "duplicate #[sim_number_domain(...)] attribute",
        ));
    }

    let mut symbol = None;
    let mut parse = None;
    let mut encode = None;
    let mut add = None;
    let mut sub = None;
    let mut mul = None;
    let mut div = None;
    let mut neg = None;
    let mut sum = None;
    let mut product = None;
    attr.parse_nested_meta(|meta| {
        if meta.path.is_ident("symbol") {
            symbol = Some(parse_unique_lit_str(&meta, symbol.as_ref(), "symbol")?);
            Ok(())
        } else if meta.path.is_ident("parse") {
            parse = Some(parse_unique_lit_str(&meta, parse.as_ref(), "parse")?);
            Ok(())
        } else if meta.path.is_ident("encode") {
            encode = Some(parse_unique_lit_str(&meta, encode.as_ref(), "encode")?);
            Ok(())
        } else if meta.path.is_ident("add") {
            add = Some(parse_unique_lit_str(&meta, add.as_ref(), "add")?);
            Ok(())
        } else if meta.path.is_ident("sub") {
            sub = Some(parse_unique_lit_str(&meta, sub.as_ref(), "sub")?);
            Ok(())
        } else if meta.path.is_ident("mul") {
            mul = Some(parse_unique_lit_str(&meta, mul.as_ref(), "mul")?);
            Ok(())
        } else if meta.path.is_ident("div") {
            div = Some(parse_unique_lit_str(&meta, div.as_ref(), "div")?);
            Ok(())
        } else if meta.path.is_ident("neg") {
            neg = Some(parse_unique_lit_str(&meta, neg.as_ref(), "neg")?);
            Ok(())
        } else if meta.path.is_ident("sum") {
            sum = Some(parse_unique_lit_str(&meta, sum.as_ref(), "sum")?);
            Ok(())
        } else if meta.path.is_ident("product") {
            product = Some(parse_unique_lit_str(&meta, product.as_ref(), "product")?);
            Ok(())
        } else {
            Err(meta.error("unsupported #[sim_number_domain(...)] entry"))
        }
    })?;

    let symbol = symbol
        .ok_or_else(|| {
            syn::Error::new_spanned(&item.sig.ident, "#[sim_number_domain] requires symbol")
        })?
        .value();
    let parse_ident = marker_ident_attr(&item.sig.ident, "sim_number_domain", "parse", parse)?;
    let encode_ident = marker_ident_attr(&item.sig.ident, "sim_number_domain", "encode", encode)?;
    strip_attrs(&mut item.attrs, &["sim_number_domain"]);
    Ok(ParsedNumberDomain {
        symbol,
        parse_ident,
        encode_ident,
        add_ident: optional_marker_ident_attr("sim_number_domain", "add", add)?,
        sub_ident: optional_marker_ident_attr("sim_number_domain", "sub", sub)?,
        mul_ident: optional_marker_ident_attr("sim_number_domain", "mul", mul)?,
        div_ident: optional_marker_ident_attr("sim_number_domain", "div", div)?,
        neg_ident: optional_marker_ident_attr("sim_number_domain", "neg", neg)?,
        sum_ident: optional_marker_ident_attr("sim_number_domain", "sum", sum)?,
        product_ident: optional_marker_ident_attr("sim_number_domain", "product", product)?,
    })
}

pub(super) fn parse_site(item: &mut ItemFn) -> syn::Result<ParsedSite> {
    let matching_attrs = item
        .attrs
        .iter()
        .filter(|attr| attr.path().is_ident("sim_site"))
        .collect::<Vec<_>>();
    let attr = matching_attrs
        .first()
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "missing #[sim_site]"))?;
    if matching_attrs.len() > 1 {
        return Err(syn::Error::new_spanned(
            attr,
            "duplicate #[sim_site(...)] attribute",
        ));
    }

    let mut symbol = None;
    let mut realize = None;
    attr.parse_nested_meta(|meta| {
        if meta.path.is_ident("symbol") {
            symbol = Some(parse_unique_lit_str(&meta, symbol.as_ref(), "symbol")?);
            Ok(())
        } else if meta.path.is_ident("realize") {
            realize = Some(parse_unique_lit_str(&meta, realize.as_ref(), "realize")?);
            Ok(())
        } else {
            Err(meta.error("unsupported #[sim_site(...)] entry"))
        }
    })?;

    let symbol = symbol
        .ok_or_else(|| syn::Error::new_spanned(&item.sig.ident, "#[sim_site] requires symbol"))?
        .value();
    let realize_ident = marker_ident_attr(&item.sig.ident, "sim_site", "realize", realize)?;
    strip_attrs(&mut item.attrs, &["sim_site"]);
    Ok(ParsedSite {
        symbol,
        realize_ident,
    })
}

fn parse_unique_lit_str(
    meta: &syn::meta::ParseNestedMeta<'_>,
    existing: Option<&LitStr>,
    name: &str,
) -> syn::Result<LitStr> {
    if existing.is_some() {
        return Err(meta.error(format!("duplicate {name} entry in marker attribute")));
    }
    meta.value()?.parse()
}

fn codec_ident_attr(
    item_ident: &Ident,
    nested_name: &str,
    value: Option<LitStr>,
) -> syn::Result<Ident> {
    let value = value.ok_or_else(|| {
        syn::Error::new_spanned(item_ident, format!("#[sim_codec] requires {nested_name}"))
    })?;
    let raw = value.value();
    syn::parse_str::<Ident>(&raw).map_err(|_| {
        syn::Error::new_spanned(
            value,
            format!("#[sim_codec] {nested_name} must name a Rust function"),
        )
    })
}

fn marker_ident_attr(
    item_ident: &Ident,
    marker: &str,
    nested_name: &str,
    value: Option<LitStr>,
) -> syn::Result<Ident> {
    let value = value.ok_or_else(|| {
        syn::Error::new_spanned(item_ident, format!("#[{marker}] requires {nested_name}"))
    })?;
    parse_marker_ident(marker, nested_name, value)
}

fn optional_marker_ident_attr(
    marker: &str,
    nested_name: &str,
    value: Option<LitStr>,
) -> syn::Result<Option<Ident>> {
    value
        .map(|value| parse_marker_ident(marker, nested_name, value))
        .transpose()
}

fn parse_marker_ident(marker: &str, nested_name: &str, value: LitStr) -> syn::Result<Ident> {
    let raw = value.value();
    syn::parse_str::<Ident>(&raw).map_err(|_| {
        syn::Error::new_spanned(
            value,
            format!("#[{marker}] {nested_name} must name a Rust function"),
        )
    })
}