Terminal Entities

Terminal entities in EL correspond to the EL_TERMINAL meta-type in BMM, and its descendants. These come in three categories:

  • instance references: references to instances generated by direct references to literals, constants, variables, or else function calls;

  • predicates: logical conditions on instance references;

  • agents: delayed routine call objects.

The following sub-sections describe these types.

Instance References

Literals

Literal values are mostly instances of the types declared in the imported models. Assumed literals correspond to the openEHR Foundation and Based types, and are expressed in the openEHR ODIN syntax, with the exception of List<T>, Set<T> and Map<K,V>, which are distinguished in EL with specific types of brackets. The corresponding classes are described in the openEHR Foundation Types specification.

Type Literal values Description

Boolean

True, False

Integer

10, -4, 1024

Signed integer values from −231 to 231-1

Real

10.0, 0.345

Signed real values from 3.4028235 × 1038

Double

10.0, 0.345

Double precision real values

Date

2004-08-12

ISO 8601-format date

Date_time

2004-08-12T12:00:59+0100

ISO 8601-format date/time

Time

12:00:59

ISO 8601-format time

Duration

P2Y8M

ISO 8601-format duration

String

"this is a string"

Uri

<https://en.wikipedia.org>

Uri in IETF RFC 3986 format

Terminology_code

[identifier]
[snomed_ct::389086002]
[snomed_ct::389086002|Hypoxia|]

Local terminology code
Terminology code in openEHR format

Array<T>

[val, val, …​]

List<T>

(val, val, …​)

Set<T>

{val, val, …​}

Map<K:Ordered, V>

{
    prim_val : val,
    prim_val : val,
    ...
    prim_val : val
}

A table of values of any type V,
keyed by primitive values of any Ordered descendant K,
typically String, Integer or Date/time types.
<val> are literals of any other type.

Object

{
    identifier : val,
    identifier : val,
    ...
    identifier : val
}

An object of any type T,
whose property names are identifier.

Interval<T>

|N..M|

the two-sided interval N ≥ x ≤ M

|>N..M|

the two-sided interval N > x ≤ M

|N..<M|

the two-sided interval N ≥ x < M

|<N|

the one-sided interval x < N

|>N|

the one-sided interval x > N

|<=N|

the one-sided interval x ≤ N

|>=N|

the one-sided interval x ≥ N

|N +/-M|

the two-sided interval of N ±M

|N±M|

the two-sided interval of N ±M

One exception to the above is tuples, which are direct instances of the BMM meta-type BMM_TUPLE. They take the literal form [a, b, c], where a, b, and c are generally of different types.

TBD: consider to not bother with Array and reserve [] for tuples.

Constants

Constants are syntactically represented using labels of which the first letter is capitalised, and may be of any type, including complex types. The following are EL expressions containing constants.

    Mph_to_kmh_factor = 1.6
    Safe_glucose_limits.has (3.5)

Variables

Symbolic variables are valid within the scope of the routine in which they are declared. They are represented by plain names such as is_smoker. An EL variable reference corresponds to a descendant of the BMM meta-type EL_INSTANTIABLE_REF<BMM_VARIABLE>, i.e. one of:

  • EL_INSTANTIABLE_REF<BMM_PARAMETER> (declared in the routine signature);

  • EL_INSTANTIABLE_REF<BMM_LOCAL> (declared as a routine local variable); or

  • EL_INSTANTIABLE_REF<BMM_RESULT> (the variable Result representing a function return value).

Property References

Property references are valid within the scope of the class in which they are declared, and may be used in any routine definition or assertion in the class. They are represented by plain names such as diabetic_status.

Function Calls

In EL expressions, computational functions may be called in the same way as for typical programming languages. An EL property reference corresponds to the BMM meta-type EL_FUNCTION_CALL, which contains an instance of the BMM meta-type EL_FUNCTION_AGENT, which in turn has as its closed_args a tuple containing a set of items each of which is in turn an expression of any kind.

Consequently, EL function calls (similarly to most programming languages) may be of any level of complexity. The simplest type of function call is to a function whose signature is <[],T>, i.e. one taking no arguments are returning a value of type T. In EL, this may be called with or without parentheses, e.g. age or age().

The following example assumes a function tnm_major_number (tnm_val: String): Integer that extracts various elements of Tumour/Node/Metastasis ('TNM') cancer staging values, such as 'Tis', 'G3' and so on, and shows two forms of call to this function.

    tnm_major_number (tnm_t)
    tnm_major_number ("Tis")

More complex function calls may include arguments of other function calls, agents, tuples, operator expressions and normal instance references.

To be evaluated, function calls must be mappable to class methods in external libraries that are available at expression evaluation time.

'Built-in' Functions

Some commonly used functions such as current_date() or similar are often thought of as 'built-in' to a language environment. In the openEHR EL context, there are no built-in functions as such; useful utility functions must be supplied by classes or interfaces included as part of an imported model. In the openEHR environment, many utility calls are available in the openEHR Base Types. They will resolve correctly as long as this model is imported, which it normally will be as part of a larger model, such as the openEHR RM.

