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, }
}
}
#(
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);
}
}
#[proc_macro]
pub fn domain(item: TokenStream) -> TokenStream {
let def = parse_macro_input!(item as DomainDef);
quote!(#def).into()
}
#[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()
}