The LSPS Expression Language is a statically typed language: all values must define their data type before they can be used on runtime. The data type determines what value the object can hold.
When you declare a local string variable (def String s
), an object that can hold a reference to a string is created in the memory.
Data types can inherit properties from each other: one type becomes the supertype of another type: An object of a data type can be used anywhere where you can use its super type, for example, when assigning values, sending function arguments, etc. For example, you can assign a variable of the type Decimal also a value of the type Integer, since Integer is a subtype of Decimal. These relationships apply to built-in as well as custom data types in a hierarchy.
The data types are implemented as Java or LSPS classes. This information is useful when you need to pass them to your Java code.
Expression Language Type | Class |
---|---|
String | java.lang.String |
Boolean | java.lang.Boolean |
Binary | com.whitestein.lsps.lang.exec.BinaryHolder |
Decimal | com.whitestein.lsps.lang.Decimal |
Integer | com.whitestein.lsps.lang.Decimal |
Date | java.util.Date |
Reference | com.whitestein.lsps.lang.exec.ReferenceHolder |
Collection | com.whitestein.lsps.lang.exec.CollectionHolder |
List | com.whitestein.lsps.lang.exec.ListHolder |
Set | com.whitestein.lsps.lang.exec.SetHolder |
Type | com.whitestein.lsps.lang.type.Type |
Map | com.whitestein.lsps.lang.exec.MapHolder |
Closure | com.whitestein.lsps.lang.exec.ClosureHolder |
Record | com.whitestein.lsps.lang.exec.RecordHolder |
Enumeration | com.whitestein.lsps.lang.exec.EnumerationImpl |
Property | com.whitestein.lsps.lang.exec.Property |
Object | java.lang.Object |
Casting takes place when you change the type of a value to another type. When "widening" a type, that is changing a value of a subtype to its supertype, the type change occurs automatically. When "narrowing" a type, you need to cast the type explicitly:
Note: Alternatively, you can use the
cast()
function of the Standard Library, for example,cast(o, type(D))
The Object data type is the super type of all data types and therefore the only data type without a supertype. It is represented by java.lang.Object. Therefore if you want to allow any data type, for example, as a parameter, use the Object data type. It is represented by the java.lang.Object class. All objects can hold the null
value.
Simple data types contain values without further data type structuring.
Objects of binary data type are typically used to hold binary data when downloading and uploading files, or working over binary database data.
It is represented by com.whitestein.lsps.lang.exec.BinaryHolder.
The Binary literals are not supported.
To define the type:
The Binary data type serves to work with binary data typically from another resource, typically, pictures, videos, etc.: You could define a Field of a Record as being of the Binary type and populate it with picture data.
A String holds a sequence of Unicode characters. The data type is implemented by the java.lang.String
class.
To define the type:
To create an instance:
A String can contain special characters defined using their ASCII codes. For example, you can use the ASCII tab code (#9
) to have a tab in your String, line feed (#10
) to make a multi-line String, etc., for example:
To escape characters, use the double-quote ("
) character:
To annotate a string that is not to be localized, add the hashtag # sign in front of the string.
The hashtag # sign signalizes that the String is to remain unlocalized and that this was the intention of the developer. Such Strings are excluded from checks of unlocalized Strings.
To create a local variable:
Boolean objects hold the values null
, true
or false
. It is implemented by the java.lang.Boolean
class.
To define the type:
To create an instance:
To create a local variable:
The expression def Boolean boolVar := true
declares and defines a local variable of the type.
Integer objects hold natural numbers and their negatives. The data type is a subtype of the Decimal type. It is represented by com.whitestein.lsps.lang.Decimal.
Note that the underlying Java type is BigDecimal; hence no relevant maximum limit applies to the value.
To define the type:
To create an instance:
To create a local variable:
Decimal objects hold numerical fixed-point values. It is represented by com.whitestein.lsps.lang.Decimal.
Note: A Decimal type is internally represented by two integer values: an unscaled value and a scale. The value is hence given as
<UNSCALED_VALUE>*10**<SCALE_VALUE>
. The integer scale defines where the decimal point is placed on the unscaled value. The scale is a 32-bit integer. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value is multiplied by ten to the power of the negation of the scale. Decimal values are, for example, 2e+12, -1.2342e0, 1.0.
When assigning a value of the type Decimal, you need to define the scale and rounding mode. The following rounding modes can be used on decimals:
The rounding mode is defined for decimal variables or for a record field of the type Decimal.
To define the type:
To define a Decimal type with Scale 100 and Rounding Model UP:
To create an instance:
To create a local variable:
Important: Decimal values are normalized if they contain 0 digits after the decimal point: decimal value 1.0 and integer value 1 have the same numerical value and therefore
1.0 == 1
.
The Date object holds a specific time. It is represented by java.util.Date.
Important: Only dates since the introduction of the Gregorian calendar, that is, the year 1582 are supported: for Dates that occurred before, a shift in days can occur rendering the date incorrect.
To define the type:
To create an instance:
To create a local variable:
Important: When constructing a date, consider using the date functions from the Standard Library.
Complex data types are composite data types based on other data types.
A Collection is a groupings of items of a particular data type. It is represented by com.whitestein.lsps.lang.exec.CollectionHolder.
Collections are ordered and immutable: once a collection is created, it cannot be changed, while you can change individual collection items. Each item of the collection represents an expression
To access an element of a Collection, you need to specify the position of the element in the List. Note that the first element of a List is on position zero. For example, [10,20,30][1]
returns 20 as position 0 of this list points to the value 10, the first element of the list.
Hierarchy of collections follows the hierarchy of their elements: List<TA> is a subtype of List<TB>, if TA is a subtype of TB. Set<TA> is a subtype of Set<TB>, if TA is a subtype of TB.
To define the type:
A List represents an ordered collection of items of a type with possible duplicate values. It is represented by the com.whitestein.lsps.lang.exec.ListHolder class.
To define the type:
To create an instance:
List of lists
To access an item in a List: Specify the position of the item in the List starting from 0: For example, [10,20,30][1]
returns 20 as position 0 points to the value 10, the first element.
Unlike on Sets, accessing list items is performance-wise efficient.
To create a List of Integers, you can use the ..
operator, the range operator:
A Set represents an ordered collection of items of a type with no duplicate values. It is represented by the com.whitestein.lsps.lang.exec.SetHolder class.
To define the type:
To create an instance:
To access an item in a Set: Specify the position of the item in the Set starting from 0: For example, {10,20,30}[1]
returns 20 as position 0 points to the value 10, the first item.
Unlike in Lists, accessing items is performance-wise inefficient.
Maps hold keys with their values and are immutable: once a map is created, it cannot be changed. You can change its key-value pairs, however, the map itself cannot be modified.
It is represented by com.whitestein.lsps.lang.exec.MapHolder.
To define the type:
Note that hierarchy of maps follows the hierarchy of its key and value types: Map<KA, VA> is a subtype of Map<KB, VB>, if KA is a subtype of KB and VA is a subtype of VB.
To create an instance:
To initialize an empty map, use the empty-map operator [->]
:
To get a value of a key, specify the key for the appropriate value in square brackets, for example, ["firstKey"->"a", "anotherKey"->"b"]["anotherKey"]
returns the string "b".
A Reference holds a reference expression that resolves to a variable or a record field (slot), not their value.
A Reference is conceptually similar to pointers in other languages. However, a Reference points to a variable or a record field, not to memory slot.
The data type is represented by com.whitestein.lsps.lang.exec.ReferenceHolder.
To define the Reference type:
To create a Reference instance, use the & reference operator:
**To get the value in the referenced slot, use the * dereference operator.
Example Use of Reference
//creates new Patient record instance with the diagnosis "flu": def Patient r := new Patient(diagnosis -> "flu"); //creates the local variable status that holds the reference to the diagnosis field of the Patient record instance: def Reference<String> status := &(r.diagnosis); //function sets the status to cured on the patient: setStatusToCured(&r.status); //implementation of the setStatusToCured() function: //def Reference<String> status:= statusReference; //*status:="cured";
A closure is an anonymous function that can declare and make use of local variables in its own namespace and use variables from its parent expression context as opposed to lambdas. It is represented by com.whitestein.lsps.lang.exec.ClosureHolder.
To define the type:
Subtyping in closures is governed by their parameters and return type: Closure A is a subtype of closure B when:
{ S1,S2,... : S } is subtype of { T1, T2,... : T } when T1 is subtype of S1, T2 is subtype of S2, etc. and S is subtype of T.
To create an instance:
Parameter types can be omitted:
The system attempts to infer the type of closure arguments and its return value. Note that the types might be resolved incorrectly. To prevent such an event, consider defining the argument type explicitly as in the example.
To evaluate a closure use the parentheses ()
operator with the closure arguments:
A user-defined Record is the subtype of the Record type. It serves to create custom structured data types. A Record can define relationships with another Records.
It is not possible to declare a Record type in the Expression Language: Records are modeled in the data-type editor of the Living Systems® Process Design Suite. However, you can create instances of Records. When you pass a Record, for example, as a function argument, it is passed by reference.
It is represented by com.whitestein.lsps.lang.exec.RecordHolder.
To create an instance:
For example:
Use the Dot operator to access Record fields or related records and fields.
Example: def declares a new variable of the MyRecord type. new creates a new MyRecord instance, which is assigned to the variable (the instance is the proper memory space with the record) . Variable r points to the MyRecord instance and it is returned by the expression.
Function call with a new Record instance as its argument:
To access fields of a Record, use the access operator .
:
Note that the dot operator .
fails with an exception if the <EXPRESSION>
with the access operator is null
. Use the safe-dot operator to prevent the exception.
When accessing a field or record that is of the type Reference, the property is automatically dereferenced. Therefore the expression (*ref).fieldName
and ref.fieldName
are identical.
The Property data type holds the type of a Field in a user-defined Record.
Example
The Type data type holds data types, for example, a Type can hold the value String data type, a particular Map data type, a Record data type, etc. It is represented by com.whitestein.lsps.lang.type.Type.
The Type data type can be used to check if object are of a particular data type. The output can be then further used when Casting the object.
To define the type:
To create an instance:
Example Usage
switch typeOf(person) case type(NaturalPerson) -> getFullName(person as NaturalPerson) case type(LegalPerson) -> getFullName(person as LegalPerson) case type(PersonGroup) -> getFullName(person as PersonGroup) default -> person.code + #" <Unknown type>" end
An Enumeration is a special data type that holds literals. It is represented by com.whitestein.lsps.lang.exec.EnumerationImpl.
You cannot create an Enumeration type in the Expression Language directly: It is modeled as a special Record type.
To define the type:
To create an instance:
Example:
You can compare enumeration literals with the comparison operators =, !=, <, >, <=, >= for enumerations of the same enumeration type. Enumeration literals are arranged as a list of values; hence the comparison is based on comparing their indexes: the order depends on the order of the literal as modeled in the Enumeration.
The Null data type signalizes an unspecified value: its only value is null
.
The data type is the subtype of every other data type, so that any object can take the null
value.
To define the type: