Version 1.0
Before using this template, first read the general instructions on how to use FMPP for code generation. You may also wish to check the Statestep website for new tips or versions of this template.
Some example Perl code is included in the package, showing how the generated code can be used to provide decision table or state machine functionality.
For state machine models, you can start by trying out 'fsm.pl', either with the model 'harelsWatch.s2' that comes with Statestep or a model of your own. First, run the model through the template in the usual way. For example, assuming the file 'harelsWatch.s2' and the template file 'model.pm.ftl' are both in the working directory:
fmpp model.pm.ftl -o harelsWatch.pm -D "doc:xml(harelsWatch.s2, {namespaceAware:false})"
Then run 'fsm.pl', using the generated Perl module:
perl -MharelsWatch=:all fsm.pl
If you want some of the path to harelsWatch.pm to form part of the
generated package name then use the name_dirs
variable
to specify the number of parent directories to include in the name.
Here, for example, 'harelsWatch.pm' is created in a 'watches'
subdirectory and one parent directory is incorporated in the package name,
making it watches::harelsWatch
:
fmpp model.pm.ftl -o watches/harelsWatch.pm \ -D "doc:xml(harelsWatch.s2, {namespaceAware:false}), name_dirs:1" perl -Mwatches::harelsWatch=:all fsm.pl(The commands above assume a Unix-like environment, in which '/' is the directory separator and '\' continues a command onto the following line.)
For a decision table, there are several possible approaches.
The code in 'decision.pl' contains code designed to exercise a
decision table where one or more of the variables have been used
to encode the outcome.
This is the approach taken in the 'activitiesOutcomes.s2' model
which comes with Statestep.
If you use fmpp
to generate the module
'activitiesOutcomes.pm' from 'activitiesOutcomes.s2' then you
can run 'decision.pl' like this:
perl -MactivitiesOutcomes=:all decision.pl
An alternative approach is to encode the outcomes in the names of the rules. For example, if the outcome of your decision table was 'pass' or 'fail' then, in the model, you could give your rules names like 'pass__rule1', 'pass__rule2', 'fail__rule3', and so on. If the generated code is in 'model.pm' and there is just one event then your code would do something like this:
use model ':all'; # ... if (my ($rule, undef) = $input->apply_rules(0)) { (my $outcome = $Rules[0][$rule]) =~ s/__.*//; # ... }
Still another possibility is simply to describe the outcome as comment text in the formula field of the rules, as is done in the 'activities.s2' sample model provided with Statestep. Both rule formulas and constraint formulas are available as strings in the generated code.
For some applications, it may be convenient to incorporate executable code directly within the rules of the model. The suggested way of embedding code in a rule formula is to precede each line of code with '#:', for example:
Event 'decide', Rule 'B':
Temperature | Weather | Wind | |
---|---|---|---|
cold warm |
fine rain |
calm | |
# go hillwalking #:print "Take a hike!\n"; |
As far as Statestep is concerned, the print
statement here is a comment just like any other;
by starting comment lines which contain Perl code with
'#:' instead of '#' we are just making it easier
to extract the embedded code.
The file 'extralogic.pl.ftl' contains FreeMarker code that
will do exactly this extraction.
If you run fmpp
using this as your template
file (in the same way as you used it with 'model.pm.ftl' above)
to process a model containing the rule above then it will
generate code like this:
sub extralogic { my ($current, $event, $rule, $next) = @_; # Event decide, rule B if ($event == 0 && $rule == 1) { print "Take a hike!\n"; } # ... # (code extracted from any other rules) # ... return $next; }
Now, whenever you call apply_rules()
in your own
code, you can pass a reference to this subroutine:
my ($rule, $next_state) = $current_state->apply_rules( $event, \&extralogic);
The way 'extra logic' works means you can also write code which helps to determine what the outcome of a rule is. Here's an example:
Event 'decide', Rule 'A':
Temperature | Weather | Wind | Activity |
---|---|---|---|
hot warm |
fine rain |
breeze | |
hillwalk windsurf |
|||
# If there are enough boards for everyone then # go windsurfing; otherwise go hillwalking. #:if (@party <= $num_boards) { #: $next = $next->new(Activity=>'windsurf'); #:} #:else { #: $next = $next->new(Activity=>'hillwalk'); #:} |
— where @party
(an array of people's names) and
$num_boards
are variables defined elsewhere in your
own code.
By including a 'return' statement in the code, you can even decide whether a rule applies at all. Here's an example showing how the previous rule could be replaced by two separate rules:
Event 'decide', Rule 'A_1':
Temperature | Weather | Wind | Activity |
---|---|---|---|
hot warm |
fine rain |
breeze | |
hillwalk | |||
# There are more people than windsurfing boards. #:return unless (@party > $num_boards); # rule does not apply |
Event 'decide', Rule 'A_2':
Temperature | Weather | Wind | Activity |
---|---|---|---|
hot warm |
fine rain |
breeze | |
windsurf | |||
# We have enough windsurfing boards for everyone. #:return if (@party > $num_boards); # rule does not apply |
This works because whenever extralogic
fails
to return a 'next state',
apply_rules()
looks for another rule instead.
Remember too that you don't have to embed code in the model to make
use of the extralogic callback mechanism — you can always write
your own extralogic
subroutine, separate from the model.