QPR ProcessAnalyzer Expressions

From QPR ProcessAnalyzer Wiki
Revision as of 15:24, 26 March 2020 by Ollvihe (talk | contribs) (→‎Arrays)
Jump to navigation Jump to search

QPR ProcessAnalyzer expression language is a versatile query engine for all the QPR ProcessAnalyzer data and models. The expression language can perform variety of calculation operations, such as aggregations, dimensioning and following relations between objects.

Basic syntax and operators

Several expressions can be separated using semicolon (;). Example:

let var1 = 2;
let var2 = 3;
var1 + var2;

Returns: 5

Expression can contain linebreaks without affecting the result.

Following basic arithmetic and logical operations are available:

  • Addition (+) can be performed on numeric types, strings (concatenate two strings) and TimeSpans (addition of two TimeSpans).
  • Subtraction (-) can be performed on numeric types, DateTimes (calculating TimeSpan between two DateTimes) and TimeSpans (difference between two TimeSpans).
  • Multiplication (*) can be performed on numeric types, and between a TimeSpan and a numeric type (multiplying TimeSpan by number which results in TimeSpan),
  • Division (/) can be performed on numeric types, between a TimeSpan and a numeric type (dividing a TimeSpan where TimeSpan must be the left hand side operand).
  • Remainder (%): can be performed on numeric types.
  • Comparison operators: ==, <, <=, >, >=, !=.
  • Logical operands: && (AND), || (OR), ! (NOT)

Quick syntax for condition statement: condition ? trueValue : falseValue. Example:

var1==4 ? "Value is 4" : "Value is other than 4"

Literals

Literals (i.e. static values defined in the expressions) can be written as follows:

Data type Examples
number
  • 123456
  • -1234
  • 0
  • 123.456
  • 0.123
  • -12.34
  • 1.5675E4
  • 35E-6
boolean
  • true
  • false
string
  • "Hello world!"
  • ""
  • "Characters to escape: \" and \\"
  • "Line 1\nLine2"

In string literals, characters " (double quote) and \ (backslash) need to be escaped using the \ (backslash) character. Linebreaks can be added with \n and tabulator with \t. Unicode characters can be added with \uXXXX where the last four characters are the unicode character code, e.g. \u001f.

For writing date and timespan value literals, use the DateTime and TimeSpan functions.

Arrays

Array is an object that is a list of any types of objects. Arrays can be created in an expression by enclosing a comma-separated list of items to brackets.

Examples:

[1, 2, 3]
Returns: An array with three elements: 1, 2 and 3.

["Dallas", "New York", "Los Angeles"]
Returns: Array of strings (region names).

[]
Returns: An empty array.

It's possible to apply operators (such as +, -, *, /, %, >, >=, <, <=, !=, ==) directly to arrays with the following rules:

  • If both operands are arrays', the operator is applied separately for each item in the arrays in a way that items at the same index are applied with each other. If the lengths of arrays are different, an exception is thrown.
  • If only the left or right side operator is an array, the operator is applied for each item in the array together with the non-array operand.
  • If both operands are not arrays, the operator is applied directly to the objects.

Examples:

[1,2,3] + [4,5,6]
Returns: [5,7,9]

[1,2,3] > 2
Returns: [false,false,true]

[1,2,null] ?? 3
Returns: [1,2,3]

[[1,2,3],[5,6,7]] + [1,2]
Returns: [[2,3,4],[7,8,9]]

Arrays can also be used directly with those operators having one operand (-, !, ~). Examples:

-[1,2,3]
Returns: [-1,-2,-3]

![true, [false, true], false]
Returns: [false,[true, false],true]

![0, [1, 0], 1]
Returns: [true, [false, true], false]

Logical operators (&& and ||) don't support arrays in the way described previously. Also, the null coalescing operator (??) supports arrays only for left side operand, whereas the right side operand cannot be an array.

Lookup operator ([ ])

Lookup operator (brackets after an array) is used to get one or several items from an array. The loopup expression (the expression inside brackets) defines either:

  • a single integer, which fetches a single item in the array, or
  • an array of integers: fetches multiple items in the array.

Note that the indexes start from the zero. Note also that using an index which is not in the array will throw an exception.

The lookup expression is evaluated in the context of the array where items are to be fetched.

Examples:

[1, 2, 3, 4][1]
Returns: 2

[1, 2, 3, 4][Count(_) - 1]
Returns: 4

["a", "b", "c", "d"][[0, Count(_) - 1]]
Returns: ["a", "d"]

[[1, 2], 3, [4, [5, 6]]][2][1][0]
Returns: 5

The lookup operator also works for dictionaries:

Examples:

{"a": 1, "b": 2, "c": 3}["b"]
Returns: 2

{1: "number", "Hello!": "text", DateTime(2020): "datetime"}[[DateTime(2020), "Hello!", 1]]
Returns: ["datetime", "text", "number"]

Also this kind of lookup can be used:

{"a": 1, "b": 2, "c": 3}.b
Returns: 2

{"a": 1, "b": 2, "c": 3}.(a+b+c)
Returns: 6