As a consequence, the total set of available utility functions for use in an EL expression is just what is available from the sum of all imported models. Assuming the openEHR Foundation and Base Types, the following kinds of functions are available for use in EL expressions:

    {Env}.current_date                                -- obtain today's date as a Iso8601_date

    blood_glucose_list: List<Real>
    {Statistical_evaluator}.max (blood_glucose_list)  -- compute a maximum of Numerics

    {Locale}.language                                 -- the primary language in the locale as a Coded_term

Container Item Access

Access to members of instances of a container type is achieved by the [] operator, which is an alias for various functions defined on the relevant types, as follows.

Operator Function Meaning

[i]

Array<T>.item(i: Integer): T

i-th element of an array; 1-based

[i]

List<T>.item(i: Integer): T

i-th element of a list; 1-based

[k]

Map<K,V>.item(k: K): V

element at key k of a Map

TBD: to achieve this generically, the above map of operators to member functions of appropriate types needs to be supplied in the model supplying the types themselves.

Container element access may be used on any expression whose effective type is a container, including function calls.

Container Item Selection and Matching

Selection of a container item matching a predicate may be achieved with an extension to the [] syntax, by supplying an agent argument whose signature is <[T], Boolean>, or in functional form, (v:T): Boolean. Here, 'selection' is understood to mean: obtain the first matching item, if it exists. This enables a reference of the following form to be constructed (final line).

    class Book {
        title: String
        pub_date: Date
    }

    book_list: List<Book>

    book_list [(b:Book) {b.title.contains("Quixote")}]

Since the signature is invariant with respect to the container type (here, List<Book>), a shorter form can be used in which the b is assumed:

    book_list [{title.contains("Quixote")}]

Similarly, any expression constructable based on the container type may be used. The following example contains an operator.

    book_list [{pub_date >= PY2003}]

For this to work, there must be an appropriate container function available. In the case of the openEHR Foundation types, this is select (<[T], Boolean>): T? defined on Container<T>; any equivalent function in a different model will do. The return type is nullable.

TBD: to achieve this generically, the map of operators to member functions of appropriate types needs to be supplied in the model supplying the types themselves.

TBD: different syntax for Container::matches()? Or could use matches plus [1] to get same effect e.g. book_list [{pub_date >= PY2003}][1] gives first book with matching pub_date.

Self Reference

As with many programming languages, a pre-defined reference to the current object is available via the plain name self. Unlike some languages, 'self' is not needed as a qualifier for properties or functions, and is generally only used as an argument in function calls.

Predicates

EL predicates are special meta-operators that are used to state tests on runtime object structures (in a similar way to Xpath).

Attached() Predicate

The attached() predicate is the EL equivalent of the expressions such as someVar == null (C, C++, C#, Java), some_var is None (Python) and similar. In EL, a reference is understood as being attached (or not) to a value. Attached status is therefore tested using attached (ref), and may be applied to any target of a BMM EL_INSTANCE_REF, which includes references to variables, properties, constants, functions and tuples.

Attached() returns a Boolean value, and thus may be negated, to form expressions such as:

    not attached (test_result) or else test_result.data.events[1].data.value > 6.5

Agents

Delayed routine calls for both functions and procedures may occur as terminals in an EL expression. The evaluation type (eval_type) of an agent is its signature. Syntactically, these take various forms. An agent can be created using a function or procedure visible in the current scope, using the keyword agent. The arguments list may range from empty to full. For a completely empty list, the routine name on its own may be used.

    |
    | define a naive obstetric risk function
    |
    obstetric_risk (age: Duration[1]; previous_pregnancies: Integer[1]): Coded_term[1]

    |
    | generate an agent with signature <[Duration, Integer], Coded_term>
    |
    agent obstetric_risk

For a partial argument list, ? symbols are used for the non-filled arguments. This generates an agent whose signature corresponds to the remaining open arguments. In the following example, an agent of the signature <[Integer], Coded_term> is generated, which, since the age of 38 years is supplied, may be thought of as a new function called obstetric_risk_38_years().

    agent obstetric_risk ('P38Y', ?)

Theoretically, an agent could be created with all arguments supplied, without the intention of immediate execution, e.g. agent obstetric_risk ('P38Y', 2), which would generate an agent of signature <[],Coded_term>. This could be later executed by simply using the receiver variable or parameter reference in the normal way, in a later expression.

Agents for procedure calls can be created in the same way as described above. In each case, the evaluation type is a signature of the form <[args]>, i.e. having no return type.

Qualified Referencing

Any terminal entity may appear as itself (in the relevant syntactic form described below) or in a form qualified by an instance reference, using standard 'dot' referencing. The qualifier provides the reference context, and is represented by the EL_SCOPED_REF property scope. Multiple qualifiers may be used in a single reference, as long as class feature visibility is satisfied, allowing such things as the following:

    person1.name
    employees[1].name.first_name
    blood_pressure.history.events[3].data.data.systolic

    agent obstetric_risks.basic_risk

Qualified referencing can be combined with selector agents to obtain an effect similar to the use of Xpath on XML data, as follows.

    book_list [{title.contains("Quixote")}].pub_date.year