bunsen/contracts/macros.rs
1//! Support macros.
2
3pub use crate::__shape_contract as shape_contract;
4
5#[doc(hidden)]
6#[macro_export]
7macro_rules! __shape_contract {
8 ($($t:tt)*) => {
9 {
10 extern crate alloc;
11 #[allow(unused_imports)]
12 use alloc::boxed::Box;
13 #[allow(unused_imports)]
14 use $crate::contracts::{ShapeContract, DimExpr, DimMatcher};
15 $crate::__proc_shape_contract!($($t)*)
16 }};
17}
18
19/// A macro to run a block of code or an expression every nth time it is called.
20///
21/// Runs the first 10 times, then doubles the period on each subsequent call,
22/// until it reaches the specified period, after which it continues to run at
23/// that period.
24///
25/// This macro is useful for scenarios where you want to limit the execution
26/// of code to every nth call, such as logging, sampling, or throttling
27/// operations.
28///
29/// ## Arguments:
30///
31/// - `$period`: [optional; default=1000] the period.
32/// - `$code`: An expression to be executed every nth time.
33///
34/// # Usage:
35/// ```rust.no_run
36/// use bunsen::contracts::run_periodically;
37///
38/// // Run a block of code every 3rd call
39/// run_periodically!(3, {
40/// println!("This will run every 3rd time.");
41/// // Your code here
42/// });
43pub use crate::__run_periodically as run_periodically;
44
45#[doc(hidden)]
46#[macro_export]
47macro_rules! __run_periodically {
48 ($code:expr) => {
49 $crate::__run_periodically!(@internal 1000, $code)
50 };
51
52 ($lock:block) => {
53 $crate::__run_periodically!(@internal 1000, $block)
54 };
55
56 ($period:literal, $code:expr) => {
57 $crate::__run_periodically!(@internal $period, $code)
58 };
59
60 ($period:literal, $lock:block) => {
61 $crate::__run_periodically!(@internal $period, $block)
62 };
63
64 (@internal $period:literal, $($tt:tt)*) => {{
65 if {
66 use core::sync::atomic::AtomicUsize;
67 use core::sync::atomic::Ordering;
68
69 static PERIOD: AtomicUsize = AtomicUsize::new(1);
70 static COUNTER: AtomicUsize = AtomicUsize::new(0);
71
72 let effective_period = PERIOD.load(Ordering::Relaxed);
73 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
74
75 if effective_period == 1 && count < 10 {
76 true
77
78 } else if (count % effective_period) == 0 {
79 // Double the period, but do not exceed the specified maximum period.
80 if effective_period < $period {
81 PERIOD.store(
82 (2 * effective_period).clamp(1, $period),
83 Ordering::Relaxed,
84 );
85 }
86 // Reset the counter when we alter the period;
87 // or periodically reset it to avoid overflow.
88 if effective_period < $period || count > $period * 100 {
89 COUNTER.store(1, Ordering::Relaxed);
90 }
91 true
92
93 } else {
94 false
95 }
96 } {
97 $($tt)*
98 }
99 }};
100}
101
102/// Define a static [`ShapeContract`](`crate::contracts::ShapeContract`).
103///
104/// See [`shape_contract`](`crate::contracts::shape_contract`) for documentation
105/// on the contract syntax.
106///
107/// ```rust,no_run
108/// use bunsen::contracts::define_shape_contract;
109///
110/// define_shape_contract!(
111/// CONTRACT,
112/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"]);
113/// ```
114pub use crate::__define_shape_contract as define_shape_contract;
115
116#[macro_export]
117#[doc(hidden)]
118macro_rules! __define_shape_contract {
119 ($name:ident, [ $($contract_expr:tt)* ] $(,)?) => {
120 static $name: $crate::contracts::ShapeContract<'static> = $crate::contracts::shape_contract![$($contract_expr)*];
121 };
122}
123
124/// Define and call
125/// [`ShapeContract::assert_shape`](`crate::contracts::ShapeContract::assert_shape`) on a static shape
126/// contract.
127///
128/// See [`crate::contracts::shape_contract`] for documentation
129/// on the contract syntax.
130/// See [`crate::contracts::ShapeContract::assert_shape`] for documentation on
131/// the assertion api.
132///
133/// ### With a Contract Expression:
134///
135/// ```rust,no_run
136/// use bunsen::contracts::assert_shape_contract;
137///
138/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
139///
140/// assert_shape_contract!(
141/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"],
142/// &shape,
143/// &[("ws", 2)],
144/// );
145/// ```
146/// ### With a pre-defined contract:
147///
148/// ```rust,no_run
149/// use bunsen::contracts::{assert_shape_contract, define_shape_contract};
150///
151/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
152///
153/// define_shape_contract!(
154/// CONTRACT,
155/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"]);
156///
157/// assert_shape_contract!(CONTRACT, &shape, &[("ws", 2)]);
158/// ```
159pub use crate::__assert_shape_contract as assert_shape_contract;
160
161#[doc(hidden)]
162#[macro_export]
163macro_rules! __assert_shape_contract {
164 ([ $($contract_expr:tt)* ], $($args:tt)*) => {{
165 $crate::__define_shape_contract!(CONTRACT, [ $($contract_expr)* ]);
166 $crate::__assert_shape_contract!(CONTRACT, $($args)*)
167 }};
168
169 ($name:ident, $shape:expr, $bindings:expr $(,)?) => {
170 $name.assert_shape($shape, $bindings)
171 };
172}
173
174/// A macro which periodically calls
175/// [`assert_shape_contract`](`crate::contracts::assert_shape_contract`).
176///
177/// See [`crate::contracts::shape_contract`] for documentation
178/// on the contract syntax.
179/// See [`crate::contracts::ShapeContract::assert_shape`] for documentation on
180/// the assertion api. See [`crate::contracts::run_periodically`] for
181/// documentation on the periodic runner.
182///
183/// ### With a Contract Expression:
184///
185/// ```rust,no_run
186/// use bunsen::contracts::assert_shape_contract_periodically;
187///
188/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
189///
190/// assert_shape_contract_periodically!(
191/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"],
192/// &shape,
193/// &[("ws", 2)],
194/// );
195/// ```
196/// ### With a pre-defined contract:
197///
198/// ```rust,no_run
199/// use bunsen::contracts::{assert_shape_contract_periodically, define_shape_contract};
200///
201/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
202///
203/// define_shape_contract!(
204/// CONTRACT,
205/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"]);
206///
207/// assert_shape_contract_periodically!(CONTRACT, &shape, &[("ws", 2)]);
208/// ```
209pub use crate::__assert_shape_contract_periodically as assert_shape_contract_periodically;
210
211#[doc(hidden)]
212#[macro_export]
213macro_rules! __assert_shape_contract_periodically {
214 ($($args:tt)*) => {
215 $crate::__run_periodically!($crate::__assert_shape_contract!($($args)*))
216 };
217}
218
219/// Define and call
220/// [`ShapeContract::unpack_shape`](`crate::contracts::ShapeContract::unpack_shape`)
221/// on a static shape contract.
222///
223/// See [`crate::contracts::shape_contract`] for documentation on the contract
224/// syntax. See [`crate::contracts::ShapeContract::unpack_shape`] for
225/// documentation on the unpack api.
226///
227/// ### With a Contract Expression:
228///
229/// ```rust,no_run
230/// use bunsen::contracts::unpack_shape_contract;
231///
232/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
233///
234/// let [h, h_win, w, w_win, c] = unpack_shape_contract!(
235/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"],
236/// &shape,
237/// &["h", "h_win", "w", "w_win", "c"],
238/// &[("ws", 2)],
239/// );
240/// assert_eq!(h, 8);
241/// assert_eq!(h_win, 4);
242/// assert_eq!(w, 10);
243/// assert_eq!(w_win, 5);
244/// assert_eq!(c, 3);
245/// ```
246///
247/// ### With a pre-defined contract:
248///
249/// ```rust,no_run
250/// use bunsen::contracts::{define_shape_contract, unpack_shape_contract};
251///
252/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
253///
254/// define_shape_contract!(
255/// CONTRACT,
256/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"]);
257///
258/// let [h, h_win, w, w_win, c] = unpack_shape_contract!(
259/// CONTRACT,
260/// &shape,
261/// &["h", "h_win", "w", "w_win", "c"],
262/// &[("ws", 2)],
263/// );
264/// assert_eq!(h, 8);
265/// assert_eq!(h_win, 4);
266/// assert_eq!(w, 10);
267/// assert_eq!(w_win, 5);
268/// assert_eq!(c, 3);
269/// ```
270/// ### Special: When you have no bindings:
271///
272/// This also works with pre-defined contracts.
273///
274/// ```rust,no_run
275/// use bunsen::contracts::unpack_shape_contract;
276///
277/// let shape = [1, 2, 3, 4 * 2, 5 * 2, 3];
278///
279/// let [h, h_win, w, w_win, ws, c] = unpack_shape_contract!(
280/// [..., "h" = "h_win" * "ws", "w" = "w_win" * "ws", "c"],
281/// &shape,
282/// &["h", "h_win", "w", "w_win", "ws", "c"],
283/// );
284/// assert_eq!(ws, 2);
285/// assert_eq!(h, 8);
286/// assert_eq!(h_win, 4);
287/// assert_eq!(w, 10);
288/// assert_eq!(w_win, 5);
289/// assert_eq!(c, 3);
290/// ```
291///
292/// ### Special: When the keys are the expression:
293///
294/// ```rust,no_run
295/// use bunsen::contracts::unpack_shape_contract;
296///
297/// let shape = [4, 5, 3];
298///
299/// let [h, w, c] = unpack_shape_contract!(["h", "w", "c"], &shape);
300/// assert_eq!(h, 4);
301/// assert_eq!(w, 5);
302/// assert_eq!(c, 3);
303/// ```
304pub use crate::__unpack_shape_contract as unpack_shape_contract;
305
306#[doc(hidden)]
307#[macro_export]
308macro_rules! __unpack_shape_contract {
309 ([ $($keys:literal),* $(,)? ], $shape:expr $(,)?) => {{
310 $crate::__define_shape_contract!(CONTRACT, [ $($keys),* ]);
311 $crate::__unpack_shape_contract!(CONTRACT, $shape, &[ $($keys),* ], &[])
312 }};
313
314 ([ $($contract_expr:tt)* ], $($args:tt)*) => {{
315 $crate::__define_shape_contract!(CONTRACT, [ $($contract_expr)* ]);
316 $crate::__unpack_shape_contract!(CONTRACT, $($args)*)
317 }};
318
319 ($contract:ident, $shape:expr, $keys:expr, $bindings:expr $(,)?) => {{
320 $contract.unpack_shape($shape, $keys, $bindings)
321 }};
322
323 ($contract:ident, $shape:expr, $keys:expr $(,)?) => {{
324 $crate::__unpack_shape_contract!($contract, $shape, $keys, &[])
325 }};
326}
327
328#[cfg(test)]
329mod tests {
330 use alloc::{
331 vec,
332 vec::Vec,
333 };
334
335 use super::*;
336
337 #[test]
338 fn test_run_periodically() {
339 let expected = vec![
340 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264, 520, 1032, 2032,
341 ];
342
343 // Block.
344 {
345 let mut results = Vec::new();
346 for i in 0..2500 {
347 run_periodically!({
348 results.push(i);
349 });
350 }
351 assert_eq!(&results, &expected);
352 }
353
354 // Expression.
355 {
356 let mut results = Vec::new();
357 for i in 0..2500 {
358 run_periodically!(results.push(i));
359 }
360 assert_eq!(&results, &expected);
361 }
362 }
363}