Acme-FSM
view release on metacpan or search on metacpan
=item I<$action>
Special flag associated with next I<{state}> in a I<[turn]>
(covered in details in L<B<process()>|/process()> method description).
B<(note)> It may be applied to a I<[turn]> of I<{fst}> or I<{state}>.
=item blackboard
Shamelesly stolen from B<DMA::FSM>.
Originally, it meant some opaque HASH passed around in B<DMA::FSM::FSM()>
where
user-code could store it's own ideas and such.
Now it's an object blessed into B<Acme::FSM>
(for user-code still, I<{fsm}> has nothing to do with it).
=item entry
Layout of state records (see below) isn't particularly complicated.
However it's far from being uniform.
I<entry> is a placeholder name for any component of (unspecified) state record
when any semantics is irrelevant.
See also I<[turn]> and B<switch()> below.
=item Finite State Machine
B<Acme::FSM>.
=item Finite State Table
=item I<{fst}>
Collection of state records.
=item FSM
Acronym for I<Finite State Machine>.
See above.
=item FST
Acronym for I<Finite State Table>.
See above.
=item internal state
=item I<{fsm}>
Some open-ended set of parameters dictating FSM's behaviour.
=item item
=item I<$item>
Something that makes sense for user-code.
B<Acme::FSM> treats it as scalar with no internals
(mostly;
one exception is covered in L<B<diag()> method|/diag()> description).
=item I<$namespace>
B<A::F> uses elaborate schema to reach various callbacks
(three of them, at the time of writing).
This optional parameter is in use by this schema.
L<B<query()> method|/query()> has more.
=item I<$rule>
A scalar identifing a I<[turn]>.
One of opaque scalars returned by B<switch()> callback
(the other is processed (modified or not) I<$item>).
L<B<process()> method|/process()> description has more.
=item B<source()>
Special piece of user-code
(it's required at construction (L<B<connect()> method|/connect()>),
L<B<query_source()> method|/query_source()> describes how FSM reaches it).
Whatever it returns (in explicit scalar context) becomes I<$item>.
=item I<$state>
A scalar identifing a I<{state}> (see below) or parameter of I<{fsm}> (see
above).
=item state flow
Just like control flow but for I<$state>s.
=item state record
=item I<{state}>
One record in I<{fst}>.
The record consists of entries (see above).
L<B<process()>|/process()> method description has more.
Should be noted, in most cases "I<{state}>" should be read as
"I<$state> I<{state}>" instead,
but that starts to smell bufallo.
=item B<switch()>
A mandatory callback associated with every I<{state}>.
L<B<process()> method|/process()> and
L<B<query_switch()> method|/query_switch()>
descriptions have more.
=item I<$turn>
No such thing.
It's I<$rule> instead (see above).
=item I<[turn]>
Specially crafted entry in I<{state}>
(covered in details in L<B<process()> method|/process()> description).
Such entry describes what next I<$state> should be picked in state flow
and what to do with I<$item>.
=item turn map
This idiom is used in place of "C<turns> I<$rule> of I<[turn]>".
elsif( 2 == @_ ) {
return $self->{_}{fst}{shift @_}{shift @_} }
elsif( 3 == @_ ) {
my $backup = $self->fst( $_[0] => $_[1] );
$self->diag( 3, q|%s {%s}{%s} entry|,
( $backup ? q|updating| : q|creating| ), @_[0,1] );
$self->{_}{fst}{shift @_}{shift @_} = pop @_;
return $backup }
else {
$self->carp( sprintf q|too many args (%i)|, scalar @_ );
return undef }}
=item B<turn()>
$bb->turn( $state ) eq '' or die;
$bb->turn( $state => 'uturn' )->[1] eq 'NEXT' or die;
Queries I<[turn]>s of arbitrary I<{state}>s.
B<turn()> doesn't manipulate entries, use L<B<fst()> method|/fst()> instead
L<if you can|/turn() and fst()>.
Modes:
=over
=item query expected behaviour
This mode is entered if there is lone scalar.
Such scalar is believed to be I<$state>.
Returns something that describes what kind of least special states are
present.
Namely:
=over
=item *
C<undef> is returned if I<$state> isn't present in the I<{fst}>
(also B<carp>s).
Also see below.
=item *
Empty string is returned if there're I<tturn> and/or I<fturn> turns.
I<turns> hash is ignored in that case.
=item *
C<HASH> is returned if there's turn map
(and neither I<tturn> nor I<fturn> is present).
B<(note)> In that case, B<turn()> checks for I<turns> is indeed a HASH,
nothing more
(however B<croaks> if that's not the case);
It may as well be empty;
Design legacy.
=item *
Returns C<HASH> for C<STOP> and C<BREAK> I<$state>s without any further
processing
(For those I<$state>s any I<$rule> is ignored and C<HASH> enables I<switch()>
callbacks to give more informative logs
(while that information is mangled anyway);
Probably bad idea).
=item *
C<undef> is returned if there's nothing to say --
neither I<tturn>, nor I<fturn>, nor turn map --
this record is kind of void.
The record should be studied to find out why.
B<carp>s in that case.
=back
=item query specific I<[turn]>
Two scalars are I<$state> and specially encoded I<$rule>
(refer to L<B<query_switch()> method|/query_switch()> about encoding).
If I<$rule> can't be decoded then B<croak>s.
Returns (after verification) requested I<$rule> as ARRAY.
While straightforward I<[turn]>s (such as C<tturn>, C<fturn>, and such) could
be in fact queried through L<B<fst()> method|/fst()> turn map needs bit more
sophisticated handling;
and that's what B<turn()> does;
in fact asking for C<turns> will result in B<croak>.
I<$action> of C<START> and C<CONTINUE> special states suffer implicit
defaulting to empty string.
=item anything else
No arguments or more then two is an non-fatal error.
Returns C<undef> (with B<carp>).
=back
=cut
# TODO:202202172011:whynot: As soon as supported perl is young enough change it to smartmatch, plz.
my %special_turns = map { $_ => 1 } qw| eturn uturn tturn fturn |;
# TODO:202202162030:whynot: Consider more elaborate (informative) returns.
sub turn {
my $self = shift @_;
unless( @_ ) {
$self->carp( q|no args| ); return undef }
elsif( 1 == @_ && !exists $self->{_}{fst}{$_[0]} ) {
$self->carp( qq|($_[0]): no such {fst} record| );
return undef }
elsif( 1 == @_ ) {
my $state = shift @_;
my $entry = $self->verify(
$self->{_}{fst}{$state}, $state, '', q|entry|, q|HASH| );
# WORKAROUND:201305070051:whynot: Otherwise there will be spurious B<carp>s about anyway useless turns in those entries.
$state eq q|STOP| || $state eq q|BREAK| and return q|HASH|;
exists $entry->{tturn} || exists $entry->{fturn} and return '';
unless( exists $entry->{turns} ) {
# XXX:201305071531:whynot: Should just B<croak> instead, probably.
$self->carp( qq|{$state}: none supported turn| );
return undef }
$self->verify( $entry->{turns}, $state, q|turns|, q|turn|, q|HASH| );
return q|HASH| }
elsif( 2 == @_ ) {
Returns I<$item> and result of L<I<dumper> callback|/dumper>.
=cut
sub query_source {
my $self = shift @_;
# WORKAROUND:20121229001530:whynot: No B<verify()>, I<{source}> can return anything.
my $item = $self->query( $self->{_}{source}, q|{source}|, @_ );
return $item, $self->query_dumper( $item ) }
=item B<query_dumper()>
$dump = $self->query_dumper( $item );
Seeks I<dumper> callback (L<configured at construction time|/dumper>).
If the callback wasn't configured uses simple hopefully informative and
C<undef> proof substitution.
Whatever the callback returns is checked to be B<defined>
(C<undef> is changed to C<"(unclear)">)
and then returned.
=cut
sub query_dumper {
my $self = shift @_;
return $self->verify(
$self->query(
# TODO:202202210258:whynot: This is inefficient, defaulting should happen in B<connect()> instead.
$self->{_}{dumper} // sub { sprintf q|(%s)|, $_[1] // q|undef| },
q|{dumper}|, @_ ) // q|(unclear)|,
# XXX:202202210304:whynot: 'source' looks like remnants of refactoring. Should investigate it deeper.
$self->state, qw| source source |, '' ) }
=item B<diag()>
$bb->diag( 3, 'going to die at %i.', __LINE__ );
Internal.
Provides unified and single-point-of-failure way to output diagnostics.
Intensity is under control of
L<I<diag_level> configuration parameter|/diag_level>.
Each object has it's own,
however it's inherited when objects are copied.
Defined levels are:
=over
=item C<0>
Nothing at all.
Even error reporting is suppressed.
=item C<1>
Default.
Errors of here-be-dragons type.
=item C<2>
Basic diagnostics for callbacks.
=item C<3>
Basic trace.
Construction, starting and leaving runs.
=item C<4>
Extended diagnostics for callbacks.
=item C<5>
Deep trace.
By the way diagnostics of I<switch> entry resolving.
=back
=cut
sub diag {
my $self = shift @_;
$self->{_}{diag_level} >= shift @_ or return $self;
# TODO:202212222141:whynot: Since something this B<sprintf> might emit warnings. And maybe it's appropriate.
printf STDERR sprintf( qq|[%s]: %s\n|,
( split m{::}, ( caller 1 )[3])[-1], shift @_ ),
map $_ // q|(undef)|, @_;
return $self }
=item B<carp()>
$bb->carp( 'something wrong...' );
Internal.
B<carp>s consistently if I<{_}{diag_level}> is B<gt> C<0>.
=back
=cut
sub carp {
my $self = shift @_;
$self->{_}{diag_level} >= 1 or return;
unshift @_, sprintf q|[%s]: |, ( split m{::}, ( caller 1 )[3])[-1];
&Carp::carp }
=head1 BUGS AND CAVEATS
=over
=item Default For Turn Map
B<(missing feature)>
It's not hard to imagine application of rather limited turn map that should
default on anything else deemed irrelevant.
Right now to achieve logic like this such defaulting ought to be merged into
B<switch()>.
That's insane.
=item Diagnostics
B<(misdesign)>
Mechanics behind diagnostics isa failure.
It's messy, fragile, misguided, and (honestly) premature.
At the moment it's useless.
=item Hash vs HASH vs Acme::FSM Ref Constructors
B<(messy, probably bug)>
L<B<connect()> method description|/connect()> _declares_ that list is copied.
( run in 3.050 seconds using v1.01-cache-2.11-cpan-140bd7fdf52 )