Define variables (let) and assign variable values (=)

Variables can be defined using the "let" operator. Variables can be used only in the same scope where they are defined. The syntax is as follows:

let variableName = variableValue;

Examples:

let myVariable1 = "myValue";
let myVariable2 = Now;
let myVariable3 = 4;

Variables can be defined without the initial value. In that case, the variables get and _empty value. Example:

let myVariable1;

It's possible to assign (set) values to variables using the following syntax:

variableName = variableValue;

Examples:

myVariable1 = "new value";
myVariable2 = Now;
myVariable3 = myVariable3 + 1;

Note that the variables need to exist to be able to set values for them.

Conditional operator (if)

Conditions can be written using the "if" operator which has the following syntax:

if (condition) {
  //run if condition is true
} else {
  //run if condition is false
}

The "else" block is not mandatory:

if (condition) {
  //run if condition is true
}

It's also possible to chain if's:

if (condition1) {
  //run if condition1 is true
} else if (condition2) {
  //run if condition2 is true (and condition1 false)
} else if (condition3) {
  //run if condition3 is true (and condition1 and condition2 false)
} else {
  //run if all of the above conditions are false
}

While operator

Loops can be defined using the following syntax:

while (condition) {
  //looped as many times as the condition is true
}

Example: looping is stopped when the counter variable reaches 5:

let counter = 0;
while (counter < 5) {
  counter = counter + 1;
}
counter;

Return operator

Lambda syntax to define functions

Functions can be defined using following syntax (called lambda syntax):

(param1, param2, param3) => (function definition)

Examples:

Function to add to numbers:
(a, b) => a + b

Function to return the current time:
() => Now

Null conditional operator (?. and ?:)

The null conditional operator (one question mark) is useful in chaining operations where there might appear null values in the middle of the chain. If not handled correctly, e.g. trying to get a property from a null value, will throw an exception. In the null conditional operator, if the result of the left-hand side expression is a null value, the next step in the chaining operation is not executed, but a null value is returned instead.

For example, the following expression throws an exception if StartTime is null:

StartTime.Truncate("month")

The null conditional operator can be used to take into account the null situation, and the following expression returns null if StartTime is null:

StartTime?.Truncate("month")

In the null conditional operator, if the left-side expression is an array or a hierarchical array, the chaining operator does not chain null values in that array (see the examples). Null conditional chaining can be applied to both contextless and hierarchical chaining operations by prefixing the chaining operator with ? character.

The null conditional operator is faster to calculate and syntax is easier to read than using if condition.

Examples:

null?.(_ + 1)
Returns: null

[1, null, 3]?.(_ + 1)
Returns: [2, null, 4]

StartTime?.Truncate("month")
Returns: null (if StartTime is null).

[1, _remove, 3]?.(_ + 1)
Returns: [2, 4]

[1, null, 3]?:(_ + 1)
Returns: [1: [2], null, 3: [4]]

There is also a null conditional lookup operator which can be applied to the lookup operation by adding ? character in the front of the lookup operator. If used, and there is a null value instead of an array, the result of the lookup operation is null (an exception would be thrown without the null conditional lookup).

Examples:

null?[3]
Returns: null

[[1, 2], null, [3]].(_?[0])
Returns: [1, null, 3]

Null coalescing operator (??)

The null coalescing operator (two question marks) can be used to replace null values with something else. The null coalescing operator works as follows:

  • If the left-hand side expression is null, the right-hand side value is returned.
  • If the left-hand side expression is not null, the left-hand side value is returned.

The null coalescing operator combined with the _remove operator is a handy way to remove null values from an array (see the examples below).

Examples:

null ?? "foo"
Returns: "foo"

1 ?? "foo"
Returns: 1

[1, null, 3].(_ ?? "foo")
Returns: [1, "foo", 3]

[1, null, 3].(_ ?? _remove)
Returns: [1, 3]

[1, null, 3]:(_ ?? _remove)
Returns: [1: [1], 3: [3]]

Key-value pairs collection

JSON style of formatting can be used to express collection of key-value pairs. These can be used to pass named settings to functions supporting this, e.g. conformance checking functions.

Example:

In the following example, the second parameter of the IsConformant function is a hierarchical objects used as key-value pair collection:
EventLog.Cases:IsConformant(myDesignModel, ["IgnoreEventTypesMissingInModel": false, "IgnoreIncompleteCases": true])

Dictionary literals

Dictionary objects can be defined in the expressions also as literals using notation

