Acme-FSM
view release on metacpan or search on metacpan
=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]>".
=back
=cut
=head1 B<connect()>
$bb1 = Acme::FSM->connect( { %options1 }, %fst1 );
$bb2 = Acme::FSM->connect( { %options2 }, { %fst2 } );
$bb3 = $bb2->connect( { %options3 } );
Creates a blackboard object.
Blackboard isa HASH, it's free to use except special C<_> key;
that key is for I<{fsm}> exclusively.
First parameter isa I<%$options>, it's required
(pass empty HASH if nothing to say).
Defined keys are:
=over
=item I<diag_level>
(positive integer)
Sets a diagnostic threshold.
It's meaning is covered in L<B<diag()> method|/diag()> documentation.
If C<undef> then set to C<1> (C<0> is B<defined>).
=item I<dumper>
(scalar or C<CODE>)
B<A::F> operates on arbitrary items and there's a diagnostic service that
sometimes insists on somehow showing those arbitrary items.
It's up to user's code to process that arbitrary data and yield some scalar
represantation.
Refer to L<B<query_dumper()> method|/query_dumper()> documentation for
details.
Optional.
Simple stringifier is provided by L<B<query_dumper()> method|/query_dumper()>
itself.
=item I<namespace>
(scalar or object(!) B<ref>)
Sets a context for various parts of I<{fsm}> and services would be resolved.
No defaults.
Refer to L<B<query()> method|/query()> documentation for details.
=item I<source>
(scalar or C<CODE>)
Sets a source of items to process to be queried.
Required.
Refer to L<B<query_source()> method|/query_source()> documentation for
details.
=back
Second is FST (Finite State Table).
It's required for class construction and ignored (if any) for object
construction.
Difference between list and HASH is the former is copied into HASH internally;
the latter HASH is just saved as reference.
The FST is just borrowed from source object during object construction.
Thus, in the synopsis, I<$bb3> and I<$bb2> use references to HASH
I<%$fst2>.
An idea behind this is to minimize memory footprint.
OTOH, maninpulations with one HASH are effectevely manipulations with FST of
any other copy-object that borrowed that FST.
IOW, anything behind FST HASH of class construction or options HASH of object
construction isa trailer and B<carp>ed.
Obviously, there's no trailer in class construction with list FST.
=cut
my @options = qw| diag_level namespace source dumper |;
sub connect {
my( $self, $opts ) = ( shift @_, shift @_ );
ref $opts eq q|HASH| or croak sprintf
q|[connect]: {options} HASH is required, got (%s) instead|, ref $opts;
my( $trailer, $leader );
if( ref $self eq '' ) {
$trailer = ref $_[0] eq q|HASH|;
$self = bless { _ => { fst => $trailer ? shift @_ : { @_ }}}, $self;
$leader = q|clean init with| }
else {
$trailer = @_;
$self = bless { _ => { %{$self->{_}} }}, ref $self;
$leader = q|stealing| }
$self->{_}{$_} = delete $opts->{$_} // $self->{_}{$_} foreach @options;
$self->{_}{diag_level} //= 1;
$self->diag( 3, q|%s (%i) items in FST|,
$leader, scalar keys %{$self->{_}{fst}} );
$self->carp( qq|($_): unknown option, ignored| ) foreach keys %$opts;
$self->carp( q|(source): unset| ) unless defined $self->{_}{source};
$trailer = ref $_[0] eq q|HASH| ? keys %{$_[0]} : @_ if $trailer;
$self->carp( qq|ignoring ($trailer) FST items in trailer| ) if $trailer;
$self->carp( q|FST has no {START} state| ) unless
exists $self->{_}{fst}{START};
$self->carp( q|FST has no {STOP} state| ) unless
exists $self->{_}{fst}{STOP};
@{$self->{_}}{qw| state action |} = qw| START VOID |;
return $self }
=head1 B<process()>
$bb = Acme::FSM->connect( { }, \%fst );
$rc = $bb->process;
This is heart, brains, liver, and so on of B<Acme::FSM>.
B<process()> is what does actual state flow.
That state flow is steered by records in I<%fst>.
Each record consists of:
=over
=item B<switch()> callback
This is some user supplied code that
always consumes whatever B<source()> callback returns (at some point in past),
(optionally) regurgitates said input,
and decides what I<[turn]> to go next.
Except when in special I<{state}> (see below) it's supplied with one argument:
I<$item>.
In special states I<$item> is missing.
B<switch()> returns two controls: I<$rule> and processed I<$item>.
What to do with returned I<$item> is determined by I<$action> (see below).
=item various I<[turn]>s
Each I<[turn]> spec is an ARRAY with two elements (trailing elements are
ignored).
First element is I<$state> to change to.
Second is I<$action> that sets what to do with I<$item> upon changing to named
I<$state>.
Here are known I<[turn]>s in order of logical treating decreasing.
=over
B<(misdesign)>
First, by design and legacy B<switch()> must report what turn the control flow
must make
(that's why it *must* return I<$rule>).
OTOH, there's some value in keeping B<switch()>es as simple as possible what
results in I<{state}> with alone C<tturn> I<{turn}> and B<switch()> that
always returns TRUE.
Changes are coming.
=item B<turn()> and B<fst()>
B<(misdesign)>
Encoding (so to speak) in use by B<turn()> (in prediction mode) is plain
stupid.
C<undef> signals two distinct conditions
(granted, both are manifest of broken I<{fst}>).
Empty string doesn't distinguish safe (both C<tturn> and C<fturn> are present)
and risky (C<tturn> or C<fturn> is missing) I<{state}>.
C<HASH> doesn't say if there's anything in turn map.
All that needs loads of workout.
=back
=cut
=head1 DIAGNOSTICS
=over
=item C<[action]: changing action: (%s) (%s)>
B<(deep trace)>, L<B<action()> method|/action()>.
Exposes change of I<$action> from previous (former I<%s>)
to current (latter I<%s>).
=item C<[action]: too many args (%i)>
B<(warning)>, L<B<action()> method|/action()>.
Obvious.
None or one argument is supposed.
=item C<[connect]: (%s): unknow option, ignored>
B<(warning)>, L<B<connect()> method|/connect()>.
Each option that's not known to B<A::F> is ignored and B<carp>ed.
There're no means for child class to force B<A::F> (as a parent class) to
accept something.
Child classes should process their options by itself.
=item C<[connect]: clean init with (%i) items in FST>
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.
B<(basic trace)>, L<B<process()> method|/process()>.
Leaving state flow with I<$state> I<%s> (the former) and I<$action> I<%s> (the
latter).
=item C<[process]: {%s}(%s): switch returned: (%s)>
B<(basic trace)>, L<B<process()> method|/process()>.
B<switch()> (of I<$state> I<%s>, the 1st, with I<$action> I<%s>, the 2nd) has
been invoked and returned something that was interpreted as I<$rule> I<%s>,
the 3rd.
=item C<[process]: {%s}(%s): unknown action>
B<(croak)>, L<B<process()> method|/process()>.
Attempt to enter I<$state> I<%s> (the former) has been made.
However, it has failed because I<$action> I<%s> (the latter) isn't known.
=item C<< [query]: [$caller]: <%s> package can't [%s] subroutine >>
B<(croak)>, L<B<query()> method|/query()>.
I<$namespace> and I<$what> has been resolved as package I<%s> (the former)
and subroutine I<%s> (the latter).
However, the package can't do such subroutine.
=item C<[query]: [query_dumper]: %s !isa defined>
=item C<[query]: [query_source]: %s !isa defined>
=item C<[query]: [query_switch]: %s !isa defined>
B<(croak)>, L<B<query()> method|/query()>.
I<$what> (I<%s> is I<$name>) should have some meaningful value.
It doesn't.
=item C<[query]: [query_dumper]: %s isa (%s): no way to resolve this>
=item C<[query]: [query_source]: %s isa (%s): no way to resolve this>
=item C<[query]: [query_switch]: %s isa (%s): no way to resolve this>
B<(croak)>, L<B<query()> method|/query()>.
C<ref $what> (the former I<%s>) is I<%s> (the latter).
I<$what> is neither CODE nor scalar.
=item C<[query]: [query_dumper]: %s isa (%s)>
=item C<[query]: [query_source]: %s isa (%s)>
=item C<[query]: [query_switch]: %s isa (%s)>
B<(deep trace)>, L<B<query()> method|/query()>.
C<ref $what> (the former I<%s>) is I<%s> (the latter).
=item C<[query]: [query_dumper]: {namespace} !isa defined>
=item C<[query]: [query_source]: {namespace} !isa defined>
=item C<[query]: [query_switch]: {namespace} !isa defined>
B<(croak)>, L<B<query()> method|/query()>.
I<$what> isn't CODE, now to resolve it I<$namespace> is required.
Apparently, it was missing all along.
=item C<[query]: [query_dumper]: {namespace} isa (%s)>
=item C<[query]: [query_source]: {namespace} isa (%s)>
=item C<[query]: [query_switch]: {namespace} isa (%s)>
B<(deep trace)>, L<B<query()> method|/query()>.
C<ref $namespace> is I<%s>.
=item C<[query]: [query_dumper]: defaulting %s to $self>
=item C<[query]: [query_source]: defaulting %s to $self>
=item C<[query]: [query_switch]: defaulting %s to $self>
B<(deep trace)>, L<B<query()> method|/query()>.
I<$namespace> is an empty string.
The blackboard object will be used to resolve the callback.
=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) 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.
( run in 0.522 second using v1.01-cache-2.11-cpan-5837b0d9d2c )