File Coverage

blib/lib/FSM/Tiny.pm
Criterion Covered Total %
statement 71 82 86.5
branch 16 28 57.1
condition 4 4 100.0
subroutine 16 18 88.8
pod 4 5 80.0
total 111 137 81.0


line stmt bran cond sub pod time code
1             package FSM::Tiny;
2              
3             # http://www.crsr.net/Programming_Languages/PerlAutomata.html
4 2     2   99156 use strict;
  2         5  
  2         87  
5 2     2   10 use warnings;
  2         5  
  2         126  
6              
7             our $VERSION = '0.03';
8             our $DEBUG = 0;
9              
10 2     2   2949 use Class::Accessor::Lite;
  2         3059  
  2         16  
11              
12             my %Defaults = (
13             current => '',
14             (map { $_ => {} } qw/rules context/),
15             (map { $_ => sub {} } qw/on_enter on_exit on_transition/)
16             );
17              
18             Class::Accessor::Lite->mk_accessors(keys %Defaults);
19              
20             sub new {
21 1     1 1 21 my $package = shift;
22 1 50       5 my %args = $_[1] ? %{ @_ } : %{ $_[0] };
  0         0  
  1         6  
23 1         10 my $self = bless +{ %Defaults, %args }, $package;
24              
25 1         3 for my $key (keys %{ $self->rules }) {
  1         5  
26 0         0 my $s = $self->rules->{$key};
27 0 0       0 if (my $r = ref $s) {
28 0 0       0 if ($r eq 'ARRAY') {
    0          
29 0         0 $self->register($key, @$s);
30             }
31             elsif ($r eq 'CODE') {
32 0         0 $self->register($key, $s);
33             }
34             }
35             else {
36 0         0 delete $self->rules->{$key};
37             }
38             }
39              
40 1         14 return $self;
41             }
42              
43 44 50   44   194 sub _log { warn "[FSM::Simele DEBUG] ".join(' ', @_) . "\n" if $DEBUG }
44              
45             sub register {
46 3     3 1 35 my $self = shift;
47 3         6 my ($key, $code, $guards) = @_;
48 3 100       8 $self->current($key) unless $self->current;
49 3   100     28 $guards ||= [];
50 3         8 _log("register: ${key}");
51 3         13 $self->rules->{$key} = FSM::Tiny::State->new(
52             code => $code,
53             guards => $guards
54             );
55             }
56              
57             sub unregister {
58 0     0 0 0 my ($self, $key) = @_;
59 0         0 _log("unregister: ${key}");
60 0         0 delete $self->rules->{$key};
61             }
62              
63             sub step {
64 42     42 1 56 my $self = shift;
65 42 50       80 my $st = $self->rules->{$self->current} or return;
66 42         270 $st->run($self->context);
67 42 100       154 my $next = $st->next($self->context) or return;
68 41         101 $self->current($next);
69 41         207 _log("next -> " . $self->current);
70 41         95 return 1;
71             }
72              
73             sub run {
74 1     1 1 8 my $self = shift;
75 1 50       2 $self->context(+{ %{ $self->context }, %{ $_[0] || {} } });
  1         5  
  1         11  
76 1         7 local $_ = $self->context;
77 1         6 $self->on_enter->($self->context);
78 1         11 while (1) {
79 42 100       229 if (!@{$self->rules->{$self->current}{guards}}) {
  42         104  
80 1         8 $self->step;
81 1         2 last;
82             }
83 41 50       241 $self->step or last;
84 41         79 $self->on_transition->($self->context);
85             }
86 1         2 $self->on_exit->($self->context);
87 1         9 $self;
88             }
89              
90             package FSM::Tiny::State;
91              
92             sub new {
93 3     3   4 my $package = shift;
94 3         9 my %args = @_;
95 3 50       5 my @guards = @{ $args{guards} || [] };
  3         12  
96 3         5 my @list;
97 3         8 while (@guards) {
98 3         7 my ($key, $code) = splice @guards, 0, 2;
99             push @list, FSM::Tiny::Guard->new(
100             key => $key,
101 3 100 100 20   40 code => (ref($code) || '') ne 'CODE' ? sub { $code } : $code
  20         61  
102             );
103             }
104 3         7 $args{guards} = \@list;
105 3         15 bless \%args, $package;
106             }
107              
108             sub next {
109 42     42   134 my ($self, $context) = @_;
110 42         39 for my $guard (@{ $self->{guards} }) {
  42         69  
111 42 100       65 return $guard->key if $guard->check($context);
112             }
113 1         3 return '';
114             }
115              
116             sub run {
117 42     42   132 my ($self, $context) = @_;
118 42         40 local $_ = $context;
119 42         102 $self->{code}->($context);
120             }
121              
122             package FSM::Tiny::Guard;
123              
124 41     41   178 sub key { shift->{key} }
125              
126 42     42   81 sub code { shift->{code} }
127              
128             sub new {
129 3     3   5 my $package = shift;
130 3         8 my %args = @_;
131 3     0   37 bless +{ key => '', code => sub { 1 }, %args }, $package;
  0         0  
132             }
133              
134             sub check {
135 42     42   616 my ($self, $context) = @_;
136 42         58 return $self->code->($context);
137             }
138              
139             1;
140             __END__