Introduction to JMPL
Language Version: 4.0
Andrew Bachmann
May 31, 2001
Introduction to JMPL
JMPL is the Java-based
Model Programming Language. JMPL is
designed to be easier to use for modellers.
The Java/C++ syntax is more familiar to users than the previous Lisp syntax. JMPL also represents a number of extensions
beyond the original Lisp-based MPL.
History of JMPL
In the beginning, there was
Lisp-based MPL, and it was good.
However, engineers who may want to use the Livingstone system are
generally not familiar or comfortable with Lisp syntax. They are more familiar with C++ and java
conventions. So, Brian Williams
designed a language that was more in keeping with these conventions. Jim Kurien presented the language to Andrew
Bachmann (me) and we worked through some issues. We made the language conform more to java conventions. Throughout this process we tried to make the
syntax as straightforward and simple as possible. As we developed the parser and compiler, we realized that certain
structures were redundant and we removed them.
We made these changes along with others. The result is JMPL today.
Differences between JMPL
and the Lisp-based MPL
When JMPL was designed,
syntactic structures were provided for improvements in the engine, either
realized or future. Where the
Lisp-based MPL had a single "mode" variable, JMPL has a state vector.
In conjunction with the
change from a single mode variable, the way to describe transitions and modes
has changed somewhat. In the Lisp-based
MPL, a single mode was represented by a syntactic structure that enclosed the
descriptions of transitions out of that mode.
In JMPL, transitions are represented by syntactic structures at the
topmost level of a component type.
Transitions are defined by a "from vector" and a "to
vector", each of which corresponds to a set of values for the state
vector, before and after the transition, respectively.
Some other syntactic
constructs have been removed.
Lisp-based MPL had a construct called "initially", which would
set up the initial state of any component of that component type. In JMPL, there is no such construct. It was removed and replaced by an
independent mechanism to supply the initial state of each component
individually. The new system is more
flexible and expressive.
The Lisp-based MPL
construct called "always" has been replaced by a java syntactic
convention. Constraints enclosed in
curly braces (constraint groups) at the topmost level of a component type serve
the same role. These constraints are
imposed when a component of that component type is instantiated.
Another difference is in
failure probabilities. Failure
probabilities used to be associated with failure modes. Essentially, these were a priori
probabilities. As such, they were only
useful at the start of the diagnosis.
The new probabilities are more expressive and flexible.
Modelling in JMPL
All models in JMPL are
broken down into two categories of source objects. A source object is either an enumeration or class. Both of these source objects allow
specification of relations that operate on them. It is not necessary to define a class or enumeration before its
use.
Template:
jmpl ::= (enumeration
| class)*
Enumerations
Enumeration
Enumerations describe
attribute types. They specify the
domain of the attribute. (the set of possible values) Enumerations are finite and discrete. Enumerations are atomic.
They can not be constructed from composing other enumerations. Enumerations do not form an inheritance
heirarchy.
Template:
enumeration ::= enum enumName
{ enumValue (, enumValue)* } ({ relation* })? ;
enumName ::= ident
enumValue
::= ident
Examples:
enum
onOffState {on, off};
enum
relativeValues {low, nominal, high} {
// some relations
relation relativeValuesEqual (
relativeValues to) {
this = to;
}
relation relativeValuesLessThan (
relativeValues to) {
((this = low) & ((to = nominal) | (to
= high))) |
((this = nominal) & (to = high));
}
relation relativeValuesMoreThan (
relativeValues to) {
((this = high) & ((to = nominal) | (to
=low))) |
((this = nominal) & (to = low));
}
relation relativeValuesNotEqual (
relativeValues to) {
this != to;
}
};
Classes
Class
Classes describe component
types, modules, and composite attribute types.
Template:
class ::= class className
(extends superName)? { classEntry* }
className ::= ident
superName
::= identifier
classEntry ::= (attribute | stateVector
| classEnumeration |
transition | failure
| relation | constraintGroup)
Example:
class
led {
onOffState currentIn;
onOffState ledState;
commandIn repairFlag;
private enum ModeType {nominal,failed};
private ModeType mode;
stateVector [mode];
transition fix(failed,nominal) {
repairFlag=repaired;
}
failure break(*,failed,low) {}
{
switch (mode) {
case nominal:
ledState = currentIn;
}
}
}
Attribute
An attribute can be an
instance of an enumeration, or an instance of another class.
Template:
attribute ::= (modifier)?
enumIdentifier attributeName ;
enumIdentifier ::= identifier
attributeName ::= ident
Example:
public
onOffState ledState;
State Vector
Each class can have at most
one state vector. The state vector
defines a set of variables that are used to encapsulate the mode of the
component. When transitions are
defined, they use the state vector as a reference for the order of the
arguments.
Templates:
stateVector ::= stateVector stateVectorVariables
;
stateVectorVariables ::= (stateVectorVariable
|
[ stateVectorVariable
(, stateVectorVariable)* ])
stateVectorVariable ::= attributeName
Examples:
stateVector
modeVar;
stateVector
[xCondition,yCondition,zCondition];
Class Enumeration
Enumerations can be defined
within the context of a class. An
enumeration defined this way can define attributes at this point. If at least one attribute is defined
simultaneously, then the enumeration can be anonymous. (defined without a name) Otherwise, these are the same as attributes
defined outside a class.
Templates:
classEnumeration ::= (modifier)?
enum (enumName)?
{ (enumValue
(, enumValue)*)? }
(enumVar
(, enumVar)*)?
({ enumEntry*
})?
enumVar ::= variable
Example:
private
enum {north, east, south, west} direction1, direction2;
public
enum onOffState {on, off};
Transitions and Failures
Transitions and failures
are birds of a feather. From states can
be specified as "*" (asterisk/star) to indicate any from state. To states can be specified as "+"
(plus) to indicate that the to state is the same as the from state. Identifiers should belong to the domain of
the variable that occurs at their location in the state vector. The constraints in the statement block must
hold in order for the transition or failure to be possible.
Transitions have an
additional argument that represents the cost of that transition. Failures have an additional argument that
represents the probability of that failure occurring.
Templates:
transition
::=
transition (transitionName)?
'(' (fromVector ',' (toVector
',' (transitionCost)?)?)? ')'
statementBlock
transitionCost ::= (identifier |
constant)
failure ::= failure (failureName)?
'(' (fromVector ',' (toVector
',' (failureProbability)?)?)? ')'
statementBlock
failureProbability ::= (identifier |
constant)
toVector ::= (toStateId |
'[' toStateId (',' toStateId)* ']' )
toStateId ::= ( '.' | '+' | identifier
)
fromVector ::= (fromStateId
| '[' fromStateId (',' fromStateId)* ']' )
fromStateId ::= ( '.' | '*' | identifier
)
Examples:
transition
turnAround([north],[south]) {
commandIn=turnAround;
}
failure
fallDown(*,[north],unlikely) {
}
Relations
Relations are named sets of
constraints. They are essentially
processed as macros, although they have some scope. For example, a relation in a class can refer to attributes of
that class at the location of the relation invocation. Relations are invoked from instances of the
class of which they are a part. (see
Constraints)
Templates:
relation
::=
relation relationName '(' (argument (',' argument)*)? ')' statementBlock
relationName ::= ident
argument
::= argumentType
argumentVar
argumentType ::= identifier
argumentVar
::= variable
Examples:
//
attach the two boxes through this class's local attributes:
//
string1, string2
relation
attach(Can can1, Can can2) {
can1.inString = string1;
can2.outString = string1;
can1.outString = string2;
can2.inString = string2;
}
Constraints
Although constraints may
sometimes look like executable code, they are not. The compiler converts these constraints into
clauses for the engine. As part of this
process, all the constraints in a particular context (transition, failure,
etc.) are logically combined. During
this combining, certain forms of trivial logical simplification might take
place. (i.e. A or not A implies
true) The clauses formed by the
compiler each conform to disjunctive normal form. (i.e. A or B or not C or...)
The clauses output are logically equivalent to the constraints input,
but may or may not bear resemblance to them, depending on the logical operators
used to combine the terms.
Templates:
constraintGroup
::= { (constraint)*
}
constraint
::= ( constraintGroup
| (if (expression) statement
(else statement)?)
| (iff (expression) statement)
| (switch '(' (identifier
| this) ')' { (casesGroup)* } )
| ';'
| (expression ';')
casesGroup ::= ( case identifier
':' )+
caseConstraintList
caseConstraintList ::= (constraint)*
Examples:
if
(a=b) { c=d; } else {e=f;}
iff
(a=b) { c=d; }
//
the following is equivalent to "a=b & c=d;"
{
a=b;
c=d;
}
switch
(onOff) {
case on: a=b;
case off: c=d;
}
Expressions
Expressions are the most
basic part of a constraint. Equality
and inequality expressions must operate on attributes or components of the same
type. Said another way, if
"a=b", then a and b must both be components or attributes of the same
type. Also, redundant syntactic forms
are supplied for the logical constructs, in order to make it easier for
modellers to use syntax that is most familiar and comfortable to them.
Relations are used like
method invocations in C++ or java. It
is important to note that relations are not functions. Relations do not return values. Please see the example for an invalid usage
of a relation as a function. Also of
note is that relations only accept identifiers as arguments. Expressions are not identifiers, and can not
be used as arguments.
Templates:
expression := ( (expression
'|' expression)
| (expression '||' expression)
| (expression '&' expression)
| (expression
'&&' expression)
| (primaryExpression '=' primaryExpression)
| (primaryExpression '=='
primaryExpression)
| (primaryExpression '!='
primaryExpression)
| ('!' expression)
| ('~' expression)
| ( (identifier'.')?relationName
'(' (primaryExpression
(','primaryExpression )*)? ')' )
)
primaryExpression := (identifier |
this)
Examples:
//
equality
this=on;
this==on;
//
conjunction
(a=b)
& (c=d);
a=b
&& (c=d);
//
disjunction
a=b
| c=d;
(a=b)
|| c=d;
//
negation/inequality
~a=b;
!a=b;
a!=b;
//
relations
object.relation(arg1,arg2);
relation2(arg);
//
relations are not functions (this is syntactically invalid)
sum(x,y)=z;
//
arguments to relations can not be expressions
//
(this is syntactically invalid)
allTrue(x=y,z=f);
Symbols/References
Names are of the fairly
standard convention from Java and C++.
Templates:
identifier
::= ident('.'ident)*
ident
::=
[A-Z,a-z,_,$][A-Z,a-z,0-9,_,$]*
Examples:
foo.bar.baz
(identifier)
foo
(ident)
Comments
There are two kinds of
comments. Line comments and group
comments. They are similar to comments
in C++ and Java.
Examples:
//
this is a line comment
/*
this is a
group comment */
Modifiers
Modifiers establish the
rules of access for attrbiutes or enumerated types that are defined within the
context of a component type. The
absence of a modifier, or the "public" modifier allows access to from
any context. The "private"
modifier only allows access from the component type.