1extern crate proc_macro;
2extern crate proc_macro2;
3extern crate quote;
4extern crate syn;
5
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::{quote, format_ident};
9use syn::{Attribute, parse_quote, ReturnType};
10
11macro_rules! parse_error {
12 ($($arg:tt)*) => {{
13 let error = syn::LitStr::new(&format!($($arg)*), Span::call_site());
14 quote!(compile_error!(#error);).into()
15 }}
16}
17
18#[derive(Hash, PartialEq, Eq, Clone, Debug)]
19enum AtomScope {
20 Initial,
21 Singleton,
22 Dependent,
23}
24
25impl AtomScope {
26 fn from(value: &str) -> Option<Self> {
27 match value {
28 "initial" => Some(Self::Initial),
29 "singleton" => Some(Self::Singleton),
30 "dependent" => Some(Self::Dependent),
31 _ => None,
32 }
33 }
34}
35
36impl ToString for AtomScope {
37 fn to_string(&self) -> String {
38 match self {
39 Self::Initial => "Initial".to_string(),
40 Self::Singleton => "Singleton".to_string(),
41 Self::Dependent => "Dependent".to_string(),
42 }
43 }
44}
45
46fn append_atom_doc(attrs: &mut Vec<Attribute>) {
47 let mut added_documentation: Vec<syn::Attribute> = vec![
48 parse_quote!(#[doc = "### Atom"]),
49 parse_quote!(#[doc = "This object is an atom. More documentation coming soon."]),
50 ];
51 attrs.append(&mut added_documentation);
52}
53
54fn parse_scope(item: TokenStream) -> Result<AtomScope, String> {
55 if item.is_empty() {
56 return Ok(AtomScope::Initial)
57 }
58
59 let Ok(item_ast) = syn::parse::<syn::Ident>(item) else {
60 return Err("malformed atom scope, expecting a single identifier.".into());
61 };
62 let Some(atom_scope) = AtomScope::from(&item_ast.to_string()) else {
63 return Err("invalid atom scope".into());
64 };
65
66 return Ok(atom_scope)
67}
68
69fn field_is_injected(field: &syn::Field) -> bool {
70 field.attrs.iter().find(|a| a.path().is_ident("inject")).is_some()
71}
72
73fn make_injections_arc(fields: &mut syn::Fields) {
74 fields.iter_mut()
75 .filter(|f| field_is_injected(f))
76 .for_each(|f| {
77 let ty = &f.ty;
78 f.ty = parse_quote!(std::sync::Arc<#ty>)
79 });
80}
81
82static mut AFABRIC_COMPTIME_REGISTERED_ATOMS: Vec<String> = Vec::new();
83
84fn atom_impl(atom_type: AtomScope, decl: syn::ItemStruct) -> TokenStream {
85 let mut decl = decl.clone();
86 let decl_name = &decl.ident;
87
88 append_atom_doc(&mut decl.attrs);
89
90 make_injections_arc(&mut decl.fields);
91
92 let injected_fields = decl.fields.iter()
93 .filter(|f| field_is_injected(f));
94 let injected_types: Vec<syn::Type> = injected_fields.clone().map(|f| f.ty.clone()).collect();
95
96 let gen_depends_on = quote!(
97 vec![
98 #(std::any::TypeId::of::<#injected_types>()),*
99 ]
100 );
101
102 let injections: Vec<syn::FieldValue> = injected_fields.clone().map(|f| {
103 let injected_type = &f.ty;
104 syn::FieldValue {
105 attrs: Vec::new(),
106 member: syn::Member::Named(f.ident.clone().unwrap()),
107 colon_token: Some(parse_quote!(:)),
108 expr: parse_quote!(
109 system.resolve(std::any::TypeId::of::<#injected_type>()).downcast_ref::<#injected_type>().unwrap()
110 )
111 }
112 }).collect();
113 let injections = quote!(
114 #(#injections),*
115 );
116
117 decl.fields.iter_mut().for_each(|f| {
119 if field_is_injected(&f) {
120 f.attrs.retain(|a| !a.path().is_ident("inject"));
121 }
122 });
123
124 let atom_type_ident = format_ident!("{}", atom_type.to_string());
125
126 unsafe {
127 AFABRIC_COMPTIME_REGISTERED_ATOMS.push(decl_name.to_string());
128 }
129
130 let gen = quote!(
131 #decl
132
133 impl #decl_name {
134 #[doc(hidden)]
135 fn __atom_create<T: afabric::AtomRegistry>(system: afabric::Context<T>) -> #decl_name {
136 #decl_name {
137 #injections
138 }
139 }
140
141 #[doc(hidden)]
142 fn __atom_meta() -> afabric::AtomMeta {
143 afabric::AtomMeta {
144 type_id: std::any::TypeId::of::<#decl_name>(),
145 atom_type: afabric::AtomType::#atom_type_ident,
146 depends_on: #gen_depends_on,
147 }
148 }
149 }
150 );
151
152 gen.into()
153}
154
155#[proc_macro_attribute]
157pub fn atom(attr: TokenStream, item: TokenStream) -> TokenStream {
158 let atom_scope_or_err = parse_scope(attr);
159 let Ok(atom_scope) = atom_scope_or_err else {
160 return parse_error!("{}", atom_scope_or_err.unwrap_err());
161 };
162
163 let Ok(decl) = syn::parse::<syn::ItemStruct>(item) else {
164 return parse_error!("atom only applies to structs.");
165 };
166
167 atom_impl(atom_scope, decl)
168}
169
170#[proc_macro_attribute]
171pub fn produces(_attr: TokenStream, item: TokenStream) -> TokenStream {
172 let Ok(func_impl) = syn::parse::<syn::ImplItemFn>(item) else {
174 return parse_error!("'produces' should only annotate functions on atoms.");
175 };
176
177 let ReturnType::Type(_, ret_type) = func_impl.clone().sig.output else {
179 return parse_error!("'produces' requires function to return discrete type, not '()'");
180 };
181
182 let inputs_count = func_impl.clone().sig.inputs.iter().count();
184 if inputs_count > 1 {
185 return parse_error!("'produces' requires function to take zero or one argument, but got {}", inputs_count);
186 }
187
188 println!("produces {} ->", quote!(#ret_type));
189
190 quote!(#func_impl).into()
191}
192
193#[proc_macro]
194pub fn define_base_atoms(_item: TokenStream) -> TokenStream {
195 unsafe {
196 let get_atoms: Vec<proc_macro2::TokenStream> = AFABRIC_COMPTIME_REGISTERED_ATOMS.iter().map(|name| {
197 let name = format_ident!("{}", name);
198 quote!(
199 let meta = #name::__atom_meta();
200 if id == meta.type_id {
201 return Some(meta)
202 }
203 )
204 }).collect();
205
206 let atom_meta: Vec<proc_macro2::TokenStream> = AFABRIC_COMPTIME_REGISTERED_ATOMS.iter().map(|name| {
207 let name = format_ident!("{}", name);
208 quote!(#name::__atom_meta())
209 }).collect();
210
211 quote!(
212 pub struct BaseAtomRegistry {}
213 impl afabric::AtomRegistry for BaseAtomRegistry {
214 fn get(id: std::any::TypeId) -> Option<afabric::AtomMeta> {
215 #(#get_atoms)*
216
217 None
218 }
219
220 fn all() -> Vec<afabric::AtomMeta> {
221 vec![
222 #(#atom_meta),*
223 ]
224 }
225 }
226 ).into()
227 }
228}