playdate_sys/sys/
macros.rs

1//! Helper macros for API access.
2
3
4#[macro_export]
5/// Print line to stdout, simulator's console or device's output channel.
6///
7/// Woks like [`std::println!`](https://doc.rust-lang.org/std/macro.println.html).
8macro_rules! println {
9	() => {{
10		$crate::log::println("")
11	}};
12
13	($($arg:tt)*) => {{
14		$crate::log::println($crate::alloc::format!("{}", format_args!($($arg)*)));
15	}};
16}
17
18
19#[macro_export]
20/// Get ref to struct of fn from API.
21///
22/// Panics if meets null-ptr or `None`, unwrap it with `expect(ctx)`.
23macro_rules! api {
24	(/$($path:ident).*) => {
25		core::concat!("api", $(core::concat!(".", core::stringify!($path))),*)
26	};
27
28	($($path:ident).*) => {{
29		unsafe {
30			$crate::api().expect("api")
31				$( .$path.as_ref().expect(core::stringify!($path)) )*
32		}
33	}};
34}
35
36#[macro_export]
37/// Try get ref to struct of fn from API, returns `Option`.
38macro_rules! api_opt {
39	($($path:ident).*) => {{
40		unsafe {
41			#![allow(unused_unsafe)]
42			$crate::api()
43				$( ?.$path.as_ref() )*
44		}
45	}};
46}
47
48#[macro_export]
49/// Try get ref to struct of fn from API,
50/// returns `Result` with [`crate::error::NullPtrError`].
51macro_rules! api_ok {
52	($($path:ident).*) => {{
53		unsafe {
54			#![allow(unused_unsafe)]
55			#![allow(unused_imports)]
56			use $crate::error::OkOrNullErr as _;
57			use $crate::error::OkOrNullFnErr as _;
58
59			$crate::api().ok_or_null()
60				$( ?.$path.ok_or_null() )*
61		}
62	}};
63}
64
65#[cfg(feature = "error-ctx")]
66#[macro_export]
67/// Try get ref to struct of fn from API,
68/// returns `Result` with [`crate::error::ctx::NullPtrError`].
69macro_rules! api_ok_ctx {
70	($($path:ident).*) => {{
71		unsafe {
72			#![allow(unused_unsafe)]
73			#![allow(unused_imports)]
74			use $crate::error::ctx::OkOrNullCtx as _;
75			use $crate::error::ctx::OkOrNullFnCtxErr as _;
76
77			// core::concat!("api", $(core::concat!(".", core::stringify!($path))),*)
78			$crate::api().ok_or_null_ctx("api")
79				$( ?.$path.ok_or_null_ctx( core::stringify!($path) ) )*
80		}
81	}};
82}
83
84
85#[macro_export]
86/// Get raw ptr to struct or fn of API,
87/// __using unsafe pointer dereferencing.__
88///
89/// Call with trailing `!` to unwrap the `Option<fn>`.
90///
91/// SEGFAULT or SIGSEGV if meets null-ptr or panics if function is `None`.
92macro_rules! api_unchecked {
93	(/$($path:ident).*) => {
94		core::concat!("api", $(core::concat!(".", core::stringify!($path))),*)
95	};
96
97	($($path:ident).*!) => {{
98		api_unchecked!($($path).*) .expect(api_unchecked!(/$($path).*))
99	}};
100
101	($($path:ident).* $(!)?) => {{
102		$crate::API $( .read().$path )*
103	}};
104}
105
106
107#[cfg(test)]
108mod tests {
109	use core::ptr::null_mut;
110
111
112	// This is also test. It must not run because we don't want SIGSEGV, it's just must build.
113	#[allow(dead_code)]
114	fn api_unchecked() {
115		unsafe {
116			let _ = api_unchecked!(file);
117			let p = api_unchecked!(sound.channel);
118			(*p).newChannel.unwrap()();
119
120			let f = api_unchecked!(file.read!);
121			f(null_mut(), null_mut(), 0);
122
123			let f = api_unchecked!(file.read).unwrap();
124			f(null_mut(), null_mut(), 0);
125
126			let f = api_unchecked!(sound.channel.newChannel).unwrap();
127			f();
128		}
129	}
130
131	#[test]
132	#[should_panic(expected = "api")]
133	fn api() {
134		let f = api!(file.read);
135		unsafe { f(null_mut(), null_mut(), 0) };
136
137		let f = api!(sound.channel.newChannel);
138		unsafe { f() };
139	}
140
141
142	#[test]
143	fn try_api_opt() {
144		fn test() -> Option<()> {
145			unsafe {
146				api_opt!(file.read)?(null_mut(), null_mut(), 0);
147				api_opt!(sound.channel.newChannel)?();
148			}
149			Some(())
150		}
151
152		assert!(test().is_none());
153	}
154
155	#[test]
156	fn try_api_ok() {
157		fn test() -> Result<(), crate::error::NullPtrError> {
158			unsafe {
159				api_ok!(file.read)?(null_mut(), null_mut(), 0);
160				api_ok!(sound.channel.newChannel)?();
161			}
162			Ok(())
163		}
164
165		let res = test();
166		assert!(res.is_err());
167	}
168
169	#[test]
170	#[cfg(feature = "error-ctx")]
171	fn try_api_ok_ctx() {
172		fn test() -> Result<(), crate::error::ctx::NullPtrError> {
173			unsafe {
174				api_ok_ctx!(file.read)?(null_mut(), null_mut(), 0);
175				api_ok_ctx!(sound.channel.newChannel)?();
176			}
177
178			Ok(())
179		}
180		let res = test();
181		assert!(res.is_err());
182		assert_eq!("api", res.unwrap_err().ctx);
183	}
184}