{ ((logicalExpression ':' logicalExpression) (',' (logicalExpression ':' logicalExpression)) )?

Examples:

{"number": 1, "text": "Hello!", "datetime": DateTime(2020)}
Result: Creates a simple object having numeric, textual and datetime values.

{1: "number", "Hello!": "text", DateTime(2020): "datetime"}
Result: Creates a simple object having numeric, textual and datetime keys.

Let("o", {"value": 1, "inc": Def("", "amount", Set("value", value + amount))});
Result: Creates object o which wraps a number in its value-property and it can be increased by given amount by calling Inc-function for the o-object.

Line comments

Line comments can be added to code using // syntax. Line comment spans to the next line break.

Let("var1", 123); //This is comment which is ignored by the calculation

Expression and Evaluation Context

An expression is a text to be evaluated and it has a result. Result can be any of the supported object types or empty. An expression may consist of multiple expressions, called sub-expressions.

Expression evaluation is always performed within some context. This context and its type define what functionalities are available. The current context is used by default in the expressions, so whenever a function or property is called, functions and properties accessible in the current context are searched first. If function or property is not found in the current context, then more generic context is used. Error is returned if the requested functionality is not available in the current context or in a generic context. Current context can be accessed explicitly by using syntax _ (underscore).

Expression Chaining and Hierarchies using . and :

Expressions can be chained together two ways:

  • Contextless chaining: When . character is used to chain expressions, the resulting objects will not have context information.
  • Hierarchical chaining: When : character is used to chain expressions, only the result of the whole chained expression will consist of hierarchical arrays where all the values in the first expression (=context object) will be bound to the arrays those values generated. If the second expression does not return an array, the result will be changed to be an array.

The second expression chained to the first one will be evaluated using the following rules:

  • If the result of the first expression is not an array, the second expression will be evaluated with the result of the first expression as its context object.
  • If the result of the first expression is an array, for every element in the array, the second expression will be evaluated with the array item as its context object. The result of the evaluation will be an array of evaluation results (one for each element in the array).
  • If any of the second expression evaluations returns an array, the resulting object will be an array of arrays. If the first expression evaluation returns a typed array, the result will be hierarchic in a way that first level results are objects that contain the information about the actual object as well as the results generated by the second level expressions.

These rules apply also when chaining more than two expressions together. For example, it is possible to generate three level hierarchy with nodes of type: event log -> case -> event: EventLogById(1).Cases.Events or EventLogById(1):Cases:Events.

Examples:

Contextless chaining: First expression not an array, second expression not an array:
"1".("Number is " + _)
Returns:
"Number is 1"

Contextless chaining: First expression is an array, second expression not an array:
[1,2,3].("Number is " + _)
Returns:
["Number is 1", "Number is 2", "Number is 3"]

Contextless chaining: First expression is an array, second expression is an array:
[1,2,3].["Number is " + _, "" + _ + ". number"]
Returns:
[ ["Number is 1", "1. number"], ["Number is 2", "2. number"], ["Number is 3", "3. number"] ]

Hierarchical chaining: First expression is an array, second expression is an array:
[1,2,3]:["Number is " + _, "" + _ + ". number"]
Returns:
[ HierarchicalArray(1, ["Number is 1", "1. number"]), HierarchicalArray(2, ["Number is 2", "2. number"]), HierarchicalArray(3, ["Number is 3", "3. number"]) ]

  • Hierarchical arrays: Whenever traversing a relation in expression language using hierarchical chaining operator ':' for chaining expressions, a hierarchical array will be returned. It is an object which behaves just like a normal array except it stores also context/root/key/label object which usually represents the object from which the array originated from, for example the original case object when querying events of a case.
  • Hierarchical objects: Arrays where at least one object in the array is itself an array is considered to be a hierarchical object. Hierarchical arrays are treated in similar way as normal arrays in hierarchical objects.
  • Depth of a hierarchical object is the number of inner arrays that there are in the object, i.e. how deep is the hierarchy.
  • Level in hierarchical object consists of all the nodes that are at specific depth in object's array hierarchy. 0 is the level at the root of the object, consisting only of the object itself as single item. Levels increase when moving towards leaves.
  • Leaf level is a level that doesn't have any sub levels.

Function Definition

FunctionDefinition is an object type that encapsulates a expression language expression and optional parameters and identifier. Statements inside FunctionDefinition object can be evaluated by using the FunctionDefinition object as if it was a normal function by using <function identifier>(<arguments>) syntax.

Evaluation of the statements happen in a new scope. FunctionDefinition objects can be created in the following ways:

  • Using Def function.
  • Whenever &-prefix is used in Def function for an attribute
  • Using function <function identifier>(<function aguments>)<function body> syntax.
  • Using (<function aguments>)=><function body> syntax.

Examples:

Def(null, "a", "b", a+b)._(1,2)
((a, b)=>a+b)._(1,2)
((a, b)=>a+b)(1,2)
Returns: 3

[Def("", "a", "b", a+b), Def("", "a", "b", a*b)].(_(1,2))
[(a, b)=>a+b, (a, b)=>a*b].(_(1,2))
Returns: [3, 2]

Def("FirstOrNull", "arr", CountTop(arr) > 0 ? arr[0] : null)
function FirstOrNull(arr) { CountTop(arr) > 0 ? arr[0] : null }
Result: Are all equivalent and create a function FirstOrNull into the current evaluation scope. Returns also the created FunctionDefinition object.
  • Arguments: Returns the an array containing all the names of all the arguments of the function.
  • Body: Returns the expression body of the function as string.
  • Name: Returns the name of the function if this is a named function.

See Also