Syntax Specification

Antlr4 files may be found for EL at in the openEHR Antlr4 Git repository.

The Antlr4 grammar for the EL syntax is shown below.

//
//  description: Antlr4 grammar for openEHR Expression Language baed on BMM meta-model.
//  author:      Thomas Beale <thomas.beale@openehr.org>
//  contributors:Pieter Bos <pieter.bos@nedap.com>
//  support:     openEHR Specifications PR tracker <https://openehr.atlassian.net/projects/SPECPR/issues>
//  copyright:   Copyright (c) 2016- openEHR Foundation <http://www.openEHR.org>
//  license:     Apache 2.0 License <http://www.apache.org/licenses/LICENSE-2.0.html>
//

parser grammar ElParser;
options { tokenVocab=ElLexer; }
import Cadl2Parser;



// ========================== EL Statements ==========================

statementBlock: statement+ EOF? ;

statement: ( declaration | assignment | assertion ) ';' ;

declaration:
      variableDecl
    | constantDecl
    ;

variableDecl: elInstantiableRef ':' typeId ( SYM_ASSIGNMENT elExpression )? ;

constantDecl: UC_ID ':' typeId SYM_EQ elExpression ;

assignment: elValueGenerator SYM_ASSIGNMENT elExpression ;

assertion: LC_ID ':' elBooleanExpr ;

// ========================== Type names ==========================

typeId: UC_ID ( '<' typeId ( ',' typeId )* '>' )? ;

// ========================== EL Expressions ==========================

//
// Expressions are either value-generators, or operator expressions (containing value-generators)
//
elExpression:
      elTerminal
    | elOperatorExpression
    | elTuple
    ;

elOperatorExpression:
      elBooleanExpr
    | elArithmeticExpr
    ;

// ------------------- Boolean-returning operator expressions --------------------

//
// Expressions evaluating to boolean values, using standard precedence;
// These map to ordinary 1- and 2-argument function calls on Boolean instances
//
elBooleanExpr:
      SYM_NOT elBooleanExpr
    | elBooleanExpr SYM_AND elBooleanExpr
    | elBooleanExpr SYM_XOR elBooleanExpr
    | elBooleanExpr SYM_OR elBooleanExpr
    | elBooleanExpr SYM_IMPLIES elBooleanExpr
    | elBooleanExpr ( SYM_IFF | SYM_EQ ) elBooleanExpr
    | elBooleanLeaf
    ;

//
// Atomic Boolean-valued expression elements
//
elBooleanLeaf:
      booleanValue
    | elForAllExpr
    | elThereExistsExpr
    | elArithmeticConstraintExpr
    | elGeneralConstraintExpr
    | '(' elBooleanExpr ')'
    | SYM_EXISTS elValueGenerator
    | elArithmeticComparisonExpr
    | elObjectComparisonExpr
    | elValueGenerator
    ;

//
//  Universal and existential quantifier
//
elForAllExpr: SYM_FOR_ALL elLocalVariableId ':' elValueGenerator '¦' elBooleanExpr ;

elThereExistsExpr: SYM_THERE_EXISTS elLocalVariableId ':' elValueGenerator '¦' elBooleanExpr ;

// Constraint expressions
// This provides a way of using one operator (matches) to compare a
// value (LHS) with a value range (RHS). As per ADL, the value range
// for ordered types like Integer, Date etc may be a single value,
// a list of values, or a list of intervals, and in future, potentially
// other comparators, including functions (e.g. divisible_by_N).
//
// For non-ordered types like String and Terminology_code, the RHS
// is in other forms, e.g. regex for Strings.
//
// The matches operator can be used to generate a Boolean value that
// may be used within an expression like any other Boolean (hence it
// is a booleanLeaf).
// TODO: non-primitive objects might be supported on the RHS in future.
elArithmeticConstraintExpr: elArithmeticLeaf SYM_MATCHES '{' cInlineOrderedObject '}' ;

elGeneralConstraintExpr: elSimpleTerminal SYM_MATCHES '{' cObjectMatcher '}' ;

// --------------------------- Arithmetic operator expressions --------------------------

//
// Comparison expressions of arithmetic operands generating Boolean results
//
elArithmeticComparisonExpr: elArithmeticExpr elComparisonBinop elArithmeticExpr ;

elComparisonBinop:
      SYM_EQ
    | SYM_NE
    | SYM_GT
    | SYM_LT
    | SYM_LE
    | SYM_GE
    ;

