ori-macro 0.1.0-alpha.1

Macros for Ori
Documentation
use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::quote;
use syn::{
    parse_macro_input, spanned::Spanned, Attribute, Data, DataStruct, DeriveInput, Fields,
    FieldsNamed, Ident,
};

use crate::krate::find_crate;

#[allow(dead_code)]
struct Attrs {
    is_prop: bool,
    is_event: bool,
    is_bind: bool,
}

impl Attrs {
    pub fn parse(attrs: &[Attribute]) -> Self {
        let mut is_prop = false;
        let mut is_event = false;
        let mut is_bind = false;

        for attr in attrs {
            if attr.path.is_ident("prop") {
                is_prop = true;
            } else if attr.path.is_ident("event") {
                is_event = true;
            } else if attr.path.is_ident("bind") {
                is_bind = true;
            }
        }

        Self {
            is_prop,
            is_event,
            is_bind,
        }
    }
}

pub fn derive_build(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let properties = properties(&input);
    let events = events(&input);
    let bindings = bindings(&input);

    let expanded = quote! {
        #properties
        #events
        #bindings
    };

    expanded.into()
}

fn data(input: &DeriveInput) -> (&DataStruct, &FieldsNamed) {
    match input.data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => (data, fields),
            Fields::Unnamed(_) => abort!(input, "tuple structs are not supported"),
            Fields::Unit => abort!(input, "unit structs are not supported"),
        },
        Data::Enum(_) => abort!(input, "enum types are not supported"),
        Data::Union(_) => abort!(input, "union types are not supported"),
    }
}

fn properties(input: &DeriveInput) -> TokenStream {
    let name = &input.ident;
    let setter = prop_setter(&input);

    let ori_core = find_crate("core");

    quote! {
        const _: () = {
            pub struct Setter<'a> {
                this: &'a mut #name,
            }

            impl<'a> Setter<'a> {
                #setter
            }

            impl #ori_core::Properties for #name {
                type Setter<'a> = Setter<'a>;

                fn setter(&mut self) -> Self::Setter<'_> {
                    Setter { this: self }
                }
            }
        };
    }
}

fn events(input: &DeriveInput) -> TokenStream {
    let name = &input.ident;
    let setter = event_setter(&input);

    let ori_core = find_crate("core");

    quote! {
        const _: () = {
            pub struct Setter<'a> {
                this: &'a mut #name,
            }

            impl<'a> Setter<'a> {
                #setter
            }

            impl #ori_core::Events for #name {
                type Setter<'a> = Setter<'a>;

                fn setter(&mut self) -> Self::Setter<'_> {
                    Setter { this: self }
                }
            }
        };
    }
}

fn bindings(input: &DeriveInput) -> TokenStream {
    let name = &input.ident;
    let setter = binding_setter(&input);

    let ori_core = find_crate("core");

    quote! {
        const _: () = {
            pub struct Setter<'a> {
                this: &'a mut #name,
            }

            impl<'a> Setter<'a> {
                #setter
            }

            impl #ori_core::Bindings for #name {
                type Setter<'a> = Setter<'a>;

                fn setter(&mut self) -> Self::Setter<'_> {
                    Setter { this: self }
                }
            }
        };
    }
}

fn prop_setter(input: &DeriveInput) -> TokenStream {
    let (_, fields) = data(input);

    let fields = fields.named.iter().filter_map(|field| {
        let name = &field.ident;
        let ty = &field.ty;

        let attrs = Attrs::parse(&field.attrs);

        if !attrs.is_prop {
            return None;
        }

        Some(quote! {
            pub fn #name(&mut self, #name: impl ::std::convert::Into<#ty>) {
                self.this.#name = ::std::convert::Into::into(#name);
            }
        })
    });

    quote! {
        #(#fields)*
    }
}

fn event_name(name: &Ident) -> Ident {
    let name = name.to_string();
    let event_name = name.strip_prefix("on_").unwrap_or(&name).to_string();
    Ident::new(&event_name, name.span())
}

fn event_setter(input: &DeriveInput) -> TokenStream {
    let (_, fields) = data(input);

    let ori_core = find_crate("core");

    let fields = fields.named.iter().filter_map(|field| {
        let name = field.ident.as_ref().unwrap();
        let event = event_name(&name);

        let ty = &field.ty;

        let attrs = Attrs::parse(&field.attrs);

        if !attrs.is_event {
            return None;
        }

        Some(quote! {
            pub fn #event<'b>(
                &mut self,
                cx: #ori_core::Scope<'b>,
                #name: impl FnMut(&<#ty as #ori_core::BindCallback>::Event) + #ori_core::Sendable + 'b
            ) {
                <#ty as #ori_core::BindCallback>::bind(&mut self.this.#name, cx, #name);
            }
        })
    });

    quote! {
        #(#fields)*
    }
}

fn binding_setter(input: &DeriveInput) -> TokenStream {
    let (_, fields) = data(input);

    let ori_core = find_crate("core");

    let fields = fields.named.iter().filter_map(|field| {
        let name = field.ident.as_ref().unwrap();
        let event = event_name(&name);

        let ty = &field.ty;

        let attrs = Attrs::parse(&field.attrs);

        if !attrs.is_bind {
            return None;
        }

        Some(quote! {
            pub fn #event<'b>(
                &mut self,
                cx: #ori_core::Scope<'b>,
                #name: &'b #ori_core::Signal<<#ty as #ori_core::Bindable>::Item>
            ) {
                <#ty as #ori_core::Bindable>::bind(&mut self.this.#name, cx, #name);
            }
        })
    });

    quote! {
        #(#fields)*
    }
}