1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
//! gentian is a proc macro that transforms generators to state machines.
//! Currently it supports loop statements, while statements, if statements, and the extended syntax for using `co_yield` and `co_return` and `return` in these statements.
//! # `gentian_attr` attribute of a function
//! It has two kinds of parameters,
//! * `state` represents the state currently used to maintain the automaton.
//! * `ret_val` represents the default return value of the function, which is usually used for the result returned by calling again after the state machine ends.
//!
//! # `co_yield` or `co_return` statement
//! This divides into three logical steps:
//! * `co_yield` or `co_return` save the current state of the coroutine.
//! * The resume point is defined immediately following the statement.
//! * Same as rust `return` semantics, it returns from the function immediately.
//!
//! # `co_await` statement
//! It's a syntax sugar for `co_yield` or `co_return` with `std::task::Poll`.
//! ````ignore
//! co_await(some_poll_func());
//! ````
//! is equivalent to
//! ````ignore
//! loop{
//! let tmp=some_poll_func();
//! if tmp.is_ready(){
//! break;
//! }
//! co_yield(Poll::Pending);
//! }
//! ````
//! This divides into two logical steps:
//! * `co_await` save the current state of the coroutine.
//! * The resume point is defined immediately following the statement and if and only if the waited poll function is ready.
//!
//! # `return` statement
//! This type of statement divides into two logical steps:
//! * `return` sets the coroutine state to indicate termination.
//! * Same as rust `return` semantics, it returns from the function immediately.
//! * When the function is called again, it returns the default return value (`ret_val`), or does nothing which means the function has no return value.
//!
//! # Example
//! The following code demonstrates the use of generators with and without a return value.
//! ````rust
//! use gentian::gentian;
//!
//! #[cfg(test)]
//! struct MyGenerator {
//! my_state_1: usize,
//! pub my_state_2: usize,
//! pub num: u32,
//! pub num1: u32,
//! }
//!
//! #[cfg(test)]
//! impl MyGenerator {
//! pub fn new() -> MyGenerator {
//! MyGenerator {
//! my_state_1: 0,
//! my_state_2: 0,
//! num: 0,
//! num1: 0,
//! }
//! }
//!
//! #[gentian]
//! #[gentian_attr(state=self.my_state_1)]
//! pub fn test_simple(&mut self) {
//! loop {
//! println!("Hello, ");
//! //co_yield;
//! while self.num1 < 99 {
//! println!("Generator{}", self.num1);
//! self.num1 += 1;
//! co_yield;
//! }
//! return;
//! }
//! }
//!
//! // state_name , return_default_value
//! #[gentian]
//! #[gentian_attr(state=self.my_state_2,ret_val=0u32)]
//! pub fn get_odd(&mut self) -> u32 {
//! loop {
//! if self.num % 2 == 1 {
//! co_yield(self.num);
//! }
//! self.num += 1;
//! }
//! }
//! }
//!
//! #[test]
//! fn test_generator_proc_macro() {
//! let mut gen = MyGenerator::new();
//! gen.test_simple(); // print Hello,
//! for _ in 0..200 {
//! gen.test_simple(); // only print 99 times `Generator`
//! }
//! gen.test_simple(); // print nothing
//! assert_eq!(gen.num1, 99);
//! for i in (1u32..1000).step_by(2) {
//! assert_eq!(gen.get_odd(), i);
//! }
//! }
//!
//! ````
//!
mod attr;
mod control_flow_graph;
mod generate_state_machines;
mod stmt;
mod test;
use crate::attr::GentianAttr;
use generate_state_machines::Generator;
use proc_macro::TokenStream;
use syn::parse_macro_input;
use syn::ItemFn;
#[proc_macro_attribute]
pub fn gentian(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let expanded = transform_to_state_machine(input);
TokenStream::from(expanded)
}
fn transform_to_state_machine(mut input: syn::ItemFn) -> proc_macro2::TokenStream {
let mut attrs: Option<GentianAttr> = None;
if !input.attrs.is_empty() {
if let Ok(a) = GentianAttr::try_from_attributes(&input.attrs) {
attrs = a;
}
}
let state_name: String;
let ret_val: String;
if let Some(attr) = &attrs {
state_name = attr.get_state_name();
ret_val = attr.get_ret_val();
} else {
state_name = "self.state".to_string();
ret_val = String::new();
}
input.attrs.clear();
if ret_val.is_empty() {
println!(
"[gentian] found function `{}` state name: {}.",
input.sig.ident, state_name
);
} else {
println!(
"[gentian] found function `{}` state name: {}, function default return value is: {}.",
input.sig.ident, state_name, ret_val
);
}
let mut generator = Generator::new();
let expanded = generator.gen_state_machines_tokenstream(input, &state_name, &ret_val);
return expanded;
}