Acme-FSM

 view release on metacpan or  search on metacpan

lib/FSM.pm  view on Meta::CPAN

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 == @_                                   ) {
        my( $state, $turn ) = @_;
        my $entry;
        $self->verify( $turn, $state, $turn, q|turn|, '' );
        if( exists $special_turns{$turn} )                                {
                                   $entry = $self->{_}{fst}{$state}{$turn} }
        elsif( !index $turn, q|turn%|    )                                {
                  $entry = $self->{_}{fst}{$state}{turns}{substr $turn, 5} }
        else                                                              {
            croak sprintf q|[turn]: {%s}(%s): unknown turn|, $state, $turn }
        $self->verify( $entry, $state, $turn, q|turn|, q|ARRAY| );
        $self->verify( $entry->[0], $state, $turn, q|state|, '' );
# XXX:20121230140241:whynot: {START}{turn}{action} is ignored anyway.
# XXX:201305072006:whynot: {CONTINUE}{turn}{action} is ignored too.
        $entry->[1] //= ''     if $state eq q|START| || $state eq q|CONTINUE|;
        $self->verify( $entry->[1], $state, $turn, q|action|, '' );
                                          return $entry }
    else                                               {
        $self->carp( sprintf q|too many args (%i)|, scalar @_ );
                                           return undef }
}

=item B<action()>

    $bb->action eq $action and die;
    $action = $bb->action( $new_action );

Queries and sets I<$action> of B<A::F> instance.
Modes:

=over

lib/FSM.pm  view on Meta::CPAN

=item B<query_source()>

    ( $item, $dump ) = $self->query_source;

Seeks B<source()> callback and acquires whatever it returns.
The callback is called in scalar context.
As useful feature, also feeds I<$item> to L<dumper callback|/query_dumper()>.
L<B<query()> method|/query()> has detailed description how B<source()>
callback is acquired.
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

lib/FSM.pm  view on Meta::CPAN


B<(basic trace)>, L<B<connect()> method|/connect()>.
During class construction FST has been found.
That FST consists of I<%i> items.
Those items are number of I<$state>s and not I<$state>s plus I<{state}>s.

=item C<[connect]: FST has no {START} state>

B<(warning)>, L<B<connect()> method|/connect()>.
I<START> I<{state}> is required.
It's missing.
This situation will definetely result in devastating failure later.

=item C<[connect]: FST has no {STOP} state>

B<(warning)>, L<B<connect()> method|/connect()>.
I<STOP> I<{state}> is required.
It's missing.
If FST is such that I<STOP> I<$state> can't be reached
(for whatever reasons)
this may be cosmetic.

=item C<[connect]: ignoring (%i) FST items in trailer>

B<(warning)>, L<B<connect()> method|/connect()>.
A trailer has been found and was ignored.
Beware, I<%i> could be misleading.
For HASH in trailer that HASH is considered and key number is reported.
For list in trailer an B<item> number in list is reported, even if it's twice
as many in FST (what isa hash).
Just because.

=item C<[connect]: (source): unset>

B<(warning)>, L<B<connect()> method|/connect()>.
I<$source> option isa unset, it should be.
There's no other way to feed items in.
This situation will definetely result in devastating failure later.

=item C<[connect]: stealing (%i) items in FST>

B<(basic trace)>, L<B<connect()> method|/connect()>.
During object construction FST has been found.
That FST consists of I<%i> items.
Those items are I<{state}>s, not I<$state>s plus I<{state}>s.

=item C<[fst]: creating {%s} record>

B<(basic trace)>, L<B<fst()> method|/fst()>.
New state record has been created, named I<%s>.

=item C<[fst]: creating {%s}{%s} entry>

B<(basic trace)>, L<B<fst()> method|/fst()>.
New entry named I<%s> (the latter) has been created in record named I<%s> (the
former).

=item C<[fst]: no args>

B<(warning)>, L<B<fst()> method|/fst()>.
No arguments, it's an error.
However, instead of promptly dieing, C<undef> is returned
in blind hope this will be devastating enough.

=item C<[fst]: (%s): no such {fst} record>

B<(warning)>, L<B<fst()> method|/fst()>.
Requested entry I<%s> is missing.
State record ought to exist beforehand for any usecase except
L<record creation|/set {state} of specific $state>.

=item C<[fst]: too many args (%i)>

B<(warning)>, L<B<fst()> method|/fst()>.
Too many args have been passed in.
And again, instead of prompt dieing C<undef> is returned.

=item C<[fst]: updating {%s} record>

B<(basic trace)>, L<B<fst()> method|/fst()>.
Named record (I<%s>) has been updated.

=item C<[fst]: updating {%s}{%s} entry>

B<(basic trace)>, L<B<fst()> method|/fst()>.
Named entry (I<%s>, the latter) in record I<%s> (the former) has been updated.

=item C<[process]: {%s}(%s): changing state: (CONTINUE)>

B<(basic trace)>, L<B<process()> method|/process()>.
Implicit change of state from C<BREAK> to C<CONTINUE> is about to happen.

=item C<[process]: {%s}(%s): entering>

B<(basic trace)>, L<B<process()> method|/process()>.
Entering state flow from I<$state> I<%s> (the former) with I<$action> I<%s>
(the latter).

=item C<[process]: {%s}(%s): going for>

B<(deep trace)>, L<B<process()> method|/process()>.
Trying to enter I<$state> I<%s> (the former) with I<$action> I<%s> (the
latter).

=item C<[process]: {%s}(%s): %s: going with>

B<(basic trace)>, L<B<process()> method|/process()>.
While in I<$state> I<%s> (1st) doing I<$action> I<%s> (2nd) the I<$item>
happens to be I<%s> (3rd).
C<undef> will look like this: C<((undef))>.

=item C<[process]: {%s}(%s): %s: turning with>

B<(basic trace)>, L<B<process()> method|/process()>.
B<switch()> (of I<$state> I<%s>, the 1st) has returned I<$rule> I<%s> (2nd)
and I<$item> I<%s>.
Now dealing with this.

=item C<[process]: {%s}(%s): leaving>

B<(basic trace)>, L<B<process()> method|/process()>.

lib/FSM.pm  view on Meta::CPAN


B<(deep trace)>, L<B<query()> method|/query()>.
Attempting to call I<%s> (the latter) method on object of I<%s> (the former)
class.

=item C<< [query]: [query_dumper]: going for <%s>::[%s] >>

=item C<< [query]: [query_source]: going for <%s>::[%s] >>

=item C<< [query]: [query_switch]: going for <%s>::[%s] >>

B<(deep trace)>, L<B<query()> method|/query()>.
Attempting to call I<%s> (the latter) subrouting of package I<%s> (the
former).

=item C<< [query]: [query_dumper]: object of <%s> can't [%s] method >>

=item C<< [query]: [query_source]: object of <%s> can't [%s] method >>

=item C<< [query]: [query_switch]: object of <%s> can't [%s] method >>

B<(croak)>, L<B<query()> method|/query()>.
The object of I<%s> (the former) class can't do I<%s> (the latter) method.

=item C<[state]: changing state: (%s) (%s)>

B<(deep trace)>, L<B<state()> method|/state()>.
Exposes change of state from previous (former I<%s>)
to current (latter I<%s>).

=item C<[state]: too many args (%i)>

B<(warning)>, L<B<state()> method|/state()>.
Obvious.
None or one argument is supposed.
B<state()> has returned C<undef> in this case,
most probably will bring havoc in a moment.

=item C<[turn]: (%s): no such {fst} record>

B<(warning)>, L<B<turn()> method|/turn()>.
Peeking for I<[turn]>s of I<%s> I<$state> yeilds nothing, there's no such
state.

=item C<[turn]: {%s}: none supported turn>

B<(warning)>, L<B<turn()> method|/turn()>.
Whatever content of I<%s> entry is FSM doesn't know how to handle it.

=item C<[turn]: {%s}(%s): unknown turn>

B<(croak)>, L<B<turn()> method|/turn()>.
There was request for I<[turn]> I<%s> (the latter) of I<$state> I<%s> (the
former).
While I<{state}> record has been found and is OK,
there is no such I<$rule>.

=item C<[turn]: no args>

B<(warning)>, L<B<turn()> method|/turn()>.
No argumets, it's an error.

=item C<[turn]: too many args (%i)>

B<(warning)>, L<B<turn()> method|/turn()>.
There's no way to handle that many (namely: I<%i>) arguments.

=item C<[verify]: {%s}{%s}: %s !isa defined>

B<(croak)>, L<B<verify()> method|/verify()>.
I<$rc> queried
from something in I<{fst}> related to I<%s> (3rd)
(value of which is I<%s> (2nd))
while in I<$state> I<%s> (1st)
isn't defined.

=item C<[verify]: {%s}{%s}: %s isa (%s), should be (%s)>

B<(croak)>, L<B<verify()> method|/verify()>.
B<ref> of I<$rc> queried
from something in I<{fst}> related to I<%s> (3rd)
(value of which is I<%s> (2nd))
while in I<$state> I<%s> (1st) is I<%s> (4th).
While it should be I<%s> (5th)
(the last one is literally I<$test>).

=back

=cut

=head1 EXAMPLES

Here are example records.
Whole I<{fst}>, honestly, might become enormous,
thus are skipped for brewity.

    alpha =>
    {
        switch => sub {
            shift % 42, ''
        },
        tturn   => [ qw/ alpha NEXT / ],
        fturn   => [ qw/ STOP horay! / ]
    }

B<source()> supposedly produces some numbers.
Then,
if I<$item> doesn't devide C<mod 42> then go for another number.
If I<$item> devides then break out.
Also, please note, C<STOP> (and C<BREAK>) is special --
it needs B<defined> I<$action> but it can be literally anything.

    bravo =>
    {
        switch => sub {
            my $item = shift;
            $item % 15 ? 'charlie' :
            $item % 5 ? 'delta' :
            $item % 3 ? 'echo' :
            undef, $item
        },



( run in 1.175 second using v1.01-cache-2.11-cpan-39bf76dae61 )