NAME
Acme::FSM - pseudo Finite State Machine.
SYNOPSIS
my $bb = Acme::FSM->connect( { %options }, { %fst } );
$bb->process;
exit 0;
DESCRIPTION
(disclaimer) Acme::FSM is currently in proof-of-concept state. There's a plan to accompany it with Acme::FSM::Simple with all diagnostics avoided and run time checks in place. And then with Acme::FSM::Tiny with run time checks stripped. Also, see BUGS AND CAVEATS later in this POD.
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 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 $state is already found but not yet entered. $action is processed when the next $state is set (though, in some sense, not yet entered). (If it doesn't make sense it's fine, refer to process() method description later in this POD.)
This notice seems to be useles. Instead, it might come handy someday.
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.
- $action
-
Special flag associated with next {state} in a [turn] (covered in details in process() method description). (note) It may be applied to a [turn] of {fst} or {state}.
- blackboard
-
Shamelesly stolen from DMA::FSM. Originally, it meant some opaque HASH passed around in DMA::FSM::FSM() where user-code could store it's own ideas and such. Now it's an object blessed into Acme::FSM (for user-code still, {fsm} has nothing to do with it).
- entry
-
Layout of state records (see below) isn't particularly complicated. However it's far from being uniform. entry is a placeholder name for any component of (unspecified) state record when any semantics is irrelevant. See also [turn] and switch() below.
- Finite State Machine
-
Acme::FSM.
- Finite State Table
- {fst}
-
Collection of state records.
- FSM
-
Acronym for Finite State Machine. See above.
- FST
-
Acronym for Finite State Table. See above.
- internal state
- {fsm}
-
Some open-ended set of parameters dictating FSM's behaviour.
- item
- $item
-
Something that makes sense for user-code. Acme::FSM treats it as scalar with no internals (mostly; one exception is covered in diag() method description).
- $namespace
-
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. query() method has more.
- $rule
-
A scalar identifing a [turn]. One of opaque scalars returned by switch() callback (the other is processed (modified or not) $item). process() method description has more.
- source()
-
Special piece of user-code (it's required at construction (connect() method), query_source() method describes how FSM reaches it). Whatever it returns (in explicit scalar context) becomes $item.
- $state
-
A scalar identifing a {state} (see below) or parameter of {fsm} (see above).
- state flow
-
Just like control flow but for $states.
- state record
- {state}
-
One record in {fst}. The record consists of entries (see above). process() method description has more. Should be noted, in most cases "{state}" should be read as "$state {state}" instead, but that starts to smell bufallo.
- switch()
-
A mandatory callback associated with every {state}. process() method and query_switch() method descriptions have more.
- $turn
-
No such thing. It's $rule instead (see above).
- [turn]
-
Specially crafted entry in {state} (covered in details in process() method description). Such entry describes what next $state should be picked in state flow and what to do with $item.
- turn map
-
This idiom is used in place of "
turns
$rule of [turn]".
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 _
key; that key is for {fsm} exclusively. First parameter isa %$options, it's required (pass empty HASH if nothing to say). Defined keys are:
- diag_level
-
(positive integer) Sets a diagnostic threshold. It's meaning is covered in diag() method documentation. If
undef
then set to1
(0
is defined). - dumper
-
(scalar or
CODE
) 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 query_dumper() method documentation for details. Optional. Simple stringifier is provided by query_dumper() method itself. - namespace
-
(scalar or object(!) ref) Sets a context for various parts of {fsm} and services would be resolved. No defaults. Refer to query() method documentation for details.
- source
-
(scalar or
CODE
) Sets a source of items to process to be queried. Required. Refer to query_source() method documentation for details.
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, $bb3 and $bb2 use references to HASH %$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 carped. Obviously, there's no trailer in class construction with list FST.
process()
$bb = Acme::FSM->connect( { }, \%fst );
$rc = $bb->process;
This is heart, brains, liver, and so on of Acme::FSM. process() is what does actual state flow. That state flow is steered by records in %fst. Each record consists of:
- switch() callback
-
This is some user supplied code that always consumes whatever source() callback returns (at some point in past), (optionally) regurgitates said input, and decides what [turn] to go next. Except when in special {state} (see below) it's supplied with one argument: $item. In special states $item is missing. switch() returns two controls: $rule and processed $item. What to do with returned $item is determined by $action (see below).
- various [turn]s
-
Each [turn] spec is an ARRAY with two elements (trailing elements are ignored). First element is $state to change to. Second is $action that sets what to do with $item upon changing to named $state. Here are known [turn]s in order of logical treating decreasing.
eturn
-
That [turn] will be choosen by FSM itself when $item at hands is
undef
(as returned by source()). switch() isn't invoked -- $item is void, there's nothing to call switch() with. However, $action may change $item. uturn
-
That [turn] will be choosen by FSM if $rule is
undef
(as returned by switch()). Idea behind this is to give an FST an option to bailout. In original DMA::FSM that's not possible (except croaking, you know). Also, see BUGS AND CAVEATS. tturn
and/orfturn
-
If any (hence 'or') is present then $rule returned by switch() is treated as Perl boolean, except
undef
it's handled byuturn
. That's how it is in original DMA::FSM. If switch() always returns TRUE (or FALSE) thenfturn
(ortturn
) can be missing. Also, see BUGS AND CAVEATS. turns
-
If neither
tturn
norfturn
is present then whatever $rule would be returned by switch() is treated as string. That string is supposed to be a key in %$turns. $rule returned is forced to be string (so if you dare to return objects, such objects should have""
overloaded). Also, see BUGS AND CAEATS.
$state is treated as a key in FST hash, except when that's a special state. Special states (in alphabetical order):
BREAK
-
Basically, it's just like
STOP
$state. All comments there (see below) apply. It's added to enable break out from loop, do something, then get back in loop. $state is changed toCONTINUE
just before returning to caller (switch() is called inBREAK
state). The choice where to implicitly change state toCONTINUE
has been completely arbitrary; probably wrong. CONTINUE
-
Just like
START
state (see below, all comments apply). WhileBREAK
is turned toCONTINUE
implicitly no other handling is made. START
-
It's $state set by connect(). $action (it's also set by connect()) is ignored (if $action is
undef
then silently replaces with empty string), switch() is invoked with no arguments (there's not anything to process yet). Whatever $item could be returned is ignored. $rule is followed. Thuseturn
can't be followed. See also BUGS AND CAVEATS. STOP
-
It's last state in the state flow. $action is retained (that's what process() will return) (however, because it's processed before
STOP
state is acknowledged it must not beundef
) but otherwise ignored. switch() is invoked with no arguments (switch() of previous {state} should have took care). Whatever $item could be returned is ignored. Whatever $rule could be returned is reported (at (basic trace) level) but otherwise ignored. Then $action is returned and state flow terminates.
Supported $actions (in alphabetical order):
NEXT
-
Drop whatever $item at hands. Request another.
If FST has such record:
somestate => { eturn => [ somestate => 'NEXT' ] }
then FSM will stay in
somestate
as long as source() callback returnsundef
. Thus consuming all resources available. No options provided to limit that consumption. SAME
-
Retains $item uncoditionally. That is, even if $item isn't defined it's kept anyway.
Beware, if FST has such record:
somestate => { eturn => [ somestate => 'SAME' ] }
then FSM will cycle here forever. That is, since source() isn't queried for other $item (what's the purpose of this action is anyway) there's no way to get out.
TSTL
-
Check if $item is defined, then go as with
SAME
orNEXT
otherwise. That actually makes sense.(note) This action name is legacy of DMA::Misc::FSM; Possibly, that's
TeST
something; Someone can just speculate whatL
could mean.
METHODS AND STUFF
Access and utility methods to deal with various moves while doing The State Flow. These aren't forbidden for use from outside, while being quite internal nevertheles.
- verify()
-
$rc = $self->query_rc( @args ); $rc = $self->verify( $rc, $state, $tag, $subject, $test );
Here comes rationale. Writing (or should I say "composing"?) correct {fst} A::F style is hard (I know what I'm talking about, I've made a dozen already). The purpose of verify() is to check if the {fst} at hands isn't fubar. Nothing more, nothing less. query_rc() is a placeholder for one of query_.*() methods, $test will be matched against
ref $rc
. Other arguments are to fill diagnostic output (if any). $state hints from what {state} $rc has been queried. $subject and $tag are short descriptive name and actual value of $rc. Yup, dealing with verify() might be fubar too.$rc is passed through (or not). This croaks if $rc isn't defined or
ref $rc
doesn't match $test. - state()
-
$bb->state eq 'something' and die; $state = $bb->state( $new_state );
Queries and sets state of A::F instance. Modes:
- fst()
-
%state = %{ $bb->fst( $state ) }; %state = %{ $bb->fst( $state => \%new_state ) }; $value = $bb->fst( $state => $entry ); $value = $bb->fst( $state => $entry => $new_value );
Queries and sets records and entries in {fst}. That is, not only entire {state}s but components of {state} are reachable too. Modes:
- query specific {state} of specific $state
-
Executed if one scalar is passed in. Returns a {state} reference with whatever entries are set. Silently returns
undef
if $state is missing from {fst}. - set {state} of specific $state
-
Executed if one scalar and HASH are passed in. Sets a {state} with key/value pairs from HASH, creating one if necessary. Created record isa copy of HASH, not a reference (not a true deep copy though) (empty \%new_state is fine too) (copying isn't by design, it's implementation's quirk). Returns record as it was before setting (
undef
is returned if there were no such $state before). - query specific $entry of specific $state
-
Executed if two scalars are passed in. Returns an entry from named state record.
- set specific $entry of specific $state
-
Executed if two scalars and anything else are passed in (no implicit intelligence about third parameter). Sets an entry in named state record, creating one (entry) if necessary. State record must exist beforehand. Entry isa exact value of least argument, not a copy. Returns whatever value $entry just had (
undef
is returned if there were none such $entry before).
None checks are made, except record must exist (for two latter uses).
- turn()
-
$bb->turn( $state ) eq '' or die; $bb->turn( $state => 'uturn' )->[1] eq 'NEXT' or die;
Queries [turn]s of arbitrary {state}s. turn() doesn't manipulate entries, use fst() method instead if you can. Modes:
- query expected behaviour
-
This mode is entered if there is lone scalar. Such scalar is believed to be $state. Returns something that describes what kind of least special states are present. Namely:
undef
is returned if $state isn't present in the {fst} (also carps). Also see below.Empty string is returned if there're tturn and/or fturn turns. turns hash is ignored in that case.
HASH
is returned if there's turn map (and neither tturn nor fturn is present). (note) In that case, turn() checks for turns is indeed a HASH, nothing more (however croaks if that's not the case); It may as well be empty; Design legacy.Returns
HASH
forSTOP
andBREAK
$states without any further processing (For those $states any $rule is ignored andHASH
enables switch() callbacks to give more informative logs (while that information is mangled anyway); Probably bad idea).undef
is returned if there's nothing to say -- neither tturn, nor fturn, nor turn map -- this record is kind of void. The record should be studied to find out why. carps in that case.
- query specific [turn]
-
Two scalars are $state and specially encoded $rule (refer to query_switch() method about encoding). If $rule can't be decoded then croaks. Returns (after verification) requested $rule as ARRAY. While straightforward [turn]s (such as
tturn
,fturn
, and such) could be in fact queried through fst() method turn map needs bit more sophisticated handling; and that's what turn() does; in fact asking forturns
will result in croak. $action ofSTART
andCONTINUE
special states suffer implicit defaulting to empty string. - anything else
-
No arguments or more then two is an non-fatal error. Returns
undef
(with carp).
- action()
-
$bb->action eq $action and die; $action = $bb->action( $new_action );
Queries and sets $action of A::F instance. Modes:
- query()
-
( $alpha, $bravo ) = $self->query( $what, $name, @items );
Internal method, then it becomes complicated. Resolves $what (some callback, there multiple of them) against $namespace, if necessary. Then invokes resolved code appropriately passing @items in, if any; Product of the callback over @items is returned back to the caller. $name is used for disgnostics only. Trust me, it all makes perfect sense.
$what must be either CODE or scalar, or else.
Strategy is like this
- $what isa CODE
-
$what is invoked with $self and @items as arguments. Important, in this case $self is passed as first argument, OO isn't involved like at all.
- $namespace is empty string
-
Trade $namespace for $self (see below) and continue.
- $namespace is scalar
-
Treat $what as a name of function in $namespace namespace.
- $namespace is object reference
-
Treat $name as a name of method of object referred by $namespace.
It really works.
- query_switch()
-
( $rule, $item ) = $self->query_switch( $item );
Internal multitool. That's the point where decisions about turns are made. (note) query_switch() converts $rule (as returned by switch()) to specially encoded scalar; it's caller's responcibility pick correct [turn] later. Strategy:
- no arguments
-
Special-state mode: invoke switch() with no arguments; ignore whatever $item has been possibly returned; return $rule alone.
- $item is
undef
-
EOF mode: ignore switch() completely; return
eturn
andundef
. - $item is not
undef
-
King-size mode: invoke switch(), pass $item as single argument. return $rule and $item (whatever it became after going through switch()).
$rule, as it was returned by switch(), is encoded like this:
- $rule is
undef
-
Return
uturn
. (note) Don't verify ifuturn
[turn] exists. - $rule is Perl TRUE and
tturn
and/orfturn
are present -
Return
tturn
(note) Don't verify iftturn
[turn] exists. - $rule is Perl FALSE and
tturn
and/orfturn
are present -
Return
fturn
(note) Don't verify iffturn
[turn] exists. - neither
tturn
orfturn
are present -
Encode $rule like this
'turn%' . $rule
and return that. B((note)> Don't verify if turn map exists. (note) Don't verify if"turn%$rule"
exists in turn map.
switch() is always invoked in list context even if $item would be ignored. If $rule shouldn't be paired with $item it won't be (it's safe to call query_switch() in scalar context then and there won't be any trailing
undef
s). - query_source()
-
( $item, $dump ) = $self->query_source;
Seeks source() callback and acquires whatever it returns. The callback is called in scalar context. As useful feature, also feeds $item to dumper callback. query() method has detailed description how source() callback is acquired. Returns $item and result of dumper callback.
- query_dumper()
-
$dump = $self->query_dumper( $item );
Seeks dumper callback (configured at construction time). If the callback wasn't configured uses simple hopefully informative and
undef
proof substitution. Whatever the callback returns is checked to be defined (undef
is changed to"(unclear)"
) and then returned. - 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 diag_level configuration parameter. Each object has it's own, however it's inherited when objects are copied.
Defined levels are:
- carp()
-
$bb->carp( 'something wrong...' );
Internal. carps consistently if {_}{diag_level} is gt
0
.
BUGS AND CAVEATS
- Default For Turn Map
-
(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 switch(). That's insane.
- Diagnostics
-
(misdesign) Mechanics behind diagnostics isa failure. It's messy, fragile, misguided, and (honestly) premature. At the moment it's useless.
- Hash vs HASH vs Acme::FSM Ref Constructors
-
(messy, probably bug) connect() method description _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 linter below.
- Linter
-
(missing feature) It might be hard to imagine, but FST might get out of hands (ie check t/process/quadratic.t). Indeed, some kind of (limited!) linter would be much desired. It's missing.
- Perl FALSE,
undef
, anduturn
-
(caveat) Back then DMA::FSM treated
undef
as any other Perl FALSE.uturn
$rule mech has madeundef
special (if switch() returnsundef
anduturn
{turn} isn't present then it's a croak). Thus, at the moment,undef
isn't FALSE (for A::F). This is counter-intuitive, actually. - Returning
undef
for misuse -
(bug) Why A::F screws with caller, in case of API violations (by returning
undef
), is beyond my understanding now. - source() and $state
-
(bug) (also see switch() and $item) By design and legacy, there is single point of input -- source(). IRL, multiple sources are normal. Implementation wise that leads to source() that on the fly figures out current $state and then somehow branches (sometimes branches out). Such source()s look horrible because they are.
- Special handling of
START
andCONTINUE
-
(bug) In contrary with
STOP
andBREAK
special states, special treatement ofSTART
andCONTINUE
is a side effect of process() method entry sequence. That's why routinely enterSTART
orCONTINUE
by state flow is of there-be-dragons type. Totally should be rewritten. - switch() and $item
-
(bug) It might be surprising, but, from experience, the state flow mostly doesn't care about $item. Mostly, {state} entered with $action
NEXT
processes input and just wonders ahead. Still those pesky $items *must* be passed around (by design). Still every switch() *must* return $item too (even if it's just a placeholder). Something must be done about it. tturn
,fturn
, and switch()-
(misdesign) First, by design and legacy switch() must report what turn the control flow must make (that's why it *must* return $rule). OTOH, there's some value in keeping switch()es as simple as possible what results in {state} with alone
tturn
{turn} and switch() that always returns TRUE. Changes are coming. - turn() and fst()
-
(misdesign) Encoding (so to speak) in use by turn() (in prediction mode) is plain stupid.
undef
signals two distinct conditions (granted, both are manifest of broken {fst}). Empty string doesn't distinguish safe (bothtturn
andfturn
are present) and risky (tturn
orfturn
is missing) {state}.HASH
doesn't say if there's anything in turn map. All that needs loads of workout.
DIAGNOSTICS
[action]: changing action: (%s) (%s)
-
(deep trace), action() method. Exposes change of $action from previous (former %s) to current (latter %s).
[action]: too many args (%i)
-
(warning), action() method. Obvious. None or one argument is supposed.
[connect]: (%s): unknow option, ignored
-
(warning), connect() method. Each option that's not known to A::F is ignored and carped. There're no means for child class to force A::F (as a parent class) to accept something. Child classes should process their options by itself.
[connect]: clean init with (%i) items in FST
-
(basic trace), connect() method. During class construction FST has been found. That FST consists of %i items. Those items are number of $states and not $states plus {state}s.
[connect]: FST has no {START} state
-
(warning), connect() method. START {state} is required. It's missing. This situation will definetely result in devastating failure later.
[connect]: FST has no {STOP} state
-
(warning), connect() method. STOP {state} is required. It's missing. If FST is such that STOP $state can't be reached (for whatever reasons) this may be cosmetic.
[connect]: ignoring (%i) FST items in trailer
-
(warning), connect() method. A trailer has been found and was ignored. Beware, %i could be misleading. For HASH in trailer that HASH is considered and key number is reported. For list in trailer an item number in list is reported, even if it's twice as many in FST (what isa hash). Just because.
[connect]: (source): unset
-
(warning), connect() method. $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.
[connect]: stealing (%i) items in FST
-
(basic trace), connect() method. During object construction FST has been found. That FST consists of %i items. Those items are {state}s, not $states plus {state}s.
[fst]: creating {%s} record
-
(basic trace), fst() method. New state record has been created, named %s.
[fst]: creating {%s}{%s} entry
-
(basic trace), fst() method. New entry named %s (the latter) has been created in record named %s (the former).
[fst]: no args
-
(warning), fst() method. No arguments, it's an error. However, instead of promptly dieing,
undef
is returned in blind hope this will be devastating enough. [fst]: (%s): no such {fst} record
-
(warning), fst() method. Requested entry %s is missing. State record ought to exist beforehand for any usecase except record creation.
[fst]: too many args (%i)
-
(warning), fst() method. Too many args have been passed in. And again, instead of prompt dieing
undef
is returned. [fst]: updating {%s} record
-
(basic trace), fst() method. Named record (%s) has been updated.
[fst]: updating {%s}{%s} entry
-
(basic trace), fst() method. Named entry (%s, the latter) in record %s (the former) has been updated.
[process]: {%s}(%s): changing state: (CONTINUE)
-
(basic trace), process() method. Implicit change of state from
BREAK
toCONTINUE
is about to happen. [process]: {%s}(%s): entering
-
(basic trace), process() method. Entering state flow from $state %s (the former) with $action %s (the latter).
[process]: {%s}(%s): going for
-
(deep trace), process() method. Trying to enter $state %s (the former) with $action %s (the latter).
[process]: {%s}(%s): %s: going with
-
(basic trace), process() method. While in $state %s (1st) doing $action %s (2nd) the $item happens to be %s (3rd).
undef
will look like this:((undef))
. [process]: {%s}(%s): %s: turning with
-
(basic trace), process() method. switch() (of $state %s, the 1st) has returned $rule %s (2nd) and $item %s. Now dealing with this.
[process]: {%s}(%s): leaving
-
(basic trace), process() method. Leaving state flow with $state %s (the former) and $action %s (the latter).
[process]: {%s}(%s): switch returned: (%s)
-
(basic trace), process() method. switch() (of $state %s, the 1st, with $action %s, the 2nd) has been invoked and returned something that was interpreted as $rule %s, the 3rd.
[process]: {%s}(%s): unknown action
-
(croak), process() method. Attempt to enter $state %s (the former) has been made. However, it has failed because $action %s (the latter) isn't known.
[query]: [$caller]: <%s> package can't [%s] subroutine
-
(croak), query() method. $namespace and $what has been resolved as package %s (the former) and subroutine %s (the latter). However, the package can't do such subroutine.
[query]: [query_dumper]: %s !isa defined
[query]: [query_source]: %s !isa defined
[query]: [query_switch]: %s !isa defined
-
(croak), query() method. $what (%s is $name) should have some meaningful value. It doesn't.
[query]: [query_dumper]: %s isa (%s): no way to resolve this
[query]: [query_source]: %s isa (%s): no way to resolve this
[query]: [query_switch]: %s isa (%s): no way to resolve this
-
(croak), query() method.
ref $what
(the former %s) is %s (the latter). $what is neither CODE nor scalar. [query]: [query_dumper]: %s isa (%s)
[query]: [query_source]: %s isa (%s)
[query]: [query_switch]: %s isa (%s)
-
(deep trace), query() method.
ref $what
(the former %s) is %s (the latter). [query]: [query_dumper]: {namespace} !isa defined
[query]: [query_source]: {namespace} !isa defined
[query]: [query_switch]: {namespace} !isa defined
-
(croak), query() method. $what isn't CODE, now to resolve it $namespace is required. Apparently, it was missing all along.
[query]: [query_dumper]: {namespace} isa (%s)
[query]: [query_source]: {namespace} isa (%s)
[query]: [query_switch]: {namespace} isa (%s)
-
(deep trace), query() method.
ref $namespace
is %s. [query]: [query_dumper]: defaulting %s to $self
[query]: [query_source]: defaulting %s to $self
[query]: [query_switch]: defaulting %s to $self
-
(deep trace), query() method. $namespace is an empty string. The blackboard object will be used to resolve the callback.
[query]: [query_dumper]: going for <%s>->[%s]
[query]: [query_source]: going for <%s>->[%s]
[query]: [query_switch]: going for <%s>->[%s]
-
(deep trace), query() method. Attempting to call %s (the latter) method on object of %s (the former) class.
[query]: [query_dumper]: going for <%s>::[%s]
[query]: [query_source]: going for <%s>::[%s]
[query]: [query_switch]: going for <%s>::[%s]
-
(deep trace), query() method. Attempting to call %s (the latter) subrouting of package %s (the former).
[query]: [query_dumper]: object of <%s> can't [%s] method
[query]: [query_source]: object of <%s> can't [%s] method
[query]: [query_switch]: object of <%s> can't [%s] method
-
(croak), query() method. The object of %s (the former) class can't do %s (the latter) method.
[state]: changing state: (%s) (%s)
-
(deep trace), state() method. Exposes change of state from previous (former %s) to current (latter %s).
[state]: too many args (%i)
-
(warning), state() method. Obvious. None or one argument is supposed. state() has returned
undef
in this case, most probably will bring havoc in a moment. [turn]: (%s): no such {fst} record
-
(warning), turn() method. Peeking for [turn]s of %s $state yeilds nothing, there's no such state.
[turn]: {%s}: none supported turn
-
(warning), turn() method. Whatever content of %s entry is FSM doesn't know how to handle it.
[turn]: {%s}(%s): unknown turn
-
(croak), turn() method. There was request for [turn] %s (the latter) of $state %s (the former). While {state} record has been found and is OK, there is no such $rule.
[turn]: no args
-
(warning), turn() method. No argumets, it's an error.
[turn]: too many args (%i)
-
(warning), turn() method. There's no way to handle that many (namely: %i) arguments.
[verify]: {%s}{%s}: %s !isa defined
-
(croak), verify() method. $rc queried from something in {fst} related to %s (3rd) (value of which is %s (2nd)) while in $state %s (1st) isn't defined.
[verify]: {%s}{%s}: %s isa (%s), should be (%s)
-
(croak), verify() method. ref of $rc queried from something in {fst} related to %s (3rd) (value of which is %s (2nd)) while in $state %s (1st) is %s (4th). While it should be %s (5th) (the last one is literally $test).
EXAMPLES
Here are example records. Whole {fst}, honestly, might become enormous, thus are skipped for brewity.
alpha =>
{
switch => sub {
shift % 42, ''
},
tturn => [ qw/ alpha NEXT / ],
fturn => [ qw/ STOP horay! / ]
}
source() supposedly produces some numbers. Then, if $item doesn't devide mod 42
then go for another number. If $item devides then break out. Also, please note, STOP
(and BREAK
) is special -- it needs defined $action but it can be literally anything.
bravo =>
{
switch => sub {
my $item = shift;
$item % 15 ? 'charlie' :
$item % 5 ? 'delta' :
$item % 3 ? 'echo' :
undef, $item
},
uturn => [ qw/ bravo NEXT / ],
turns =>
{
charlie => [ qw/ charlie SAME / ],
delta => [ qw/ delta SAME / ],
echo => [ qw/ echo SAME / ]
}
}
Again, source() supposedly produces some numbers. Then some kind of FizBuzz happens. Also, returning undef
as default raises questions. However, it's acceptable for example.
Now, quick demonstration, that's how this FizzBuzz would look using DMA::FSM capabilities (and A::F of v2.2.7 syntax).
bravo_foo =>
{
switch => sub {
my $item = shift;
$item % 15 ? !0 : !1, $item
},
tturn => [ qw/ charlie SAME / ],
fturn => [ qw/ bravo_bar SAME / ]
},
bravo_bar =>
{
switch => sub {
my $item = shift;
$item % 5 ? !0 : !1, $item
},
tturn => [ qw/ delta SAME / ],
fturn => [ qw/ bravo_baz SAME / ]
},
bravo_baz =>
{
switch => sub {
my $item = shift;
$item % 3 ? !0 : !1, $item
},
tturn => [ qw/ echo SAME / ],
fturn => [ qw/ bravo NEXT / ]
}
World of a difference. Furthermore, if you dare, take a look at t/process/quadratic.t and t/process/sort.t. But better don't, those are horrible.
Now, illustration what $namespace configuration parameter opens.
Classics:
my $bb = Acme::FSM->connect(
{ },
alpha => {
switch => sub {
shift % 42, ''
}
}
);
Illustrations below are heavily stripped for brevity (Perlwise and A::Fwise).
Separate namespace:
package Secret::Namespace;
sub bravo_sn {
shift;
shift % 42, ''
}
package Regular::Namespace;
use Acme::FSM;
my $bb = Acme::FSM->connect(
{ namespace => 'Secret::Namespace' },
alpha => {
switch => 'bravo_sn'
}
);
Separate class:
package Secret::Class;
sub new { bless { }, shift }
sub bravo_sc {
shift;
shift % 42, ''
}
package Regular::Class;
use Secret::Class;
use Acme::FSM;
my $not_bb = Secret::Class->new;
my $bb = Acme::FSM->connect(
{ namespace => $not_bb },
alpha => {
switch => 'bravo_sc'
}
);
And, finally, A::F implodes upon itself:
package Secret::FSM;
use parent qw/ Acme::FSM /;
sub connect {
my $class = shift;
my $bb = $class->SUPER::connect( @_ );
} # or just skip constructor, if not needed
sub bravo_sf {
shift;
shift % 42, ''
}
package main;
my $bb = Secret::FSM->connect(
{ namespace => '' },
alpha => {
switch => 'bravo_sf'
}
);
COPYRIGHT AND LICENSE
Original Copyright: 2008 Dale M Amon. All rights reserved.
Original License:
LGPL-2.1
Artistic
BSD
Original Code Acquired from:
http://www.cpan.org/authors/id/D/DA/DALEAMON/DMAMisc-1.01-3.tar.gz
Copyright 2012, 2013, 2022 Eric Pozharski <whynto@pozharski.name>
GNU LGPLv3
AS-IS, NO-WARRANTY, HOPE-TO-BE-USEFUL