line
stmt
bran
cond
sub
pod
time
code
1
# Original Author: Dale M. Amon
2
# $Id: FSM.pm 564 2025-02-13 21:33:15Z whynot $
3
# Copyright 2012, 2013, 2022 Eric Pozharski
4
# Copyright 2025 Eric Pozharski
5
# GNU LGPLv3
6
# AS-IS, NO-WARRANTY, HOPE-TO-BE-USEFUL
7
8
25
25
11793079
use strict;
25
62
25
1083
9
25
25
164
use warnings;
25
96
25
2214
10
11
package Acme::FSM;
12
25
25
3958
use version 0.77; our $VERSION = version->declare( v2.3.6 );
25
18272
25
288
13
14
25
25
3225
use Carp qw| croak |;
25
61
25
91154
15
16
# TODO:202212202012:whynot: L<|/Linter>
17
18
=head1 NAME
19
20
Acme::FSM - pseudo Finite State Machine.
21
22
=cut
23
24
=head1 SYNOPSIS
25
26
my $bb = Acme::FSM->connect( { %options }, { %fst } );
27
$bb->process;
28
exit 0;
29
30
=cut
31
32
=head1 DESCRIPTION
33
34
B<(disclaimer)>
35
B is currently in proof-of-concept state.
36
There's a plan to accompany it with B with all diagnostics
37
avoided and run time checks in place.
38
And then with B with run time checks stripped.
39
Also, see L later in this POD|/BUGS AND CAVEATS>.
40
41
=head2 Concerning Inheritance
42
43
Through out code only methods are used to access internal state.
44
Supposedly, that will enable scaling some day later.
45
The only exception is L|/connect()> for obvious reasons.
46
47
Through out code neither internal state nor FST records are cached ever.
48
The only one seamingly inconsistent fragment is inside main loop when next
49
I<$state> is already found but not yet entered.
50
I<$action> is processed when the next I<$state> is set
51
(though, in some sense, not yet entered).
52
(If it doesn't make sense it's fine, refer to
53
L method|/process()> description later in this POD.)
54
55
This notice seems to be useles.
56
Instead, it might come handy someday.
57
58
=cut
59
60
=head2 Terminology
61
62
There're some weird loaded words in this POD,
63
most probably, poorly choosen
64
(but those are going to stay anyway).
65
So here comes disambiguation list.
66
67
=over
68
69
=item I<$action>
70
71
Special flag associated with next I<{state}> in a I<[turn]>
72
(covered in details in L|/process()> method description).
73
B<(note)> It may be applied to a I<[turn]> of I<{fst}> or I<{state}>.
74
75
=item blackboard
76
77
Shamelesly stolen from B.
78
Originally, it meant some opaque HASH passed around in B
79
where
80
user-code could store it's own ideas and such.
81
Now it's an object blessed into B
82
(for user-code still, I<{fsm}> has nothing to do with it).
83
84
=item entry
85
86
Layout of state records (see below) isn't particularly complicated.
87
However it's far from being uniform.
88
I is a placeholder name for any component of (unspecified) state record
89
when any semantics is irrelevant.
90
See also I<[turn]> and B below.
91
92
=item Finite State Machine
93
94
B.
95
96
=item Finite State Table
97
98
=item I<{fst}>
99
100
Collection of state records.
101
102
=item FSM
103
104
Acronym for I.
105
See above.
106
107
=item FST
108
109
Acronym for I.
110
See above.
111
112
=item internal state
113
114
=item I<{fsm}>
115
116
Some open-ended set of parameters dictating FSM's behaviour.
117
118
=item item
119
120
=item I<$item>
121
122
Something that makes sense for user-code.
123
B treats it as scalar with no internals
124
(mostly;
125
one exception is covered in L method|/diag()> description).
126
127
=item I<$namespace>
128
129
B uses elaborate schema to reach various callbacks
130
(three of them, at the time of writing).
131
This optional parameter is in use by this schema.
132
L method|/query()> has more.
133
134
=item I<$rule>
135
136
A scalar identifing a I<[turn]>.
137
One of opaque scalars returned by B callback
138
(the other is processed (modified or not) I<$item>).
139
L method|/process()> description has more.
140
141
=item B
142
143
Special piece of user-code
144
(it's required at construction (L method|/connect()>),
145
L method|/query_source()> describes how FSM reaches it).
146
Whatever it returns (in explicit scalar context) becomes I<$item>.
147
148
=item I<$state>
149
150
A scalar identifing a I<{state}> (see below) or parameter of I<{fsm}> (see
151
above).
152
153
=item state flow
154
155
Just like control flow but for I<$state>s.
156
157
=item state record
158
159
=item I<{state}>
160
161
One record in I<{fst}>.
162
The record consists of entries (see above).
163
L|/process()> method description has more.
164
Should be noted, in most cases "I<{state}>" should be read as
165
"I<$state> I<{state}>" instead,
166
but that starts to smell bufallo.
167
168
=item B
169
170
A mandatory callback associated with every I<{state}>.
171
L method|/process()> and
172
L method|/query_switch()>
173
descriptions have more.
174
175
=item I<$turn>
176
177
No such thing.
178
It's I<$rule> instead (see above).
179
180
=item I<[turn]>
181
182
Specially crafted entry in I<{state}>
183
(covered in details in L method|/process()> description).
184
Such entry describes what next I<$state> should be picked in state flow
185
and what to do with I<$item>.
186
187
=item turn map
188
189
This idiom is used in place of "C I<$rule> of I<[turn]>".
190
191
=back
192
193
=cut
194
195
=head1 B
196
197
$bb1 = Acme::FSM->connect( { %options1 }, %fst1 );
198
$bb2 = Acme::FSM->connect( { %options2 }, { %fst2 } );
199
$bb3 = $bb2->connect( { %options3 } );
200
201
Creates a blackboard object.
202
Blackboard isa HASH, it's free to use except special C<_> key;
203
that key is for I<{fsm}> exclusively.
204
First parameter isa I<%$options>, it's required
205
(pass empty HASH if nothing to say).
206
Defined keys are:
207
208
=over
209
210
=item I
211
212
(positive integer)
213
Sets a diagnostic threshold.
214
It's meaning is covered in L method|/diag()> documentation.
215
If C then set to C<1> (C<0> is B).
216
217
=item I
218
219
(scalar or C)
220
B operates on arbitrary items and there's a diagnostic service that
221
sometimes insists on somehow showing those arbitrary items.
222
It's up to user's code to process that arbitrary data and yield some scalar
223
represantation.
224
Refer to L method|/query_dumper()> documentation for
225
details.
226
Optional.
227
Simple stringifier is provided by L method|/query_dumper()>
228
itself.
229
230
=item I
231
232
(scalar or object(!) B[) ]
233
Sets a context for various parts of I<{fsm}> and services would be resolved.
234
No defaults.
235
Refer to L method|/query()> documentation for details.
236
237
=item I
238
239
(scalar or C)
240
Sets a source of items to process to be queried.
241
Required.
242
Refer to L method|/query_source()> documentation for
243
details.
244
245
=back
246
247
Second is FST (Finite State Table).
248
It's required for class construction and ignored (if any) for object
249
construction.
250
Difference between list and HASH is the former is copied into HASH internally;
251
the latter HASH is just saved as reference.
252
The FST is just borrowed from source object during object construction.
253
Thus, in the synopsis, I<$bb3> and I<$bb2> use references to HASH
254
I<%$fst2>.
255
An idea behind this is to minimize memory footprint.
256
OTOH, maninpulations with one HASH are effectevely manipulations with FST of
257
any other copy-object that borrowed that FST.
258
259
IOW, anything behind FST HASH of class construction or options HASH of object
260
construction isa trailer and Bed.
261
Obviously, there's no trailer in class construction with list FST.
262
263
=cut
264
265
my @options = qw| diag_level namespace source dumper |;
266
sub connect {
267
770
770
0
11861005
my( $self, $opts ) = ( shift @_, shift @_ );
268
770
100
3577
ref $opts eq q|HASH| or croak sprintf
269
q|[connect]: {options} HASH is required, got (%s) instead|, ref $opts;
270
768
1449
my( $trailer, $leader );
271
768
100
2216
if( ref $self eq '' ) {
272
748
1798
$trailer = ref $_[0] eq q|HASH|;
273
748
100
3714
$self = bless { _ => { fst => $trailer ? shift @_ : { @_ }}}, $self;
274
748
1675
$leader = q|clean init with| }
275
else {
276
20
40
$trailer = @_;
277
20
37
$self = bless { _ => { %{$self->{_}} }}, ref $self;
20
215
278
20
62
$leader = q|stealing| }
279
768
100
10311
$self->{_}{$_} = delete $opts->{$_} // $self->{_}{$_} foreach @options;
280
768
100
2812
$self->{_}{diag_level} //= 1;
281
$self->diag( 3, q|%s (%i) items in FST|,
282
768
1376
$leader, scalar keys %{$self->{_}{fst}} );
768
3765
283
284
768
2405
$self->carp( qq|($_): unknown option, ignored| ) foreach keys %$opts;
285
768
100
2395
$self->carp( q|(source): unset| ) unless defined $self->{_}{source};
286
768
100
3719
$trailer = ref $_[0] eq q|HASH| ? keys %{$_[0]} : @_ if $trailer;
2
100
8
287
768
100
1799
$self->carp( qq|ignoring ($trailer) FST items in trailer| ) if $trailer;
288
$self->carp( q|FST has no {START} state| ) unless
289
768
100
2230
exists $self->{_}{fst}{START};
290
$self->carp( q|FST has no {STOP} state| ) unless
291
768
100
3080
exists $self->{_}{fst}{STOP};
292
768
2118
@{$self->{_}}{qw| state action |} = qw| START VOID |;
768
3487
293
768
3021
return $self }
294
295
=head1 B
296
297
$bb = Acme::FSM->connect( { }, \%fst );
298
$rc = $bb->process;
299
300
This is heart, brains, liver, and so on of B.
301
B is what does actual state flow.
302
That state flow is steered by records in I<%fst>.
303
Each record consists of:
304
305
=over
306
307
=item B callback
308
309
This is some user supplied code that
310
always consumes whatever B callback returns (at some point in past),
311
(optionally) regurgitates said input,
312
and decides what I<[turn]> to go next.
313
Except when in special I<{state}> (see below) it's supplied with one argument:
314
I<$item>.
315
In special states I<$item> is missing.
316
B returns two controls: I<$rule> and processed I<$item>.
317
What to do with returned I<$item> is determined by I<$action> (see below).
318
319
=item various I<[turn]>s
320
321
Each I<[turn]> spec is an ARRAY with two elements (trailing elements are
322
ignored).
323
First element is I<$state> to change to.
324
Second is I<$action> that sets what to do with I<$item> upon changing to named
325
I<$state>.
326
Here are known I<[turn]>s in order of logical treating decreasing.
327
328
=over
329
330
=item C
331
332
That I<[turn]> will be choosen by FSM itself when I<$item> at hands is
333
C
334
(as returned by B).
335
I isn't invoked -- I<$item> is void, there's nothing to call
336
I with.
337
However, I<$action> may change I<$item>.
338
339
=item C
340
341
That I<[turn]> will be choosen by FSM if I<$rule> is C
342
(as returned by B).
343
Idea behind this is to give an FST an option to bailout.
344
In original B that's not possible (except Bing, you know).
345
Also, see L|/Perl FALSE, undef, and uturn>.
346
347
=item C and/or C
348
349
If any (hence 'or') is present then I<$rule> returned by B is
350
treated as Perl boolean, except C it's handled by C.
351
That's how it is in original B.
352
If B always returns TRUE (or FALSE) then C (or C) can
353
be missing.
354
Also, see L|/tturn, fturn, and switch()>.
355
356
=item C
357
358
If neither C nor C is present then whatever I<$rule> would be
359
returned by B is treated as string.
360
That string is supposed to be a key in I<%$turns>.
361
I<$rule> returned is forced to be string (so if you dare to return objects,
362
such objects should have C<""> overloaded).
363
Also, see L|/Default For Turn Map>.
364
365
=back
366
367
I<$state> is treated as a key in FST hash, except when that's a special state.
368
Special states (in alphabetical order):
369
370
=over
371
372
=item C
373
374
Basically, it's just like C I<$state>.
375
All comments there (see below) apply.
376
It's added to enable break out from loop, do something, then get back in loop.
377
I<$state> is changed to C just before returning to caller
378
(I is called in C state).
379
The choice where to implicitly change state to C has been completely
380
arbitrary;
381
probably wrong.
382
383
=item C
384
385
Just like C state (see below, all comments apply).
386
While C is turned to C implicitly no other handling is made.
387
388
=item C
389
390
It's I<$state> set by B.
391
I<$action> (it's also set by B) is ignored
392
(if I<$action> is C then silently replaces with empty string),
393
B is invoked with no arguments
394
(there's not anything to process yet).
395
Whatever I<$item> could be returned is ignored.
396
I<$rule> is followed.
397
Thus C can't be followed.
398
See also L|/Special handling of START and CONTINUE>.
399
400
=item C
401
402
It's last state in the state flow.
403
I<$action> is retained
404
(that's what B will return)
405
(however, because it's processed before C state is acknowledged it must
406
not be C)
407
but otherwise ignored.
408
B is invoked with no arguments
409
(B of previous I<{state}> should have took care).
410
Whatever I<$item> could be returned is ignored.
411
Whatever I<$rule> could be returned is reported (at I<(basic trace)> level)
412
but otherwise ignored.
413
Then I<$action> is returned and state flow terminates.
414
415
=back
416
417
Supported I<$action>s (in alphabetical order):
418
419
=over
420
421
=item C
422
423
Drop whatever I<$item> at hands.
424
Request another.
425
426
If FST has such record:
427
428
somestate => { eturn => [ somestate => 'NEXT' ] }
429
430
then FSM will stay in C as long as I callback returns
431
C.
432
Thus consuming all resources available.
433
No options provided to limit that consumption.
434
435
=item C
436
437
Retains I<$item> uncoditionally.
438
That is, even if I<$item> isn't B it's kept anyway.
439
440
B, if FST has such record:
441
442
somestate => { eturn => [ somestate => 'SAME' ] }
443
444
then FSM will cycle here forever.
445
That is, since I isn't queried for other I<$item>
446
(what's the purpose of this action is anyway)
447
there's no way to get out.
448
449
=item C
450
451
Check if I<$item> is B, then go as with C or C otherwise.
452
That actually makes sense.
453
454
I<(note)> This action name is legacy of B;
455
Possibly, that's C something;
456
Someone can just speculate what C could mean.
457
458
=back
459
460
=back
461
462
=cut
463
464
sub process {
465
690
690
0
41035
my $self = shift @_;
466
690
1331
my( $branch, $turn );
467
468
# XXX:202201072033:whynot: C and C being handled specially is a side-effect of this extra sequence. Should be moved in the main loop with special handling. This results in there-be-dragons uncertainty.
469
690
1795
$self->diag( 3, q|{%s}(%s): entering|, $self->state, $self->action );
470
690
1664
$branch = $self->query_switch;
471
672
1440
$turn = $self->turn( $self->state, $branch );
472
580
1798
$self->diag( 5, q|{%s}(%s): switch returned: (%s)|, @$turn, $branch );
473
580
1431
$self->state( $turn->[0] );
474
580
1467
$self->action( $turn->[1] );
475
476
580
1277
my( $item, $dump ) = $self->query_source;
477
580
1711
$self->diag( 3, q|{%s}(%s): %s: going with|, @$turn, $dump );
478
479
# No one gets out of this loop without the state tables permission!
480
580
843
while ( 1 ) {
481
# We should never see an undefined state unless we've made a mistake.
482
# NOTE:202201072131:whynot: As a matter of fact, we don't now.
483
178144
326855
$self->verify( $self->fst( $self->state ),
484
$self->state, '', q|record|, q|HASH| );
485
486
178062
378434
( $branch, $item ) = $self->query_switch( $item );
487
178049
454897
$self->diag( 5, q|{%s}(%s): switch returned: (%s)|, @$turn, $branch );
488
178048
326347
$dump = $self->query_dumper( $item );
489
178046
624666
$turn = $self->turn( $self->state, $branch );
490
177962
468942
$self->diag( 3, q|{%s}(%s): %s: turning with|,
491
$turn->[0], $branch, $dump );
492
177962
411644
$self->state( $turn->[0] );
493
177960
426885
$self->action( $turn->[1] );
494
495
177957
398080
$self->diag( 5, q|{%s}(%s): going for|, @$turn );
496
177957
100
364157
$turn->[0] eq q|STOP| and last;
497
177718
100
347239
$turn->[0] eq q|BREAK| and last;
498
177581
100
384837
$turn->[1] eq q|SAME| and redo;
499
87377
100
219807
$turn->[1] eq q|NEXT| and next;
500
7360
100
100
29467
$turn->[1] eq q|TSTL| && defined $item and redo;
501
7342
100
20220
$turn->[1] eq q|TSTL| and next;
502
14
2118
croak sprintf q|[process]: {%s}(%s): unknown action|, @$turn }
503
continue {
504
87345
184659
( $item, $dump ) = $self->query_source;
505
87342
209086
$self->diag( 5, q|{%s}(%s): %s: going with|, @$turn, $dump ) }
506
507
376
1016
$self->diag( 3, q|{%s}(%s): leaving|, @$turn );
508
# XXX:20121231215139:whynot: Nothing to B, leaving anyway.
509
376
806
$branch = $self->query_switch;
510
359
1000
$self->diag( 5, q|{%s}(%s): switch returned: (%s)|, @$turn, $branch );
511
359
100
960
$self->diag( 3, q|{%s}(%s): changing state: (CONTINUE)|, @$turn )
512
->state( q|CONTINUE| ) if $turn->[0] eq q|BREAK|;
513
359
734
return $self->action }
514
515
516
=head1 METHODS AND STUFF
517
518
Access and utility methods to deal with various moves while doing The State
519
Flow.
520
These aren't forbidden for use from outside,
521
while being quite internal nevertheles.
522
523
=over
524
525
=cut
526
527
=item B
528
529
$rc = $self->query_rc( @args );
530
$rc = $self->verify( $rc, $state, $tag, $subject, $test );
531
532
Here comes rationale.
533
Writing (or should I say "composing"?) correct {fst} B style is hard
534
(I know what I'm talking about, I've made a dozen already).
535
The purpose of B is to check if the I<{fst}> at hands isn't fubar.
536
Nothing more, nothing less.
537
B is a placeholder for one of B methods,
538
I<$test> will be matched against C[. ]
539
Other arguments are to fill diagnostic output (if any).
540
I<$state> hints from what I<{state}> I<$rc> has been queried.
541
I<$subject> and I<$tag> are short descriptive name and actual value of I<$rc>.
542
Yup, dealing with B might be fubar too.
543
544
I<$rc> is passed through (or not).
545
This Bs if I<$rc> isn't B or C[ doesn't match ]
546
I<$test>.
547
548
=cut
549
550
# TODO:202202150137:whynot: Replace C with B, plz.
551
sub verify {
552
1352283
1352283
1
1860222
my $self = shift @_;
553
# XXX:202202092101:whynot: Nope, needs I<$state> because sometimes I<{state}> isn't entered yet.
554
1352281
2561353
my( $entry, $state, $what, $manifest, $test ) = @_;
555
1352281
100
2367798
defined $entry or croak sprintf q|[verify]: {%s}(%s): %s !isa defined|,
556
$state, $what, $manifest;
557
1352009
100
2471874
ref $entry eq $test or croak sprintf
558
q|[verify]: {%s}(%s): %s isa (%s), should be (%s)|,
559
$state, $what, $manifest, ref $entry, $test;
560
1351943
2550899
return $entry }
561
562
=item B
563
564
$bb->state eq 'something' and die;
565
$state = $bb->state( $new_state );
566
567
Queries and sets state of B instance.
568
Modes:
569
570
=over
571
572
=item no argument
573
574
Returns state of instance.
575
Note, Perl FALSE B parameter.
576
577
=item lone scalar
578
579
Sets I<$state> of instance.
580
Returns previous I<$state>.
581
582
=back
583
584
=cut
585
586
sub state {
587
1455932
1455932
1
2068468
my $self = shift @_;
588
1455930
100
2331174
unless( @_ ) {
100
589
1277260
3064656
return $self->{_}{state} }
590
0
0
elsif( 1 == @_ ) {
591
178669
325517
my $backup = $self->state;
592
178668
415949
$self->diag( 5, q|changing state: (%s) (%s)|, $backup, $_[0] );
593
178668
303691
$self->{_}{state} = shift @_;
594
178668
264028
return $backup }
595
else {
596
1
6
$self->carp( sprintf q|too many args (%i)|, scalar @_ );
597
1
5
return undef }}
598
599
=item B
600
601
%state = %{ $bb->fst( $state ) };
602
%state = %{ $bb->fst( $state => \%new_state ) };
603
$value = $bb->fst( $state => $entry );
604
$value = $bb->fst( $state => $entry => $new_value );
605
606
Queries and sets records and entries in I<{fst}>.
607
That is, not only entire I<{state}>s
608
but components of I<{state}> are reachable too.
609
Modes:
610
611
=over
612
613
=item query specific I<{state}> of specific I<$state>
614
615
Executed if one scalar is passed in.
616
Returns a I<{state}> reference with whatever entries are set.
617
Silently returns C if I<$state> is missing from I<{fst}>.
618
619
=item set I<{state}> of specific I<$state>
620
621
Executed if one scalar and HASH are passed in.
622
Sets a I<{state}> with key/value pairs from HASH,
623
creating one if necessary.
624
Created record isa copy of HASH, not a reference
625
(not a true deep copy though)
626
(empty I<\%new_state> is fine too)
627
(copying isn't by design, it's implementation's quirk).
628
Returns record as it was before setting
629
(C is returned if there were no such I<$state> before).
630
631
=item query specific I<$entry> of specific I<$state>
632
633
Executed if two scalars are passed in.
634
Returns an entry from named state record.
635
636
=item set specific I<$entry> of specific I<$state>
637
638
Executed if two scalars and anything else are passed in
639
(no implicit intelligence about third parameter).
640
Sets an entry in named state record,
641
creating one (entry) if necessary.
642
State record must exist beforehand.
643
Entry isa exact value of least argument, not a copy.
644
Returns whatever value I<$entry> just had
645
(C is returned if there were none such I<$entry> before).
646
647
=back
648
649
None checks are made, except record must exist (for two latter uses).
650
651
=cut
652
653
sub fst {
654
236843
236843
1
385625
my $self = shift @_;
655
236843
100
1051533
unless( @_ ) {
100
100
100
100
100
656
1
6
$self->carp( q|no args| );
657
1
7
return undef }
658
0
100
0
elsif( 2 == @_ && ref $_[1] eq q|HASH| ) {
659
2
11
my $backup = $self->fst( $_[0] );
660
2
50
14
$self->diag( 3, q|%s {%s} record|,
661
( $backup ? q|updating| : q|creating| ), $_[0] );
662
# XXX:202202150056:whynot: Copy is a side-effect instead.
663
2
6
$self->{_}{fst}{shift @_} = {%{ pop @_ }};
2
11
664
2
10
return $backup }
665
0
0
elsif( !exists $self->{_}{fst}{$_[0]} ) {
666
91
298
$self->carp( qq|($_[0]): no such {fst} record| );
667
91
395
return undef }
668
0
0
elsif( 1 == @_ ) {
669
178070
455839
return $self->{_}{fst}{shift @_} }
670
0
0
elsif( 2 == @_ ) {
671
58674
190877
return $self->{_}{fst}{shift @_}{shift @_} }
672
0
0
elsif( 3 == @_ ) {
673
3
13
my $backup = $self->fst( $_[0] => $_[1] );
674
3
100
20
$self->diag( 3, q|%s {%s}{%s} entry|,
675
( $backup ? q|updating| : q|creating| ), @_[0,1] );
676
3
10
$self->{_}{fst}{shift @_}{shift @_} = pop @_;
677
3
13
return $backup }
678
else {
679
1
13
$self->carp( sprintf q|too many args (%i)|, scalar @_ );
680
1
6
return undef }}
681
682
=item B
683
684
$bb->turn( $state ) eq '' or die;
685
$bb->turn( $state => 'uturn' )->[1] eq 'NEXT' or die;
686
687
Queries I<[turn]>s of arbitrary I<{state}>s.
688
B doesn't manipulate entries, use L method|/fst()> instead
689
L.
690
Modes:
691
692
=over
693
694
=item query expected behaviour
695
696
This mode is entered if there is lone scalar.
697
Such scalar is believed to be I<$state>.
698
Returns something that describes what kind of least special states are
699
present.
700
Namely:
701
702
=over
703
704
=item *
705
706
C is returned if I<$state> isn't present in the I<{fst}>
707
(also Bs).
708
Also see below.
709
710
=item *
711
712
Empty string is returned if there're I and/or I turns.
713
I hash is ignored in that case.
714
715
=item *
716
717
C is returned if there's turn map
718
(and neither I nor I is present).
719
B<(note)> In that case, B checks for I is indeed a HASH,
720
nothing more
721
(however B if that's not the case);
722
It may as well be empty;
723
Design legacy.
724
725
=item *
726
727
Returns C for C and C I<$state>s without any further
728
processing
729
(For those I<$state>s any I<$rule> is ignored and C enables I
730
callbacks to give more informative logs
731
(while that information is mangled anyway);
732
Probably bad idea).
733
734
=item *
735
736
C is returned if there's nothing to say --
737
neither I, nor I, nor turn map --
738
this record is kind of void.
739
The record should be studied to find out why.
740
Bs in that case.
741
742
=back
743
744
=item query specific I<[turn]>
745
746
Two scalars are I<$state> and specially encoded I<$rule>
747
(refer to L method|/query_switch()> about encoding).
748
If I<$rule> can't be decoded then Bs.
749
Returns (after verification) requested I<$rule> as ARRAY.
750
While straightforward I<[turn]>s (such as C, C, and such) could
751
be in fact queried through L method|/fst()> turn map needs bit more
752
sophisticated handling;
753
and that's what B does;
754
in fact asking for C will result in B.
755
I<$action> of C and C special states suffer implicit
756
defaulting to empty string.
757
758
=item anything else
759
760
No arguments or more then two is an non-fatal error.
761
Returns C (with B).
762
763
=back
764
765
=cut
766
767
# TODO:202202172011:whynot: As soon as supported perl is young enough change it to smartmatch, plz.
768
my %special_turns = map { $_ => 1 } qw| eturn uturn tturn fturn |;
769
# TODO:202202162030:whynot: Consider more elaborate (informative) returns.
770
sub turn {
771
357981
357981
1
622439
my $self = shift @_;
772
357981
50
1375256
unless( @_ ) {
100
100
100
773
1
5
$self->carp( q|no args| ); return undef }
1
6
774
0
100
0
elsif( 1 == @_ && !exists $self->{_}{fst}{$_[0]} ) {
775
1
7
$self->carp( qq|($_[0]): no such {fst} record| );
776
1
6
return undef }
777
0
0
elsif( 1 == @_ ) {
778
179190
273479
my $state = shift @_;
779
my $entry = $self->verify(
780
179190
406729
$self->{_}{fst}{$state}, $state, '', q|entry|, q|HASH| );
781
# WORKAROUND:201305070051:whynot: Otherwise there will be spurious Bs about anyway useless turns in those entries.
782
179185
100
100
554575
$state eq q|STOP| || $state eq q|BREAK| and return q|HASH|;
783
178814
100
100
560737
exists $entry->{tturn} || exists $entry->{fturn} and return '';
784
24234
100
51155
unless( exists $entry->{turns} ) {
785
# XXX:201305071531:whynot: Should just B instead, probably.
786
10163
34607
$self->carp( qq|{$state}: none supported turn| );
787
10163
46138
return undef }
788
14071
38944
$self->verify( $entry->{turns}, $state, q|turns|, q|turn|, q|HASH| );
789
14068
28864
return q|HASH| }
790
0
0
elsif( 2 == @_ ) {
791
178787
323496
my( $state, $turn ) = @_;
792
178786
237423
my $entry;
793
178786
387353
$self->verify( $turn, $state, $turn, q|turn|, '' );
794
178785
100
370836
if( exists $special_turns{$turn} ) {
50
795
164774
356830
$entry = $self->{_}{fst}{$state}{$turn} }
796
elsif( !index $turn, q|turn%| ) {
797
14011
45527
$entry = $self->{_}{fst}{$state}{turns}{substr $turn, 5} }
798
else {
799
0
0
croak sprintf q|[turn]: {%s}(%s): unknown turn|, $state, $turn }
800
178785
380042
$self->verify( $entry, $state, $turn, q|turn|, q|ARRAY| );
801
178668
426895
$self->verify( $entry->[0], $state, $turn, q|state|, '' );
802
# XXX:20121230140241:whynot: {START}{turn}{action} is ignored anyway.
803
# XXX:201305072006:whynot: {CONTINUE}{turn}{action} is ignored too.
804
178602
100
100
611671
$entry->[1] //= '' if $state eq q|START| || $state eq q|CONTINUE|;
100
805
178602
428433
$self->verify( $entry->[1], $state, $turn, q|action|, '' );
806
178568
339300
return $entry }
807
else {
808
0
0
$self->carp( sprintf q|too many args (%i)|, scalar @_ );
809
0
0
return undef }
810
}
811
812
=item B
813
814
$bb->action eq $action and die;
815
$action = $bb->action( $new_action );
816
817
Queries and sets I<$action> of B instance.
818
Modes:
819
820
=over
821
822
=item query I<$action>
823
824
No arguments -- returns current I<$action> of the instance.
825
Note, Perl FALSE B parameter.
826
827
=item set I<$action>
828
829
One scalar -- sets action of the instance.
830
Returns previous I<$action>.
831
832
=back
833
834
=cut
835
836
sub action {
837
358207
358207
1
499074
my $self = shift @_;
838
358206
100
613454
unless( @_ ) {
100
839
179664
361173
return $self->{_}{action} }
840
0
0
elsif( 1 == @_ ) {
841
178541
298795
my $backup = $self->action;
842
178540
428522
$self->diag( 5, q|changing action: (%s) (%s)|, $backup, $_[0] );
843
178539
302337
$self->{_}{action} = shift @_;
844
178539
264255
return $backup }
845
else {
846
1
11
$self->carp( sprintf q|too many args (%i)|, scalar @_ );
847
1
7
return undef }}
848
849
=item B
850
851
( $alpha, $bravo ) = $self->query( $what, $name, @items );
852
853
Internal method, then it becomes complicated.
854
Resolves I<$what> (some callback, there multiple of them) against
855
I<$namespace>, if necessary.
856
Then invokes resolved code appropriately passing I<@items> in, if any;
857
Product of the callback over I<@items> is returned back to the caller.
858
I<$name> is used for disgnostics only.
859
Trust me, it all makes perfect sense.
860
861
I<$what> must be either CODE or scalar, or else.
862
863
Strategy is like this
864
865
=over
866
867
=item I<$what> isa CODE
868
869
I<$what> is invoked with I<$self> and I<@items> as arguments.
870
Important, in this case I<$self> is passed as first argument,
871
OO isn't involved like at all.
872
873
=item I<$namespace> is empty string
874
875
Trade I<$namespace> for I<$self> (see below) and continue.
876
877
=item I<$namespace> is scalar
878
879
Treat I<$what> as a name of function in I<$namespace> namespace.
880
881
=item I<$namespace> is object reference
882
883
Treat I<$name> as a name of method of object referred by I<$namespace>.
884
885
=back
886
887
It really works.
888
889
=cut
890
891
sub query {
892
412651
412651
1
898649
my( $self, $topic, $manifest ) = ( shift @_, shift @_, shift @_ );
893
412651
2281421
my $caller = ( split m{::}, ( caller 1 )[3] )[-1];
894
412651
100
1082275
defined $topic or croak sprintf q|[%s]: %s !isa defined|,
895
$caller, $manifest, $self->state;
896
412616
1102620
$self->diag( 5, q|[%s]: %s isa (%s)|, $caller, $manifest, ref $topic );
897
412615
100
1281936
ref $topic eq q|CODE| and return $topic->( $self, @_ );
898
48
100
1418
ref $topic eq '' or croak sprintf
899
q|[%s]: %s isa (%s): no way to resolve this|,
900
$caller, $manifest, ref $topic;
901
40
100
692
defined $self->{_}{namespace} or croak
902
qq|[$caller]: {namespace} !isa defined|;
903
36
64
my $anchor = $self->{_}{namespace};
904
36
63
my $backup = $topic;
905
36
100
100
187
if( ref $anchor eq '' && $anchor eq '' ) {
906
12
39
$self->diag( 5, q|[%s]: defaulting %s to $self|, $caller, $manifest );
907
12
20
$anchor = $self }
908
36
109
$self->diag( 5, q|[%s]: {namespace} isa (%s)|, $caller, ref $anchor );
909
36
100
95
unless( ref $anchor eq '' ) {
910
24
67
$self->diag( 5, q|[%s]: going for <%s>->[%s]|,
911
$caller, ref $anchor, $topic );
912
24
148
$topic = $anchor->can( $topic );
913
24
100
1209
$topic or croak sprintf q|[%s]: object of <%s> can't [%s] method|,
914
$caller, ref $anchor, $backup;
915
16
65
return $anchor->$topic( @_ ) }
916
else {
917
12
35
$self->diag( 5, q|[%s]: going for <%s>::[%s]|,
918
$caller, $anchor, $topic );
919
12
88
$topic = UNIVERSAL::can( $anchor, $topic );
920
12
100
695
$topic or croak sprintf q|[%s]: <%s> package can't [%s] subroutine|,
921
$caller, $anchor, $backup;
922
8
28
return $topic->( $self, @_ ) }}
923
924
=item B
925
926
( $rule, $item ) = $self->query_switch( $item );
927
928
Internal multitool.
929
That's the point where decisions about turns are made.
930
B<(note)>
931
B converts I<$rule> (as returned by B) to specially
932
encoded scalar;
933
it's caller's responcibility pick correct I<[turn]> later.
934
Strategy:
935
936
=over
937
938
=item no arguments
939
940
Special-state mode:
941
invoke B with no arguments;
942
ignore whatever I<$item> has been possibly returned;
943
return I<$rule> alone.
944
945
=item I<$item> is C
946
947
EOF mode:
948
ignore B completely;
949
return C and C.
950
951
=item I<$item> is not C
952
953
King-size mode:
954
invoke B, pass I<$item> as single argument.
955
return I<$rule> and I<$item>
956
(whatever it became after going through B).
957
958
=back
959
960
I<$rule>, as it was returned by B, is encoded like this:
961
962
=over
963
964
=item I<$rule> is C
965
966
Return C.
967
B<(note)>
968
Don't verify if C I<[turn]> exists.
969
970
=item I<$rule> is Perl TRUE and C and/or C are present
971
972
Return C
973
B<(note)>
974
Don't verify if C I<[turn]> exists.
975
976
=item I<$rule> is Perl FALSE and C and/or C are present
977
978
Return C
979
B<(note)>
980
Don't verify if C I<[turn]> exists.
981
982
=item neither C or C are present
983
984
Encode I<$rule> like this C<'turn%' . $rule> and return that.
985
B((note)>
986
Don't verify if turn map exists.
987
B<(note)>
988
Don't verify if C<"turn%$rule"> exists in turn map.
989
990
=back
991
992
B is always invoked in list context even if I<$item> would be
993
ignored.
994
If I<$rule> shouldn't be paired with I<$item> it won't be
995
(it's safe to call B in scalar context then and
996
there won't be any trailing Cs).
997
998
=cut
999
1000
sub query_switch {
1001
179219
179219
1
278980
my $self = shift @_;
1002
179219
251137
my @turn;
1003
# WORKAROUND:20121229000801:whynot: No B, B does its checks by itself.
1004
179219
100
100
622708
@turn = $self->query(
1005
$self->fst( $self->state, q|switch| ),
1006
sprintf( q|{%s}{switch}|, $self->state ),
1007
@_ ) if !@_ || defined $_[0];
1008
179166
722217
my $kind = $self->turn( $self->state );
1009
179164
100
100
717113
$turn[0] =
100
100
100
1010
@_ && !defined $_[0] ? q|eturn| :
1011
# TODO:202201071700:whynot: Make C special only when C is present, plz.
1012
!defined $turn[0] ? q|uturn| :
1013
# FIXME:201304230145:whynot: Defaulting to basics here looks as bad as Bing.
1014
# TODO:202212202039:whynot: L.
1015
$kind ? qq|turn%$turn[0]| :
1016
$turn[0] ? q|tturn| : q|fturn|;
1017
179164
100
497530
return @_ ? @turn : $turn[0] }
1018
1019
=item B
1020
1021
( $item, $dump ) = $self->query_source;
1022
1023
Seeks B callback and acquires whatever it returns.
1024
The callback is called in scalar context.
1025
As useful feature, also feeds I<$item> to L.
1026
L method|/query()> has detailed description how B
1027
callback is acquired.
1028
Returns I<$item> and result of L callback|/dumper>.
1029
1030
=cut
1031
1032
sub query_source {
1033
87946
87946
1
148726
my $self = shift @_;
1034
# WORKAROUND:20121229001530:whynot: No B, I<{source}> can return anything.
1035
87946
254370
my $item = $self->query( $self->{_}{source}, q|{source}|, @_ );
1036
87938
559319
return $item, $self->query_dumper( $item ) }
1037
1038
=item B
1039
1040
$dump = $self->query_dumper( $item );
1041
1042
Seeks I callback (L).
1043
If the callback wasn't configured uses simple hopefully informative and
1044
C proof substitution.
1045
Whatever the callback returns is checked to be B
1046
(C is changed to C<"(unclear)">)
1047
and then returned.
1048
1049
=cut
1050
1051
sub query_dumper {
1052
266010
266010
1
400529
my $self = shift @_;
1053
return $self->verify(
1054
$self->query(
1055
# TODO:202202210258:whynot: This is inefficient, defaulting should happen in B instead.
1056
261946
100
261946
1402211
$self->{_}{dumper} // sub { sprintf q|(%s)|, $_[1] // q|undef| },
1057
266010
66
1459837
q|{dumper}|, @_ ) // q|(unclear)|,
100
1058
# XXX:202202210304:whynot: 'source' looks like remnants of refactoring. Should investigate it deeper.
1059
$self->state, qw| source source |, '' ) }
1060
1061
=item B
1062
1063
$bb->diag( 3, 'going to die at %i.', __LINE__ );
1064
1065
Internal.
1066
Provides unified and single-point-of-failure way to output diagnostics.
1067
Intensity is under control of
1068
L configuration parameter|/diag_level>.
1069
Each object has it's own,
1070
however it's inherited when objects are copied.
1071
1072
Defined levels are:
1073
1074
=over
1075
1076
=item C<0>
1077
1078
Nothing at all.
1079
Even error reporting is suppressed.
1080
1081
=item C<1>
1082
1083
Default.
1084
Errors of here-be-dragons type.
1085
1086
=item C<2>
1087
1088
Basic diagnostics for callbacks.
1089
1090
=item C<3>
1091
1092
Basic trace.
1093
Construction, starting and leaving runs.
1094
1095
=item C<4>
1096
1097
Extended diagnostics for callbacks.
1098
1099
=item C<5>
1100
1101
Deep trace.
1102
By the way diagnostics of I entry resolving.
1103
1104
=back
1105
1106
=cut
1107
1108
sub diag {
1109
1394756
1394756
1
1939077
my $self = shift @_;
1110
1394756
100
3121634
$self->{_}{diag_level} >= shift @_ or return $self;
1111
# TODO:202212222141:whynot: Since something this B might emit warnings. And maybe it's appropriate.
1112
444
100
5667
printf STDERR sprintf( qq|[%s]: %s\n|,
1113
( split m{::}, ( caller 1 )[3])[-1], shift @_ ),
1114
map $_ // q|(undef)|, @_;
1115
444
1213
return $self }
1116
1117
=item B
1118
1119
$bb->carp( 'something wrong...' );
1120
1121
Internal.
1122
Bs consistently if I<{_}{diag_level}> is B C<0>.
1123
1124
=back
1125
1126
=cut
1127
1128
sub carp {
1129
11025
11025
1
22499
my $self = shift @_;
1130
11025
100
26375
$self->{_}{diag_level} >= 1 or return;
1131
11018
74915
unshift @_, sprintf q|[%s]: |, ( split m{::}, ( caller 1 )[3])[-1];
1132
11018
1622416
&Carp::carp }
1133
1134
=head1 BUGS AND CAVEATS
1135
1136
=over
1137
1138
=item Default For Turn Map
1139
1140
B<(missing feature)>
1141
It's not hard to imagine application of rather limited turn map that should
1142
default on anything else deemed irrelevant.
1143
Right now to achieve logic like this such defaulting ought to be merged into
1144
B.
1145
That's insane.
1146
1147
=item Diagnostics
1148
1149
B<(misdesign)>
1150
Mechanics behind diagnostics isa failure.
1151
It's messy, fragile, misguided, and (honestly) premature.
1152
At the moment it's useless.
1153
1154
=item Hash vs HASH vs Acme::FSM Ref Constructors
1155
1156
B<(messy, probably bug)>
1157
L method description|/connect()> _declares_ that list is copied.
1158
Of course, it's not true deep copy
1159
({fst} might contain CODE, it's not clear how to copy it).
1160
It's possible, one day list trailer variant of initialization may be put to
1161
sleep.
1162
See also L.
1163
1164
=item Linter
1165
1166
B<(missing feature)>
1167
It might be hard to imagine,
1168
but FST might get out of hands
1169
(ie check F).
1170
Indeed, some kind of (limited!) linter would be much desired.
1171
It's missing.
1172
1173
=item Perl FALSE, C, and C
1174
1175
B<(caveat)>
1176
Back then B treated C as any other Perl FALSE.
1177
C I<$rule> mech has made C special
1178
(if B returns C and C I<{turn}> isn't present then
1179
it's a B).
1180
Thus, at the moment, C isn't FALSE (for B).
1181
This is counter-intuitive, actually.
1182
1183
=item Returning C for misuse
1184
1185
B<(bug)>
1186
Why B screws with caller,
1187
in case of API violations (by returning C),
1188
is beyond my understanding now.
1189
1190
=item B and I<$state>
1191
1192
B<(bug)> (also see L and I<$item>|/switch() and $item>)
1193
By design and legacy,
1194
there is single point of input -- B.
1195
IRL, multiple sources are normal.
1196
Implementation wise that leads to B that on the fly figures out
1197
current I<$state> and then somehow branches (sometimes branches out).
1198
Such Bs look horrible because they are.
1199
1200
=item Special handling of C and C
1201
1202
B<(bug)>
1203
In contrary with C and C special states,
1204
special treatement of C and C is a side effect of
1205
L method|/process()> entry sequence.
1206
That's why routinely enter C or C by state flow is of
1207
there-be-dragons type.
1208
Totally should be rewritten.
1209
1210
=item B and I<$item>
1211
1212
B<(bug)>
1213
It might be surprising, but, from experience,
1214
the state flow mostly doesn't care about I<$item>.
1215
Mostly, I<{state}> entered with I<$action> C processes input
1216
and just wonders ahead.
1217
Still those pesky I<$item>s *must* be passed around (by design).
1218
Still every B *must* return I<$item> too
1219
(even if it's just a placeholder).
1220
Something must be done about it.
1221
1222
=item C, C, and B
1223
1224
B<(misdesign)>
1225
First, by design and legacy B must report what turn the control flow
1226
must make
1227
(that's why it *must* return I<$rule>).
1228
OTOH, there's some value in keeping Bes as simple as possible what
1229
results in I<{state}> with alone C I<{turn}> and B that
1230
always returns TRUE.
1231
Changes are coming.
1232
1233
=item B and B
1234
1235
B<(misdesign)>
1236
Encoding (so to speak) in use by B (in prediction mode) is plain
1237
stupid.
1238
C signals two distinct conditions
1239
(granted, both are manifest of broken I<{fst}>).
1240
Empty string doesn't distinguish safe (both C and C are present)
1241
and risky (C or C is missing) I<{state}>.
1242
C doesn't say if there's anything in turn map.
1243
All that needs loads of workout.
1244
1245
=back
1246
1247
=cut
1248
1249
=head1 DIAGNOSTICS
1250
1251
=over
1252
1253
=item C<[action]: changing action: (%s) (%s)>
1254
1255
B<(deep trace)>, L method|/action()>.
1256
Exposes change of I<$action> from previous (former I<%s>)
1257
to current (latter I<%s>).
1258
1259
=item C<[action]: too many args (%i)>
1260
1261
B<(warning)>, L method|/action()>.
1262
Obvious.
1263
None or one argument is supposed.
1264
1265
=item C<[connect]: (%s): unknow option, ignored>
1266
1267
B<(warning)>, L method|/connect()>.
1268
Each option that's not known to B is ignored and Bed.
1269
There're no means for child class to force B (as a parent class) to
1270
accept something.
1271
Child classes should process their options by itself.
1272
1273
=item C<[connect]: clean init with (%i) items in FST>
1274
1275
B<(basic trace)>, L method|/connect()>.
1276
During class construction FST has been found.
1277
That FST consists of I<%i> items.
1278
Those items are number of I<$state>s and not I<$state>s plus I<{state}>s.
1279
1280
=item C<[connect]: FST has no {START} state>
1281
1282
B<(warning)>, L method|/connect()>.
1283
I I<{state}> is required.
1284
It's missing.
1285
This situation will definetely result in devastating failure later.
1286
1287
=item C<[connect]: FST has no {STOP} state>
1288
1289
B<(warning)>, L method|/connect()>.
1290
I I<{state}> is required.
1291
It's missing.
1292
If FST is such that I I<$state> can't be reached
1293
(for whatever reasons)
1294
this may be cosmetic.
1295
1296
=item C<[connect]: ignoring (%i) FST items in trailer>
1297
1298
B<(warning)>, L method|/connect()>.
1299
A trailer has been found and was ignored.
1300
Beware, I<%i> could be misleading.
1301
For HASH in trailer that HASH is considered and key number is reported.
1302
For list in trailer an B- number in list is reported, even if it's twice
1303
as many in FST (what isa hash).
1304
Just because.
1305
1306
=item C<[connect]: (source): unset>
1307
1308
B<(warning)>, L method|/connect()>.
1309
I<$source> option isa unset, it should be.
1310
There's no other way to feed items in.
1311
This situation will definetely result in devastating failure later.
1312
1313
=item C<[connect]: stealing (%i) items in FST>
1314
1315
B<(basic trace)>, L method|/connect()>.
1316
During object construction FST has been found.
1317
That FST consists of I<%i> items.
1318
Those items are I<{state}>s, not I<$state>s plus I<{state}>s.
1319
1320
=item C<[fst]: creating {%s} record>
1321
1322
B<(basic trace)>, L method|/fst()>.
1323
New state record has been created, named I<%s>.
1324
1325
=item C<[fst]: creating {%s}{%s} entry>
1326
1327
B<(basic trace)>, L method|/fst()>.
1328
New entry named I<%s> (the latter) has been created in record named I<%s> (the
1329
former).
1330
1331
=item C<[fst]: no args>
1332
1333
B<(warning)>, L method|/fst()>.
1334
No arguments, it's an error.
1335
However, instead of promptly dieing, C is returned
1336
in blind hope this will be devastating enough.
1337
1338
=item C<[fst]: (%s): no such {fst} record>
1339
1340
B<(warning)>, L method|/fst()>.
1341
Requested entry I<%s> is missing.
1342
State record ought to exist beforehand for any usecase except
1343
L.
1344
1345
=item C<[fst]: too many args (%i)>
1346
1347
B<(warning)>, L method|/fst()>.
1348
Too many args have been passed in.
1349
And again, instead of prompt dieing C is returned.
1350
1351
=item C<[fst]: updating {%s} record>
1352
1353
B<(basic trace)>, L method|/fst()>.
1354
Named record (I<%s>) has been updated.
1355
1356
=item C<[fst]: updating {%s}{%s} entry>
1357
1358
B<(basic trace)>, L method|/fst()>.
1359
Named entry (I<%s>, the latter) in record I<%s> (the former) has been updated.
1360
1361
=item C<[process]: {%s}(%s): changing state: (CONTINUE)>
1362
1363
B<(basic trace)>, L method|/process()>.
1364
Implicit change of state from C to C is about to happen.
1365
1366
=item C<[process]: {%s}(%s): entering>
1367
1368
B<(basic trace)>, L method|/process()>.
1369
Entering state flow from I<$state> I<%s> (the former) with I<$action> I<%s>
1370
(the latter).
1371
1372
=item C<[process]: {%s}(%s): going for>
1373
1374
B<(deep trace)>, L method|/process()>.
1375
Trying to enter I<$state> I<%s> (the former) with I<$action> I<%s> (the
1376
latter).
1377
1378
=item C<[process]: {%s}(%s): %s: going with>
1379
1380
B<(basic trace)>, L method|/process()>.
1381
While in I<$state> I<%s> (1st) doing I<$action> I<%s> (2nd) the I<$item>
1382
happens to be I<%s> (3rd).
1383
C will look like this: C<((undef))>.
1384
1385
=item C<[process]: {%s}(%s): %s: turning with>
1386
1387
B<(basic trace)>, L method|/process()>.
1388
B (of I<$state> I<%s>, the 1st) has returned I<$rule> I<%s> (2nd)
1389
and I<$item> I<%s>.
1390
Now dealing with this.
1391
1392
=item C<[process]: {%s}(%s): leaving>
1393
1394
B<(basic trace)>, L method|/process()>.
1395
Leaving state flow with I<$state> I<%s> (the former) and I<$action> I<%s> (the
1396
latter).
1397
1398
=item C<[process]: {%s}(%s): switch returned: (%s)>
1399
1400
B<(basic trace)>, L method|/process()>.
1401
B (of I<$state> I<%s>, the 1st, with I<$action> I<%s>, the 2nd) has
1402
been invoked and returned something that was interpreted as I<$rule> I<%s>,
1403
the 3rd.
1404
1405
=item C<[process]: {%s}(%s): unknown action>
1406
1407
B<(croak)>, L method|/process()>.
1408
Attempt to enter I<$state> I<%s> (the former) has been made.
1409
However, it has failed because I<$action> I<%s> (the latter) isn't known.
1410
1411
=item C<< [query]: [$caller]: <%s> package can't [%s] subroutine >>
1412
1413
B<(croak)>, L method|/query()>.
1414
I<$namespace> and I<$what> has been resolved as package I<%s> (the former)
1415
and subroutine I<%s> (the latter).
1416
However, the package can't do such subroutine.
1417
1418
=item C<[query]: [query_dumper]: %s !isa defined>
1419
1420
=item C<[query]: [query_source]: %s !isa defined>
1421
1422
=item C<[query]: [query_switch]: %s !isa defined>
1423
1424
B<(croak)>, L method|/query()>.
1425
I<$what> (I<%s> is I<$name>) should have some meaningful value.
1426
It doesn't.
1427
1428
=item C<[query]: [query_dumper]: %s isa (%s): no way to resolve this>
1429
1430
=item C<[query]: [query_source]: %s isa (%s): no way to resolve this>
1431
1432
=item C<[query]: [query_switch]: %s isa (%s): no way to resolve this>
1433
1434
B<(croak)>, L method|/query()>.
1435
C[ (the former I<%s>) is I<%s> (the latter). ]
1436
I<$what> is neither CODE nor scalar.
1437
1438
=item C<[query]: [query_dumper]: %s isa (%s)>
1439
1440
=item C<[query]: [query_source]: %s isa (%s)>
1441
1442
=item C<[query]: [query_switch]: %s isa (%s)>
1443
1444
B<(deep trace)>, L method|/query()>.
1445
C[ (the former I<%s>) is I<%s> (the latter). ]
1446
1447
=item C<[query]: [query_dumper]: {namespace} !isa defined>
1448
1449
=item C<[query]: [query_source]: {namespace} !isa defined>
1450
1451
=item C<[query]: [query_switch]: {namespace} !isa defined>
1452
1453
B<(croak)>, L method|/query()>.
1454
I<$what> isn't CODE, now to resolve it I<$namespace> is required.
1455
Apparently, it was missing all along.
1456
1457
=item C<[query]: [query_dumper]: {namespace} isa (%s)>
1458
1459
=item C<[query]: [query_source]: {namespace} isa (%s)>
1460
1461
=item C<[query]: [query_switch]: {namespace} isa (%s)>
1462
1463
B<(deep trace)>, L method|/query()>.
1464
C[ is I<%s>. ]
1465
1466
=item C<[query]: [query_dumper]: defaulting %s to $self>
1467
1468
=item C<[query]: [query_source]: defaulting %s to $self>
1469
1470
=item C<[query]: [query_switch]: defaulting %s to $self>
1471
1472
B<(deep trace)>, L method|/query()>.
1473
I<$namespace> is an empty string.
1474
The blackboard object will be used to resolve the callback.
1475
1476
=item C<< [query]: [query_dumper]: going for <%s>->[%s] >>
1477
1478
=item C<< [query]: [query_source]: going for <%s>->[%s] >>
1479
1480
=item C<< [query]: [query_switch]: going for <%s>->[%s] >>
1481
1482
B<(deep trace)>, L method|/query()>.
1483
Attempting to call I<%s> (the latter) method on object of I<%s> (the former)
1484
class.
1485
1486
=item C<< [query]: [query_dumper]: going for <%s>::[%s] >>
1487
1488
=item C<< [query]: [query_source]: going for <%s>::[%s] >>
1489
1490
=item C<< [query]: [query_switch]: going for <%s>::[%s] >>
1491
1492
B<(deep trace)>, L method|/query()>.
1493
Attempting to call I<%s> (the latter) subrouting of package I<%s> (the
1494
former).
1495
1496
=item C<< [query]: [query_dumper]: object of <%s> can't [%s] method >>
1497
1498
=item C<< [query]: [query_source]: object of <%s> can't [%s] method >>
1499
1500
=item C<< [query]: [query_switch]: object of <%s> can't [%s] method >>
1501
1502
B<(croak)>, L method|/query()>.
1503
The object of I<%s> (the former) class can't do I<%s> (the latter) method.
1504
1505
=item C<[state]: changing state: (%s) (%s)>
1506
1507
B<(deep trace)>, L method|/state()>.
1508
Exposes change of state from previous (former I<%s>)
1509
to current (latter I<%s>).
1510
1511
=item C<[state]: too many args (%i)>
1512
1513
B<(warning)>, L method|/state()>.
1514
Obvious.
1515
None or one argument is supposed.
1516
B has returned C in this case,
1517
most probably will bring havoc in a moment.
1518
1519
=item C<[turn]: (%s): no such {fst} record>
1520
1521
B<(warning)>, L method|/turn()>.
1522
Peeking for I<[turn]>s of I<%s> I<$state> yeilds nothing, there's no such
1523
state.
1524
1525
=item C<[turn]: {%s}: none supported turn>
1526
1527
B<(warning)>, L method|/turn()>.
1528
Whatever content of I<%s> entry is FSM doesn't know how to handle it.
1529
1530
=item C<[turn]: {%s}(%s): unknown turn>
1531
1532
B<(croak)>, L method|/turn()>.
1533
There was request for I<[turn]> I<%s> (the latter) of I<$state> I<%s> (the
1534
former).
1535
While I<{state}> record has been found and is OK,
1536
there is no such I<$rule>.
1537
1538
=item C<[turn]: no args>
1539
1540
B<(warning)>, L method|/turn()>.
1541
No argumets, it's an error.
1542
1543
=item C<[turn]: too many args (%i)>
1544
1545
B<(warning)>, L method|/turn()>.
1546
There's no way to handle that many (namely: I<%i>) arguments.
1547
1548
=item C<[verify]: {%s}{%s}: %s !isa defined>
1549
1550
B<(croak)>, L method|/verify()>.
1551
I<$rc> queried
1552
from something in I<{fst}> related to I<%s> (3rd)
1553
(value of which is I<%s> (2nd))
1554
while in I<$state> I<%s> (1st)
1555
isn't defined.
1556
1557
=item C<[verify]: {%s}{%s}: %s isa (%s), should be (%s)>
1558
1559
B<(croak)>, L method|/verify()>.
1560
B[ of I<$rc> queried ]
1561
from something in I<{fst}> related to I<%s> (3rd)
1562
(value of which is I<%s> (2nd))
1563
while in I<$state> I<%s> (1st) is I<%s> (4th).
1564
While it should be I<%s> (5th)
1565
(the last one is literally I<$test>).
1566
1567
=back
1568
1569
=cut
1570
1571
=head1 EXAMPLES
1572
1573
Here are example records.
1574
Whole I<{fst}>, honestly, might become enormous,
1575
thus are skipped for brewity.
1576
1577
alpha =>
1578
{
1579
switch => sub {
1580
shift % 42, ''
1581
},
1582
tturn => [ qw/ alpha NEXT / ],
1583
fturn => [ qw/ STOP horay! / ]
1584
}
1585
1586
B supposedly produces some numbers.
1587
Then,
1588
if I<$item> doesn't devide C then go for another number.
1589
If I<$item> devides then break out.
1590
Also, please note, C (and C) is special --
1591
it needs B I<$action> but it can be literally anything.
1592
1593
bravo =>
1594
{
1595
switch => sub {
1596
my $item = shift;
1597
$item % 15 ? 'charlie' :
1598
$item % 5 ? 'delta' :
1599
$item % 3 ? 'echo' :
1600
undef, $item
1601
},
1602
uturn => [ qw/ bravo NEXT / ],
1603
turns =>
1604
{
1605
charlie => [ qw/ charlie SAME / ],
1606
delta => [ qw/ delta SAME / ],
1607
echo => [ qw/ echo SAME / ]
1608
}
1609
}
1610
1611
Again, B supposedly produces some numbers.
1612
Then some kind of FizBuzz happens.
1613
Also, returning C as default raises questions.
1614
However, it's acceptable for example.
1615
1616
Now, quick demonstration, that's how this FizzBuzz would look
1617
using B capabilities (and B of I syntax).
1618
1619
bravo_foo =>
1620
{
1621
switch => sub {
1622
my $item = shift;
1623
$item % 15 ? !0 : !1, $item
1624
},
1625
tturn => [ qw/ charlie SAME / ],
1626
fturn => [ qw/ bravo_bar SAME / ]
1627
},
1628
bravo_bar =>
1629
{
1630
switch => sub {
1631
my $item = shift;
1632
$item % 5 ? !0 : !1, $item
1633
},
1634
tturn => [ qw/ delta SAME / ],
1635
fturn => [ qw/ bravo_baz SAME / ]
1636
},
1637
bravo_baz =>
1638
{
1639
switch => sub {
1640
my $item = shift;
1641
$item % 3 ? !0 : !1, $item
1642
},
1643
tturn => [ qw/ echo SAME / ],
1644
fturn => [ qw/ bravo NEXT / ]
1645
}
1646
1647
World of a difference.
1648
Furthermore, if you dare, take a look at
1649
F and F.
1650
But better don't, those are horrible.
1651
1652
Now, illustration what I<$namespace> configuration parameter opens.
1653
1654
Classics:
1655
1656
my $bb = Acme::FSM->connect(
1657
{ },
1658
alpha => {
1659
switch => sub {
1660
shift % 42, ''
1661
}
1662
}
1663
);
1664
1665
Illustrations below are heavily stripped for brevity
1666
(Perlwise and Bwise).
1667
1668
Separate namespace:
1669
1670
package Secret::Namespace;
1671
1672
sub bravo_sn {
1673
shift;
1674
shift % 42, ''
1675
}
1676
1677
package Regular::Namespace;
1678
use Acme::FSM;
1679
1680
my $bb = Acme::FSM->connect(
1681
{ namespace => 'Secret::Namespace' },
1682
alpha => {
1683
switch => 'bravo_sn'
1684
}
1685
);
1686
1687
Separate class:
1688
1689
package Secret::Class;
1690
1691
sub new { bless { }, shift }
1692
sub bravo_sc {
1693
shift;
1694
shift % 42, ''
1695
}
1696
1697
package Regular::Class;
1698
use Secret::Class;
1699
use Acme::FSM;
1700
1701
my $not_bb = Secret::Class->new;
1702
my $bb = Acme::FSM->connect(
1703
{ namespace => $not_bb },
1704
alpha => {
1705
switch => 'bravo_sc'
1706
}
1707
);
1708
1709
And, finally, B implodes upon itself:
1710
1711
package Secret::FSM;
1712
use parent qw/ Acme::FSM /;
1713
1714
sub connect {
1715
my $class = shift;
1716
my $bb = $class->SUPER::connect( @_ );
1717
} # or just skip constructor, if not needed
1718
sub bravo_sf {
1719
shift;
1720
shift % 42, ''
1721
}
1722
1723
package main;
1724
1725
my $bb = Secret::FSM->connect(
1726
{ namespace => '' },
1727
alpha => {
1728
switch => 'bravo_sf'
1729
}
1730
);
1731
1732
=cut
1733
1734
=head1 COPYRIGHT AND LICENSE
1735
1736
Original Copyright: 2008 Dale M Amon. All rights reserved.
1737
1738
Original License:
1739
1740
LGPL-2.1
1741
Artistic
1742
BSD
1743
1744
Original Code Acquired from:
1745
1746
http://www.cpan.org/authors/id/D/DA/DALEAMON/DMAMisc-1.01-3.tar.gz
1747
1748
Copyright 2012, 2013, 2022 Eric Pozharski
1749
Copyright 2025 Eric Pozharski
1750
1751
GNU LGPLv3
1752
1753
AS-IS, NO-WARRANTY, HOPE-TO-BE-USEFUL
1754
1755
=cut
1756
1757
1