Statestep Perl 5 Template

Version 1.0

Notes

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.

Using the 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.

Advanced Use: Embedding Code in the Model

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.