pai_macros/
lib.rs

1//! Procedural macros used together with [pai](https://docs.rs/pai)
2use proc_macro::TokenStream;
3use quote::quote;
4
5struct Code;
6
7impl Code {
8	fn generate(stream: TokenStream) -> TokenStream {
9		// Names for first and second argument
10		const CTX: &str = "ctx";
11		const FRAME: &str = "frame";
12		
13		// Parse as a function
14		let input = syn::parse_macro_input!(stream as syn::ItemFn);
15		let mut insns = Vec::new();
16
17		// Extract different parts
18		let syn::ItemFn { attrs, vis, mut sig, block } = input;
19		let funcident = sig.ident;
20		let funcout = sig.output;
21
22		// The initial idents we keep in signature unchanged Need to store a
23		// reference to them so that we can refer to them when creating new
24		// variables.
25		let mut idents = Vec::new();
26
27		// let mut sep: usize = 2;
28		let inputs = std::mem::take(&mut sig.inputs);
29		let mut inputs = inputs.into_iter().collect::<Vec<syn::FnArg>>();
30
31		// This part is a bit messy, but we want to support all possible cases of
32		// including or omitting `ctx` or `frame`. In addition, the user may or may
33		// not have specified extra arguments.
34		if let Some(ctx) = inputs.first() {
35			if Self::is_ptr_to(ctx, "Secondary") {
36				// fn(&mut Context, ???)
37				let ctx = inputs.remove(0);
38				let ident = Self::get_ident(&ctx);
39				idents.push(ident);
40			} else {
41				// fn(???)
42				// Don't know what was supplied, try parsing it as CallFrame below
43				idents.push(syn::Ident::new(CTX, proc_macro2::Span::call_site()));
44			};
45			
46			if let Some(frame) = inputs.first() {
47				if Self::is_ptr_to(frame, "CallFrame") {
48					// fn(???, &CallFrame, arg...)
49					let frame = inputs.remove(0);
50					let ident = Self::get_ident(&frame);
51					idents.push(ident);
52				} else {
53					// fn(???, arg...)
54					idents.push(syn::Ident::new(FRAME, proc_macro2::Span::call_site()));
55				}
56			} else {
57				// fn(&mut Context)
58				// No arguments after Context
59				idents.push(syn::Ident::new(FRAME, proc_macro2::Span::call_site()));
60			}
61		} else {
62			// fn()
63			// No arguments is supplied at all
64			idents.push(syn::Ident::new(CTX, proc_macro2::Span::call_site()));
65			idents.push(syn::Ident::new(FRAME, proc_macro2::Span::call_site()));
66		}
67
68		// Start parsing the arguments the user wants parsed.
69		let ctx = idents.first().expect("no context object");
70		let frame = idents.get(1).expect("no frame object");
71		for (i, input) in inputs.into_iter().enumerate() {
72			let ident = Self::get_ident(&input);
73			if let syn::FnArg::Typed(syn::PatType {ty, .. }) = input {
74				let ty = *(ty).clone();
75				let ins = Self::parse_type(ty, &ident, frame, ctx, i, false);
76				insns.push(ins);
77			} else {
78				panic!("unable to parse argument with ident {ident:?}");
79			}
80		}
81
82		let stmts = &block.stmts;
83		let v = quote! {
84			#(#attrs)* #vis fn #funcident<T> (#ctx: &mut pai::ctx::Secondary<T, pai::Error>, #frame: &pai::api::CallFrame) #funcout {
85				#(#insns)*
86				#(#stmts)*
87			}
88		};
89		v.into()
90	}
91
92
93
94	fn is_ptr_to(arg: &syn::FnArg, check: &str) -> bool {
95		if let syn::FnArg::Typed(syn::PatType {ty, .. }) = arg {
96			let ty = *(ty).clone();
97			if let syn::Type::Reference(p) = ty {
98				let ty = *(p.elem).clone();
99				
100				if let syn::Type::Path(p) = ty {
101					if p.qself.is_none() && Self::path_is_matching(&p.path, check) {
102						println!("matched generic");
103						true
104					} else {
105						println!("{:?}", p.path.get_ident());
106						let ret = p.path.is_ident(check);
107						println!("match = {ret}");
108						ret
109					}
110				} else {
111					false
112				}
113			} else {
114				false
115			}
116		} else {
117			false
118		}
119	}
120
121
122	fn path_is_option(path: &syn::Path) -> bool {
123		path.leading_colon.is_none()
124			&& path.segments.len() == 1
125			&& path.segments.iter().next().unwrap().ident == "Option"
126	}
127	fn path_is_matching(path: &syn::Path, check: &str) -> bool {
128		path.leading_colon.is_none()
129			&& path.segments.len() == 1
130			&& path.segments.iter().next().unwrap().ident == check
131	}
132	
133	fn parse_type(ty: syn::Type, ident: &proc_macro2::Ident, frame: &proc_macro2::Ident, ctx: &proc_macro2::Ident, argnum: usize, inopt: bool) -> proc_macro2::TokenStream {
134		let framearg = quote! { #frame.arg(#argnum, #ctx.client_mut())? };
135		if let syn::Type::Path(p) = ty {
136			if p.path.is_ident("i64") {
137				assert!(!inopt);
138				quote! { let #ident = #framearg.as_i64(); }
139			} else if p.path.is_ident("i32") {
140				assert!(!inopt);
141				quote! { let #ident = #framearg.as_i32(); }
142			} else if p.path.is_ident("i16") {
143				assert!(!inopt);
144				quote! { let #ident = #framearg.as_i16(); }
145			} else if p.path.is_ident("i8") {
146				assert!(!inopt);
147				quote! { let #ident = #framearg.as_i8(); }
148			} else if p.path.is_ident("isize") {
149				assert!(!inopt);
150				quote! { let #ident = #framearg.as_isize(); }
151			} else if p.path.is_ident("u64") {
152				assert!(!inopt);
153				quote! { let #ident = #framearg.as_u64(); }
154			} else if p.path.is_ident("u32") {
155				assert!(!inopt);
156				quote! { let #ident = #framearg.as_u32(); }
157			} else if p.path.is_ident("u16") {
158				assert!(!inopt);
159				quote! { let #ident = #framearg.as_u16(); }
160			} else if p.path.is_ident("u8") {
161				assert!(!inopt);
162				quote! { let #ident = #framearg.as_u8(); }
163			} else if p.path.is_ident("usize") {
164				assert!(!inopt);
165				quote! { let #ident = #framearg.as_usize(); }
166			} else if p.path.is_ident("String") {
167				if !inopt {
168					quote! { let #ident = #framearg.read_ptr_as_str(#ctx.client_mut())?; }
169				} else {
170					quote! { let #ident: Option<String> = #framearg.read_ptr_as_str(#ctx.client_mut()).ok(); }
171				}
172			} else if p.qself.is_none() && Self::path_is_option(&p.path) {
173				assert!(!inopt);
174				let type_params = &p.path.segments.first().unwrap().arguments;
175				// It should have only on angle-bracketed param ("<String>"):
176				let generic_arg = match type_params {
177					syn::PathArguments::AngleBracketed(params) => params.args.first().unwrap(),
178					_ => panic!("unable to parse {ident:?}"),
179				};
180				match generic_arg {
181					syn::GenericArgument::Type(ty) => {
182						Self::parse_type(ty.clone(), ident, frame, ctx, argnum, true)
183					},
184					_ => panic!("unable to parse {ident:?}"),
185				}
186			} else {
187				panic!("custom idents not supported yet");
188				// let custom = p.path.get_ident().expect("unable to get ident for {ident:?}");
189				// if !inopt {
190				// 	quote! { let #ident = #framearg.to_struct::<#custom>(#ctx.client_mut())?; }
191				// } else {
192				// 	quote! { let #ident: Option<#custom> = #framearg.to_struct::<#custom>(#ctx.client_mut()).ok(); }
193				// }
194			}
195		} else {
196			panic!("expected Type::Path");
197		}
198	}
199	
200	fn get_ident(arg: &syn::FnArg) -> syn::Ident {
201		if let syn::FnArg::Typed(syn::PatType {ty: _, pat, .. }) = arg {
202			let pat = *(pat).clone();
203			if let syn::Pat::Ident(id) = pat {
204				return id.ident.clone();
205			}
206		}
207		panic!("unable to find ident");
208	}
209}
210
211/// Helper macro for more convenient hooking of functions. See an example of use
212/// in
213/// [hook-autogen.rs](https://github.com/rstenvi/pai/blob/main/examples/hook-autogen.rs)
214#[proc_macro_attribute]
215pub fn pai_hook(_attr: TokenStream, stream: TokenStream) -> TokenStream {
216	Code::generate(stream)
217}
218
219struct RegsCode;
220
221impl RegsCode {
222	fn generate(stream: TokenStream) -> TokenStream {
223		let input = syn::parse_macro_input!(stream as syn::DeriveInput);
224		let name = input.ident.clone();
225		let syn::Data::Struct(input) = input.data else { panic!("") };
226		let mut gets = Vec::new();
227		let mut sizes = Vec::new();
228		let mut sp: Option<(syn::Ident, syn::Type)> = None;
229		let mut pc: Option<(syn::Ident, syn::Type)> = None;
230		let mut set_sysno: Option<(syn::Ident, syn::Type)> = None;
231		let mut get_sysno: Option<syn::Ident> = None;
232		let mut fields = Vec::new();
233		for field in input.fields.iter() {
234			let ident = field.ident.as_ref().unwrap().clone();
235
236			for attr in field.attrs.iter() {
237				let p = attr.path();
238				if p.is_ident("sp") {
239					sp = Some((ident.clone(), field.ty.clone()));
240				} else if p.is_ident("pc") {
241					pc = Some((ident.clone(), field.ty.clone()));
242				} else if p.is_ident("sysno") {
243					set_sysno = Some((ident.clone(), field.ty.clone()));
244					get_sysno = Some(ident.clone());
245				} else if p.is_ident("setsysno") {
246					set_sysno = Some((ident.clone(), field.ty.clone()));
247				} else if p.is_ident("getsysno") {
248					get_sysno = Some(ident.clone());
249				}
250			}
251
252			let name = format!("{ident}");
253			fields.push(name.clone());
254			let ins = quote! { #name => Some(std::mem::offset_of!(Self, #ident)), };
255			gets.push(ins);
256			let ins = quote! { #name => Some(std::mem::size_of_val(&self.#ident)), };
257			sizes.push(ins);
258		}
259		let spcode = if let Some((sp, ty)) = sp {
260			quote! {
261				fn _get_sp(&self) -> u64 {
262					self.#sp as u64
263				}
264				fn _set_sp(&mut self, sp: u64) {
265					self.#sp = sp as #ty;
266				}
267			}
268		} else {
269			quote!()
270		};
271		let pccode = if let Some((pc, ty)) = pc {
272			quote! {
273				fn _get_pc(&self) -> u64 {
274					self.#pc as u64
275				}
276				fn _set_pc(&mut self, pc: u64) {
277					self.#pc = pc as #ty;
278				}
279			}
280		} else {
281			quote!()
282		};
283		let sysnocode = if let Some(get) = get_sysno {
284			let (set, ty) = set_sysno.unwrap();
285			quote! {
286				fn _get_sysno(&self) -> usize {
287					self.#get as usize
288				}
289				fn _set_sysno(&mut self, sysno: usize) {
290					self.#set = sysno as #ty;
291				}
292			}
293		} else {
294			quote!()
295		};
296		let res = quote! {
297			impl #name {
298				fn _offset_of(&self, regs: &str) -> Option<usize> {
299					match regs {
300						#(#gets)*
301						_ => None,
302					}
303				}
304				fn _size_of(&self, regs: &str) -> Option<usize> {
305					match regs {
306						#(#sizes)*
307						_ => None,
308					}
309				}
310				fn _fields(&self) -> &[&str] {
311					&[#(#fields),*]
312				}
313				unsafe fn _get_value(&self, offset: usize, size: usize, data: &mut Vec<u8>) {
314					let v: *const u8 = self as *const Self as *const u8;
315					let v = unsafe { v.byte_add(offset) };
316					let v = unsafe { std::slice::from_raw_parts(v, size) };
317					data.extend_from_slice(v);
318				}
319				unsafe fn _set_value(&mut self, offset: usize, data: &[u8]) {
320					let v: *mut u8 = self as *mut Self as *mut u8;
321					let v = unsafe { v.byte_add(offset) };
322					let v: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(v, data.len()) };
323					for (i, b) in v.iter_mut().enumerate() {
324						*b = data[i];
325					}
326				}
327				#spcode
328				#pccode
329				#sysnocode
330			}
331		};
332		res.into()
333	}
334
335}
336
337/// Used internally by [pai](https://docs.rs/pai) to create registers that are
338/// accessible cross-architecture.
339#[proc_macro_derive(PaiRegs, attributes(sp, pc, sysno, getsysno, setsysno))]
340pub fn derive_regs_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
341	RegsCode::generate(input)
342}