LSPS documentation logo
LSPS Documentation
Expression Language

Business models created in LSPS use the LSPS Expression Language, a statically typed language with support for functional and object-oriented programming, to define value of properties, conditions, variable, assignments, etc.

The basic unit in the language is an expression, a piece of code with operators, literals, and keywords of the language. Each expression returns a single value, for example:

  • 3+2 returns 5;
  • def String var := "hello" returns "hello", the value of the right side of the expression;

To get the idea, evaluate expressions in the Expression Evaluator REPL view: To display it in your PDS, go to Window > Show View > Other and in the displayed dialog search for the view.

expressionEvaluatorREPL.png
Enter an expression into the view and click Evaluate or press CTRL+Enter. To go through previously entered expressions, press the arrow-up key and to use auto-completion, press CTRL+Space.

This is all fine but an expression is a little bit too simple to accommodate all the logic that we might need to define say element properties: That is why definitions of properties, assignments, etc. can hold an expression block– a grouping of expressions. In REPL, you are working within such an expression block. To clear the block, click Clear Output and Variables button.

In a block, you can chain expressions with a semicolon ; (the last expression does no require a semicolon). This allows you to use the expression variables: such variables must have a unique name within the given block. Mind also that they are visible within the block and its child blocks, while remaining invisible to any parent blocks. The return value of an expression block is the value returned by its last expression (comments are marked with // and are ignored):

Expression blocks can make use of expression variables: to declare such a variable, we use the def keyword with the syntax def <TYPE> <VARIABLE_NAME>).

//creates an expression variable logMessage
//and assigns it the value "hello":
def String logMessage := "hello "; 
//appends "world!" to the logMessage vvariable:
logMessage + "world!"
//returns `"hello world!"`:

You can create an expression block explicitly with the begin and end keywords.

This expression block returns 3:

begin
   def Integer var := 3;
   begin
    var := 5
   end;
  var;
end

while this expression block fails to return a value:

 begin
   def Integer var := 3;
   begin
    var := 5
   end;
  var;
end;
var;
expressionEvaluatorREPLView.png
Note that the REPL view supports calls to Standard Library resources, such as functions, data types, etc. For example, the call of the function getYear() returns the year from a date:

getYear(now()) //returns the current year

Note: When evaluating your expression in the Expression Evaluator REPL view, the expressions are evaluated outside of any model: under normal circumstances, on runtime, expressions are evaluated in a context of their model instance so they can access the data of the model instance, such as, when and by whom the instance was created. Therefore definitions from your resources and definitions that require execution such contexts, such as, getCurrentPerson() or getCurrentTodo(), are not allowed in REPL. If you want to know more about contexts, refer to namespaces and contexts.

Let's go through the operators and data types of the Expression Language:

  • First, let's try some arithmetics:
    • 1 + 1
    • 1 + 1.12
    • 1/1.1
    • 1%1.1
    • 3*3
    • 3/3
    • 3**3
  • Let's try some concatenation:
    • "Hello, " + "world!"
    • "Hello, number " + 1
    • "1" + 1
    • 1 + "hello"

      This results in an invalid expression since the + operator that has Integer and String as its input does not exist, while "" + 1 + "hello" will evaluate just fine.

  • Let's try some comparing:
    • "Anne" < "James"

      Strings are compared lexicographically: A appears before J in alphabet.

    • "James" like "J*"
    • "James" like "J???s"
    • "James" like "Jo*"
    • 1 == 1,00
    • "Bob"<=>"Anne"
    • "Bob"<=>"John"
    • "Bob"<=>"Bob"
    • d'2015-12-24 20:00:00.000' < d'2017-12-24 20:00:00.000'
  • And now let's do some logic:
    • true and false
    • true and true
    • true or false
    • false or true
    • true xor false
    • !true
  • Let's chain multiple expressions:
    • "This will not return!";"Hello, " + #10 + "world!";
    • "This will not return!";"Hello, " + #10 + "world!"

      Note that only the value of the last expression is returned.

    • "This will not return!" "Hello, " + #10 + "world!": this is invalid.
  • Create Collections with some items:
    • Sets: {1, 2, 3} and {1, 1, 2, 3}
    • Lists: [1, 1, 2, 3]
    • [ 1 -> "Sunday", 2 -> "Monday"] is a Map, more specifically Map<Integer, String>.

      Mind that Collections and Maps are immutable:

      def Set<Integer> mySet := { 1, 2, 3 };
      mySet.add(4);
      mySet
      

      The return value is { 1, 2, 3}: The set with 4 was returned by mySet.add(4), but the value has not been assigned to anything. To change the content of a Collection, create a new one and resign.

      mySet := mySet.add(4)
       = {1,2,3,4}
      
  • Evaluate the closure {x:Integer -> "Integer" + x}

    The closure has an Integer input parameter called x in the closure contexts: the closure a String: It is of type { Integer : String }; However, it doesn't do much, since we cannot call it:

    • Create a variable that will hold the closure:
      def {Integer:String} closureVar := {x:Integer -> toString(x)};
    • Call the closure: closureVar(5)

      Important: It is recommended to always explicitly define the type of the closure inputs, in the example, we explicitly define that x is an Integer in x:Integer.

  • Also type of value is a data type: type(Integer) returns the Integer Type, the Type being a data type.
  • &var is a reference to a variable.
    //creates an expression variable:
    def String stringVar := "hello";
    &stringVar;
    *(&stringVar);
  • Check types of objects:
    • with the instanceof operator of the Expression Language:
      "hello" instanceof String;
      
    • with the function isInstance() of the Standard Library:
      isInstance(3, Integer);
      
      The difference between the built-in and the function is that the built-in cannot take an expression as as its right side:
      def Type<Object> stringType := String;
      "hello" instanceof stringType;//does not evaluate
      isInstance("hello", stringType);

