Skip to main content

orengine_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::parse_macro_input;
6
7/// Generates code for [`Future::poll`](std::future::Future::poll).
8///
9/// # Must have above
10///
11/// * `this` with `io_request_data` (`Option<IoRequestData>`) fields;
12///
13/// * `cx` with `waker` method that returns ([`Waker`](std::task::Waker)) which contains
14/// `*const orengine::runtime::Task` in [`data`](std::task::Waker::data);
15///
16/// * declared variable `ret` (`usize`) which can be used in `ret_statement`.
17///
18/// # Arguments
19///
20/// * `do_request` - the request code
21/// * `ret_statement` - the return statement which can use `ret`(`usize`) variable.
22#[proc_macro]
23pub fn poll_for_io_request(input: TokenStream) -> TokenStream {
24    let input_elems = parse_macro_input!(input as syn::ExprTuple).elems;
25
26    let do_request = &input_elems[0];
27    let ret_statement = &input_elems[1];
28
29    let expanded = quote! {
30        if let Some(mut io_request_data) = this.io_request_data.take() {
31            match io_request_data.ret() {
32                Ok(io_request_data_ret) => {
33                    ret = io_request_data_ret;
34                    return Poll::Ready(Ok(#ret_statement));
35                }
36                Err(err) => {
37                    unsafe {
38                        return Poll::Ready(Err(err));
39                    }
40                }
41            }
42        }
43
44        let task = unsafe { orengine::get_task_from_context!(cx) };
45        this.io_request_data = Some(IoRequestData::new(task));
46
47        #do_request;
48
49        return Poll::Pending;
50    };
51
52    TokenStream::from(expanded)
53}
54
55/// Generates code for [`Future::poll`](std::future::Future::poll).
56///
57/// # The difference between `poll_for_io_request` and `poll_for_time_bounded_io_request`
58///
59/// `poll_for_time_bounded_io_request` deregisters the time bounded task after execution.
60///
61/// # Must have above
62///
63/// * `this` with `io_request_data` (`Option<IoRequestData>`) and `deadline`
64/// ([`Instant`](std::time::Instant)) fields;
65///
66/// * `cx` with `waker` method that returns ([`Waker`](std::task::Waker)) which contains
67/// `*const orengine::runtime::Task` in [`data`](std::task::Waker::data);
68///
69/// * declared variable `ret` (`usize`) which can be used in `ret_statement`;
70///
71/// * `worker` from `local_worker()`.
72///
73/// # Arguments
74///
75/// * `do_request` - the request code
76/// * `ret_statement` - the return statement which can use `ret`(`usize`) variable.
77#[proc_macro]
78pub fn poll_for_time_bounded_io_request(input: TokenStream) -> TokenStream {
79    let input_elems = parse_macro_input!(input as syn::ExprTuple).elems;
80
81    let do_request = &input_elems[0];
82    let ret_statement = &input_elems[1];
83
84    let expanded = quote! {
85        if let Some(mut io_request_data) = this.io_request_data.take() {
86            match io_request_data.ret() {
87                Ok(io_request_data_ret) => {
88                    ret = io_request_data_ret;
89                    worker.deregister_time_bounded_io_task(&this.deadline);
90
91                    return Poll::Ready(Ok(#ret_statement));
92                }
93                Err(err) => {
94                    if err.kind() != std::io::ErrorKind::TimedOut {
95                        worker.deregister_time_bounded_io_task(&this.deadline);
96                    }
97
98                    return Poll::Ready(Err(err));
99                }
100            }
101        }
102
103        let task = unsafe { orengine::get_task_from_context!(cx) };
104        this.io_request_data = Some(IoRequestData::new(task));
105
106        #do_request;
107
108        return Poll::Pending;
109    };
110
111    TokenStream::from(expanded)
112}
113
114/// Generates a test function with provided locality.
115fn generate_test(input: TokenStream, is_local: bool) -> TokenStream {
116    let input = parse_macro_input!(input as syn::ItemFn);
117    let body = &input.block;
118    let attrs = &input.attrs;
119    let signature = &input.sig;
120    let name = &signature.ident;
121    let name_str = name.to_string();
122    if signature.inputs.len() > 0 {
123        panic!("Test function must have zero arguments!");
124    }
125    let spawn_fn = if is_local {
126        quote! { orengine::test::run_test_and_block_on_local }
127    } else {
128        quote! { orengine::test::run_test_and_block_on_shared }
129    };
130
131    let expanded = quote! {
132        #[test]
133        #(#attrs)*
134        fn #name() {
135            println!("Test {} started!", #name_str.to_string());
136            #spawn_fn(async {
137                #body
138            });
139            println!("Test {} finished!", #name_str.to_string());
140        }
141    };
142
143    TokenStream::from(expanded)
144}
145
146/// Generates a test function with running an `Executor` with `local` task.
147///
148/// # The difference between `test_local` and [`test_shared`]
149///
150/// `test_local` generates a test function that runs an `Executor` with `local` task.
151/// [`test_shared`] generates a test function that runs an `Executor` with `shared` task.
152///
153/// # Example
154///
155/// ```ignore
156/// #[orengine_macros::test_local]
157/// fn test_sleep() {
158///     let start = std::time::Instant::now();
159///     orengine::sleep(std::time::Duration::from_secs(1)).await;
160///     assert!(start.elapsed() >= std::time::Duration::from_secs(1));
161/// }
162/// ```
163///
164/// # Note
165///
166/// Code above is equal to:
167///
168/// ```ignore
169/// #[test]
170/// fn test_sleep() {
171///     println!("Test sleep started!");
172///     orengine::test::run_test_and_block_on_local(async {
173///         let start = std::time::Instant::now();
174///         orengine::sleep(std::time::Duration::from_secs(1)).await;
175///         assert!(start.elapsed() >= std::time::Duration::from_secs(1));
176///     });
177///     println!("Test sleep finished!");
178/// }
179/// ```
180#[proc_macro_attribute]
181pub fn test_local(_: TokenStream, input: TokenStream) -> TokenStream {
182    generate_test(input, true)
183}
184
185/// Generates a test function with running an `Executor` with `local` task.
186///
187/// # The difference between `test_shared` and [`test_local`]
188///
189/// [`test_shared`] generates a test function that runs an `Executor` with `shared` task.
190/// `test_local` generates a test function that runs an `Executor` with `local` task.
191///
192/// # Example
193///
194/// ```ignore
195/// #[orengine_macros::test_shared]
196/// fn test_sleep() {
197///     let start = std::time::Instant::now();
198///     orengine::sleep(std::time::Duration::from_secs(1)).await;
199///     assert!(start.elapsed() >= std::time::Duration::from_secs(1));
200/// }
201/// ```
202///
203/// # Note
204///
205/// Code above is equal to:
206///
207/// ```ignore
208/// #[test]
209/// fn test_sleep() {
210///     println!("Test sleep started!");
211///     orengine::test::run_test_and_block_on_shared(async {
212///         let start = std::time::Instant::now();
213///         orengine::sleep(std::time::Duration::from_secs(1)).await;
214///         assert!(start.elapsed() >= std::time::Duration::from_secs(1));
215///     });
216///     println!("Test sleep finished!");
217/// }
218/// ```
219#[proc_macro_attribute]
220pub fn test_shared(_: TokenStream, input: TokenStream) -> TokenStream {
221    generate_test(input, false)
222}