Commit e5f9df1a authored by FloorIsJava's avatar FloorIsJava
Browse files

add statemachine_new

parent d175f950
0.1.0, 2020-09-06
0.1.1, 2020-09-09 (UNRELEASED)
More documentation
Allow unit structs for statemachines
Add statemachine_new! macro
Lots and lots of tests
0.1.0, 2020-09-09
Initial publish.
\ No newline at end of file
......@@ -13,11 +13,10 @@ statemachine-macro = "0.1"
## Getting Started
```rs
use statemachine_macro::statemachine;
use statemachine_macro::*;
statemachine! {
#[derive(Default)]
struct Foo {}
struct Foo;
enum FooState consumes [char] from Start accepts [NonEmpty];
......@@ -31,7 +30,7 @@ statemachine! {
}
fn main() {
let mut foo: Foo = Default::default();
let mut foo: Foo = statemachine_new!(Foo{});
assert!(!foo.is_accepting());
foo.consume('a');
assert!(foo.is_accepting());
......
......@@ -6,8 +6,9 @@
//! use statemachine_macro::*;
//!
//! statemachine! {
//! #[derive(Default)]
//! pub struct Foo {
//! allow_x: bool
//! pub allow_x: bool
//! }
//!
//! enum FooState consumes [char, i32] from Start accepts [Done];
......@@ -58,16 +59,259 @@
//! _ => Error
//! }
//! }
//!
//! let mut foo: Foo = Default::default();
//! foo.consume('a');
//! assert!(foo.is_accepting());
//! assert!(!foo.allow_x);
//! foo.reset(FooState::Start);
//!
//! foo.consume('b');
//! assert!(!foo.is_accepting());
//! assert!(foo.allow_x);
//! foo.consume('x');
//! assert!(foo.is_accepting());
//! ```
mod model;
mod util;
use proc_macro;
use syn::parse_macro_input;
use quote::ToTokens;
use syn::{parse_macro_input, parse_quote};
/// Creates a state machine.
///
/// # The Statemachine Struct
///
/// The statemachine struct that is generated (the first item in the macro body) has methods with the following signatures.
///
/// ```
/// use statemachine_macro::*;
///
/// statemachine! {
/// struct Foo;
/// enum FooState consumes [char] from Start;
/// }
///
/// /*impl Foo {
/// /// Changes the statemachine to the given state.
/// fn reset(&mut self, state: FooState) { ... }
///
/// /// Returns true if the statemachine is in an accepting state.
/// fn is_accepting(&self) -> bool { ... }
///
/// /// Performs a transition for the given input symbol.
/// fn consume<T: ...>(&mut self, val: T) { ... }
/// }*/
/// ```
///
/// These methods are currently not provided as a trait implementation. This may change in a future major version.
///
/// # Examples
///
/// Basic consuming:
/// ```
/// use statemachine_macro::*;
///
/// statemachine! {
/// pub struct Foo;
///
/// enum FooState consumes [char] from Even accepts [Odd];
///
/// Even => {
/// _ => Odd
/// },
///
/// Odd => {
/// _ => Even
/// }
/// }
///
/// let mut foo = statemachine_new!(Foo{});
/// assert!(!foo.is_accepting());
/// foo.consume(' ');
/// assert!(foo.is_accepting());
/// foo.consume(' ');
/// assert!(!foo.is_accepting());
/// foo.consume(' ');
/// assert!(foo.is_accepting());
/// ```
///
/// Resetting the state machine:
/// ```
/// use statemachine_macro::*;
///
/// #[derive(Debug)]
/// struct Money;
///
/// statemachine! {
/// pub struct Foo;
///
/// enum FooState consumes [Money] from Unpaid accepts [Paid];
/// }
///
/// let mut foo = statemachine_new!(Foo{});
/// assert!(!foo.is_accepting());
/// foo.reset(FooState::Paid); // mwahahaha free real estate
/// assert!(foo.is_accepting());
/// ```
///
/// Advanced consuming with multiple types:
/// ```
/// use statemachine_macro::*;
///
/// statemachine! {
/// pub struct Foo {
/// cheater: bool
/// }
///
/// enum FooState consumes [u32, i32] from Even accepts [Odd];
///
/// Even => {
/// u32 match x => {
/// if x % 2 == 0 {
/// Even
/// } else {
/// Odd
/// }
/// },
/// i32 match v => if *v < 0 {
/// self.cheater = true;
/// Even
/// },
/// i32 match x => panic!("Hey! Are you trying to cheat?")
/// },
///
/// Odd => {
/// u32 match x => {
/// if x % 2 == 0 {
/// Even
/// } else {
/// Odd
/// }
/// },
/// i32 match v => if *v < 0 {
/// self.cheater = true;
/// Odd
/// },
/// i32 match x => panic!("Hey! Are you trying to cheat?")
/// }
/// }
///
/// let mut foo = statemachine_new!(Foo{ cheater: false });
/// assert!(!foo.cheater);
/// assert!(!foo.is_accepting());
/// foo.consume(5u32);
/// assert!(!foo.cheater);
/// assert!(foo.is_accepting());
/// foo.consume(4u32);
/// assert!(!foo.cheater);
/// assert!(!foo.is_accepting());
/// foo.consume(4u32);
/// assert!(!foo.cheater);
/// assert!(!foo.is_accepting());
/// foo.consume(-3i32);
/// assert!(foo.cheater);
/// assert!(!foo.is_accepting());
/// ```
///
/// # Syntax
///
/// The following is the syntax of the macro contents. The starting nonterminal is 'statemachine'.
///
/// ```txt
/// statemachine ::= struct-item state-description ( state-behaviors )?
///
/// state-description ::=
/// "enum" ident "consumes" "[" type ( "," type )* "]"
/// ( "accepts" "[" ident ( "," ident )* "]" )? ";"
///
/// state-behaviors ::= state-behavior ( "," state-behavior )*
///
/// state-behavior ::= ident "=>" "{" ( state-transitions )? "}"
///
/// state-transitions ::= state-transition ( "," state-transition )*
///
/// state-transition ::= transition-trigger | transition-pattern | transition-catchall
///
/// transition-pattern ::= type "match" pattern "=>" ( transition-guard )? expr
///
/// transition-guard ::= "if" expr
///
/// transition-catchall ::= "_" "=>" expr
///
/// transition-trigger ::= "@" ( enter-trigger | leave-trigger | loop-trigger )
///
/// enter-trigger ::= "enter" "=>" expr
/// leave-trigger ::= "leave" "=>" expr
/// loop-trigger ::= "loop" "=>" expr
/// ```
#[proc_macro]
pub fn statemachine(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let statemachine = parse_macro_input!(input as model::StateMachine);
statemachine.to_stream().into()
}
/// Creates a statemachine.
///
/// Because statemachines do some magic processing to the underlying struct, regular struct literals do not work. By wrapping your struct literals in `statemachine_new!`, you can circumvent this restriction.
///
/// Note that you can alternatively derive `Default` for your statemachine struct.
///
/// # Examples
///
/// Without `statemachine_new!`:
/// ```compile_fail
/// use statemachine_macro::*;
///
/// statemachine! {
/// struct Foo {
/// bar: i32
/// }
///
/// enum FooState consumes [char] from Start;
/// }
///
/// let _foo = Foo { bar: 3 };
/// ```
///
/// With `statemachine_new!`:
/// ```
/// use statemachine_macro::*;
///
/// statemachine! {
/// struct Foo {
/// bar: i32
/// }
///
/// enum FooState consumes [char] from Start;
/// }
///
/// let _foo = statemachine_new!(Foo { bar: 3 });
/// ```
///
/// By deriving `Default`:
/// ```
/// use statemachine_macro::*;
///
/// statemachine! {
/// #[derive(Default)]
/// struct Foo {
/// bar: i32
/// }
///
/// enum FooState consumes [char] from Start;
/// }
///
/// let _foo: Foo = Foo { bar: 3, ..Default::default() };
/// let _baz: Foo = Default::default();
/// ```
#[proc_macro]
pub fn statemachine_new(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut expr: syn::ExprStruct = syn::parse(input).expect("Note: Did you mean to use Foo{} for a statemachine Foo with no fields?");
expr.fields.push(parse_quote! {
_statemachine_state: Default::default()
});
expr.into_token_stream().into()
}
\ No newline at end of file
......@@ -45,7 +45,7 @@ impl Transition {
if let Some(i) = idx {
syn::Ident::new(&format!("V{}", i), proc_macro2::Span::call_site())
} else {
panic!("No such type ident!");
panic!("No such type in symbol list!");
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment