Acme-FSM

 view release on metacpan or  search on metacpan

lib/FSM.pm  view on Meta::CPAN


=cut

sub action                        {
    my $self = shift @_;
    unless( @_ )                 {
        return $self->{_}{action} }
    elsif( 1 == @_ )             {
        my $backup = $self->action;
        $self->diag( 5, q|changing action: (%s) (%s)|, $backup, $_[0] );
        $self->{_}{action} = shift @_;
        return $backup            }
    else                         {
        $self->carp( sprintf q|too many args (%i)|, scalar @_ );
        return undef              }}

=item B<query()>

    ( $alpha, $bravo ) = $self->query( $what, $name, @items );

Internal method, then it becomes complicated.
Resolves I<$what> (some callback, there multiple of them) against
I<$namespace>, if necessary.
Then invokes resolved code appropriately passing I<@items> in, if any;
Product of the callback over I<@items> is returned back to the caller.
I<$name> is used for disgnostics only.
Trust me, it all makes perfect sense.

I<$what> must be either CODE or scalar, or else.

Strategy is like this

=over

=item I<$what> isa CODE

I<$what> is invoked with I<$self> and I<@items> as arguments.
Important, in this case I<$self> is passed as first argument,
OO isn't involved like at all.

=item I<$namespace> is empty string

Trade I<$namespace> for I<$self> (see below) and continue.

=item I<$namespace> is scalar

Treat I<$what> as a name of function in I<$namespace> namespace.

=item I<$namespace> is object reference

Treat I<$name> as a name of method of object referred by I<$namespace>.

=back

It really works.

=cut

sub query                      {
    my( $self, $topic, $manifest ) = ( shift @_, shift @_, shift @_ );
    my $caller = ( split m{::}, ( caller 1 )[3] )[-1];
    defined $topic                  or croak sprintf q|[%s]: %s !isa defined|,
      $caller, $manifest, $self->state;
    $self->diag( 5, q|[%s]: %s isa (%s)|, $caller, $manifest, ref $topic );
    ref $topic eq q|CODE|                    and return $topic->( $self, @_ );
    ref $topic eq ''                                          or croak sprintf
     q|[%s]: %s isa (%s): no way to resolve this|,
     $caller, $manifest, ref $topic;
    defined $self->{_}{namespace}                                     or croak
     qq|[$caller]: {namespace} !isa defined|;
    my $anchor = $self->{_}{namespace};
    my $backup = $topic;
    if( ref $anchor eq '' && $anchor eq '' ) {
        $self->diag( 5, q|[%s]: defaulting %s to $self|, $caller, $manifest );
        $anchor = $self                       }
    $self->diag( 5, q|[%s]: {namespace} isa (%s)|, $caller, ref $anchor );
    unless( ref $anchor eq '' )     {
        $self->diag( 5, q|[%s]: going for <%s>->[%s]|,
          $caller, ref $anchor, $topic );
        $topic = $anchor->can( $topic );
        $topic     or croak sprintf q|[%s]: object of <%s> can't [%s] method|,
         $caller, ref $anchor, $backup;
        return $anchor->$topic( @_ ) }
    else                            {
        $self->diag( 5, q|[%s]: going for <%s>::[%s]|,
          $caller, $anchor, $topic );
        $topic = UNIVERSAL::can( $anchor, $topic );
        $topic   or croak sprintf q|[%s]: <%s> package can't [%s] subroutine|,
         $caller, $anchor, $backup;
        return $topic->( $self, @_ ) }}

=item B<query_switch()>

    ( $rule, $item ) = $self->query_switch( $item );

Internal multitool.
That's the point where decisions about turns are made.
B<(note)>
B<query_switch()> converts I<$rule> (as returned by B<switch()>) to specially
encoded scalar;
it's caller's responcibility pick correct I<[turn]> later.
Strategy:

=over

=item no arguments

Special-state mode:
invoke B<switch()> with no arguments;
ignore whatever I<$item> has been possibly returned;
return I<$rule> alone.

=item I<$item> is C<undef>

EOF mode:
ignore B<switch()> completely;
return C<eturn> and C<undef>.

=item I<$item> is not C<undef>

King-size mode: 

lib/FSM.pm  view on Meta::CPAN

    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.
Of course, it's not true deep copy
({fst} might contain CODE, it's not clear how to copy it).
It's possible, one day list trailer variant of initialization may be put to
sleep.
See also L<linter below|/Linter>.

=item Linter

B<(missing feature)>
It might be hard to imagine,
but FST might get out of hands
(ie check F<t/process/quadratic.t>).
Indeed, some kind of (limited!) linter would be much desired.
It's missing.

=item Perl FALSE, C<undef>, and C<uturn>

B<(caveat)>
Back then B<DMA::FSM> treated C<undef> as any other Perl FALSE.
C<uturn> I<$rule> mech has made C<undef> special
(if B<switch()> returns C<undef> and C<uturn> I<{turn}> isn't present then
it's a B<croak>).
Thus, at the moment, C<undef> isn't FALSE (for B<A::F>).
This is counter-intuitive, actually.

=item Returning C<undef> for misuse

B<(bug)>
Why B<A::F> screws with caller,
in case of API violations (by returning C<undef>),
is beyond my understanding now.

=item B<source()> and I<$state>



( run in 2.588 seconds using v1.01-cache-2.11-cpan-7fcb06a456a )