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