//
// Expressions evaluating to values of arithmetic types, using standard precedence
//
elArithmeticExpr:
      <assoc=right> elArithmeticExpr '^' elArithmeticExpr
    | elArithmeticExpr ( '/' | SYM_ASTERISK | '%' ) elArithmeticExpr
    | elArithmeticExpr ( '+' | '-' ) elArithmeticExpr
    | elArithmeticLeaf
    ;

// TODO: need to be able to plug in terminal to allow decision tables in expressions
elArithmeticLeaf:
      elArithmeticValue
    | '(' elArithmeticExpr ')'
    | elValueGenerator
    | dlSimpleCaseTable
    ;

elArithmeticValue:
      integerValue
    | realValue
    | dateValue
    | dateTimeValue
    | timeValue
    | durationValue
    ;

// -------------------- Equality operator expressions for other types ------------------------

//
// Compare any kind of objects
//
elObjectComparisonExpr: elSimpleTerminal elEqualityBinop elSimpleTerminal ;

elEqualityBinop:
    SYM_EQ
  | SYM_NE
  ;

//
// -------------------------- tuples -----------------------------
//

elTuple: '[' elExpression ( ',' elExpression )+ ']';

//
// -------------------------- value-generating expressions -----------------------------
//

elTerminal:
      elSimpleTerminal
    | dlDecisionTable
    ;

elSimpleTerminal:
      primitiveObject
    | elValueGenerator
    ;

//
// TODO: Can't syntactically distinguish between a local variable
//       and a property or constant reference.
//
elValueGenerator:
      SYM_SELF
    | elBareRef
    | elScopedFeatureRef
    ;

//
// An unscoped reference of some kind
// Will map to EL_WRITABLE_VARIABLE or EL_PROPERTY_REF (unscoped)
//
elBareRef:
      elInstantiableRef
    | elFunctionCall
    | elConstantId
    ;

//
// Instantiable feature refs; may be the target of an assignment
//
elInstantiableRef:
      SYM_RESULT
    | elBoundVariableId
    | elLocalVariableId
    ;


//
// Scoped feature references. Has to have at least one scoping element, either a
// class name in {} or else a baseRef name; then any number of bareRefs.
// Will map to any EL_FEATURE_REF (scoped)
//
elScopedFeatureRef: elScoper elBareRef ;

elScoper: ( '{' typeId '}' | elBareRef ) '.'  ( elBareRef '.' )* ;

//
// A variable bound to a data source, lexical form '$xxxx'
// TODO: analyse how a boundVariableId can be created as a built-in feature
//
elBoundVariableId: BOUND_VARIABLE_ID ;

elLocalVariableId: LC_ID ;

elConstantId: UC_ID ;

//
// Function calls
//
elFunctionCall: LC_ID ( '(' elExprList ')' )? ;

elExprList: elExpression ( ',' elExpression )* ;

//
// -------------------------- decision tables -----------------------------
//

dlDecisionTable:
      dlBinaryChoice
    | dlCaseTable
    | dlConditionTable
    ;

dlCaseTable:
    | dlSimpleCaseTable
    | dlGeneralCaseTable
    ;

//
// condition chains (if/then statement equivalent)
// choice in
//   =========================================================
//   er_positive and
//   her2_negative and
//   not ki67.in_range (#high):    #luminal_A,
//   ---------------------------------------------------------
//   er_positive and
//   her2_negative and
//   ki67.in_range (#high):        #luminal_B_HER2_negative,
//   ---------------------------------------------------------
//   *:                            #none
//   =========================================================
//
dlConditionTable: SYM_CHOICE SYM_IN BLOCK_DELIM ( dlConditionBranch ',' )+ ( dlConditionBranch | dlConditionDefaultBranch ) BLOCK_DELIM ;

dlConditionBranch: elBooleanExpr ':' elExpression ;

dlConditionDefaultBranch: SYM_ASTERISK ':' elExpression ;

//
// Binary-choice version of condition table, using old-school
// C/Java syntax:
// booleanExpr ? x : y ;
//
dlBinaryChoice:  elBooleanExpr '?' elSimpleTerminal ':' elSimpleTerminal ;