Note: Along with a few other types, we have omitted the Record type, which is the parent of the Record types (Records): Records are complex data structure types that can contain multiple fields of non-complex data types (fields cannot be of a Record type) and be related to each other. They serve to represent your business data. While instances of Records are created in the Expression Language, it is not possible to create Record types themselves in the Expression Language since Records are modelled using the Modeling Language. You will model Records later.

Now, do the following:

  1. Create a local variable called closureVar of type Closure, which takes a list of integers as its input parameter and returns a String.
  2. Assign the variable a closure that concatenates the input list into a single String and returns the String.
  3. Create a list with integers 1-100 and feed it to the closure variable.

The result should look something like this:

def {List<Integer>:String} closureVar;
closureVar := { myIntList:List<Integer> ->
     def String output;
     foreach Integer i in myIntList do
       output := output + i + ", "
     end;
     output
     };
def List<Integer> myIntList := 1..1000;
closureVar(myIntList)

Function Call

A function is a set of instructions that can take arguments, perform the instructions and return a value: the return value will generally depend on the arguments.

Unlike closures, functions cannot be defined just in any expression block. You will define a custom function later. For now, we will use the functions provided by the Standard Library. To call functions, use the syntax <FUNCTION_CALL>(<ARG_1>, <ARG_>).

Let's try it out:

Creating a date value like d'yyyy-MM-dd HH:mm:ss.SSS' is quite cumbersome: luckily there is a function that will make your life much simpler: In the REPL view, start typing date and press Ctrl + space to display the auto-completion menu. Click on the date function you like and define its parameters so it returns the correct date value.

functioncallAutoCompletion.png

Extension Method Call

If a function has one required parameter, it can be defined as an extension method: this means that you can call it with the syntax:

<ParameterObject>.<function_name>()

Hence, instead of writing toString(myInteger), you can write myInteger.toString(), which is more convenient, since you can use autocompletion options of an Integer.

To find out, if a function of the Standard Library is an extension method, go to its declaration (click the function call while holding the CTRL key) and look for @Extensionmethod annotation or Extension method flag, depending on whether the function definition file uses the text or graphical editor.

Here, we check if the function definition has an Extension method flag and then rewrite the function call to an equivalent extension method call:

def List<Integer> myList := collect(1..10, {x:Integer -> x +2 });
(1..10).collect({x:Integer -> x +2 })

Type Hierarchy and Casting

Data types constitute a hierarchy: A type can be a subtype or a supertype of another type. If a type is a subtype of another type, it can be used instead of the supertype, but not vice versa. The strict typing prevents usage of incorrect types; for example, you cannot accidentally pass a Date value to a String variable unless you explicitly cast it to a String.

For the types of the language, the most generic data type is the Object type: all data types are subtypes of the Object type: when a variable is of the Object type, you can assign it a value of any type.

In a complex type, one complex type is a subtype of another complex type if their inner members are each other's subtypes: Map<KA, VA>; is subtype of Map<KB, VB>, if KA is a subtype of KB and VA is a subtype of VB.

Let's try it out:

In the REPL view, create an Object variable, for example, def Object myObject, and assign it a value of any type. To check the type of the value in the object, run typeOf(myObject). The expression def Object myObject := [1,2,3]; typeOf(myObject), returns type List<Integer>

Otherwise, the data type structure of the built-in data types is pretty flat:

  • The Decimal type and Integer type with the Decimal type being the super type: Wherever you can use a Decimal value, you can use an Integer value.
  • Null is a special data type with the sole value null: all other types are super types of the Null type so any data type can have the value null.
  • The Collection type and, the Set and List types: Collection is abstract; it serves to allow you to decide whether something will be a Set or List later during execution.
  • PropertyPath has Property as its subtype (A property path is a route to a Record field, such as, Person.name).

Let's try it out; Evaluate the following:

  • def Collection<Object> myObject := [1,2,3]; typeOf(myObject)
  • def Collection<Object> myObject := {1,2,3}; typeOf(myObject)
  • check the Null subtype:
    isSubtype(Null, String);
    isSubtype(type(Null), String);
    isSubtype(getType(null), Date);
    isInstance(null, String);

If you want to change the value type, typically from a supertype to a subtype, you can use the build-in cast mechanism. Let's try it out:

  • 1 as Decimal
  • 1.00 as Integer
  • 1.12 as Integer

Note that similarly to the case of instanceof vs isInstance(), there is operator as and the cast() function of the Standard Library. The as operator cannot take an expression as as its right side:

def Integer i := 1;
 = 1
 
typeOf(i)
 = Integer
 
123.00 as typeOf(i)
 = no viable alternative at input '('
 
cast(123.00, typeOf(i))
 = 123

Related Documentation

For details on the Expression Language, refer to the Expression Language Guide. For quick reference, use the Expression Language Reference Card.

Proceed to Models.