File Coverage

blib/lib/FSA/Rules.pm
Criterion Covered Total %
statement 250 285 87.7
branch 97 132 73.4
condition 7 9 77.7
subroutine 40 42 95.2
pod 22 22 100.0
total 416 490 84.9


line stmt bran cond sub pod time code
1             package FSA::Rules;
2              
3 5     5   87045 use strict;
  5         11  
  5         175  
4 5     5   96 use 5.006_002;
  5         11  
  5         139  
5 5     5   21 use Scalar::Util 1.01 ();
  5         127  
  5         14131  
6             $FSA::Rules::VERSION = '0.34';
7              
8             =head1 Name
9              
10             FSA::Rules - Build simple rules-based state machines in Perl
11              
12             =head1 Synopsis
13              
14             my $fsa = FSA::Rules->new(
15             ping => {
16             do => sub {
17             print "ping!\n";
18             my $state = shift;
19             $state->result('pong');
20             $state->machine->{count}++;
21             },
22             rules => [
23             game_over => sub { shift->machine->{count} >= 20 },
24             pong => sub { shift->result eq 'pong' },
25             ],
26             },
27              
28             pong => {
29             do => sub { print "pong!\n" },
30             rules => [ ping => 1, ], # always goes back to ping
31             },
32             game_over => { do => sub { print "Game Over\n" } }
33             );
34              
35             $fsa->start;
36             $fsa->switch until $fsa->at('game_over');
37              
38             =head1 Description
39              
40             This class implements a simple state machine pattern, allowing you to quickly
41             build rules-based state machines in Perl. As a simple implementation of a
42             powerful concept, it differs slightly from an ideal DFA model in that it does
43             not enforce a single possible switch from one state to another. Rather, it
44             short circuits the evaluation of the rules for such switches, so that the
45             first rule to return a true value will trigger its switch and no other switch
46             rules will be checked. (But see the C attribute and parameter to
47             C.) It differs from an NFA model in that it offers no back-tracking.
48             But in truth, you can use it to build a state machine that adheres to either
49             model--hence the more generic FSA moniker.
50              
51             FSA::Rules uses named states so that it's easy to tell what state you're in
52             and what state you want to go to. Each state may optionally define actions
53             that are triggered upon entering the state, after entering the state, and upon
54             exiting the state. They may also define rules for switching to other states,
55             and these rules may specify the execution of switch-specific actions. All
56             actions are defined in terms of anonymous subroutines that should expect an
57             FSA::State object itself to be passed as the sole argument.
58              
59             FSA::Rules objects and the FSA::State objects that make them up are all
60             implemented as empty hash references. This design allows the action
61             subroutines to use the FSA::State object passed as the sole argument, as well
62             as the FSA::Rules object available via its C method, to stash data
63             for other states to access, without the possibility of interfering with the
64             state or the state machine itself.
65              
66             =head2 Serialization
67              
68             As of version 0.24, FSA::Rules supports serialization by L
69             2.05 and later. In other words, FSA::Rules can function as a persistent state
70             machine.
71              
72             However, FSA::Rules stores data outside of FSA::Rules objects, in private data
73             structures inside the FSA::Rules module itself. Therefore, unless you want to
74             clone your FSA::Rules object, you must let it fall out of scope after you
75             serialize it, so that its data will be cleared from memory. Otherwise, if you
76             freeze and thaw an FSA::Rules object in a single process without Cing
77             the original, there will be I copies of the object stored by FSA::Rules.
78              
79             So how does it work? Because the rules are defined as code references, you
80             must use Storable 2.05 or later and set its C<$Deparse> and C<$Eval> variables
81             to true values:
82              
83             use Storable qw(freeze thaw);
84              
85             local $Storable::Deparse = 1;
86             local $Storable::Eval = 1;
87              
88             my $frozen = freeze($fsa);
89             $fsa = thaw($frozen);
90              
91             The only caveat is that, while Storable can serialize code references, it
92             doesn't properly reference closure variables. So if your rules code references
93             are closures, you'll have to serialize the data that they refer to yourself.
94              
95             =cut
96              
97             ##############################################################################
98              
99             =head1 Class Interface
100              
101             =head2 Constructor
102              
103             =head3 new
104              
105             my $fsa = FSA::Rules->new(
106             foo_state => { ... },
107             bar_state => { ... },
108             );
109              
110             $fsa = FSA::Rules->new(
111             \%params,
112             foo_state => { ... },
113             bar_state => { ... },
114             );
115              
116             Constructs and returns a new FSA::Rules object. An optional first argument
117             is a hash reference that may contain one or more of these keys:
118              
119             =over
120              
121             =item start
122              
123             Causes the C method to be called on the machine before returning it.
124              
125             =item done
126              
127             A value to which to set the C attribute.
128              
129             =item strict
130              
131             A value to which to set the C attribute.
132              
133             =item state_class
134              
135             The name of the class to use for state objects. Defaults to "FSA::State". Use
136             this parameter if you want to use a subclass of FSA::State.
137              
138             =item state_params
139              
140             A hash reference of parameters to pass as a list to the C
141             constructor.
142              
143             =back
144              
145             All other parameters define the state table, where each key is the name of a
146             state and the following hash reference defines the state, its actions, and its
147             switch rules. These state specifications will be converted to FSA::State
148             objects available via the C method. The first state parameter is
149             considered to be the start state; call the C method to automatically
150             enter that state.
151              
152             The supported keys in the state definition hash references are:
153              
154             =over
155              
156             =item label
157              
158             label => 'Do we have a username?',
159             label => 'Create a new user',
160              
161             A label for the state. It might be the question that is being asked within the
162             state (think decision tree), the answer to which determines which rule will
163             trigger the switch to the next state. Or it might merely describe what's
164             happening in the state.
165              
166             =item on_enter
167              
168             on_enter => sub { ... }
169             on_enter => [ sub {... }, sub { ... } ]
170              
171             Optional. A code reference or array reference of code references. These will
172             be executed when entering the state, after any switch actions defined by the
173             C of the previous state. The FSA::State for which the C
174             actions are defined will be passed to each code reference as the sole
175             argument.
176              
177             =item do
178              
179             do => sub { ... }
180             do => [ sub {... }, sub { ... } ]
181              
182             Optional. A code reference or array reference of code references. These are
183             the actions to be taken while in the state, and will execute after any
184             C actions. The FSA::State object for which the C actions are
185             defined will be passed to each code reference as the sole argument.
186              
187             =item on_exit
188              
189             on_exit => sub { ... }
190             on_exit => [ sub {... }, sub { ... } ]
191              
192             Optional. A code reference or array reference of code references. These will
193             be executed when exiting the state, before any switch actions (defined by
194             C). The FSA::State object for which the C actions are defined
195             will be passed to each code reference as the sole argument.
196              
197             =item rules
198              
199             Optional. The rules for switching from the state to other states. This is an
200             array reference but shaped like a hash. The keys are the names of the states
201             to consider moving to, while the values are the rules for switching to that
202             state. The rules will be executed in the order specified in the array
203             reference, and I unless the C attribute has
204             been set to a true value. So for the sake of efficiency it's worthwhile to
205             specify the switch rules most likely to evaluate to true before those more
206             likely to evaluate to false.
207              
208             Rules themselves are best specified as hash references with the following
209             keys:
210              
211             =over
212              
213             =item rule
214              
215             A code reference or value that will be evaluated to determine whether to
216             switch to the specified state. The value must be true or the code reference
217             must return a true value to trigger the switch to the new state, and false not
218             to switch to the new state. When executed, it will be passed the FSA::State
219             object for the state for which the rules were defined, along with any other
220             arguments passed to C or C--the methods that execute
221             the rule code references. These arguments may be inputs that are specifically
222             tested to determine whether to switch states. To be polite, rules should not
223             transform the passed values if they're returning false, as other rules may
224             need to evaluate them (unless you're building some sort of chaining rules--but
225             those aren't really rules, are they?).
226              
227             =item message
228              
229             An optional message that will be added to the current state when the rule
230             specified by the C parameter evaluates to true. The message will also be
231             used to label switches in the output of the C method.
232              
233             =item action
234              
235             A code reference or an array reference of code references to be executed
236             during the switch, after the C actions have been executed in the
237             current state, but before the C actions execute in the new state.
238             Two arguments will be passed to these code references: the FSA::State object
239             for the state for which they were defined, and the FSA::State object for the
240             new state (which will not yet be the current state).
241              
242             =back
243              
244             A couple of examples:
245              
246             rules => [
247             foo => {
248             rule => 1
249             },
250             bar => {
251             rule => \&goto_bar,
252             message => 'Have we got a bar?',
253             },
254             yow => {
255             rule => \&goto_yow,
256             message => 'Yow!',
257             action => [ \&action_one, \&action_two],
258             }
259             ]
260              
261             A rule may also simply be a code reference or value that will be evaluated
262             when FSA::Rules is determining whether to switch to the new state. You might want
263             just specify a value or code reference if you don't need a message label or
264             switch actions to be executed. For example, this C specification:
265              
266             rules => [
267             foo => 1
268             ]
269              
270             Is equivalent to this C specification:
271              
272             rules => [
273             foo => { rule => 1 }
274             ]
275              
276             And finally, you can specify a rule as an array reference. In this case, the
277             first item in the array will be evaluated to determine whether to switch to
278             the new state, and any other items must be code references that will be
279             executed during the switch. For example, this C specification:
280              
281             rules => [
282             yow => [ \&check_yow, \&action_one, \&action_two ]
283             ]
284              
285             Is equivalent to this C specification:
286              
287             rules => [
288             yow => {
289             rule => \&check_yow,
290             action => [ \&action_one, \&action_two ],
291             }
292             ]
293              
294             =back
295              
296             =cut
297              
298             my (%machines, %states);
299              
300             sub new {
301 35     35 1 5526 my $class = shift;
302 35         173 my $self = bless {}, $class;
303 35 100       124 my $params = ref $_[0] ? shift : {};
304 35         315 my $fsa = $machines{$self} = {
305             done => undef,
306             notes => {},
307             stack => [],
308             table => {},
309             self => $self,
310             };
311              
312             # Weaken the circular reference.
313 35         210 Scalar::Util::weaken $fsa->{self};
314              
315 35   100     238 $params->{state_class} ||= 'FSA::State';
316 35   100     270 $params->{state_params} ||= {};
317 35         118 while (@_) {
318 60         97 my $state = shift;
319 60         73 my $def = shift;
320 60 100       166 $self->_croak(qq{The state "$state" already exists})
321             if exists $fsa->{table}{$state};
322              
323             # Setup enter, exit, and do actions.
324 59         124 for (qw(on_enter do on_exit)) {
325 177 100       379 if (my $ref = ref $def->{$_}) {
326 59 100       283 $def->{$_} = [$def->{$_}] if $ref eq 'CODE';
327             } else {
328 118         309 $def->{$_} = [];
329             }
330             }
331              
332             # Create the state object and cache the state data.
333 59         116 my $obj = $params->{state_class}->new(%{$params->{state_params}});
  59         261  
334 59         107 $def->{name} = $state;
335 59         96 $def->{machine} = $self;
336 59         116 $fsa->{table}{$state} = $obj;
337 59         71 push @{$fsa->{ord}}, $obj;
  59         136  
338 59         187 $states{$obj} = $def;
339              
340             # Weaken the circular reference.
341 59         258 Scalar::Util::weaken $def->{machine};
342             }
343              
344             # Setup rules. We process the table a second time to catch invalid
345             # references.
346 34         64 while (my ($key, $obj) = each %{$fsa->{table}}) {
  91         354  
347 58         148 my $def = $states{$obj};
348 58 100       149 if (my $rule_spec = $def->{rules}) {
349 32         40 my @rules;
350 32         81 while (@$rule_spec) {
351 44         74 my $state = shift @$rule_spec;
352 44 100       182 $self->_croak(
353             qq{Unknown state "$state" referenced by state "$key"}
354             ) unless $fsa->{table}{$state};
355              
356 43         54 my $rules = shift @$rule_spec;
357 43 100       117 my $exec = ref $rules eq 'ARRAY' ? $rules : [$rules];
358 43         53 my $rule = shift @$exec;
359 43         42 my $message;
360 43 100       97 if (ref $rule eq 'HASH') {
361 5 50       12 $self->_croak(
362             qq{In rule "$state", state "$key": you must supply a rule.}
363             ) unless exists $rule->{rule};
364 5 100       20 $exec = ref $rule->{action} eq 'ARRAY'
    100          
365             ? $rule->{action}
366             : [$rule->{action}]
367             if exists $rule->{action};
368 5 100       14 $message = $rule->{message} if exists $rule->{message};
369 5         6 $rule = $rule->{rule};
370             }
371             # Used to convert a raw value to a code reference here, but as
372             # it ended up as a closure and these don't serialize very
373             # well, I pulled it out. Now try_switch has to check to see if
374             # a rule is a literal value each time it's called. This
375             # actually makes it faster for literal values, but a little
376             # slower for code references.
377              
378 43         200 push @rules, {
379             state => $fsa->{table}{$state},
380             rule => $rule,
381             exec => $exec,
382             message => $message,
383             };
384              
385             # Weaken the circular reference.
386 43         185 Scalar::Util::weaken $rules[-1]->{state};
387             }
388 31         130 $def->{rules} = \@rules;
389             } else {
390 26         88 $def->{rules} = [];
391             }
392             }
393              
394             # Handle any parameters.
395 33 100       138 $self->start if $params->{start};
396 33 100       101 $self->done($params->{done}) if exists $params->{done};
397 33 100       109 $self->strict($params->{strict}) if exists $params->{strict};
398 33         221 return $self;
399             }
400              
401             ##############################################################################
402              
403             =head1 Instance Interface
404              
405             =head2 Instance Methods
406              
407             =head3 start
408              
409             my $state = $fsa->start;
410              
411             Starts the state machine by setting the state to the first state defined in
412             the call to C. If the machine is already in a state, an exception will
413             be thrown. Returns the start state FSA::State object.
414              
415             =cut
416              
417             sub start {
418 21     21 1 49 my $self = shift;
419 21         55 my $fsa = $machines{$self};
420 21 100       83 $self->_croak(
421             'Cannot start machine because it is already running'
422             ) if $fsa->{current};
423 20 50       84 my $state = $fsa->{ord}[0] or return $self;
424 20         52 $self->curr_state($state);
425 20         55 return $state;
426             }
427              
428             ##############################################################################
429              
430             =head3 at
431              
432             $fsa->switch until $fsa->at('game_over');
433              
434             Requires a state name. Returns false if the current machine state does not
435             match the name. Otherwise, it returns the state.
436              
437             =cut
438              
439             sub at {
440 47     47 1 1019 my ($self, $name) = @_;
441 47 100       86 $self->_croak("You must supply a state name") unless defined $name;
442 46         80 my $fsa = $machines{$self};
443 46 100       109 $self->_croak(qq{No such state "$name"})
444             unless exists $fsa->{table}{$name};
445 45 50       103 my $state = $self->curr_state or return;
446 45 100       77 return unless $state->name eq $name;
447 3         8 return $state;
448             }
449              
450             ##############################################################################
451              
452             =head3 curr_state
453              
454             my $curr_state = $fsa->curr_state;
455             $fsa->curr_state($curr_state);
456              
457             Get or set the current FSA::State object. Pass a state name or object to set
458             the state. Setting a new state will cause the C actions of the
459             current state to be executed, if there is a current state, and then execute
460             the C and C actions of the new state. Returns the new FSA::State
461             object when setting the current state.
462              
463             =cut
464              
465             sub curr_state {
466 273     273 1 377 my $self = shift;
467 273         458 my $fsa = $machines{$self};
468 273         869 my $curr = $fsa->{current};
469 273 100       943 return $curr unless @_;
470              
471 142         193 my $state = shift;
472 142 100       314 unless (ref $state) {
473 15         18 my $name = $state;
474 15 100       58 $state = $fsa->{table}{$name}
475             or $self->_croak(qq{No such state "$name"});
476             }
477              
478             # Exit the current state.
479 141 100       371 $curr->exit if $curr;
480             # Run any switch actions.
481 141 100       413 if (my $exec = delete $fsa->{exec}) {
482 107         387 $_->($curr, $state) for @$exec;
483             }
484              
485             # Push the new state onto the stack and cache the index.
486 141         155 push @{$fsa->{stack}}
  141         399  
487             => [$state->name => { result => undef, message => undef}];
488 141         192 push @{$states{$state}->{index}}, $#{$fsa->{stack}};
  141         342  
  141         262  
489              
490             # Set the new state.
491 141         240 $fsa->{current} = $state;
492 141         288 $state->enter;
493 141         246 $state->do;
494 140         254 return $state;
495             }
496              
497             ##############################################################################
498              
499             =head3 state
500              
501             Deprecated alias for C. This method will issue a warning and
502             will be removed in a future version of FSA::Rules. Use C,
503             instead.
504              
505             =cut
506              
507             sub state {
508 0     0 1 0 require Carp;
509 0         0 Carp::carp(
510             'The state() method has been deprecated. Use curr_state() instead'
511             );
512 0         0 shift->curr_state(@_);
513             }
514              
515             ##############################################################################
516              
517             =head3 prev_state
518              
519             my $prev_state = $fsa->prev_state;
520              
521             Returns the FSA::State object representing the previous state. This is useful
522             in states where you need to know what state you came from, and can be very
523             useful in "fail" states.
524              
525             =cut
526              
527             sub prev_state {
528 5     5 1 7 my $self = shift;
529 5         14 my $stacktrace = $self->raw_stacktrace;
530 5 50       18 return unless @$stacktrace > 1;
531 5         38 return $machines{$self}->{table}{$stacktrace->[-2][0]};
532             }
533              
534             ##############################################################################
535              
536             =head3 states
537              
538             my @states = $fsa->states;
539             my $states = $fsa->states;
540             my $state = $fsa->states($state_name);
541             @states = $fsa->states(@state_names);
542             $states = $fsa->states(@state_names);
543              
544             Called with no arguments, this method returns a list or array reference of all
545             of the FSA::State objects that represent the states defined in the state
546             machine. When called with a single state name, it returns the FSA::State object
547             object for that state. When called with more than one state name arguments,
548             it returns a list or array reference of those states.
549              
550             If called with any state names that did not exist in the original definition of
551             the state machine, this method will C.
552              
553             =cut
554              
555             sub states {
556 16     16 1 443 my $self = shift;
557 16         37 my $fsa = $machines{$self};
558 16 50       57 return wantarray ? @{$fsa->{ord}} : $fsa->{ord} unless @_;
  4 100       23  
559              
560 12 100       24 if (my @errors = grep { not exists $fsa->{table}{$_} } @_) {
  13         77  
561 1         8 $self->_croak("No such state(s) '@errors'");
562             }
563              
564 11 100       71 return $fsa->{table}{+shift} unless @_ > 1;
565 1 50       4 return wantarray ? @{$fsa->{table}}{@_} : [ @{$fsa->{table}}{@_} ];
  1         8  
  0         0  
566              
567             }
568              
569             ##############################################################################
570              
571             =head3 try_switch
572              
573             my $state = $fsa->try_switch;
574             $state = $fsa->try_switch(@inputs);
575              
576             Checks the switch rules of the current state and switches to the first new
577             state for which a rule returns a true value. The evaluation of switch rules
578             short-circuits to switch to the first state for which a rule evaluates to a
579             true value unless the C attribute is set to a true value.
580              
581             If is set to a true value, I rules will be evaluated, and if
582             more than one returns a true statement, an exception will be thrown. This
583             approach guarantees that every attempt to switch from one state to another
584             will have one and only one possible destination state to which to switch, thus
585             satisfying the DFA pattern.
586              
587             All arguments passed to C will be passed to the switch rule code
588             references as inputs. If a switch rule evaluates to true and there are
589             additional switch actions for that rule, these actions will be executed after
590             the C actions of the current state (if there is one) but before the
591             C actions of the new state. They will be passed the current state
592             object and the new state object as arguments.
593              
594             Returns the FSA::State object representing the state to which it switched and
595             C if it cannot switch to another state.
596              
597             =cut
598              
599             sub try_switch {
600 109     109 1 140 my $self = shift;
601 109         183 my $fsa = $machines{$self};
602 109         151 my $state = $fsa->{current};
603             # XXX Factor this out to the state class to evaluate the rules?
604 109         243 my @rules = $state->_rules;
605 109         124 my $next;
606 109         263 while (my $rule = shift @rules) {
607 134         215 my $code = $rule->{rule};
608 134 100       437 next unless ref $code eq 'CODE' ? $code->($state, @_) : $code;
    100          
609              
610             # Make sure that no other rules evaluate to true in strict mode.
611 108 100 100     338 if (@rules && $self->strict) {
612 4 100       10 if ( my @new = grep {
  4         10  
613             my $c = $_->{rule};
614 4 100       26 ref $c eq 'CODE' ? $c->( $state, @_ ) : $c
615             } @rules ) {
616 2         7 $self->_croak(
617             'Attempt to switch from state "', $state->name, '"',
618             ' improperly found multiple destination states: "',
619 1         4 join('", "', map { $_->{state}->name } $rule, @new), '"'
620             );
621             }
622             }
623              
624             # We're good to go.
625 107         269 $fsa->{exec} = $rule->{exec};
626 107 100       286 $state->message($rule->{message}) if defined $rule->{message};
627 107         353 $next = $self->curr_state($rule->{state});
628 106         186 last;
629             }
630 107         216 return $next;
631             }
632              
633             ##############################################################################
634              
635             =head3 switch
636              
637             my $state = eval { $fsa->switch(@inputs) };
638             print "No can do" if $@;
639              
640             The fatal form of C. This method attempts to switch states and
641             returns the FSA::State object on success and throws an exception on failure.
642              
643             =cut
644              
645             sub switch {
646 102     102 1 277 my $self = shift;
647 102         208 my $ret = $self->try_switch(@_);
648 101 100       439 return $ret if defined $ret;
649 1         5 $self->_croak(
650             'Cannot determine transition from state "',
651             $machines{$self}->{current}->name, '"'
652             );
653             }
654              
655             ##############################################################################
656              
657             =head3 done
658              
659             my $done = $fsa->done;
660             $fsa->done($done);
661             $fsa->done( sub {...} );
662              
663             Get or set a value to indicate whether the engine is done running. Or set it
664             to a code reference to have that code reference called each time C is
665             called without arguments and have I return value returned. A code
666             reference should expect the FSA::Rules object passed in as its only argument.
667             Note that this varies from the pattern for state actions, which should expect
668             the relevant FSA::State object to be passed as the argument. Call the
669             C method on the FSA::Rules object if you want the current state
670             in your C code reference.
671              
672             This method can be useful for checking to see if your state engine is done
673             running, and calling C when it isn't. States can set it to a true
674             value when they consider processing complete, or you can use a code reference
675             that determines whether the machine is done. Something like this:
676              
677             my $fsa = FSA::Rules->new(
678             foo => {
679             do => { $_[0]->machine->done(1) if ++$_[0]->{count} >= 5 },
680             rules => [ foo => 1 ],
681             }
682             );
683              
684             Or this:
685              
686             my $fsa = FSA::Rules->new(
687             foo => {
688             do => { ++shift->machine->{count} },
689             rules => [ foo => 1 ],
690             }
691             );
692             $fsa->done( sub { shift->{count} >= 5 });
693              
694             Then you can just run the state engine, checking C to find out when
695             it's, uh, done.
696              
697             $fsa->start;
698             $fsa->switch until $fsa->done;
699              
700             Although you could just use the C method if you wanted to do that.
701              
702             Note that C will be reset to C by a call to C when it's
703             not a code reference. If it I a code reference, you need to be sure to
704             write it in such a way that it knows that things have been reset (by examining
705             states, for example, all of which will have been removed by C).
706              
707             =cut
708              
709             sub done {
710 76     76 1 4230 my $self = shift;
711 76         122 my $fsa = $machines{$self};
712 76 100       186 if (@_) {
713 23         38 $fsa->{done} = shift;
714 23         84 return $self;
715             }
716 53         66 my $code = $fsa->{done};
717 53 100       228 return $code unless ref $code eq 'CODE';
718 6         13 return $code->($self);
719             }
720              
721             ##############################################################################
722              
723             =head3 strict
724              
725             my $strict = $fsa->strict;
726             $fsa->strict(1);
727              
728             Get or set the C attribute of the state machine. When set to true, the
729             strict attribute disallows the short-circuiting of rules and allows a transfer
730             if only one rule returns a true value. If more than one rule evaluates to
731             true, an exception will be thrown.
732              
733             =cut
734              
735             sub strict {
736 24     24 1 40 my $self = shift;
737 24 100       153 return $machines{$self}->{strict} unless @_;
738 5         20 $machines{$self}->{strict} = shift;
739 5         10 return $self;
740             }
741              
742             ##############################################################################
743              
744             =head3 run
745              
746             $fsa->run;
747              
748             This method starts the FSA::Rules engine (if it hasn't already been set to a
749             state) by calling C, and then calls the C method repeatedly
750             until C returns a true value. In other words, it's a convenient
751             shortcut for:
752              
753             $fsa->start unless $self->curr_state;
754             $fsa->switch until $self->done;
755              
756             But be careful when calling this method. If you have no failed switches
757             between states and the states never set the C attribute to a true value,
758             then this method will never die or return, but run forever. So plan carefully!
759              
760             Returns the FSA::Rules object.
761              
762             =cut
763              
764             sub run {
765 6     6 1 15 my $self = shift;
766 6 100       22 $self->start unless $self->curr_state;
767 6         22 $self->switch until $self->done;
768 6         32 return $self;
769             }
770              
771             ##############################################################################
772              
773             =head3 reset
774              
775             $fsa->reset;
776              
777             The C method clears the stack and notes, sets the current state to
778             C, and sets C to C (unless C is a code reference).
779             Also clears any temporary data stored directly in the machine hash reference
780             and the state hash references. Use this method when you want to reuse your
781             state machine. Returns the DFA::Rules object.
782              
783             my $fsa = FSA::Rules->new(@state_machine);
784             $fsa->done(sub {$done});
785             $fsa->run;
786             # do a bunch of stuff
787             $fsa->{miscellaneous} = 42;
788             $fsa->reset->run;
789             # $fsa->{miscellaneous} does not exist
790              
791             =cut
792              
793             sub reset {
794 3     3 1 8 my $self = shift;
795 3         104 my $fsa = $machines{$self};
796 3         9 $fsa->{current} = undef;
797 3         7 $fsa->{notes} = {};
798 3 50       22 $fsa->{done} = undef unless ref $fsa->{done} eq 'CODE';
799 3         6 @{$fsa->{stack}} = ();
  3         23  
800 3         14 for my $state ($self->states) {
801 6         6 @{$states{$state}->{index}} = ();
  6         19  
802 6         26 delete $state->{$_} for keys %$state;
803             }
804 3         15 delete $self->{$_} for keys %$self;
805 3         13 return $self;
806             }
807              
808             ##############################################################################
809              
810             =head3 notes
811              
812             $fsa->notes($key => $value);
813             my $val = $fsa->notes($key);
814             my $notes = $fsa->notes;
815              
816             The C method provides a place to store arbitrary data in the state
817             machine, just in case you're not comfortable using the FSA::Rules object
818             itself, which is an empty hash. Any data stored here persists for the lifetime
819             of the state machine or until C is called.
820              
821             Conceptually, C contains a hash of key-value pairs.
822              
823             C<< $fsa->notes($key => $value) >> stores a new entry in this hash.
824             C<< $fsa->notes->($key) >> returns a previously stored value.
825             C<< $fsa->notes >>, called without arguments, returns a reference to the
826             entire hash of key-value pairs.
827              
828             Returns the FSA::Rules object when setting a note value.
829              
830             =cut
831              
832             sub notes {
833 25     25 1 36 my $self = shift;
834 25         58 my $fsa = $machines{$self};
835 25 100       79 return $fsa->{notes} unless @_;
836 22         30 my $key = shift;
837 22 100       100 return $fsa->{notes}{$key} unless @_;
838 10         34 $fsa->{notes}{$key} = shift;
839 10         41 return $self;
840             }
841              
842             ##############################################################################
843              
844             =head3 last_message
845              
846             my $message = $fsa->last_message;
847             $message = $fsa->last_message($state_name);
848              
849             Returns the last message of the current state. Pass in the name of a state to
850             get the last message for that state, instead.
851              
852             =cut
853              
854             sub last_message {
855 2     2 1 5 my $self = shift;
856 2 100       10 return $self->curr_state->message unless @_;
857 1         3 return $self->states(@_)->message;
858             }
859              
860             ##############################################################################
861              
862             =head3 last_result
863              
864             my $result = $fsa->last_result;
865             $result = $fsa->last_result($state_name);
866              
867             Returns the last result of the current state. Pass in the name of a state to
868             get the last result for that state, instead.
869              
870             =cut
871              
872             sub last_result {
873 2     2 1 4 my $self = shift;
874 2 100       10 return $self->curr_state->result unless @_;
875 1         3 return $self->states(@_)->result;
876             }
877              
878             ##############################################################################
879              
880             =head3 stack
881              
882             my $stack = $fsa->stack;
883              
884             Returns an array reference of all states the machine has been in since it was
885             created or since C was last called, beginning with the first state
886             and ending with the current state. No state name will be added to the stack
887             until the machine has entered that state. This method is useful for debugging.
888              
889             =cut
890              
891             sub stack {
892 3     3 1 7 my $self = shift;
893 3         5 return [map { $_->[0] } @{$machines{$self}->{stack}}];
  9         29  
  3         16  
894             }
895              
896             ##############################################################################
897              
898             =head3 raw_stacktrace
899              
900             my $stacktrace = $fsa->raw_stacktrace;
901              
902             Similar to C, this method returns an array reference of the states
903             that the machine has been in. Each state is an array reference with two
904             elements. The first element is the name of the state and the second element is
905             a hash reference with two keys, "result" and "message". These are set to the
906             values (if used) set by the C and C methods on the
907             corresponding FSA::State objects.
908              
909             A sample state:
910              
911             [
912             some_state,
913             {
914             result => 7,
915             message => 'A human readable message'
916             }
917             ]
918              
919             =cut
920              
921 18     18 1 1322 sub raw_stacktrace { $machines{shift()}->{stack} }
922              
923             ##############################################################################
924              
925             =head3 stacktrace
926              
927             my $trace = $fsa->stacktrace;
928              
929             Similar to C, except that the Cs and Cs are
930             output in a human readable format with nicely formatted data (using
931             Data::Dumper). Functionally there is no difference from C
932             unless your states are storing references in their Cs or Cs
933              
934             For example, if your state machine ran for only three states, the output may
935             resemble the following:
936              
937             State: foo
938             {
939             message => 'some message',
940             result => 'a'
941             }
942              
943             State: bar
944             {
945             message => 'another message',
946             result => [0, 1, 2]
947             }
948              
949             State: bar
950             {
951             message => 'and yet another message',
952             result => 2
953             }
954              
955             =cut
956              
957             sub stacktrace {
958 1     1 1 4 my $states = shift->raw_stacktrace;
959 1         2 my $stacktrace = '';
960 1         842 require Data::Dumper;
961 1         6352 local $Data::Dumper::Terse = 1;
962 1         2 local $Data::Dumper::Indent = 1;
963 1         2 local $Data::Dumper::Quotekeys = 0;
964 1         2 local $Data::Dumper::Sortkeys = 1;
965 1         2 local $Data::Dumper::Useperl = $] < 5.008;
966 1         4 foreach my $state (@$states) {
967 4         12 $stacktrace .= "State: $state->[0]\n";
968 4         12 $stacktrace .= Data::Dumper::Dumper($state->[1]);
969 4         265 $stacktrace .= "\n";
970             }
971 1         5 return $stacktrace;
972             }
973              
974             ##############################################################################
975              
976             =head3 graph
977              
978             my $graph_viz = $fsa->graph(@graph_viz_args);
979             $graph_viz = $fsa->graph(\%params, @graph_viz_args);
980              
981             Constructs and returns a L object useful for generating
982             graphical representations of the complete rules engine. The parameters to
983             C are all those supported by the GraphViz constructor; consult the
984             L documentation for details.
985              
986             Each node in the graph represents a single state. The label for each node in
987             the graph will be either the state label or if there is no label, the state
988             name.
989              
990             Each edge in the graph represents a rule that defines the relationship between
991             two states. If a rule is specified as a hash reference, the C key
992             will be used as the edge label; otherwise the label will be blank.
993              
994             An optional hash reference of parameters may be passed as the first argument
995             to C. The supported parameters are:
996              
997             =over
998              
999             =item with_state_name
1000              
1001             This parameter, if set to true, prepends the name of the state and two
1002             newlines to the label for each node. If a state has no label, then the state
1003             name is simply used, regardless. Defaults to false.
1004              
1005             =item wrap_nodes
1006              
1007             =item wrap_node_labels
1008              
1009             This parameter, if set to true, will wrap the node label text. This can be
1010             useful if the label is long. The line length is determined by the
1011             C parameter. Defaults to false.
1012              
1013             =item wrap_edge_labels
1014              
1015             =item wrap_labels
1016              
1017             This parameter, if set to true, will wrap the edge text. This can be useful if
1018             the rule message is long. The line length is determined by the C
1019             parameter. Defaults to false C is deprecated and will be removed
1020             in a future version.
1021              
1022             =item text_wrap
1023              
1024             =item wrap_length
1025              
1026             The line length to use for wrapping text when C or C
1027             is set to true. C is deprecated and will be removed in a future
1028             version. Defaults to 25.
1029              
1030             =item node_params
1031              
1032             A hash reference of parameters to be passed to the GraphViz C
1033             method when setting up a state as a node. Only the C
1034             ignored. See the C documentation for the list of
1035             supported parameters.
1036              
1037             =item edge_params
1038              
1039             A hash reference of parameters to be passed to the GraphViz C
1040             method when setting up a state as a node. See the
1041             C documentation for the list of supported
1042             parameters.
1043              
1044             =back
1045              
1046             B If either C or C is not available on your
1047             system, C will simply will warn and return.
1048              
1049             =cut
1050              
1051             sub graph {
1052 0     0 1 0 my $self = shift;
1053 0 0       0 my $params = ref $_[0] ? shift : {};
1054              
1055 0         0 eval "use GraphViz 2.00; use Text::Wrap";
1056 0 0       0 if ($@) {
1057 0         0 warn "Cannot create graph object: $@";
1058 0         0 return;
1059             }
1060              
1061             # Handle backwards compatibility.
1062 0 0       0 $params->{wrap_node_labels} = $params->{wrap_nodes}
1063             unless exists $params->{wrap_node_labels};
1064 0 0       0 $params->{wrap_edge_labels} = $params->{wrap_labels}
1065             unless exists $params->{wrap_edge_labels};
1066 0 0       0 $params->{wrap_length} = $params->{text_wrap}
1067             unless exists $params->{wrap_length};
1068              
1069             # Set up defaults.
1070 0   0     0 local $Text::Wrap::columns = $params->{wrap_length} || 25;
1071 0 0       0 my @node_params = %{ $params->{node_params} || {} };
  0         0  
1072 0 0       0 my @edge_params = %{ $params->{edge_params} || {} };
  0         0  
1073              
1074             # Iterate over the states.
1075 0         0 my $machine = $machines{$self};
1076 0         0 my $graph = GraphViz->new(@_);
1077 0         0 for my $state (@{ $machine->{ord} }) {
  0         0  
1078 0         0 my $def = $states{$state};
1079 0         0 my $name = $def->{name};
1080              
1081 0 0       0 my $label = !$def->{label} ? $name
    0          
1082             : $params->{with_state_name} ? "$name\n\n$def->{label}"
1083             : $def->{label};
1084              
1085 0 0       0 $graph->add_node(
1086             $name,
1087             @node_params,
1088             label => $params->{wrap_node_labels} ? wrap('', '', $label) : $label,
1089             );
1090 0 0       0 next unless exists $def->{rules};
1091 0         0 for my $condition (@{ $def->{rules} }) {
  0         0  
1092 0         0 my $rule = $condition->{state}->name;
1093 0         0 my @edge = ($name => $rule);
1094 0 0       0 if ($condition->{message}) {
1095 0 0       0 push @edge, label => $params->{wrap_edge_labels}
1096             ? wrap('', '', $condition->{message})
1097             : $condition->{message};
1098             }
1099 0         0 $graph->add_edge( @edge, @edge_params );
1100             }
1101             }
1102 0         0 return $graph;
1103             }
1104              
1105             ##############################################################################
1106              
1107             =head3 DESTROY
1108              
1109             This method cleans up an FSA::Rules object's internal data when it is released
1110             from memory. In general, you don't have to worry about the C method
1111             unless you're subclassing FSA::Rules. In that case, if you implement your own
1112             C method, just be sure to call C to prevent
1113             memory leaks.
1114              
1115             =cut
1116              
1117             # This method deletes the record from %machines, which has a reference to each
1118             # state, so those are deleted too. Each state refers back to the FSA::Rules
1119             # object itself, so as each of them is destroyed, it's removed from %states
1120             # and the FSA::Rules object gets all of its references defined in this file
1121             # freed, too. No circular references, so no problem.
1122              
1123 36     36   2977 sub DESTROY { delete $machines{+shift}; }
1124              
1125             ##############################################################################
1126              
1127             # Private error handler.
1128             sub _croak {
1129 9     9   15 shift;
1130 9         69 require Carp;
1131 9         1724 Carp::croak(@_);
1132             }
1133              
1134             ##############################################################################
1135              
1136             =begin comment
1137              
1138             Let's just keep the STORABLE methods hidden. They should just magically work.
1139              
1140             =head3 STORABLE_freeze
1141              
1142             =cut
1143              
1144             sub STORABLE_freeze {
1145 1     1 1 48 my ($self, $clone) = @_;
1146 1 50       5 return if $clone;
1147 1         4 my $fsa = $machines{$self};
1148 1         5 return ( $self, [ { %$self }, $fsa, @states{ @{ $fsa->{ord} } } ] );
  1         121  
1149             }
1150              
1151             ##############################################################################
1152              
1153             =head3 STORABLE_thaw
1154              
1155             =end comment
1156              
1157             =cut
1158              
1159             sub STORABLE_thaw {
1160 1     1 1 4733 my ($self, $clone, $junk, $data) = @_;
1161 1 50       6 return if $clone;
1162 1         10 %{ $self } = %{ shift @$data };
  1         3  
  1         4  
1163 1         3 my $fsa = shift @$data;
1164 1         3 $machines{ $self } = $fsa;
1165 1         4 @states{ @{ $fsa->{ord} } } = @$data;
  1         6  
1166 1         12 return $self;
1167             }
1168              
1169             ##############################################################################
1170              
1171             package FSA::State;
1172             $FSA::State::VERSION = '0.34';
1173              
1174             =head1 FSA::State Interface
1175              
1176             FSA::State objects represent individual states in a state machine. They are
1177             passed as the first argument to state actions, where their methods can be
1178             called to handle various parts of the processing, set up messages and results,
1179             or access the state machine object itself.
1180              
1181             Like FSA::Rules objects, FSA::State objects are empty hashes, so you can feel
1182             free to stash data in them. But note that each state object is independent of
1183             all others, so if you want to stash data for other states to access, you'll
1184             likely have to stash it in the state machine object (in its hash
1185             implementation or via the C method), or retrieve other states from
1186             the state machine using its C method and then access their hash data
1187             directly.
1188              
1189             =head2 Constructor
1190              
1191             =head3 new
1192              
1193             my $state = FSA::State->new;
1194              
1195             Constructs and returns a new FSA::State object. Not intended to be called
1196             directly, but by FSA::Rules.
1197              
1198             =cut
1199              
1200             sub new {
1201 59     59   90 my $class = shift;
1202 59         197 return bless {@_} => $class;
1203             }
1204              
1205             ##############################################################################
1206              
1207             =head2 Instance Methods
1208              
1209             =head3 name
1210              
1211             my $name = $state->name;
1212              
1213             Returns the name of the state.
1214              
1215             =cut
1216              
1217 244     244   3088 sub name { $states{shift()}->{name} }
1218              
1219             ##############################################################################
1220              
1221             =head3 label
1222              
1223             my $label = $state->label;
1224              
1225             Returns the label of the state.
1226              
1227             =cut
1228              
1229 2     2   15 sub label { $states{shift()}->{label} }
1230              
1231             ##############################################################################
1232              
1233             =head3 machine
1234              
1235             my $machine = $state->machine;
1236              
1237             Returns the FSA::Rules object for which the state was defined.
1238              
1239             =cut
1240              
1241 216     216   1264 sub machine { $states{shift()}->{machine} }
1242              
1243             ##############################################################################
1244              
1245             =head3 result
1246              
1247             my $fsa = FSA::Rules->new(
1248             # ...
1249             some_state => {
1250             do => sub {
1251             my $state = shift;
1252             # Do stuff...
1253             $state->result(1); # We're done!
1254             },
1255             rules => [
1256             bad => sub { ! shift->result },
1257             good => sub { shift->result },
1258             ]
1259             },
1260             # ...
1261             );
1262              
1263             This is a useful method to store results on a per-state basis. Anything can be
1264             stored in the result slot. Each time the state is entered, it gets a new
1265             result slot. Call C without arguments in a scalar context to get the
1266             current result; call it without arguments in an array context to get all of
1267             the results for the state for each time it has been entered into, from first
1268             to last. The contents of each result slot can also be viewed in a
1269             C or C.
1270              
1271             =cut
1272              
1273             sub result {
1274 9     9   28 my $self = shift;
1275 9 100       30 return $self->_state_slot('result') unless @_;
1276             # XXX Yow!
1277 4         11 $machines{$self->machine}->{stack}[$states{$self}->{index}[-1]][1]{result}
1278             = shift;
1279 4         12 return $self;
1280             }
1281              
1282             ##############################################################################
1283              
1284             =head3 message
1285              
1286             my $fsa = FSA::Rules->new(
1287             # ...
1288             some_state => {
1289             do => sub {
1290             my $state = shift;
1291             # Do stuff...
1292             $state->message('hello ', $ENV{USER});
1293             },
1294             rules => [
1295             bad => sub { ! shift->message },
1296             good => sub { shift->message },
1297             ]
1298             },
1299             # ...
1300             );
1301              
1302             This is a useful method to store messages on a per-state basis. Anything can
1303             be stored in the message slot. Each time the state is entered, it gets a new
1304             message slot. Call C without arguments in a scalar context to get
1305             the current message; call it without arguments in an array context to get all
1306             of the messages for the state for each time it has been entered into, from
1307             first to last. The contents of each message slot can also be viewed in a
1308             C or C.
1309              
1310             =cut
1311              
1312             sub message {
1313 10     10   27 my $self = shift;
1314 10 100       37 return $self->_state_slot('message') unless @_;
1315             # XXX Yow!
1316 4         20 $machines{$self->machine}->{stack}[$states{$self}->{index}[-1]][1]{message}
1317             = join '', @_;
1318 4         10 return $self;
1319             }
1320              
1321             ##############################################################################
1322              
1323             =head3 prev_state
1324              
1325             my $prev = $state->prev_state;
1326              
1327             A shortcut for C<< $state->machine->prev_state >>.
1328              
1329             =head3 done
1330              
1331             my $done = $state->done;
1332             $state->done($done);
1333              
1334             A shortcut for C<< $state->machine->done >>. Note that, unlike C and
1335             C, the C attribute is stored machine-wide, rather than
1336             state-wide. You'll generally call it on the state object when you want to tell
1337             the machine that processing is complete.
1338              
1339             =head3 notes
1340              
1341             my $notes = $state->notes;
1342             $state->notes($notes);
1343              
1344             A shortcut for C<< $state->machine->notes >>. Note that, unlike C and
1345             C, notes are stored machine-wide, rather than state-wide. It is
1346             therefore probably the most convenient way to stash data for other states to
1347             access.
1348              
1349             =cut
1350              
1351 4     4   28 sub prev_state { shift->machine->prev_state(@_) }
1352 11     11   54 sub notes { shift->machine->notes(@_) }
1353 13     13   27 sub done { shift->machine->done(@_) }
1354              
1355             ##############################################################################
1356              
1357             =head3 enter
1358              
1359             Executes all of the C actions. Called by FSA::Rules's
1360             C method, and not intended to be called directly.
1361              
1362             =cut
1363              
1364             sub enter {
1365 141     141   166 my $self = shift;
1366 141         219 my $state = $states{$self};
1367 141         148 $_->($self) for @{$state->{on_enter}};
  141         347  
1368 141         205 return $self;
1369             }
1370              
1371             ##############################################################################
1372              
1373             =head3 do
1374              
1375             Executes all of the C actions. Called by FSA::Rules's C
1376             method, and not intended to be called directly.
1377              
1378             =cut
1379              
1380             sub do {
1381 141     141   159 my $self = shift;
1382 141         212 my $state = $states{$self};
1383 141         139 $_->($self) for @{$state->{do}};
  141         377  
1384 140         1020 return $self;
1385             }
1386              
1387             ##############################################################################
1388              
1389             =head3 exit
1390              
1391             Executes all of the C actions. Called by FSA::Rules's C
1392             method, and not intended to be called directly.
1393              
1394             =cut
1395              
1396             sub exit {
1397 109     109   127 my $self = shift;
1398 109         184 my $state = $states{$self};
1399 109         114 $_->($self) for @{$state->{on_exit}};
  109         283  
1400 109         204 return $self;
1401             }
1402              
1403             ##############################################################################
1404              
1405             =head3 DESTROY
1406              
1407             This method cleans up an FSA::State object's internal data when it is released
1408             from memory. In general, you don't have to worry about the C method
1409             unless you're subclassing FSA::State. In that case, if you implement your own
1410             C method, just be sure to call C to prevent
1411             memory leaks.
1412              
1413             =cut
1414              
1415 61     61   1456 sub DESTROY { delete $states{+shift}; }
1416              
1417             ##############################################################################
1418              
1419             # Used by message() and result() to get messages and results from the stack.
1420              
1421             sub _state_slot {
1422 11     11   20 my ($self, $slot) = @_;
1423 11         54 my $trace = $self->machine->raw_stacktrace;
1424 11         19 my $state = $states{$self};
1425             return wantarray
1426 11 100       66 ? map { $_->[1]{$slot} } @{$trace}[@{$state->{index}} ]
  5         32  
  3         17  
  3         9  
1427             : $trace->[$state->{index}[-1]][1]{$slot};
1428             }
1429              
1430             ##############################################################################
1431             # Called by FSA::Rules->try_switch to get a list of the rules. I wonder if
1432             # rules should become objects one day?
1433              
1434             sub _rules {
1435 109     109   119 my $self = shift;
1436 109         166 my $state = $states{$self};
1437 109         105 return @{$state->{rules}}
  109         337  
1438             }
1439              
1440             1;
1441             __END__