canrun_codegen 0.2.0

Procedural macros used by the Canrun crate
Documentation
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parse_macro_input, Result, Token};

struct DomainDef {
    canrun_internal: bool,
    domain_visibility: syn::Visibility,
    domain_name: syn::Ident,
    domain_types: Vec<syn::Type>,
}

impl Parse for DomainDef {
    fn parse(input: ParseStream) -> Result<Self> {
        let domain_visibility = input.parse()?;

        let domain_name: syn::Ident = input.parse()?;

        let content;
        syn::braced!(content in input);

        let raw_types: Punctuated<syn::Type, Token![,]> =
            content.parse_terminated(syn::Type::parse)?;
        let domain_types: Vec<_> = raw_types.into_iter().collect();

        Ok(DomainDef {
            canrun_internal: false,
            domain_visibility,
            domain_name,
            domain_types,
        })
    }
}

impl quote::ToTokens for DomainDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let DomainDef {
            canrun_internal,
            domain_visibility,
            domain_name,
            domain_types,
        } = self;

        let canrun_mod = if *canrun_internal {
            format_ident!("crate")
        } else {
            format_ident!("canrun")
        };

        let (fields, variants): (Vec<_>, Vec<_>) = (0..domain_types.len())
            .map(|n| (format_ident!("t{}", n), format_ident!("T{}", n)))
            .unzip();

        let value_name = format_ident!("{}Value", domain_name);

        let result = quote! {
            #[doc="A custom Domain generated by the domain! macro."]
            #[doc="TODO: Figure out how to interpolate something useful here"]
            #[derive(std::fmt::Debug)]
            #domain_visibility struct #domain_name {
                #(#fields: #canrun_mod::domains::DomainValues<#domain_types>),*
            }

            impl<'a> #canrun_mod::domains::Domain<'a> for #domain_name {
                type Value = #value_name;
                fn new() -> Self {
                    #domain_name {
                        #(#fields: #canrun_mod::domains::DomainValues::new(),)*
                    }
                }
                fn unify_domain_values(
                    state: #canrun_mod::state::State<'a, Self>,
                    a: Self::Value,
                    b: Self::Value,
                ) -> Option<#canrun_mod::state::State<Self>> {
                    use #canrun_mod::value::{Val, IntoVal};
                    match (a, b) {
                        #(
                            (#value_name::#variants(a), #value_name::#variants(b)) => {
                                state.unify::<#domain_types>(&a.into_val(), &b.into_val())
                            }
                        ,)*
                        _ => None, // This should only happen if a DomainVal constructor allows two values with different types.
                    }
                }
            }

            #(
                impl<'a> #canrun_mod::domains::DomainType<'a, #domain_types> for #domain_name {
                    fn values_as_ref(
                        &self,
                    ) -> &#canrun_mod::domains::DomainValues<#domain_types> {
                        &self.#fields
                    }
                    fn values_as_mut(
                        &mut self,
                    ) -> &mut #canrun_mod::domains::DomainValues<#domain_types> {
                        &mut self.#fields
                    }
                    fn wrap_domain_val(val: #canrun_mod::value::Val<#domain_types>) -> #value_name {
                        #value_name::#variants(val)
                    }
                }
            )*

            impl<'a> Clone for #domain_name {
                fn clone(&self) -> Self {
                    #domain_name {
                        #(#fields: self.#fields.clone()),*
                    }
                }
            }

            #[doc(hidden)]
            #[derive(std::fmt::Debug)]
            #domain_visibility enum #value_name {
                #(#variants(#canrun_mod::value::Val<#domain_types>)),*
            }

            impl Clone for #value_name {
                fn clone(&self) -> Self {
                    match self {
                        #(#value_name::#variants(val) => #value_name::#variants(val.clone())),*
                    }
                }
            }
        };
        result.to_tokens(tokens);
    }
}

/// Generate [`Domain`](../canrun/domains/index.html) structs and other
/// associated types and impls.
///
/// See the [Canrun docs](../canrun/domains/macro.domains.html) for details.
#[proc_macro]
pub fn domain(item: TokenStream) -> TokenStream {
    let def = parse_macro_input!(item as DomainDef);
    quote!(#def).into()
}

/// Internal use to allow domains to be generated inside the canrun crate
/// without crate name weirdness
#[proc_macro]
#[doc(hidden)]
pub fn canrun_internal_domain(item: TokenStream) -> TokenStream {
    let mut def = parse_macro_input!(item as DomainDef);
    def.canrun_internal = true;
    quote!(#def).into()
}