#![cfg_attr(test, allow(dead_code, unreachable_code))]
pub extern crate log;
extern crate marksman_escape;
extern crate variant_count;
pub use variant_count::VariantCount;
mod macro_def;
pub trait MachineDotfile {
fn name() -> &'static str;
fn type_vars() -> Vec <String>;
fn extended_state_names() -> Vec <&'static str>;
fn extended_state_types() -> Vec <&'static str>;
fn extended_state_defaults() -> Vec <&'static str>;
fn self_reference() -> &'static str;
fn states() -> Vec <&'static str>;
fn state_data_names() -> Vec <Vec <&'static str>>;
fn state_data_types() -> Vec <Vec <&'static str>>;
fn state_data_defaults() -> Vec <Vec <&'static str>>;
fn state_data_pretty_defaults() -> Vec <Vec <String>>;
fn state_initial() -> &'static str;
fn state_terminal() -> &'static str;
fn events() -> Vec <&'static str>;
fn event_sources() -> Vec <&'static str>;
fn event_targets() -> Vec <&'static str>;
fn event_actions() -> Vec <&'static str>;
fn dotfile() -> String where Self : Sized {
machine_dotfile::<Self> (true, false, false)
}
fn dotfile_show_defaults() -> String where Self : Sized {
machine_dotfile::<Self> (false, false, false)
}
fn dotfile_pretty_defaults() -> String where Self : Sized {
machine_dotfile::<Self> (false, true, false)
}
fn dotfile_hide_actions() -> String where Self : Sized {
machine_dotfile::<Self> (true, false, true)
}
}
#[derive(Debug, PartialEq)]
pub enum HandleEventException {
WrongState
}
fn machine_dotfile <M : MachineDotfile>
(hide_defaults : bool, pretty_defaults : bool, hide_actions : bool) -> String
{
let mut s = String::new();
s.push_str (
"digraph {\n \
overlap=scale\n \
rankdir=LR\n \
node [shape=record, style=rounded, fontname=\"Sans Bold\"]\n \
edge [fontname=\"Sans\"]\n");
{ s.push_str (format!(
" subgraph cluster_{} {{\n", M::name()).as_str());
let title_string = {
let mut s = String::new();
s.push_str (M::name());
if !M::type_vars().is_empty() {
s.push_str ("<");
let type_vars = M::type_vars();
for string in type_vars {
s.push_str (string.as_str());
s.push_str (",");
}
assert_eq!(s.pop(), Some (','));
s.push_str (">");
}
s
};
s.push_str (format!(" label=<{}", escape (title_string)).as_str());
let mut mono_font = false;
let extended_state_names = M::extended_state_names();
let extended_state_types = M::extended_state_types();
let extended_state_defaults = M::extended_state_defaults();
debug_assert_eq!(extended_state_names.len(), extended_state_types.len());
debug_assert_eq!(extended_state_types.len(), extended_state_defaults.len());
if !extended_state_names.is_empty() {
s.push_str ("<FONT FACE=\"Mono\"><BR/><BR/>\n");
mono_font = true;
debug_assert!(mono_font);
let mut extended_string = String::new();
let separator = ",<BR ALIGN=\"LEFT\"/>\n";
let longest_fieldname = extended_state_names.iter().fold (
0, |longest, ref fieldname| std::cmp::max (longest, fieldname.len())
);
let longest_typename = extended_state_types.iter().fold (
0, |longest, ref typename| std::cmp::max (longest, typename.len())
);
for (i,f) in extended_state_names.iter().enumerate() {
let spacer1 : String = std::iter::repeat (' ')
.take (longest_fieldname - f.len())
.collect();
let spacer2 : String = std::iter::repeat (' ')
.take (longest_typename - extended_state_types[i].len())
.collect();
if !hide_defaults && !extended_state_defaults[i].is_empty() {
extended_string.push_str (escape (format!(
"{}{} : {}{} = {}",
f, spacer1, extended_state_types[i], spacer2, extended_state_defaults[i]
)).as_str());
} else {
extended_string.push_str (escape (format!(
"{}{} : {}", f, spacer1, extended_state_types[i]
)).as_str());
}
extended_string.push_str (format!("{}", separator).as_str());
}
let len = extended_string.len();
extended_string.truncate (len - separator.len());
s.push_str (format!("{}", extended_string).as_str());
} s.push_str ("<BR ALIGN=\"LEFT\"/>");
let self_reference = M::self_reference();
if !self_reference.is_empty() && mono_font {
s.push_str (format!("@ {}<BR ALIGN=\"CENTER\"/>", self_reference).as_str());
}
if !extended_state_names.is_empty() {
s.push_str ("\n ");
}
if mono_font {
s.push_str ("</FONT><BR/>");
}
s.push_str (">\
\n shape=record\
\n style=rounded\
\n fontname=\"Sans Bold Italic\"\n");
} s.push_str (
" INITIAL [label=\"\", shape=circle, width=0.2, \
style=filled, fillcolor=black]\n");
let state_data_names = M::state_data_names();
let state_data_types = M::state_data_types();
let state_data_defaults : Vec <Vec <String>> = if !pretty_defaults {
M::state_data_defaults().into_iter().map (
|v| v.into_iter().map (str::to_string).collect()
).collect()
} else {
let pretty_defaults = M::state_data_pretty_defaults();
pretty_defaults.into_iter().map (
|v| v.into_iter().map (|pretty_newline| {
let mut pretty_br = String::new();
let separator = "<BR ALIGN=\"LEFT\"/>\n";
for line in pretty_newline.lines() {
pretty_br.push_str (escape (line.to_string()).as_str());
pretty_br.push_str (separator);
}
let len = pretty_br.len();
pretty_br.truncate (len - separator.len());
pretty_br
}).collect()
).collect()
};
debug_assert_eq!(state_data_names.len(), state_data_types.len());
debug_assert_eq!(state_data_types.len(), state_data_defaults.len());
for (i, state) in M::states().iter().enumerate() {
let mut mono_font = false;
let state_data_names = &state_data_names[i];
let state_data_types = &state_data_types[i];
let state_data_defaults = &state_data_defaults[i];
debug_assert_eq!(state_data_names.len(), state_data_types.len());
debug_assert_eq!(state_data_types.len(), state_data_defaults.len());
s.push_str (format!(" {} [label=<<B>{}</B>", state, state).as_str());
if !state_data_names.is_empty() {
if !mono_font {
s.push_str ("|<FONT FACE=\"Mono\"><BR/>\n");
mono_font = true;
}
let mut data_string = String::new();
let separator = ",<BR ALIGN=\"LEFT\"/>\n";
let longest_fieldname = state_data_names.iter().fold (
0, |longest, ref fieldname| std::cmp::max (longest, fieldname.len())
);
let longest_typename = state_data_types.iter().fold (
0, |longest, ref typename| std::cmp::max (longest, typename.len())
);
for (i,f) in state_data_names.iter().enumerate() {
let spacer1 : String = std::iter::repeat (' ')
.take(longest_fieldname - f.len())
.collect();
let spacer2 : String = std::iter::repeat (' ')
.take(longest_typename - state_data_types[i].len())
.collect();
if !hide_defaults && !state_data_defaults[i].is_empty() {
data_string.push_str (escape (format!(
"{}{} : {}{} = {}",
f, spacer1, state_data_types[i], spacer2, state_data_defaults[i]
)).as_str());
} else {
data_string.push_str (escape (format!(
"{}{} : {}", f, spacer1, state_data_types[i]
)).as_str());
}
data_string.push_str (format!("{}", separator).as_str());
}
let len = data_string.len();
data_string.truncate (len - separator.len());
s.push_str (format!("{}", data_string).as_str());
}
if mono_font {
s.push_str ("<BR ALIGN=\"LEFT\"/></FONT>");
}
s.push_str (">]\n");
} s.push_str (format!(
" INITIAL -> {}\n", M::state_initial()).as_str());
let event_sources = M::event_sources();
let event_targets = M::event_targets();
let event_actions = M::event_actions();
let mut universal = false;
for (i, event) in M::events().into_iter().enumerate() {
let source = event_sources[i];
let mut target = event_targets[i];
let action = event_actions[i];
if target.is_empty() { target = source;
}
if source == "*" {
universal = true;
}
s.push_str (format!(
" \"{}\" -> \"{}\" [label=<<FONT FACE=\"Sans Italic\">{}</FONT>",
source, target, event
).as_str());
let mut mono_font = false;
if !hide_actions && !action.is_empty() {
match action {
"{}" | "{ }" => {}
_ => {
if !mono_font {
s.push_str ("<FONT FACE=\"Mono\"><BR/>");
mono_font = true;
}
let action_string = {
let mut s : String = action.split_whitespace().map (
|s| {
let mut s = s.to_string();
s.push (' ');
s
}
).collect();
assert_eq!(s.pop(), Some (' '));
s
};
s.push_str (format!("{}", escape (action_string)).as_str());
}
}
}
if mono_font {
s.push_str ("</FONT>");
}
s.push_str (">]\n");
} if universal {
for state in M::states() {
s.push_str (format!(
" {} -> \"*\" [style=dashed, color=gray]", state).as_str());
}
}
let state_terminal = M::state_terminal();
if !state_terminal.is_empty() {
s.push_str (
" TERMINAL [label=\"\", shape=doublecircle, width=0.2,\
\n style=filled, fillcolor=black]\n");
s.push_str (format!(
" {} -> TERMINAL\n", state_terminal).as_str());
}
s.push_str (
" }\n\
}");
s
} #[inline]
fn escape (s : String) -> String {
use marksman_escape::Escape;
String::from_utf8 (Escape::new (s.bytes()).collect()).unwrap()
}
#[cfg(doc)]
pub mod example {
use crate::def_machine_debug;
def_machine_debug! {
Door (open_count : u64) @ door {
STATES [
state Closed (knock_count : u64) {
exit { println!("final knock count: {}", knock_count); }
}
state Opened () {
entry { println!("open count: {}", open_count); }
}
]
EVENTS [
event Knock <Closed> () { knock_count } => { *knock_count += 1; }
event Open <Closed> => <Opened> () {} => { *open_count += 1; }
event Close <Opened> => <Closed> ()
]
initial_state: Closed {
initial_action: { println!("hello"); }
}
terminal_state: Closed {
terminate_success: { println!("goodbye") }
terminate_failure: {
panic!("door was left: {:?}", door.state())
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial() {
{
def_machine!{
Test () {
STATES [ state A () ]
EVENTS [ ]
initial_state: A
}
}
let test = Test::initial();
assert_eq!(test.state_id(), StateId::A);
} {
def_machine_debug!{
Test () {
STATES [ state A () ]
EVENTS [ ]
initial_state: A
}
}
let test = Test::initial();
assert_eq!(test.state_id(), StateId::A);
}
}
#[test]
fn test_new() {
{
def_machine!{
Test () {
STATES [ state A () ]
EVENTS [ ]
initial_state: A
}
}
let test = Test::new (ExtendedState::new());
assert_eq!(test.state_id(), StateId::A);
} {
def_machine_debug!{
Test () {
STATES [ state A () ]
EVENTS [ ]
initial_state: A
}
}
let test = Test::new (ExtendedState::new());
assert_eq!(test.state_id(), StateId::A);
} {
def_machine_nodefault!{
Test () {
STATES [ state A () ]
EVENTS [ ]
initial_state: A
}
}
let test = Test::new (ExtendedState::new().unwrap());
assert_eq!(test.state_id(), StateId::A);
} {
def_machine_nodefault_debug!{
Test () {
STATES [ state A () ]
EVENTS [ ]
initial_state: A
}
}
let test = Test::new (ExtendedState::new().unwrap());
assert_eq!(test.state_id(), StateId::A);
}
}
#[test]
fn test_event_internal() {
{
def_machine!{
Test () {
STATES [ state A () ]
EVENTS [ event E <A> () ]
initial_state: A
}
}
let mut test = Test::initial();
test.handle_event (EventId::E.into()).unwrap();
} {
def_machine_debug!{
Test () {
STATES [ state A () ]
EVENTS [ event E <A> () ]
initial_state: A
}
}
let mut test = Test::initial();
test.handle_event (EventId::E.into()).unwrap();
} {
def_machine_nodefault!{
Test () {
STATES [ state A () ]
EVENTS [ event E <A> () ]
initial_state: A
}
}
let mut test = Test::new (ExtendedState::new().unwrap());
test.handle_event (EventParams::E{}.into()).unwrap();
} {
def_machine_nodefault_debug!{
Test () {
STATES [ state A () ]
EVENTS [ event E <A> () ]
initial_state: A
}
}
let mut test = Test::new (ExtendedState::new().unwrap());
test.handle_event (EventParams::E{}.into()).unwrap();
}
}
#[test]
fn test_event_external() {
{
def_machine!{
Test () {
STATES [
state A ()
state B ()
]
EVENTS [ event E <A> => <B> () ]
initial_state: A
}
}
let mut test = Test::initial();
test.handle_event (EventId::E.into()).unwrap();
assert_eq!(test.state_id(), StateId::B);
} {
def_machine_debug!{
Test () {
STATES [
state A ()
state B ()
]
EVENTS [ event E <A> => <B> () ]
initial_state: A
}
}
let mut test = Test::initial();
test.handle_event (EventId::E.into()).unwrap();
assert_eq!(test.state_id(), StateId::B);
} {
def_machine_nodefault!{
Test () {
STATES [
state A ()
state B ()
]
EVENTS [ event E <A> => <B> () ]
initial_state: A
}
}
let mut test = Test::new (ExtendedState::new().unwrap());
test.handle_event (EventParams::E{}.into()).unwrap();
assert_eq!(test.state_id(), StateId::B);
} {
def_machine_nodefault_debug!{
Test () {
STATES [
state A ()
state B ()
]
EVENTS [ event E <A> => <B> () ]
initial_state: A
}
}
let mut test = Test::new (ExtendedState::new().unwrap());
test.handle_event (EventParams::E{}.into()).unwrap();
assert_eq!(test.state_id(), StateId::B);
}
}
}