Acme-FSM

 view release on metacpan or  search on metacpan

lib/FSM.pm  view on Meta::CPAN

# Original Author:      Dale M. Amon
# $Id: FSM.pm 564 2025-02-13 21:33:15Z whynot $
# Copyright 2012, 2013, 2022 Eric Pozharski <whynot@pozharski.name>
# Copyright 2025 Eric Pozharski <wayside.ultimate@tuta.io>
# GNU LGPLv3
# AS-IS, NO-WARRANTY, HOPE-TO-BE-USEFUL

use strict;
use warnings;

package Acme::FSM;
use version 0.77; our $VERSION = version->declare( v2.3.6 );

use Carp qw| croak |;

# TODO:202212202012:whynot: L<|/Linter>

=head1 NAME

Acme::FSM - pseudo Finite State Machine.

=cut

=head1 SYNOPSIS

    my $bb = Acme::FSM->connect( { %options }, { %fst } );
    $bb->process;
    exit 0;

=cut

=head1 DESCRIPTION

B<(disclaimer)>
B<Acme::FSM> is currently in proof-of-concept state.
There's a plan to accompany it with B<Acme::FSM::Simple> with all diagnostics
avoided and run time checks in place.
And then with B<Acme::FSM::Tiny> with run time checks stripped.
Also, see L<B<BUGS AND CAVEATS> later in this POD|/BUGS AND CAVEATS>.

=head2 Concerning Inheritance

Through out code only methods are used to access internal state.
Supposedly, that will enable scaling some day later.
The only exception is L<B<connect()>|/connect()> for obvious reasons.

Through out code neither internal state nor FST records are cached ever.
The only one seamingly inconsistent fragment is inside main loop when next
I<$state> is already found but not yet entered.
I<$action> is processed when the next I<$state> is set
(though, in some sense, not yet entered).
(If it doesn't make sense it's fine, refer to
L<B<process()> method|/process()> description later in this POD.)

This notice seems to be useles.
Instead, it might come handy someday.

=cut

=head2 Terminology

There're some weird loaded words in this POD,
most probably, poorly choosen
(but those are going to stay anyway).
So here comes disambiguation list.

=over

=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

lib/FSM.pm  view on Meta::CPAN


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>

B<(bug)> (also see L<B<switch()> and I<$item>|/switch() and $item>)
By design and legacy,
there is single point of input -- B<source()>.
IRL, multiple sources are normal.
Implementation wise that leads to B<source()> that on the fly figures out
current I<$state> and then somehow branches (sometimes branches out).
Such B<source()>s look horrible because they are.

=item Special handling of C<START> and C<CONTINUE>

B<(bug)>
In contrary with C<STOP> and C<BREAK> special states,
special treatement of C<START> and C<CONTINUE> is a side effect of
L<B<process()> method|/process()> entry sequence.
That's why routinely enter C<START> or C<CONTINUE> by state flow is of
there-be-dragons type.
Totally should be rewritten.

=item B<switch()> and I<$item>



( run in 0.983 second using v1.01-cache-2.11-cpan-b16cb0d3907 )