Crate macro_machine [] [src]

State machine generator

State machine consists of: Name, initial state, List of States, List of Commands, list of States Nodes. Each State Node contain: Name, State Context (optional), list of Command Reactions. Each Command Reaction contain: Command to react on, user-defined code of reaction (optional) and next State of machine (optional).

Simplest state machine example:

#[macro_use] extern crate macro_machine;

declare_machine!(
    Simple (A) // Name and initial State
    states[A,B] // list of States
    commands[Next] // list of Commands
    (A: // State Node
        Next => B; // Command Reaction. Just change state to B
    )
    (B:
        Next => A;
    )
);

use Simple::*;

let mut machine = Simple::new();
assert!(match machine.get_current_state(){States::A{..}=>true,_=>false});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){States::B{..}=>true,_=>false});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){States::A{..}=>true,_=>false});

You can add some intelligence to machine:

#[macro_use] extern crate macro_machine;

declare_machine!(
    Simple (A{counter:0}) // Name and initial State with initial value
    states[A,B] // list of States
    commands[Next] // list of Commands
    (A context{counter:i16}: // State Node and this state context description with binding name
        Next {context.counter=context.counter+1}=> B{counter:context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our x value.
    )
    (B context{counter:i16}:
        Next {context.counter=context.counter+1}=> A{counter:context.counter};
    )
);

use Simple::*;

let mut machine = Simple::new();
assert!(match machine.get_current_state(){
    States::A{context}=> if context.counter == 0 {true} else {false}, // We are in state A and have our initial value 0
    _=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){
    States::B{context}=> if context.counter == 1 {true} else {false}, // We are in state B and have counter == 1
    _=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){
    States::A{context}=> if context.counter == 2 {true} else {false}, // Again in state A and have counter == 2
    _=>false
});
#[macro_use] extern crate macro_machine;

declare_machine!(
    Simple (A{counter:0}) // Name and initial State with initial value
    states[A,B] // list of States
    commands[Next] // list of Commands
    (A context{counter:i16}: // State Node and this state context description with binding name
        >> {context.counter = context.counter+1;} // Execute when enter state A
        << {context.counter = context.counter+1;} // Execute when leave state A
        Next {context.counter=context.counter+1;} => B{counter:context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our x value.
    )
    (B context{counter:i16}:
        Next {context.counter=context.counter+1} => A{counter:context.counter};
    )
);

use Simple::*;

let mut machine = Simple::new();
assert!(match machine.get_current_state(){

    // We are in state A and have value 1. Because Enter State callback executed.

    States::A{context}=> if context.counter == 1 {true} else {false},
    _=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){

    // We are in state B and have counter == 3. Increment happen on User Code execution. Execution of Leave state callback happen after we transfer data to the next state.

    States::B{context}=> {println!("context counter: {}", context.counter);if context.counter == 3 {true} else {false}},
    _=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){

    // Again in state A and have counter == 5. Increment happen on User Code execution and on state A enter.

    States::A{context}=> if context.counter == 5 {true} else {false},
    _=>false
});

Example of Machine-scoped context. This context exist in machine life-time.

Lets count machine's state changes:

#[macro_use] extern crate macro_machine;

declare_machine!(
    Simple machine_context{counter: i16} (A) // Declare machine scoped context
    states[A,B]
    commands[Next]
    (A :
        >> {machine_context.counter=machine_context.counter+1;} // Add 1 when enter in state
        Next => B; // Just switch to other state
    )
    (B :
        >> {machine_context.counter=machine_context.counter+1;}
        Next => A;
    )
);

use Simple::*;

let mut machine = Simple::new(0);
let context = machine.get_inner_context();
assert!(context.counter == 1);
machine.execute(&Simple::Commands::Next).unwrap();
let context = machine.get_inner_context();
assert!(context.counter == 2);
machine.execute(&Simple::Commands::Next).unwrap();
let context = machine.get_inner_context();
assert!(context.counter == 3);

Macros

declare_machine