//
// Case tables, e.g.:
//     Result := case qCSI_score in
//        ============================
//        0:          expr0,
//        ----------------------------
//        |1..2|:     expr1,
//        ----------------------------
//        |3..5|:     expr2,
//        ----------------------------
//        |6..8|:     expr3,
//        ----------------------------
//        |≥ 9|:      expr4
//        ============================
//     ;
//
dlGeneralCaseTable: SYM_CASE elExpression SYM_IN BLOCK_DELIM ( dlGeneralCaseBranch ',' )+ ( dlGeneralCaseBranch | dlGeneralCaseDefaultBranch ) BLOCK_DELIM ;

dlGeneralCaseBranch: primitiveObject ':' elExpression ;

dlGeneralCaseDefaultBranch: SYM_ASTERISK ':' elExpression ;

//
// Simple value-based (typed) Case tables, e.g.:
// case gfr_range in
//   =================
//   |>20|:      1,
//   |10..20|:   0.75,
//   |<10|:      0.5
//   =================
//   ;
//
dlSimpleCaseTable: SYM_CASE elSimpleTerminal SYM_IN BLOCK_DELIM ( dlSimpleCaseBranch ',' )+ ( dlSimpleCaseBranch | dlSimpleCaseDefaultBranch ) BLOCK_DELIM ;

dlSimpleCaseBranch: primitiveObject ':' elSimpleTerminal ;

dlSimpleCaseDefaultBranch: SYM_ASTERISK ':' elSimpleTerminal ;

The Antlr4 lexer for the EL syntax is shown below.

//
//  description: Antlr4 grammar for openEHR Expression Language baed on BMM meta-model.
//  author:      Thomas Beale <thomas.beale@openehr.org>
//  contributors:Pieter Bos <pieter.bos@nedap.com>
//  support:     openEHR Specifications PR tracker <https://openehr.atlassian.net/projects/SPECPR/issues>
//  copyright:   Copyright (c) 2016- openEHR Foundation <http://www.openEHR.org>
//  license:     Apache 2.0 License <http://www.apache.org/licenses/LICENSE-2.0.html>
//

lexer grammar ElLexer;
import Cadl2Lexer, SymbolsLexer, GeneralIdsLexer;

channels {
    COMMENT
}

// ------------------ lines and comments ------------------
CMT_LINE : '--' ~[\r\n]* -> channel(COMMENT) ;
BLOCK_DELIM : '====' '='* EOL ;
BLOCK_CMT_LINE_EMPTY : WS* '|' WS* EOL -> channel(COMMENT) ;
//BLOCK_CMT_LINE_1 :     WS+ '|' [ \t] ~[\r\n]+ EOL -> channel(COMMENT) ;
BLOCK_CMT_LINE_2 : { _input.LA(-1) == '\n' }? WS* '|' [ \t] ~[\r\n]+ -> channel(COMMENT) ;

EOL      : '\r'? '\n'   -> channel(HIDDEN) ;
WS       : [ \t\r]+     -> channel(HIDDEN) ;

// --------- keywords ----------

SYM_SELF    : 'Self' ;
SYM_IN      : 'in' ;
SYM_CHOICE  : 'choice' ;
SYM_CASE    : 'case' ;

SYM_RESULT  : 'Result' ;

// --------- symbols ----------
SYM_ASSIGNMENT: ':=' ;
SYM_INTERROGATION: '?' ;
SYM_LEFT_GUILLEMET: '«' ;
SYM_RIGHT_GUILLEMET: '»' ;

SYM_BROKEN_BAR: '¦' ;

SYM_DOUBLE_MINUS: '--' ;
SYM_DOUBLE_PLUS: '++' ;

SYM_THEN     : 'then' | 'THEN' ;
SYM_AND      : 'and' | 'AND' | '∧' ;
SYM_OR       : 'or' | 'OR' | '∨' ;
SYM_XOR      : 'xor' | 'XOR' ;
SYM_NOT      : 'not' | 'NOT' | '!' | '~' | '¬' ;
SYM_IMPLIES  : 'implies' | '⇒' | '→' ;
SYM_IFF      : '⇔' | '↔' ;
SYM_FOR_ALL  : 'for_all' | '∀' ;
SYM_THERE_EXISTS: 'there_exists' | '∃' ;
SYM_MATCHES  : 'matches' | 'is_in' | '∈' ;
SYM_ASSERT   : 'assert' ;

// Non-null assertion operator; we allow the box symbol from modal logic here
SYM_EXISTS   : 'exists' | '□' ;

BOUND_VARIABLE_ID: '$' LC_ID ;

// ---------- local code that are not ADL codes -------
// e.g. [heart_rate]
LOCAL_TERM_CODE_REF: '[' ALPHANUM_US_CHAR+ ']' ;