PLEXIL Reference¶
20 Mar 2023
About this chapter¶
This chapter documents the Standard PLEXIL plan language, and how to create and compile Plexil files for execution.
We explain PLEXIL primarily though examples, using Standard PLEXIL syntax.
This is not a rigorous treatment of PLEXIL, but hopefully – in combination with the Example PLEXIL Plans – provides enough to enable you to program in PLEXIL effectively.
The PLEXIL langauge is summarized briefly in the previous chapter, Overview. Its execution semantics are explained more deeply in the following chapter, Detailed Semantics, which cites papers that provide a rigorous formal definition of the language. The PLEXIL syntax is formalized in XML schemas found in the software distribution.
Note
Although this chapter focuses on the Standard PLEXIL language and its semantics, as independent from the exact behavior of a particular implementation of PLEXIL, explanations of how the supplied PLEXIL Executive behaves have been provided in places we felt this information helpful.
Nodes¶
A Standard PLEXIL plan consists of one node, which may have child nodes. A node is a structure specifying a certain kind of behavior.
A node is notated by a pair of curly braces containing the node’s specification, preceded optionally by a name for the node. The simplest node is the empty node.
{ }
We can give it a name:
DoNothing: { }
The node’s name (also called its Id), denoted by an identifier and
colon preceding the opening brace, is optional. An anonymous (nameless)
node is valid, though it cannot be referenced anywhere explicitly,
except within the node itself (by using the Self
keyword, described
below). In practice, every node has a name; an anonymous node is
assigned a unique name when compiled into PLEXIL’s XML form for
execution.
A node and its parent, immediate children, and siblings (this structure will be explained later) must have unique names. Uniqueness of names across more distant relationships in a plan is not required, especially since these nodes cannot reference each other (more on referencing scope below).
Standard PLEXIL is case-sensitive, but whitespace-insensitive, so the
DoNothing
plan above can also be written, for example, in either of
the following ways:
DoNothing:
{
}
DoNothing: {
}
Through composition, nodes form a tree-shaped hierarchy. The root of the tree is the root or top level node. A PLEXIL source file must contain exactly one top level node.
Nodes have two components: a set of attributes that drive the execution of the node, and a “body” which specifies what the execution of the node accomplishes.
Nodes which have no attributes may omit the enclosing braces. Examples will be provided below.
Node Attributes¶
Nodes may contain attributes, which include local variables, an interface, conditions, and a comment. Attributes are optional, and some have specific default values. When attributes are specified, they must occur first in node’s form, i.e. immediately following the opening curly brace.
Variables¶
A node may declare variables, which are local to the node. PLEXIL
currently supports variables of types Boolean
, Integer
,
Real
, String
, and arrays of these four basic types. Examples
of declarations of the basic types are as follows.
Boolean isReset;
Integer n;
Real pi;
String message;
These examples of variable declarations do not specify initial values for the variables. Uninitialized variables of all types except arrays are given the value Unknown. Here are the same variable declarations with initial values specified. Initial values must be literals – expressions are not allowed. (This limitation will be removed in a forthcoming release.)
Boolean isReset = true;
Integer n = 123;
Real pi = 3.14159;
String message = "hello there";
Arrays are declared by following the variable name with square brackets containing the size of the array. Array variables do not default to Unknown, but rather to an allocated array, all of whose elements are Unknown. The first example below declares an array of 100 integers. The second declares a smaller array of real numbers, with the first three elements initialized (the remaining seven are Unknown).
Integer scores[100];
Real defaults[10] = #(1.3 2.0 3.5);
Variables have lexical scope, which mean they are visible only within the node and any descendants of the node. Scope can be explicitly limited using the Interface clause described below. Here is an example of an empty node that declares some variables.
DoNothing1:
{
String name = "Fred";
Real MaxTemp = 100.0;
}
So far we’ve been using empty nodes as examples simply because we haven’t yet introduced the other nodes. The example above is illustrative but would serve no practical purpose, since its variables cannot be used in any way.
Note
Variable declarations and interface declarations (described in the following section) must occur prior to any other kinds of attributes in a node definition. They may be intermixed.
Interface¶
A node’s interface is the set of variables it can read and/or write
(assign) to. By default, the interface of a node N is the union of its
parent’s interface and the variables declared in N. Interface clauses
impose a restriction on the set of variables inherited from the
parent’s interface by specifying the only variables from the parent
that are accessible. There are two kinds of interface clauses. The
In
clause specifies variables that can be read. The InOut
clause
specifies those that can be read or written. All stated variables must
be part of the parent’s node’s interface, otherwise the clause is in
error. Furthermore, read-only variables in the parent cannot be declared
InOut
. Here’s an example of an empty node with interface clauses.
Test:
{
In Integer x, y;
InOut String z;
Integer a, b;
}
Variables x
and y
are assumed to be readable, and z
readable
and writable, in Test’s parent node. No other variables in Test’s
ancestors will be accessible. Variables a
and b
are local
variables in Test.
A node’s interface variables are also called its parameters. It is an error for a node to declare a variable having the same name as a variable that appears in its interface.
Note
Variable declarations (described in the previous section) and interface declarations must occur prior to any other kinds of attributes in a node definition. They can be intermixed.
Conditions¶
A node can specify up to eight conditions that govern precisely how the node is executed. Exact details are described in the Node State Transition Diagrams document.
StartCondition // Node won't begin until this is true
EndCondition // Node won't terminate until this is true
ExitCondition // Node will terminate (if executing) or be skipped (if waiting) if this is true
RepeatCondition // Node will repeat if this is true
SkipCondition // Node will be skipped if this is true when node begins
PreCondition // Node will fail if this is false when node begins
PostCondition // Node will fail if this is false when node ends
InvariantCondition // Node will fail if this is false while node is executing
A condition specifies a PLEXIL Boolean expression. Expressions are described in a section below. Here are some varied examples of conditions:
StartCondition Node1.outcome == SUCCESS;
EndCondition SignalEndOfPlan.state == FINISHED ||
SendAbortUpdate.state == FINISHED ||
abort_due_to_exception;
PreCondition Request_Human_Consent.state == FINISHED &&
Lookup(ZZZZCWEC5520J) == 1;
PostCondition AtGoal;
InvariantCondition Lookup(ZZZZCWEC5520J) == 1;
RepeatCondition Count < 10;
Here is an example of an empty node with some declarations and conditions:
Step2:
{
Real temperature;
Real MaxTemp = 100.0;
StartCondition Step1.state == FINISHED;
InvariantCondition temperature < MaxTemp;
}
The conditions specify that this node should begin execution after node Step1 finishes, and that the temperature should remain less than MaxTemp throughout execution. (Note that PLEXIL does not provide named constants, only variables). Incidentally, this an an example of a potentially useful empty node. Empty nodes are often used to wait for a condition (expressed through the start condition) and/or to test or verify a condition (expressed here through the invariant condition).
Leaf Nodes¶
As described in the Overview, PLEXIL has many kinds of nodes. The type of a given node is identified by the node’s body. A node’s body is what immediately follows its attributes (described in the previous sections).
Nodes that do not contain or decompose into child nodes form the leaves in a PLEXIL plan tree. These nodes are called leaf nodes and are part of Core PLEXIL, which is the subset of PLEXIL that is executed directly.
Empty Node¶
All the examples presented above are Empty
nodes. Empty nodes
contain only attributes. They have no external behavior (i.e. no
direct effect on an external system or a plan variable). In practice,
empty nodes are quite useful and common. A typical use is for
verification of a state in the external world. Here’s a node that
verifies a temperature reading.
VerifyTemp:
{
PostCondition Lookup(engine_temperature) > 100.0;
}
Assignment¶
An Assignment
node has the following basic form:
<variable> = <expression>;
The <variable>
part of the assignment, referred to as its
left-hand side (LHS), must be a writable variable in the node’s
interface. The expression
, referred to as the right-hand side
(RHS) of the assignment, can be any PLEXIL expression of a type
compatible with the variable’s type.
The following are examples of assignment nodes. Note that some context, in particular the variables’ declarations, are not shown.
IncrementCounter:
{
ExecutionCount = 1 + ExecutionCount;
}
CopyEntry:
{
TemperatureReadings[i] = x;
}
As with other nodes, Assignment
nodes without attributes may omit the
braces and/or names. The preceding examples could be rewritten as:
IncrementCounter: ExecutionCount = 1 + ExecutionCount;
TemperatureReadings[i] = x;
Assignments and Concurrency¶
If two nodes in a PLEXIL plan attempt to assign the same variable simultaneously, this is an error condition. The PLEXIL compiler does not detect the possibility of concurrent assignment, and unfortunately the current PLEXIL executive behaves ungracefully when it is attempted: it issues a message about the conflict and then aborts execution.
If your plan contains such nodes, this contention problem can be
resolved with the Priority
clause. Here’s a trivial contrived
example:
ConcurrentAssignment: Concurrence
{
Integer x;
A:
{
Priority 1;
x = 0;
}
B:
{
Priority 2;
x = 1;
}
}
Without the Priority
clauses, a runtime error would result. The
Priority
clause orders the execution of nodes from the lowest
priority number to the highest. In this example, node A will execute
first, then B, and the final value of x
will be 1.
It is probably best to design your plans such that multiple assignments to the same variable are avoided.
Note
A future release of PLEXIL will no longer abort the Executive
when multiple Assignment
nodes on the same variable are
eligible to execute simultaneously. Instead, it will execute the
conflicting Assignment
nodes one at a time, in an unspecified
order.
Command¶
A Command
node has the form:
[<variable> =] <command_name> ([<argument_list>]);
where:
command_name
is an identifier or a parenthesized string expression;argument_list
is an optional comma-separated list of zero or more arguments, which may be either literal values, variables, or array element references (other kinds of expressions are not supported).
The assignment of the command’s return value (assuming it returns a value) to a variable is optional, and if specified, must be a writable variable in the node’s interface.
The following are examples of command nodes. Note that some context is not shown, e.g. the declaration of the command (discussed next) and that of the variable receiving the return value.
StopRover: { stop(); }
SetWaypoint: { set_waypoint (x, y, z); }
GetSpeed: { speed = get_speed(); }
PrintSpeed: { print("Got speed: ", speed); }
As with assignment nodes, if no attributes are required, the braces may be omitted. Names may also be ommitted:
StopRover: stop();
SetWaypoint: set_waypoint (x, y, z);
GetSpeed: speed = get_speed();
print("Got speed: ", speed);
Commands must be declared at the top of the file in which they are used. Here are declarations for the commands above, and a few more examples:
Command stop();
Command set_waypoint(Real x, Real y, Real z);
Real Command get_speed();
// Parameter names are optional, though usually aid readability.
Boolean Command set_speed (Real);
String Command getMessage (Integer channel);
// Ellipses specify that one or more arguments can be provided but don't restrict the types
Command print(...);
By default, a Command
node finishes when the executive receives a
command handle for its command, via the PLEXIL external interface (see
the Interfacing section of this manual). See Resource Model
for a description of command handles.
Note that the finishing of the command node is distinct from the finishing of the command itself; command execution may be ongoing even after the node finishes. We elaborate further on this point.
Asynchronous command execution¶
Note that commands do not wait, or block execution of the plan. Rather, the command executes in the external system asynchronously with the plan. To examine the progress of the command, the plan should inspect its handle.
A command may take arbitrarily long to complete in the external system. If the command returns a value, and this value is assigned to a variable in the command node, the node should wait for the value, i.e. the command’s completion, before ending execution. This is accomplished with an appropriate end condition. Here’s an example:
ConfirmProceed:
{
Boolean result;
EndCondition isKnown(result);
PostCondition result;
result = QueryYesNo("Proceed with instructions?");
}
In this example, the end condition makes the node wait for the command’s result (whose initial value is Unknown). It further stipulates, via the postcondition, that success of this node requires a positive user confirmation.
However, this idiom is cumbersome to code and difficult or impossible to
get right in the general case. For example, if a command assigns to a
variable that already has a value, the isKnown
test is unhelpful.
Fortunately, Plexil provides a convenient form for synchronous
commanding – see the section below.
Optionally, command nodes may specify resource requirements for the affected command. The syntax and semantics for this is described in the Resource Model chapter.
Utility Commands¶
Several convenient utilities, in the form of commands, are available in Plexil. Currently there are two commands that print PLEXIL expressions to the standard output stream (e.g. the Unix terminal).
print (exp1, exp2, ...)
prints expressions without any added characters.pprint (exp1, exp2, ...)
, short for “pretty print” is likeprint
but adds spaces between the expressions and a final newline.
The utility commands are automatically available when running Plexil through the Test Executive. Otherwise they are available by including the Utility Adapter.
Update¶
An Update
node serves to relay information outside the executive. For
example, it can be used to update a planner or other system that has
invoked the executive, with status about execution of the plan. The
manner in which this information is sent is determined by the external
interface for the executive. An update
consists of name/value pairs; an update should include one or more such
pairs. The ``Update`` keyword identifies an Update node, and has the
form:
Update <name> = (<value> | <variable>) [, <name> = (<value> | <variable>) ]*;
where name
is an identifier, and the right hand side is either a
value
which is a literal (e.g. 5, “foo”), or a variable
which is
an identifier naming a declared variable that is visible to the node.
Any number of such name/value pairs can be given, separated by commas.
Here’s an example:
SendAbortUpdate:
{
StartCondition MonitorAbortSignal.state == FINISHED;
Update taskId = taskTypeAndId[1], result = -2, message = "abort";
}
As with assignment and command nodes, if no attributes are required, the containing braces may be omitted.
Library Call¶
A Library Call
node has the following form as its body.
LibraryCall <Callee> [<alias_list>];
where <Callee>
is the ID of the invoked library node. The
<alias_list>
is an optional list of aliases, which are pairs of
the form
<parameter> = <expression>
An alias allows one to rename/assign a node parameter (i.e. a variable present in the interface of the library node) with an actual value or declared variable.
Nodes called from a LibraryCall
must be declared prior to the
LibraryCall
. The declaration syntax is:
LibraryNode <Callee>[(<parameter_list>)];
Note
Historically library nodes were declared using the
LibraryAction
keyword. The LibraryNode
keyword may be used
interchangeably with LibraryAction
, and is preferred going
forward.
Here’s a contrived example of a call to trivial library node. The first
file defines the library node F
, and the second file contains a node
that calls F
.
--- begin F.ple ---
F:
{
In Integer i;
InOut Integer j;
j = j * j + i;
}
---- end F.ple ----
--- begin LibraryCallTest.ple ---
LibraryNode F (In Integer i, InOut Integer j);
LibraryCallTest:
{
Integer k = 2;
LibraryCall F(i=12, j=k);
}
A library node can be any type of node (e.g. Sequence, Command) but it must be a top level node, that is, the outermost node in a file. Conversely, any top level node can be used as a library node.
As with assignment, command, and update nodes, if no attributes are required, the containing braces may be omitted:
LibraryNode makePhoneCall(In Integer number);
CallHome: LibraryCall makePhoneCall(number=5551212);
Library Call execution¶
Prior to execution of a Plexil plan, at every point of a library call, a copy of the invoked library node is statically inserted in place of the call. Hence, “call” is technically a misnomer, and the mechanism for library execution is essentially a “macro” style code substitution. The executed plan is a single monolithic node with all library calls replaced by their invoked nodes. Thus, repeated “calls” to the same library node can produce a large plan for execution.
Compound Nodes¶
Compound nodes are translated into simple (Core PLEXIL) nodes prior to execution. A Core PLEXIL plan is a tree consisting of the leaf nodes described in the previous section, plus the List Node, described in this section under Concurrence.
Sequence¶
A Sequence
executes its child nodes in the given order.
Because sequential execution is so often the intended and expected
behavior of a plan, the Sequence
keyword is optional:
{
<node1>;
...
<nodeN>;
}
Note
Sequence
is currently an alias for CheckedSequence
. Because of
the overhead of checking for child node success, and the default
behavior in other sequential languages is to continue sequential
execution after a child node fails (e.g. shell scripting), future PLEXIL
release may instead alias it to UncheckedSequence. To ensure your plans
do not change behavior, please consider explicitly using either
CheckedSequence
or UncheckedSequence
.
CheckedSequence¶
A CheckedSequence
executes its child nodes in the given order. If any
node fails (i.e. terminates with outcome FAILURE
), the
CheckedSequence
also terminates with outcome FAILURE
and failure
type INVARIANT_CONDITION_FAILED
. A CheckedSequence
succeeds if and
only if all its nodes succeed. An empty CheckedSequence
always succeeds.
A CheckedSequence
is denoted as follows.
CheckedSequence
{
<node1>;
...
<nodeN>;
}
Unchecked Sequence¶
An UncheckedSequence
simply executes its child nodes in the given order.
An UncheckedSequence
succeeds by default.
UncheckedSequence
{
<node1>;
...
<nodeN>;
}
Concurrence¶
A Concurrence
encloses zero or more child nodes, which may execute
concurrently. Precisely, there are no execution constraints on the
child nodes other than those imposed by explicit conditions (those found
in each child node as well as in the Concurrence
form itself).
Concurrence translates directly to a Core PLEXIL NodeList
node.
Concurrence
{
<node1>;
...
<nodeN>;
}
A Concurrence
finishes when all its children have finished. If a
different behavior is desired, such as ordering constraints between
children, or finishing before all children have executed, this behavior
must be specified explicitly through conditions in the Concurrence
and
its children. Here is a contrived example that illustrates a Concurrence
with a particular execution protocol:
Command inform(String message);
Boolean Command DoIt(Integer n);
Root: Concurrence
{
Integer x;
Inform:
inform("Plan executing...");
Init:
x = GetX();
Commence:
{
Boolean result;
StartCondition Init.state == FINISHED;
PostCondition result;
SynchronousCommand result = DoIt(x);
}
InformSuccess:
{
StartCondition Commence.outcome == SUCCESS;
inform("Operation succeeded!");
}
InformFailure:
{
StartCondition Commence.outcome == FAILURE;
inform("Operation failed!");
}
}
In the example above, the Inform and Init nodes are unconstrained – they can start immediately. The Commence node waits for Init to finish before it can start. After it finishes, either InformSuccess or InformFailure will execute, depending on the result.
Note
If more than one child node is eligible for execution at a given moment, and PLEXIL is being executed on a sequential machine, the actual order of execution is unspecified. In any context where the exact execution order of nodes really matters, it must be encoded explicitly in the plan.
Try¶
In a Try
sequence, the child nodes are executed in sequence, until one
succeeds. The remaining nodes are skipped. A Try
succeeds if and only if
one of its nodes succeed. An empty Try
always fails.
The PLEXIL Try
is distinct from the try-catch idiom found in many
popular programming languages.
Try
{
<node1>;
...
<nodeN>;
}
If-Elseif-Else¶
This is the traditional if-then-else construct, with optional “elseif”
and “else” parts. The if
and optional elseif
clauses each
specify a condition, and a node to execute if this condition is true;
they are evaluated in the order listed until one condition succeeds. The
optional else
clause provides a default node which is executed if
none of the conditions evaluates to true.
Note
Previous versions of the PLEXIL compiler required an endif
keyword to terminate the if
node. This requirement has been
eliminated since PLEXIL 4.5. The endif
keyword is still accepted by
the compiler for backward compatibility.
Each clause may have multiple child nodes.
if C1
<node-1>
[elseif C2
<node-2> ]*
[else
<node-3> ]
where C1, C2 are Boolean expressions.
Specifically, if C1 evaluates true, node-1 will be executed. If C1 is
false or unknown, C2 is then evaluated, etc. If an if
statement
has no true conditions, and does not supply an else
clause, it will
invoke no action.
Examples:
if true
{
foo();
bar();
}
elseif 2 == 2
bar();
else
baz();
if ( Lookup(raining) )
Concurrence
{
Wipers: turn_on_wipers();
Lights: turn_on_lights();
}
While Loop¶
This is a traditional while loop.
while C
<node>
where C is a Boolean expression. Example:
while ! Lookup(RoverWheelStuck)
RoverDriveOneMeter();
Do Loop (PLEXIL 4.6 and later releases)¶
This is a traditional do-while loop.
do <node>
while C
where C is a Boolean expression. Example:
do
RoverDriveOneMeter();
while ! Lookup(RoverWheelStuck)
For Loop¶
This is a traditional For loop, limited to a numeric variant.
for (T V = Z; C; E)
<node>
where T is either Integer
or Real
, V is a variable name, Z is a
numeric expression for the initial value of V, C is a Boolean
expression indicating when to continue the
loop, and E is a numeric expression for updating V after each iteration.
Examples:
for (Integer i = 0; i <= 5; i + 1) pprint ("i: ", i);
for (Integer i = 2; i <= n; i + 1)
{
result = s1 + s2;
s1 = s2;
s2 = result;
}
OnCommand¶
OnCommand
implements a “handler” for an external command. It is used
in multiple executive settings where one executive receives a command
sent by another executive. It has the following syntax.
OnCommand <command-name> [<parameter-declaration>]
<node>
where:
<command-name>
is a string expression naming the command to be handled;<parameter-declaration>
is an optional list of zero or more comma-separated variable declarations for parameters; and<node>
is an action to be performed upon receiving the command.
Example:
OnCommand "Sum" (Integer a, Integer b)
Increment: { SendReturnValue(a + b); }
Requirements¶
OnCommand
expects the following commands to be implemented:
String Command ReceiveCommand(String command_name)
- waits for the named command to be received. When the command is received, returns a handle which is used to fetch the command’s parameters, and a command acknowledgment value ofCOMMAND_SUCCESS
.Any Command GetParameter(String handle, Integer index)
- waits (if necessary) for the specified parameter to be published. When the parameter is received, returns the parameter value, and a command acknowledgement ofCOMMAND_SUCCESS
.Command SendReturnValue(String handle, Any return_val)
- publishes return_val as the result of the command referenced by the handle argument.
Caution
The OnCommand
macro automatically provides the handle value; it should not be supplied by the user.
The external system must respond with a command acknowledgement.
The IpcAdapter interface module provided with the PLEXIL distribution
implements these commands; see Inter-Executive Communication.
But any PLEXIL
application which implements these commands as specified here can use
OnCommand
.
Failure behavior¶
In the event of an interface error in receiving the command or its
parameters, the OnCommand
node will have an outcome of FAILURE
and a failure type of INVARIANT_CONDITION_FAILED
.
Returning a value from the command¶
Important
Every OnCommand
node is required to call the command
SendReturnValue(<value>)
where <value> can be any legal PLEXIL expression with a known value.
If a SendReturnValue
command is not present in the body, a
SendReturnValue(true)
command is automatically generated, and runs
after the body node has finished executing.
Caution
If the SendReturnValue
command is not acknowledged, the
OnCommand
node will never finish.
The requirement to issue, and acknowledge, a SendReturnValue
command
may be removed in a future release of the PLEXIL Executive.
For more information, see Inter-Executive Communication.
OnMessage¶
OnMessage
is similar to OnCommand
, but only receives text sent
by the command SendMessage
, and may not have parameters.
OnMessage <message>
<node>
Where:
<message>
is a string expression; and<node>
is an action to be performed upon receiving that message.
Example:
OnMessage “ConnectionEstablished”
BeginProcess();
This “handler” for messages can be invoked by the following command in the IpcAdapter:
SendMessage(<string>)
Requirements¶
The OnMessage
macro requires the application to provide the
following command:
Command ReceiveMessage(String msg)
- Waits (if necessary) for a message equal to the supplied string to be published. Once the message is received, it returns a command acknowledgement value ofCOMMAND_SUCCESS
.
The IpcAdapter implements this command as described, as well as the
corresponding SendMessage
command
Failure behavior¶
In the event of an interfacing error, the OnMessage
node will have
an outcome of FAILURE
and a failure type of
INVARIANT_CONDITION_FAILED
.
For more information, see Inter-Executive Communication.
Synchronous Command¶
The SynchronousCommand
Extended PLEXIL macro simplfies some common
uses of Command
nodes. Simply put, a SynchronousCommand
does
not terminate until the command has completed.
In Standard PLEXIL, its syntax is:
SynchronousCommand [<var> =] <command>([<arg> [, <arg>]*])
[Checked]
[Timeout <interval-expression> [, <tolerance-expression>]]
;
The Checked
and Timeout
options can be combined in either
order.
The following example illustrates the most basic use of
SynchronousCommand
, when neither Checked
nor Timeout
option is supplied, and the command return value is ignored:
SynchronousCommand foo();
This is expanded to:
{
EndCondition self.command_handle == COMMAND_SUCCESS;
foo();
}
When the command’s return value is assigned, the expansion becomes a bit more complex:
{
Integer x;
SynchronousCommand x = foo();
}
In essence, this becomes:
{
Integer x;
Concurrence
{
Integer _temp_;
{
EndCondition self.command_handle = COMMAND_SUCCESS;
_temp_ = foo();
}
{
StartCondition isKnown(_temp_);
x = _temp_;
}
}
}
When the Checked
option is provided, the SynchronousCommand
fails if the command handle is anything but COMMAND_SUCCESS
:
SynchronousCommand foo() Checked;
Becomes:
{
EndCondition self.command_handle == COMMAND_SUCCESS;
PostCondition self.command_handle == COMMAND_SUCCESS;
foo();
}
E.g. if the command returns a command handle value of COMMAND_FAILED,
the SynchronousCommand
node will have an outcome of FAILURE
and a failure type of POSTCONDITION_FAILED
.
Combining the Checked option with a return value assignment requires
that the command returns both a command handle value of
COMMAND_SUCCESS
and a known return value:
{
Integer x;
SynchronousCommand x = foo() Checked;
}
Effectively expands to:
{
Integer x;
Concurrence
{
Integer _temp_;
{
InvariantCondition self.command_handle != COMMAND_DENIED
&& self.command_handle != COMMAND_FAILED
&& self.command_handle != COMMAND_INTERFACE_ERROR;
EndCondition self.command_handle = COMMAND_SUCCESS;
PostCondition isKnown(_temp_);
_temp_ = foo();
}
{
StartCondition isKnown(_temp_);
x = _temp_;
}
}
}
In other words, this example will only have an outcome of SUCCESS
if the command returns a known value and a command handle of
COMMAND_SUCCESS
.
The Timeout
option causes SynchronousCommand
to fail if the
command does not return a command handle within the specified
interval.
SynchronousCommand foo() Timeout 2.0;
Expands to:
{
InvariantCondition Lookup(time) < self.EXECUTING.START + 2.0;
EndCondition self.command_handle == COMMAND_SUCCESS;
foo();
}
If foo()
fails to return a command handle value within 2.0 time
units (usually seconds) of the node’s start time, the node will have
an outcome of FAILURE
and a failure type of INVARIANT_FAILED
.
This section begs elaboration of several aspects of PLEXIL not yet discussed in detail.
Time. As mentioned in the Overview, time is not a special concept in PLEXIL – it’s just an external world state; specifically, a real-valued state variable named
time
. This variable may be referenced explicitly, e.g.Lookup (time)
, though in most cases it is used implicitly: the Plexil executive reads it from the external world at every cycle and uses it for time-related computations in a plan, such as the timeout inSynchronousCommand
described here. The tolerance parameter to the timeout is simply the tolerance given to the Lookup that queriestime
for this node.Command Handles. These are described in the Resource Model chapter, but we must note here that instances of SynchronousCommand without return values require that certain command handles are supported by the Plexil application. Specifically, for SynchronousCommand to work, the application must return one of the following handles for the command invoked: COMMAND_SUCCESS, COMMAND_FAILED, COMMAND_DENIED.
Wait¶
The Wait
node does just that – waits for a specified amount of time
to pass:
Wait <duration> [<tolerance>]
where <duration>
and <tolerance>
are Real-valued expressions
for the duration of the Wait
, and the interval at which the
duration is checked. (Time units are application-specific, but are
typically seconds). <tolerance>
is optional and defaults to
<interval>
.
Examples:
Real rtol = 0.5;
Real rdelay = 1.414;
Wait 2.0; // wait 2.0 units
Wait 5.0, 0.1; // wait 5.0 units with a tolerance of 0.1 units
Wait rdelay, rtol; // wait, using variables
Delay1: Wait 3.10; // a wait node named Delay1
Data Types and Expressions¶
PLEXIL supports the following data types: integer, real, string, Boolean (logical expressions), and arrays (homogeneous arrays of any type except array itself). PLEXIL provides a variety of operations on each of these types.
An expression in PLEXIL is either a literal value, a variable, a lookup, or a combination of any of these formed by operators. In particular, expressions can contain expressions (i.e. they can be arbitrarily complex). Expressions can occur within node conditions, the target of assignments, and resource specifications.
Unknown/isKnown¶
Each PLEXIL type is extended by a special value UNKNOWN
, i.e. any
expression can evaluate to UNKNOWN
. The unknown value occurs in the
following cases.
It’s the default initial value for variables, a node’s outcome, and array elements.
It results when a lookup fails.
It results when a requested node timepoint has not occurred. Node timepoints are discussed below.
It is a valid value for Plexil logical expressions.
The UNKNOWN
value is not a literal – it may not be used in a
PLEXIL plan. It is tested solely through the isKnown
operator, which
returns false if its argument evaluates to UNKNOWN
, and true
otherwise. An example of the use of isKnown
is found in the section
above on Command nodes.
Numeric Expressions¶
Numeric expressions include literals (integers, real numbers), variables (of type Integer or Real), lookups and node timepoint values (both discussed below), and arithmetic operations: addition, subtraction, multiplication, division, square root, minimum, maximum, and absolute value. In addition, arrays have as numeric expressions their size, element index, and, for arrays of numeric type, their elements.
Here are varied examples of each of the aforementioned types of numeric expressions.
234
12.9
X /* where X was declared Integer */
Bar /* where Bar was declared Real */
Lookup(ExternalTemperature)
TakePicture.EXECUTING.START /* a node timepoint */
Bar + 4.5
X - (30 + Lookup(x) )
3 * X
(3 * X)/(X - 20)
sqrt(X)
abs(X)
Entries[X] /* where Entries is an array of Integer or Real */
Precedence and associativity rules for these operators are consistent with the standard rules for C and C++. Parentheses can be used to make explicit the intended semantics.
Integers and Reals may be mixed in Real-valued numeric expressions. Integer values are automatically promoted to Real in mixed calculations, so are legal in all contexts where a Real is expected. However, a Real value cannot be used where an Integer is expected, e.g. as an array index, nor can a Real value be assigned to an Integer variable or array element.
Numeric Type Conversions¶
Plexil offers the following type conversion operators for converting a Real to an Integer:
ceil(r) /* returns least positive integer greater than or equal to r */
floor(r) /* returns most positive integer less than or equal to r */
round(r) /* as defined in the C language standard */
trunc(r) /* rounds toward 0 */
real_to_int(r) /* For converting a Real that is known to be exactly integer-valued */
In each conversion function, if the supplied Real is out of range for an
Integer, UNKNOWN is returned. Additionally, real_to_int
will return
UNKNOWN if the supplied Real is not exactly an integer value.
Boolean Expressions¶
PLEXIL employs a ternary logic, extending the usual Boolean logic with a third value, Unknown, described in a section above. Though strictly a misnomer, the term Boolean is used throughout this manual and PLEXIL itself to describe operators, expressions, and values in this ternary logic.
Logical expressions include the Boolean literals true
and false
,
Boolean
-typed variables, lookups, comparisons, logical operations,
array elements (of Boolean
arrays), and the isKnown
operator.
The logical connectives, their syntax in PLEXIL, and arity (number of operands allowed) are as follows:
Negation (Not) !, NOT 1
Conjunction (And) &&, AND 2 or more
Disjunction (Or) ||, OR 2 or more
Exclusive Or XOR 2
When restricted to Boolean (true
or false
) values in their
constituents, logical expressions in PLEXIL follow the standard rules of
Boolean logic. Here is how PLEXIL handles the Unknown value, again a
standard interpretation.
true && Unknown = Unknown
false && Unknown = false
Unknown && Unknown = Unknown
true || Unknown = true
false || Unknown = Unknown
Unknown || Unknown = Unknown
true XOR Unknown = Unknown
false XOR Unknown = Unknown
Unknown XOR Unknown = Unknown
! Unknown = Unknown
The operators AND
and OR
are evaluated left to right in a
short-circuit fashion. Conjunctions have value true
until an
operand evaluates to false
or Unknown; this value becomes the value
of the expression. Similarly, disjunctions have value false
until an
operand evaluates to true
or Unknown.
The comparison operators, all of which take exactly two operands, are:
Equality ==
Inequality !=
Less than <
Greater than >
Less than or equal <=
Greater than or equal >=
In these comparision expressions, if any operand evaluates to Unknown, the entire expression yields Unknown.
Here are varied examples of logical expressions.
true
false
CommandReceived /* where CommandReceived was declared Boolean */
! CommandReceived
Lookup(RoverInitialized) /* where RoverInitialized is declared a Boolean lookup */
count <= 30 /* where count was declared Integer */
Lookup(RoverBatteryCharge) > 120.0 /* where RoverBatteryCharged is declared a Real lookup */
Lookup(RoverInitialized) || CommandReceived
Flags[3] /* where Flags is an array of Boolean */
isKnown(val) /* where val is any variable */
node3.state == FINISHED && node3.outcome == SUCCESS
Note
Precedence and associativity rules for these operators are consistent with the standard rules for C and C++. Parentheses can be used to make explicit the intended grouping.
String Expressions¶
String expressions include literal strings, variables (of type String), lookups, and string concatenations. Examples of each are as follows.
"foo"
"Would you like to continue?"
Username /* where Username was declared string */
Lookup(username)
"Hello, " + "Fred" => "Hello, Fred"
"Hello, " + Username
The only comparison operations currently defined on strings are ==
and !=
.
The strlen
operator returns the length of a String as an Integer.
Dates and Durations¶
One may want to reason about time. PLEXIL provides basic support for date, time, and duration types as defined by the ISO-8601 standard. See http://en.wikipedia.org/wiki/ISO_8601 for a detailed description of this standard and the date/duration formats, as these are covered only by example here.
Date and Duration expressions¶
PLEXIL expressions can have type Date
or Duration
. The former
includes time and combined date/time expressions. Dates and
durations are encoded as strings in the ISO-8601 format. Here are some
examples of Date and Duration variable declarations.
Duration dur1; // uninitialized duration variable
Date date1; // uninitialized date variable
Duration dur2 = Duration("PT60M"); // 60 minutes
Date date2 = Date("2012-05-26T20:42:00.00Z"); // UTC time
Date date3 = "2011-12-03T00:42:12.00"; // local time
Dates and Durations are expressed as literals using the Date
and
Duration
constructor, respectively. These are exemplified in the
variable initializations shown above. Here are more examples:
// subtract 1.5 seconds from the given date.
date3 = date3 - Duration("PT1.5S");
// Calculate the duration between two dates.
dur2 = date3 - Date("2011-05-16T03:19:00");
Finally, here is a simple practical use of these types: a node that starts on or after a given date, and runs for a specified duration:
Date Lookup time;
Date Lookup start;
Duration Lookup duration;
Test:
{
Start Lookup(time, 1) >= Lookup(start);
End Lookup(time, 1) >= Self.EXECUTING.START + Lookup(duration);
}
Additional PLEXIL plans illustrating varied uses of dates and durations
may be found in the directory plexil/examples/temporal
in the PLEXIL
source code distribution.
Caution
At present, date and duration literals are not checked for valid syntax. Also, unspecified behavior will result if an arithmetic operation involving dates or durations yields a negative value.
Operations using Dates and Durations¶
The following arithmetic operations involving dates and durations are supported.
date - date = duration
date +- duration = date
duration +- duration = duration
duration * number = duration
duration / number = duration
duration / duration = duration
duration mod duration = duration
duration mod number = duration
abs duration = duration
Dates can be compared with the operators <, >, <=, >=, ==, and !=, as can Durations. Dates and Durations cannot be directly compared.
Date and Duration representation¶
At present, dates and durations are not defined in Core PLEXIL. Recall that in Core PLEXIL, time is represented as a unitless real number, whose actual unit is application defined.
Expressions of type Date in the full PLEXIL language are translated into Core PLEXIL for execution, where they are converted to real numbers representing absolute time as seconds since the Unix epoch of Jan 1, 1970 (1970-01-01T00:00:00Z to be precise). This is a highly standard convention. At present, PLEXIL does not support the use of alternate epochs.
Similarly, Duration expressions are converted into real numbers representing seconds.
Caution
A key limitation in the current Plexil executive is that it does not recognize dates and durations as distinct from other real numbers. Therefore, for example, if date or duration values are inspected or printed in a running plan, a unitless real number will be shown. The PLEXIL team hopes to remedy this and make dates and durations better supported in general.
Arrays¶
PLEXIL provides just one aggregate data type, the array. At present, the array type in PLEXIL is somewhat limited compared to what’s found in modern programming languages. PLEXIL arrays are homogenous and one-dimensional: a sequence of values of a single scalar data type, indexed by integers beginning with 0. Specifically, arrays may of type Integer, Real, String, or Boolean only.
PLEXIL provides both variables and literals of array type. Like other variables, array variables must be declared prior to use. An array declaration specifies its name, type, maximum size (number of elements), and, optionally, initial values for some or all of the array’s elements. The memory needed by an array is allocated (for its maximum size) when the array is declared. Unlike scalar variables, array variables are not initialized to the Unknown value by default; rather, each element of the array is initialized to Unknown. Array indices start with 0.
The following examples illustrate the key properties of PLEXIL arrays.
Boolean flags[10];
This an array of ten booleans. Each element has the value Unknown (i.e. each element will fail the isKnown test).
Integer X[6] = #(1 3 5);
This example illustrates initialization of elements and the array literal. This array of 6 integers is initialized with an array containing 1,3, and 5 as its first three elements. That is, X[0] = 1, X[1] = 3, and X[2] = 5. The last three elements of X are Unknown. It is an error to initialize an array variable with an array containing more elements than its maximum size. (As an aside, the syntax for the array literal is taken from Common Lisp).
Arrays support the following operations. Assume an array named X.
Read an element:
X[<index>]
, where<index>
can be any integral expression. Array elements are a kind of expression, and thus may be used in any place where expressions are allowed.Assign an element:
X[<index>] =``<expression>
. Assignments can occur only in assignment nodes.Assign an entire array:
X = Y
, where Y is either an array variable or an array literal. It is an error ifY
represents an array larger thanX
. IfY
is smaller thanX
then the remaining elements inX
will be filled withUnknown
.Get the size of an array as an Integer:
arraySize(Y)
, where Y is an array-valued expression.Get the maximum size of an array as an Integer:
arrayMaxSize(Y)
, where Y is an array-valued expression.
Node State¶
A PLEXIL node can access its own internal state, or the internal state of other nodes, but only those nodes which are its siblings, children, or parent. (The internal state of more distant relatives is not accessible).
Node state consists of:
The current execution state of a node
The start and end times of each state a node has encountered
The outcome value of a node, if it has terminated
The failure type of a node, if it has failed
For command nodes, the last command handle received.
Each of these values is a unique type, with the exception of start and
end times, which are of type Date
. The only operations that can be
performed with these values are comparison for equality or inequality
with each other, or against a literal value.
The syntax for referencing these types of information is the following,
where <Id>
is the node’s identifier.
<Id>.state
returns one of INACTIVE, WAITING, EXECUTING, FINISHED, ITERATION_ENDED, FAILING, FINISHING.
<Id>.<state>.<timepoint>
where <state>
is one of the seven states listed above, and <timepoint>
is one of START, END,
will return the time elapsed (as a real number) since the given state
started or ended (respectively) for the given node. If the requested
timepoint has not occurred, the value of this variable is Unknown. For
an explanation of time in PLEXIL, see the Overview.
<Id>.outcome
returns one of SUCCESS, FAILURE, or SKIPPED, if the given node has terminated (else it will return Unknown).
<Id>.failure
returns one of INVARIANT_CONDITION_FAILED, POST_CONDITION_FAILED, PRE_CONDITION_FAILED, PARENT_FAILED, if the node has terminated with failure (else it will return Unknown).
<Id>.command_handle
returns one of COMMAND_ACCEPTED, COMMAND_SUCCESS, COMMAND_RCVD_BY_SYSTEM, COMMAND_SENT_TO_SYSTEM, COMMAND_FAILED, or COMMAND_DENIED, if the node is executing (else it will return Unknown).
External State (Lookups)¶
External state is read through lookups. Lookups access states using domain-specific measurement names. The syntax for a lookup is:
Lookup(<state_name> [(<param>*)] [, <tolerance>])
where <state_name>
is either an identifier or a string expression that evaluates to
the desired state name. States can have parameters, which are specified
by a comma-separated list of literal state names or string expressions
that follow the state name. Tolerance, which is optional, must be a real
number or real-valued variable; it specifies the granularity of accuracy
for the lookup, and defaults to 0.0. Lookups may not be overloaded –
only one Lookup with a given name may be used.
Note
For the state name, literal names are unquoted, while string expressions are parenthesized. For state parameters, literal names are double-quoted, while string expressions are given no special treatment.
Here are some basic examples:
Lookup(time) // queries the state named "time"
Lookup((pressureSensorName), 1.0) // queries the state named by the
// pressureSensorName variable
Lookup(At("rock1")) // queries the parameterized state At("rock1")
String expressions used for state names can include Lookup themselves. For example, here an external query is used to get the name of a sensor for a Lookup:
Lookup((Lookup(ModuleVoltageSensorName("Crew Habitat"))), 0.1)
The tolerance parameter is optional and defaults to 0.0. If given, it must be a real number and specifies the minimum value by which the state must have changed since its last reading in order to be read again. The example above says to read the module voltage sensor when it changes at least 0.1. Tolerances are unitless in Plexil; the unit of measure they represent is specified by the queried external system. The tolerance parameter is meaningless and ignored in certain contexts. See the following section for an explanation.
Execution of Lookups¶
There are two contexts for lookups that are important to distinguish. One is the asynchronous context implied by a node’s gate conditions (Start, Skip, End, Repeat). These conditons passively “wait” to become true. Lookups found in these conditions are processed as subscriptions to the external system for updates to the requested states. It is only in this context that tolerance is meaningful. These Lookup forms are compiled into LookupOnChange in Core PLEXIL’s XML representation.
The second context for lookups is the synchronous context implied by a node’s check conditions (Pre, Post, Invariant) and its body. In these contexts, a lookup is processed on demand, that is, its value is fetched at specific points in execution of the node. Tolerance is meaningless in this context, and ignored if specified. These Lookup forms are compiled into LookupNow in Core PLEXIL’s XML representation.
The “time” state¶
The state name time
is predefined in the Plexil executive. It
returns the system time as a real number, which is compatible with the
Date type. The units and epoch of the returned value are system
dependent. On the typical platforms that support PLEXIL, they would be
in POSIX/Unix time, i.e. the number of seconds since January 1, 1970
midnight UTC (1970-01-01T00:00:00.000Z).
Even though time
is predefined, it must still be declared in the
plan, in one of the following ways.
Date Lookup time;
Real Lookup time;
Note
Due to how the PLEXIL executive’s interface to the system clock is implemented, a tolerance parameter is required for time lookups. E.g. to specify a tolerance of one time unit:
Lookup(time, 1)
Global Declarations¶
If a plan contains calls to system commands (i.e. command nodes), uses library nodes, or queries world state using lookups, these must be pre-declared.
These global declarations must occur first in Plexil files, before the top-level node. They can occur in any order; declarations for commands, lookups, and library nodes can be intermixed freely.
Including global declarations as a standard practice has several advantages. First, it allows you to define and view your plan’s entire external interface in one place, rather than having it scattered throughout the plan. Second, it enables static checking of your plan. Static checks will insure that your declarations are consistent and that all uses of declared items in the plan are correct.
The following are examples of global declarations.
Programming in Plexil¶
Plexil Files¶
A file containing Plexil code can have any name, though its extension
must be .ple
. A Plexil file must contain exactly one construct, i.e.
a single node. Your application may comprise many Plexil files; in this
case, one file will contain the top level node, and the rest will
contain library nodes.
We strongly recommend that the top level node in a Standard PLEXIL file
be named the same as the file, e.g. file HaltAndCatchFire.ple should
contain the top level node named HaltAndCatchFire
.
plexilc
, the Plexil translator¶
Plexil plans and Plexilscript scripts must be translated into XML for
execution by the PLEXIL Executive. The plexilc
utility performs this
translation for several different PLEXIL syntaxes.
E.g. given a Plexil file foo.ple
, translate it with the following
command:
plexilc foo.ple
If foo.ple
is free of errors, this command will create the Core
PLEXIL XML file foo.plx
.
plexilc
chooses the translator for its inputs based on the file
name’s extension. Input languages supported by plexilc
are:
.ple - Standard PLEXIL
.plp - Standard PLEXIL with preprocessing (see below)
.pst - Plexilscript, the scripting language for the Test Executive
.pli - Plexilisp (deprecated), a Lisp-like syntax used prior to the development of the Standard PLEXIL language.
plexilc
supports the following command-line options (this list is
obtainable by calling plexilc
with no arguments):
-c, -check Run static checker on output (only valid for plan files)
-d, -debug <logfile> Print debug information to <logfile>
-h, -help Print this help and exit
-o, -output <outfile> Write translator output to <outfile>
-q, -quiet Parse files quietly
-v, -version Print translator version and exit
Some options are not supported by all source file formats.
If there are errors in your Plexil code, plexilc
will report them,
along with their line numbers and character positions. No output file is
created. Often, when there are many errors, correcting one of them will
take care of subsequent errors.
If plexilc
outputs only warnings about your Plexil code, the
translated output file is still created. Warnings usually indicate
potentially serious errors in the program’s logic, so they should be
inspected and dealt with.
Preprocessing PLEXIL Plans¶
The Standard PLEXIL compiler as of release 4.5 now accepts C
preprocessor statements such as #include
and #define
. This is a
convenient way to share (e.g.) Command, Lookup, and LibraryAction
declarations, and constant definitions, across several PLEXIL source
files.
plexilc
automatically invokes the preprocessor when the input file
name ends in .plp
Executing PLEXIL Plans¶
See the PLEXIL Executive page for details on executing PLEXIL plans.
Comments¶
There are two kinds of comments in a Standard PLEXIL plan. The source code can include comments to help document the code but that are not preserved in the translated Core PLEXIL XML output. These are notated in the C/C++ style syntax for block and single line comments. Examples of each are as follows.
Second, Plexil nodes have the option of including a single
Comment
clause, which must be the first item in the node’s attribute section. Here’s an example:The
Comment
clause gets preserved in the compiled (XML) version of the plan, unlike other comments.