macro_machines/
lib.rs

1//! State machine macros with logging and graphviz DOT file generation.
2//!
3//! [Repository](https://github.com/spearman/macro-machines)
4//!
5//! An example that shows a number of features of the macro syntax is a `Door`
6//! state machine with:
7//!
8//! - two *states*: `Closed` (with *state-local variable* `knock_count` and an
9//!   exit action) and a simple state `Open` (with no state variables or
10//!   actions)
11//! - three *events*: one *internal event* `Knock` (with *action* on the
12//!   `Closed` state) and two *external events* `Open` (with associated action)
13//!   and `Close` (without any action)
14//! - an *extended state variable* `open_count` -- this variable is initialized
15//!   once and is independent of the current machine state
16//!
17//! ```text
18//! def_machine_debug! {
19//!   Door (open_count : u64) @ door {
20//!     STATES [
21//!       state Closed (knock_count : u64) {
22//!         exit {
23//!           println!("knock count: {}", knock_count);
24//!           println!("open count: {}", open_count);
25//!         }
26//!       }
27//!       state Opened ()
28//!     ]
29//!     EVENTS [
30//!       event Knock <Closed> () { knock_count } => { *knock_count += 1; }
31//!       event Open  <Closed> => <Opened> ()  {} => { *open_count += 1; }
32//!       event Close <Opened> => <Closed> ()
33//!     ]
34//!     initial_state:  Closed {
35//!       initial_action: {
36//!         println!("hello");
37//!         println!("open_count: {:?}", door.as_ref().open_count);
38//!       }
39//!     }
40//!     terminal_state: Closed {
41//!       terminate_success: {
42//!         println!("open_count: {:?}", door.as_ref().open_count);
43//!         println!("goodbye")
44//!       }
45//!       terminate_failure: {
46//!         panic!("door was left: {:?}", door.state())
47//!       }
48//!     }
49//!   }
50//! }
51//! ```
52//!
53//! Within state entry and exit action blocks all extended state and local state
54//! variables are in scope.
55//!
56//! In event actions, mutable references to extended state variables will
57//! implicitly be brought into scope of the associated action block, however
58//! local state variables need to be explicitly listed in the LHS brace of the
59//! action construct to be accessible (e.g. the `knock_count` local state
60//! variable in the `Knock` event action of the current example).
61//!
62//! When making a universal or external transition, first state exit actions are
63//! performed, followed by event actions, and then after initializing the new
64//! state, state entry actions.
65//!
66//! To make the state machine accessible in initial and terminal action blocks,
67//! the macro implementation requires an identifier be introduced, `door`,
68//! following the `@` symbol. This variable is then brought into scope as an
69//! alias for a mutable self-reference in initial and terminal action blocks.
70//!
71//! Initial and terminal actions are always before and after any state entry and
72//! exit actions, respectively.
73//!
74//! The `Door::dotfile()` function will generate a '.dot' file string that can
75//! be saved and rendered as a PNG with layout generated by graphviz `dot` tool:
76//!
77//! ```text
78//! $ dot -Tpng door.dot > door.png
79//! ```
80//!
81//! ![](https://raw.githubusercontent.com/spearman/macro-machines/master/door.png)
82
83#![cfg_attr(test, allow(dead_code, unreachable_code))]
84
85pub extern crate log;
86extern crate marksman_escape;
87extern crate variant_count;
88
89pub use variant_count::VariantCount;
90
91mod macro_def;
92
93/// Methods for DOT file creation
94// TODO: if we had a proper Machine trait with associated state and event ID
95// types, some of this would be redundant
96pub trait MachineDotfile {
97  // required
98  fn name()                       -> &'static str;
99  fn type_vars()                  -> Vec <String>;
100  fn extended_state_names()       -> Vec <&'static str>;
101  fn extended_state_types()       -> Vec <&'static str>;
102  fn extended_state_defaults()    -> Vec <&'static str>;
103  fn self_reference()             -> &'static str;
104  fn states()                     -> Vec <&'static str>;
105  fn state_data_names()           -> Vec <Vec <&'static str>>;
106  fn state_data_types()           -> Vec <Vec <&'static str>>;
107  fn state_data_defaults()        -> Vec <Vec <&'static str>>;
108  fn state_data_pretty_defaults() -> Vec <Vec <String>>;
109  fn state_initial()              -> &'static str;
110  fn state_terminal()             -> &'static str;
111  fn events()                     -> Vec <&'static str>;
112  fn event_sources()              -> Vec <&'static str>;
113  fn event_targets()              -> Vec <&'static str>;
114  fn event_actions()              -> Vec <&'static str>;
115  // provided: these are intended to be called by the user
116  /// Generate a DOT file for the state machine that hides default expressions
117  /// for state fields and extended state fields, but shows event actions
118  fn dotfile() -> String where Self : Sized {
119    machine_dotfile::<Self> (true, false, false)
120  }
121  /// Generate a DOT file for the state machine that shows default expressions
122  /// for state fields and extended state fields
123  fn dotfile_show_defaults() -> String where Self : Sized {
124    machine_dotfile::<Self> (false, false, false)
125  }
126  /// Generate a DOT file for the state machine that pretty prints the *values*
127  /// of default expressions for state fields and extended state fields.
128  ///
129  /// &#9888; Calling this this function evaluates default expressions and
130  /// pretty prints the resulting values at runtime.
131  fn dotfile_pretty_defaults() -> String where Self : Sized {
132    machine_dotfile::<Self> (false, true, false)
133  }
134  /// Do not show event actions
135  fn dotfile_hide_actions() -> String where Self : Sized {
136    machine_dotfile::<Self> (true, false, true)
137  }
138}
139
140/// Describes an exceptional result when attempting to handle an event.
141///
142/// Currently the only exception is the '`WrongState`' exception.
143#[derive(Debug, Eq, PartialEq)]
144pub enum HandleEventException {
145  WrongState
146}
147
148//
149//  private functions
150//
151
152/// Private DOT file creation function
153fn machine_dotfile <M : MachineDotfile>
154  (hide_defaults : bool, pretty_defaults : bool, hide_actions : bool) -> String
155{
156  let mut s = String::new();
157  //
158  // begin graph
159  //
160  // overlap = scale for neato layouts
161  s.push_str (
162    "digraph {\n  \
163       overlap=scale\n  \
164       rankdir=LR\n  \
165       node [shape=record, style=rounded, fontname=\"Sans Bold\"]\n  \
166       edge [fontname=\"Sans\"]\n");
167
168    //
169  { // begin subgraph
170    //
171  s.push_str (format!(
172    "  subgraph cluster_{} {{\n", M::name()).as_str());
173  let title_string = {
174    let mut s = String::new();
175    s.push_str (M::name());
176    if !M::type_vars().is_empty() {
177      s.push ('<');
178      let type_vars = M::type_vars();
179      for string in type_vars {
180        s.push_str (string.as_str());
181        s.push (',');
182      }
183      assert_eq!(s.pop(), Some (','));
184      s.push ('>');
185    }
186    s
187  };
188  s.push_str (format!("    label=<{}", escape (title_string)).as_str());
189
190  //  extended state
191  let mut mono_font           = false;
192  let extended_state_names    = M::extended_state_names();
193  let extended_state_types    = M::extended_state_types();
194  let extended_state_defaults = M::extended_state_defaults();
195  debug_assert_eq!(extended_state_names.len(), extended_state_types.len());
196  debug_assert_eq!(extended_state_types.len(), extended_state_defaults.len());
197
198  if !extended_state_names.is_empty() {
199    s.push_str ("<FONT FACE=\"Mono\"><BR/><BR/>\n");
200    mono_font = true;
201    //  for each extended state field, print a line
202    // TODO: we are manually aligning the columns of the field name and field
203    // type, is there a better way ? (record node, html table, format width?)
204    debug_assert!(mono_font);
205
206    let mut extended_string = String::new();
207    let separator = ",<BR ALIGN=\"LEFT\"/>\n";
208
209    let longest_fieldname = extended_state_names.iter()
210      .fold (0, |longest, fieldname| std::cmp::max (longest, fieldname.len()));
211
212    let longest_typename = extended_state_types.iter()
213      .fold (0, |longest, typename| std::cmp::max (longest, typename.len()));
214
215    for (i,f) in extended_state_names.iter().enumerate() {
216      let spacer1 : String =
217        std::iter::repeat_n (' ', longest_fieldname - f.len()).collect();
218      let spacer2 : String =
219        std::iter::repeat_n (' ', longest_typename - extended_state_types[i].len())
220          .collect();
221
222      if !hide_defaults && !extended_state_defaults[i].is_empty() {
223        extended_string.push_str (escape (format!(
224          "{}{} : {}{} = {}",
225          f, spacer1, extended_state_types[i], spacer2, extended_state_defaults[i]
226        )).as_str());
227      } else {
228        extended_string.push_str (escape (format!(
229          "{}{} : {}", f, spacer1, extended_state_types[i]
230        )).as_str());
231      }
232      extended_string.push_str (separator.to_string().as_str());
233    }
234
235    let len = extended_string.len();
236    extended_string.truncate (len - separator.len());
237    s.push_str (extended_string.as_str());
238  } // end extended state
239
240  s.push_str ("<BR ALIGN=\"LEFT\"/>");
241  let self_reference = M::self_reference();
242  if !self_reference.is_empty() && mono_font {
243    s.push_str (format!("@ {self_reference}<BR ALIGN=\"CENTER\"/>").as_str());
244  }
245  if !extended_state_names.is_empty() {
246    s.push_str ("\n      ");
247  }
248
249  // TODO: extended state transitions
250
251  if mono_font {
252    s.push_str ("</FONT><BR/>");
253  }
254  s.push_str (">\
255    \n    shape=record\
256    \n    style=rounded\
257    \n    fontname=\"Sans Bold Italic\"\n");
258  } // end begin subgraph
259
260  //
261  // nodes (states)
262  //
263  // initial node
264  s.push_str (
265    "    INITIAL [label=\"\", shape=circle, width=0.2, \
266           style=filled, fillcolor=black]\n");
267  // states
268  let state_data_names    = M::state_data_names();
269  let state_data_types    = M::state_data_types();
270  let state_data_defaults : Vec <Vec <String>> = if !pretty_defaults {
271    M::state_data_defaults().into_iter().map (
272      |v| v.into_iter().map (str::to_string).collect()
273    ).collect()
274  } else {
275    let pretty_defaults = M::state_data_pretty_defaults();
276    pretty_defaults.into_iter().map (
277      |v| v.into_iter().map (|pretty_newline| {
278        let mut pretty_br = String::new();
279        let separator = "<BR ALIGN=\"LEFT\"/>\n";
280        for line in pretty_newline.lines() {
281          pretty_br.push_str (escape (line.to_string()).as_str());
282          pretty_br.push_str (separator);
283        }
284        let len = pretty_br.len();
285        pretty_br.truncate (len - separator.len());
286        pretty_br
287      }).collect()
288    ).collect()
289  };
290  debug_assert_eq!(state_data_names.len(), state_data_types.len());
291  debug_assert_eq!(state_data_types.len(), state_data_defaults.len());
292
293  // for each state: node
294  for (i, state) in M::states().iter().enumerate() {
295    let mut mono_font       = false;
296    let state_data_names    = &state_data_names[i];
297    let state_data_types    = &state_data_types[i];
298    let state_data_defaults = &state_data_defaults[i];
299    debug_assert_eq!(state_data_names.len(), state_data_types.len());
300    debug_assert_eq!(state_data_types.len(), state_data_defaults.len());
301    s.push_str (format!("    {state} [label=<<B>{state}</B>").as_str());
302    // NOTE: within the mono font block leading whitespace in the source
303    // is counted as part of the layout so we don't indent these lines
304    if !state_data_names.is_empty() {
305      if !mono_font {
306        s.push_str ("|<FONT FACE=\"Mono\"><BR/>\n");
307        mono_font = true;
308      }
309      let mut data_string = String::new();
310      let separator = ",<BR ALIGN=\"LEFT\"/>\n";
311      let longest_fieldname = state_data_names.iter()
312        .fold (0, |longest, fieldname| std::cmp::max (longest, fieldname.len()));
313      let longest_typename = state_data_types.iter()
314        .fold (0, |longest, typename| std::cmp::max (longest, typename.len()));
315      for (i,f) in state_data_names.iter().enumerate() {
316        let spacer1 : String =
317          std::iter::repeat_n (' ', longest_fieldname - f.len()).collect();
318        let spacer2 : String =
319          std::iter::repeat_n (' ', longest_typename - state_data_types[i].len())
320            .collect();
321        if !hide_defaults && !state_data_defaults[i].is_empty() {
322          data_string.push_str (escape (format!(
323            "{}{} : {}{} = {}",
324            f, spacer1, state_data_types[i], spacer2, state_data_defaults[i]
325          )).as_str());
326        } else {
327          data_string.push_str (escape (format!(
328            "{}{} : {}", f, spacer1, state_data_types[i]
329          )).as_str());
330        }
331        data_string.push_str (separator.to_string().as_str());
332      }
333      let len = data_string.len();
334      data_string.truncate (len - separator.len());
335      s.push_str (data_string.as_str());
336    }
337
338    /*
339    if s.chars().last().unwrap() == '>' {
340      let len = s.len();
341      s.truncate (len-5);
342    } else {
343      s.push_str ("</FONT>");
344    }
345    */
346
347    // state guards
348    // TODO
349
350    if mono_font {
351      s.push_str ("<BR ALIGN=\"LEFT\"/></FONT>");
352    }
353    s.push_str (">]\n");
354  } // end for each state: node
355  // end nodes (states)
356
357  //
358  // transitions (events)
359  //
360  // initial transition edge
361  // TODO: show initial action
362  s.push_str (format!(
363    "    INITIAL -> {}\n", M::state_initial()).as_str());
364  let event_sources = M::event_sources();
365  let event_targets = M::event_targets();
366  let event_actions = M::event_actions();
367  let mut universal = false;
368  // for each event: transition edge
369  for (i, event) in M::events().into_iter().enumerate() {
370    let source = event_sources[i];
371    let mut target = event_targets[i];
372    let action = event_actions[i];
373    if target.is_empty() {  // internal transition source == target
374      target = source;
375    }
376
377    if source == "*" {
378      universal = true;
379    }
380    s.push_str (format!(
381      "    \"{source}\" -> \"{target}\" [label=<<FONT FACE=\"Sans Italic\">{event}</FONT>"
382    ).as_str());
383
384    let mut mono_font = false;
385    // params
386    // TODO
387    // guards
388    // TODO
389
390    if !hide_actions && !action.is_empty() {
391      match action {
392        // don't render empty actions
393        "{}" | "{ }" => {}
394        _ => {
395          if !mono_font {
396            s.push_str ("<FONT FACE=\"Mono\"><BR/>");
397            mono_font = true;
398          }
399          // replace whitespace with single spaces
400          let action_string = {
401            let mut s : String = action.split_whitespace().map (
402              |s| {
403                let mut s = s.to_string();
404                s.push (' ');
405                s
406              }
407            ).collect();
408            assert_eq!(s.pop(), Some (' '));
409            s
410          };
411          // TODO: different formatting if params or guards were present
412          //action = "  ".to_string() + action.as_str();
413          s.push_str (escape (action_string).as_str());
414        }
415      }
416    }
417
418    if mono_font {
419      s.push_str ("</FONT>");
420    }
421    s.push_str (">]\n");
422  } // end for each event: transition edge
423
424  if universal {
425    for state in M::states() {
426      s.push_str (format!(
427        "    {state} -> \"*\" [style=dashed, color=gray]").as_str());
428    }
429  }
430
431  // terminal transition: node + edge
432  // TODO: show terminal action(s)
433  let state_terminal = M::state_terminal();
434  if !state_terminal.is_empty() {
435    s.push_str (
436      "    TERMINAL [label=\"\", shape=doublecircle, width=0.2,\
437     \n      style=filled, fillcolor=black]\n");
438    s.push_str (format!(
439      "    {state_terminal} -> TERMINAL\n").as_str());
440  }
441  // end transitions
442
443  //
444  //  end graph
445  //
446  s.push_str (
447    "  }\n\
448    }");
449  s
450} // end fn machine_dotfile
451
452/// Escape HTML special characters
453#[inline]
454fn escape (s : String) -> String {
455  use marksman_escape::Escape;
456  String::from_utf8 (Escape::new (s.bytes()).collect()).unwrap()
457}
458
459#[cfg(doc)]
460pub mod example {
461  //! Example generated state machine
462  use crate::def_machine_debug;
463  def_machine_debug! {
464    Door (open_count : u64) @ door {
465      STATES [
466        state Closed (knock_count : u64) {
467          exit { println!("final knock count: {}", knock_count); }
468        }
469        state Opened () {
470          entry { println!("open count: {}", open_count); }
471        }
472      ]
473      EVENTS [
474        event Knock <Closed> () { knock_count } => { *knock_count += 1; }
475        event Open  <Closed> => <Opened> ()  {} => { *open_count += 1; }
476        event Close <Opened> => <Closed> ()
477      ]
478      initial_state:  Closed {
479        initial_action: { println!("hello"); }
480      }
481      terminal_state: Closed {
482        terminate_success: { println!("goodbye") }
483        terminate_failure: {
484          panic!("door was left: {:?}", door.state())
485        }
486      }
487    }
488  }
489}
490
491#[cfg(test)]
492mod tests {
493  use super::*;
494  #[test]
495  fn initial() {
496    {
497      def_machine!{
498        Test () {
499          STATES [ state A () ]
500          EVENTS [ ]
501          initial_state: A
502        }
503      }
504      let test = Test::initial();
505      assert_eq!(test.state_id(), StateId::A);
506    } {
507      def_machine_debug!{
508        Test () {
509          STATES [ state A () ]
510          EVENTS [ ]
511          initial_state: A
512        }
513      }
514      let test = Test::initial();
515      assert_eq!(test.state_id(), StateId::A);
516    }
517  }
518  #[test]
519  fn new() {
520    {
521      def_machine!{
522        Test () {
523          STATES [ state A () ]
524          EVENTS [ ]
525          initial_state: A
526        }
527      }
528      let test = Test::new (ExtendedState::new());
529      assert_eq!(test.state_id(), StateId::A);
530    } {
531      def_machine_debug!{
532        Test () {
533          STATES [ state A () ]
534          EVENTS [ ]
535          initial_state: A
536        }
537      }
538      let test = Test::new (ExtendedState::new());
539      assert_eq!(test.state_id(), StateId::A);
540    } {
541      def_machine_nodefault!{
542        Test () {
543          STATES [ state A () ]
544          EVENTS [ ]
545          initial_state: A
546        }
547      }
548      let test = Test::new (ExtendedState::new().unwrap());
549      assert_eq!(test.state_id(), StateId::A);
550    } {
551      def_machine_nodefault_debug!{
552        Test () {
553          STATES [ state A () ]
554          EVENTS [ ]
555          initial_state: A
556        }
557      }
558      let test = Test::new (ExtendedState::new().unwrap());
559      assert_eq!(test.state_id(), StateId::A);
560    }
561  }
562  #[test]
563  fn event_internal() {
564    {
565      def_machine!{
566        Test () {
567          STATES [ state A () ]
568          EVENTS [ event E <A> () ]
569          initial_state: A
570        }
571      }
572      let mut test = Test::initial();
573      test.handle_event (EventId::E.into()).unwrap();
574    } {
575      def_machine_debug!{
576        Test () {
577          STATES [ state A () ]
578          EVENTS [ event E <A> () ]
579          initial_state: A
580        }
581      }
582      let mut test = Test::initial();
583      test.handle_event (EventId::E.into()).unwrap();
584    } {
585      def_machine_nodefault!{
586        Test () {
587          STATES [ state A () ]
588          EVENTS [ event E <A> () ]
589          initial_state: A
590        }
591      }
592      let mut test = Test::new (ExtendedState::new().unwrap());
593      test.handle_event (EventParams::E{}.into()).unwrap();
594    } {
595      def_machine_nodefault_debug!{
596        Test () {
597          STATES [ state A () ]
598          EVENTS [ event E <A> () ]
599          initial_state: A
600        }
601      }
602      let mut test = Test::new (ExtendedState::new().unwrap());
603      test.handle_event (EventParams::E{}.into()).unwrap();
604    }
605  }
606  #[test]
607  fn event_external() {
608    {
609      def_machine!{
610        Test () {
611          STATES [
612            state A ()
613            state B ()
614          ]
615          EVENTS [ event E <A> => <B> () ]
616          initial_state: A
617        }
618      }
619      let mut test = Test::initial();
620      test.handle_event (EventId::E.into()).unwrap();
621      assert_eq!(test.state_id(), StateId::B);
622    } {
623      def_machine_debug!{
624        Test () {
625          STATES [
626            state A ()
627            state B ()
628          ]
629          EVENTS [ event E <A> => <B> () ]
630          initial_state: A
631        }
632      }
633      let mut test = Test::initial();
634      test.handle_event (EventId::E.into()).unwrap();
635      assert_eq!(test.state_id(), StateId::B);
636    } {
637      def_machine_nodefault!{
638        Test () {
639          STATES [
640            state A ()
641            state B ()
642          ]
643          EVENTS [ event E <A> => <B> () ]
644          initial_state: A
645        }
646      }
647      let mut test = Test::new (ExtendedState::new().unwrap());
648      test.handle_event (EventParams::E{}.into()).unwrap();
649      assert_eq!(test.state_id(), StateId::B);
650    } {
651      def_machine_nodefault_debug!{
652        Test () {
653          STATES [
654            state A ()
655            state B ()
656          ]
657          EVENTS [ event E <A> => <B> () ]
658          initial_state: A
659        }
660      }
661      let mut test = Test::new (ExtendedState::new().unwrap());
662      test.handle_event (EventParams::E{}.into()).unwrap();
663      assert_eq!(test.state_id(), StateId::B);
664    }
665  }
666}