cellular_raza_core_derive/lib.rs
1#![warn(missing_docs)]
2#![warn(clippy::missing_docs_in_private_items)]
3//! This crate provides powerful derive macros to automatically implement the `UpdateCycle` and `UpdateMechanics` traits.
4//! For the future, we are planning to have similar functionality with other concepts associated to CellAgents.
5
6use proc_macro::{TokenStream, TokenTree};
7use quote::{quote, ToTokens};
8use syn::{parse::ParseStream, parse_macro_input, spanned::Spanned, DeriveInput};
9
10#[proc_macro_derive(AuxStorage, attributes(UpdateCycle, UpdateMechanics))]
11/// Derives the `UpdateCycle` and `UpdateMechanics` trait automatically for the containing struct.
12pub fn aux_storage(input: TokenStream) -> TokenStream {
13 // Parse the input tokens into a syntax tree
14 let ast = parse_macro_input!(input as DeriveInput);
15
16 // Build the output, possibly using quasi-quotation
17 let struct_name = ast.ident;
18 let struct_generics = ast.generics.clone();
19 let mut result = TokenStream::new();
20
21 let data: syn::DataStruct = match ast.data {
22 syn::Data::Struct(data) => data,
23 _ => panic!("Usage of #[UpdateCycle] on a non-struct type"),
24 };
25 for field in data.fields.iter() {
26 // Update Cycle
27 if field.attrs.iter().any(|x| match &x.meta {
28 syn::Meta::Path(path) => path.is_ident("UpdateCycle"),
29 _ => false,
30 }) {
31 let name = &field.ident;
32 let res2 = quote! {
33 impl #struct_generics UpdateCycle for #struct_name #struct_generics {
34 fn set_cycle_events(&mut self, events: Vec<CycleEvent>) {
35 self.#name.set_cycle_events(events)
36 }
37
38 fn get_cycle_events(&self) -> Vec<CycleEvent> {
39 self.#name.get_cycle_events()
40 }
41
42 fn add_cycle_event(&mut self, event: CycleEvent) {
43 self.#name.add_cycle_event(event)
44 }
45 }
46 };
47 result.extend(TokenStream::from(res2));
48 }
49 // Update Mechanics
50 else if field.attrs.iter().any(|x| match &x.meta {
51 syn::Meta::Path(path) => path.is_ident("UpdateMechanics"),
52 _ => false,
53 }) {
54 let name = &field.ident;
55 let generic_args = match &field.ty {
56 syn::Type::Path(path) => {
57 path.path
58 .segments
59 .first()
60 .and_then(|segment| match &segment.arguments {
61 syn::PathArguments::AngleBracketed(arg) => {
62 Some(arg.args.clone().into_iter().collect::<Vec<_>>())
63 }
64 _ => None,
65 })
66 }
67 _ => None,
68 }
69 .or(Some(Vec::new()))
70 .unwrap();
71
72 let position_generic = generic_args[0].clone();
73 let velocity_generic = generic_args[1].clone();
74 let force_generic = generic_args[2].clone();
75
76 let res2 = quote! {
77 impl #struct_generics UpdateMechanics <#(#generic_args),*> for #struct_name #struct_generics
78 where
79 F: Clone + core::ops::AddAssign<F> + num::Zero,
80 {
81 fn set_last_position(&mut self, pos: #position_generic) {
82 self.#name.set_last_position(pos)
83 }
84 fn previous_positions(&self) -> std::collections::vec_deque::Iter<#position_generic> {
85 self.#name.previous_positions()
86 }
87 fn set_last_velocity(&mut self, vel: #velocity_generic) {
88 self.#name.set_last_velocity(vel)
89 }
90 fn previous_velocities(&self) -> std::collections::vec_deque::Iter<#velocity_generic> {
91 self.#name.previous_velocities()
92 }
93 fn add_force(&mut self, force: #force_generic) {
94 self.#name.add_force(force);
95 }
96 fn get_current_force(&self) -> #force_generic {
97 self.#name.get_current_force()
98 }
99 fn clear_forces(&mut self) {
100 self.#name.clear_forces()
101 }
102 }
103 };
104 result.extend(TokenStream::from(res2));
105 }
106 }
107
108 // Hand the output tokens back to the compiler
109 TokenStream::from(result)
110}
111
112fn parse_non_delimiter_tokens(tokenstream: TokenStream) -> Vec<TokenStream> {
113 use itertools::Itertools;
114 tokenstream
115 .into_iter()
116 .group_by(|token| match token {
117 TokenTree::Punct(p) => p.as_char() == char::from(','),
118 _ => false,
119 })
120 .into_iter()
121 .filter_map(|(is_comma, group)| if !is_comma { Some(group) } else { None })
122 .map(|group| TokenStream::from_iter(group.into_iter()))
123 .collect()
124}
125
126#[proc_macro]
127/// Simple macro that checks if two supplied values are identical.
128/// If this is the case it will insert the specified expression.
129/// Otherwise it inserts nothing.
130///
131/// The macro can be used to omit code when two idents are not identical.
132/// It can also be used to check if an identifier is contained in a range of identifiers.
133/// ```
134/// # use cellular_raza_core_derive::identical;
135/// // Identifiers are not matching. This means that
136/// // the last statement will never be inserted
137/// identical!(MyFirstIdentifier, MySecondIdentifier, assert!(false));
138///
139/// // Identifiers are matching. The last statement
140/// // (in this case `assert!(true)`) will be inserted into the code.
141/// identical!(SameIdent, SameIdent, assert!(true));
142///
143/// // The String "hamster" is inserted here since both identifiers are matching.
144/// assert_eq!("hamster", identical!(Id1, Id1, "hamster"));
145///
146/// // Identifiers are not equal if their capitalization does not match
147/// identical!(caps, Caps, assert!(false));
148///
149/// // This works since 1_f64 is turned into a string and then
150/// // compared to "1_f64" which is identically the same.
151/// identical!(1_f64, "1_f64", assert!(true));
152/// ```
153///
154/// The macro is not going to compile if given only two idents
155/// ```compile_fail
156/// identical!(Id1, Id2);
157/// ```
158///
159/// The same holds true if we do not supply two identifiers.
160/// ```compile_fail
161/// identical!(Id1, println!("asdf"));
162/// ```
163pub fn identical(tokenstream: TokenStream) -> TokenStream {
164 let tokens = parse_non_delimiter_tokens(tokenstream);
165 let tokens_length = tokens.len();
166 if tokens_length != 3 {
167 panic!("Macro requires two identifiers to compare against each other and one expression to insert");
168 } else {
169 let m1 = tokens[0].clone();
170 let m2 = tokens[1].clone();
171 let expr = TokenStream::from_iter(tokens.into_iter().skip(2).into_iter());
172 if m1.to_string() == m2.to_string() {
173 expr
174 } else {
175 TokenStream::from(quote!())
176 }
177 }
178}
179
180/* #[derive(Clone, Debug)]
181enum Aspect {
182 Mechanics,
183 Cycle,
184 Interaction,
185 CellularReactions,
186}
187
188fn element_to_aspect(element: syn::Expr) -> syn::Result<Aspect> {
189 match element.clone() {
190 syn::Expr::Path(path) => {
191 let ident: syn::Ident = path.path.segments.into_iter().next().unwrap().ident;
192 if ident == "Mechanics" {
193 Ok(Aspect::Mechanics)
194 } else if ident.to_string().to_lowercase() == "cycle" {
195 Ok(Aspect::Cycle)
196 } else if ident.to_string().to_lowercase() == "interaction" {
197 Ok(Aspect::Interaction)
198 } else if ident.to_string().to_lowercase() == "cellularreactions" {
199 Ok(Aspect::CellularReactions)
200 } else {
201 Err(syn::Error::new(
202 element.span(),
203 format!("Expected one of [Mechanics, Cycle, Interaction, CellularReactions]"),
204 ))
205 }
206 }
207 _ => Err(syn::Error::new(element.span(), "Expected expression here.")),
208 }
209}
210
211struct SimulationInformation {
212 setup: syn::Ident,
213 settings: syn::Ident,
214 aspects: Vec<Aspect>,
215}
216
217impl syn::parse::Parse for SimulationInformation {
218 fn parse(input: ParseStream) -> syn::parse::Result<Self> {
219 let setup: syn::Ident = input.parse()?;
220 let _: syn::token::Comma = input.parse()?;
221 let settings: syn::Ident = input.parse()?;
222 let _: syn::token::Comma = input.parse()?;
223 let aspects: syn::ExprArray = input.parse()?;
224
225 let aspects = aspects
226 .elems
227 .into_iter()
228 .map(element_to_aspect)
229 .collect::<syn::Result<Vec<_>>>()?;
230
231 Ok(Self {
232 setup,
233 settings,
234 aspects,
235 })
236 }
237}
238
239///
240#[proc_macro]
241pub fn run_full_simulation(input: TokenStream) -> TokenStream {
242 // let mut tokens = parse_non_delimiter_tokens(input).into_iter();
243 // let setup = tokens.next().unwrap();
244 // let settings = tokens.next().unwrap();
245 // let aspects = tokens.next().unwrap();
246
247 let SimulationInformation {
248 setup,
249 settings,
250 aspects,
251 } = parse_macro_input!(input as SimulationInformation);
252
253 TokenStream::from(quote!({
254 // TODO construct the aux storage from the simulation aspects
255 struct AuxStorage {}
256 1_u8
257 }))
258}*/