FHIR Mapping Language (FML)
0.1.0 - STU 1 International flag

FHIR Mapping Language (FML), published by HL7 International / FHIR Infrastructure. This guide is not an authorized publication; it is the continuous build for version 0.1.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/HL7/mapping-language-ig/ and changes regularly. See the Directory of published versions

FML Grammar

Page standards status: Informative

This page contains the Antlr 4.0 grammar for FML.

Raw FML grammar

/**
 * Define a grammar called FhirMapper
 */
grammar mapping;

// starting point for parsing a mapping file
// in case we need nested ConceptMaps, we need to have this rule:
// structureMap : mapId conceptMap* structure* imports* group+

structureMap
  : metadataDeclaration*? conceptMapDeclaration*? mapDeclaration? structureDeclaration*? importDeclaration*? constantDeclaration*? groupDeclaration+ EOF
  ;

conceptMapDeclaration
  : 'conceptmap' url '{' conceptMapPrefix+ conceptMapCodeMap+ '}'
  ;

conceptMapPrefix
  : 'prefix' ID '=' url
  ;

conceptMapCodeMap
  : conceptMapSource '-' conceptMapTarget
  ;

conceptMapSource
  : ID ':' code
  ;

conceptMapTarget
  : ID ':' code
  ;

code
  : ID
  | STRING
  | DOUBLE_QUOTED_STRING
  ;

mapDeclaration
  : 'map' url '=' identifier
  ;

metadataDeclaration
  : METADATA_PREFIX qualifiedIdentifier '=' (literal | markdownLiteral)?  // value is optional to allow descendant maps to remove values from parents
  ;

markdownLiteral
  : TRIPLE_QUOTED_STRING_LITERAL
  ;

url
  : STRING
  | DOUBLE_QUOTED_STRING
  ;

identifier
  : ID
  | IDENTIFIER
  | DELIMITEDIDENTIFIER
  | 'as'
  | 'contains'
  | 'in'
  | 'is'
  | 'asc'
  | 'desc'
  | 'sort'
  ;

structureDeclaration
  : 'uses' url ('alias' identifier)? 'as' modelMode
  ;

constantDeclaration 
  : 'let' ID '=' fpExpression ';' // which might just be a literal
  ;

groupDeclaration
  : 'group' ID parameters extends? typeMode? mapRules
  ;

parameters
  : '(' parameter (',' parameter)+ ')'
  ;

parameter
  : parameterMode ID typeIdentifier?
  ;

mapRules
  : '{' mapRule* '}'
  ;

typeMode
  : '<<' groupTypeMode '>>'
  ;

extends
  : 'extends' ID
  ;

typeIdentifier
  : ':' identifier
  ;

mapRule
  : qualifiedIdentifier '->' qualifiedIdentifier ':' identityFieldList ruleName? ';'  #mapSimpleBatchIdentity
  | qualifiedIdentifier '->' qualifiedIdentifier ruleName? ';'                         #mapSimpleCopy
  | mapTransformationRule ';'                                                          #mapFhirMarkup
 	;

mapTransformationRule
  : ruleSources ('->' ruleTargets)? dependentExpression? ruleName?
  ;

identityFieldList
  : identifier (',' identifier)*
  ;

ruleName
  : DOUBLE_QUOTED_STRING
  ;

ruleSources
  : ruleSource (',' ruleSource)*
  ;

ruleSource
  : qualifiedIdentifier 
    typeIdentifier? 
    sourceCardinality? 
    sourceDefault? 
    sourceListMode? 
    alias? 
    whereClause? 
    checkClause? 
    log?
  ;

ruleTargets
  : ruleTarget (',' ruleTarget)*
  ;

ruleTarget
  : qualifiedIdentifier ('=' transform)? alias? targetListMode?
  | '(' fpExpression ')' alias? targetListMode?     // pure fhirpath based variables
  | groupInvocation alias?     // alias is not required when simply invoking a group
  ;

sourceCardinality
  : INTEGER '..' upperBound
  ;

upperBound
  : INTEGER
  | '*'
  ;

qualifiedIdentifier
  : (DELIMITEDIDENTIFIER | ID | IDENTIFIER | 'imports' | 'source' | 'target' | 'group' | 'prefix' | 'map' | 'uses' | 'let' | 'types' | 'extends' | 'where' | 'check' | 'alias' | 'div' | 'contains' | 'as' | 'is' | 'asc' | 'desc' | 'first' | 'last' | 'sort' ) 
    ('.' (DELIMITEDIDENTIFIER | ID | IDENTIFIER | 'imports' | 'source' | 'target' | 'group' | 'prefix' | 'map' | 'uses' | 'let' | 'types' | 'extends' | 'where' | 'check' | 'alias' | 'div' | 'contains' | 'as' | 'is' | 'asc' | 'desc' | 'first' | 'last' | 'sort'))*
  // : identifier ('.' identifier '[x]'?)*
  ;

sourceDefault
  : 'default' '(' fpExpression ')'
  | 'default' DOUBLE_QUOTED_STRING      // very old format from original version
  ;

alias
  : 'as' identifier
  ;

whereClause
  : 'where' '(' fpExpression ')'
  ;

checkClause
  : 'check' '(' fpExpression ')'
  ;

log
  : 'log' '(' fpExpression ')'
  ;

dependentExpression
  : 'then' (groupInvocation (',' groupInvocation)* mapRules? | mapRules)
  ;

importDeclaration
	: 'imports' url
	;

transform
  : literal           // trivial constant transform
  | qualifiedIdentifier       // 'copy' transform
  | groupInvocation        // other named transforms
  | '(' fpExpression ')'      // fhirpath based expressions
  ;

groupInvocation
  : identifier '(' groupParamList? ')'
  ;

groupParamList
  : groupParam (',' groupParam)*
  ;

groupParam
  : literal
  | ID
  | fpExpression   // this is to support the `evaluate` transform, parser validation/visitor should check that this is only used there.
  ;

fpExpression
        : fpTerm                                                      #termExpression
        | fpExpression '.' fpInvocation                               #invocationExpression
        | fpExpression '[' fpExpression ']'                           #indexerExpression
        | ('+' | '-') fpExpression                                    #polarityExpression
        | fpExpression ('*' | '/' | 'div' | 'mod') fpExpression       #multiplicativeExpression
        | fpExpression ('+' | '-' | '&') fpExpression                 #additiveExpression
        | fpExpression ('is' | 'as') fpTypeSpecifier                  #typeExpression
        | fpExpression ('|') fpExpression                             #unionExpression
        | fpExpression ('<=' | '<' | '>' | '>=') fpExpression         #inequalityExpression
        | fpExpression ('=' | '~' | '!=' | '!~') fpExpression         #equalityExpression
        | fpExpression ('in' | 'contains') fpExpression               #membershipExpression
        | fpExpression ('and') fpExpression                           #andExpression
        | fpExpression ('or' | 'xor') fpExpression                    #orExpression
        | fpExpression ('implies') fpExpression                       #impliesExpression
        //| (IDENTIFIER)? '=>' fpExpression                           #lambdaExpression
        ;

fpTerm
        : fpInvocation                                            #invocationTerm
        | literal                                                 #literalTerm
        | fpExternalConstant                                      #externalConstantTerm
        | '(' fpExpression ')'                                    #parenthesizedTerm
        ;

fpInvocation                          // Terms that can be used after the function/member invocation '.'
        : fpFunction                                            #functionInvocation
        | identifier                                            #memberInvocation
        | '$this'                                               #thisInvocation
        | '$index'                                              #indexInvocation
        | '$total'                                              #totalInvocation
        ;

fpExternalConstant
        : '%' ( identifier | STRING )
        ;

fpFunction
        : 'sort' '(' (fpSortArgument (',' fpSortArgument)*)? ')'
        | qualifiedIdentifier '(' fpParamList? ')'
        ;

fpSortArgument
        : fpExpression ('asc' | 'desc')?                          #sortDirectionArgument
        ;

fpParamList
        : fpExpression (',' fpExpression)*
        ;

fpTypeSpecifier
        : qualifiedIdentifier
        ;

constant
  : ID
  ;

// Enum rules for FHIR ValueSet bindings
sourceListMode
    : 'first' | 'not_first' | 'last' | 'not_last' | 'only_one'
    ;

targetListMode
    : 'first'
    | 'share' ID    // ID is the listRuleId; rules sharing the same id are coalesced into one target instance
    | 'last'
    | 'single'
    ;

groupTypeMode
    : 'types' | 'type+'
    ;

modelMode
    : 'source' | 'queried' | 'target' | 'produced'
    ;

parameterMode
    : 'source' | 'target'
    ;

literal
  : NULL_LITERAL                                          #nullLiteral
  | BOOL                                                  #booleanLiteral
  | fpQuantity                                            #quantityLiteral
  | LONGNUMBER                                            #longNumberLiteral
  | (INTEGER | DECIMAL)                                   #numberLiteral
  | DATE                                                  #dateLiteral
  | DATETIME                                              #dateTimeLiteral
  | TIME                                                  #timeLiteral
  | STRING                                                #stringLiteral
  | DOUBLE_QUOTED_STRING                                  #quotedStringLiteral
  ;

  // : BOOL
  // | DATE
  // | DATE_TIME
  // | TIME
  // | NUMBER
  // // | ID            // added to allow for constant use
  // | DELIMITED_IDENTIFIER
  // | SINGLE_QUOTED_STRING
  // | DOUBLE_QUOTED_STRING
  // ;

// note that quantity has to require units here because if not there is no differentiator from a bare number
fpQuantity
    : (INTEGER | DECIMAL) ('year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond')          #quantityWithDate
    | (INTEGER | DECIMAL) ('years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds')  #quantityWithDatePlural
    | (INTEGER | DECIMAL) STRING                                                                                      #quantityWithUcum // UCUM syntax for units of measure
    ;

    /*
     * Syntax for embedded ConceptMaps excluded for now
     *
    conceptMap
        : 'conceptMap' '"#'	IDENTIFIER '{' (prefix)+ conceptMapping '}'
        ;

    prefix
    	: 'prefix' conceptMappingVar '=' URL
    	;

    conceptMappingVar
    	:  IDENTIFIER
    	;
    conceptMapping
    	:  conceptMappingVar ':' field
    	   (('<=' | '=' | '==' | '!=' '>=' '>-' | '<-' | '~') conceptMappingVar ':' field) | '--'
    	;
    */




/****************************************************************
    Lexical rules from FhirPath
*****************************************************************/

NULL_LITERAL
  : '{' '}'
  ;

BOOL
    : 'true'
    | 'false'
    ;

DATE
    : '@' DATEFORMAT
    ;

DATETIME
    : '@' DATEFORMAT 'T' (TIMEFORMAT TIMEZONEOFFSETFORMAT?)?
    ;

TIME
    : '@' 'T' TIMEFORMAT
    ;

fragment DATEFORMAT
    : [0-9][0-9][0-9][0-9] ('-'[0-9][0-9] ('-'[0-9][0-9])?)?
    ;

fragment TIMEFORMAT
    : [0-9][0-9] (':'[0-9][0-9] (':'[0-9][0-9] ('.'[0-9]+)?)?)?
    ;

fragment TIMEZONEOFFSETFORMAT
    : ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9])
    ;

LONGNUMBER
    : [0-9]+ 'L'
    ;

DECIMAL
    : [0-9]* '.' [0-9]+
    ;

INTEGER
    : [0-9]+
    ;

// // Also allows leading zeroes now (just like CQL and XSD)
// NUMBER
//     : [0-9]+('.' [0-9]+)?
//     ;

ID
    : ([A-Za-z])([A-Za-z0-9])*
    ;


// FHIR_ELEMENT_PATH_WITH_SLICE
//   : ID ('.' ID (':' ID)? '[x]'?)*
//   ;

IDENTIFIER
    : ([A-Za-z] | '_')([A-Za-z0-9] | '_')*            // Added _ to support CQL (FHIR could constrain it out)
    ;

DELIMITEDIDENTIFIER
    : '`' (ESC | .)*? '`'
    ;

STRING
    : '\'' (ESC | .)*? '\''
    ;

// Kept for FML-specific syntax (metadata, rule names, etc.)
DOUBLE_QUOTED_STRING
  : '"' (ESC | .)*? '"'
  // : '"' ( ~["\r\n] )* '"' 
  ;

TRIPLE_QUOTED_STRING_LITERAL
  : '"""' [\r\n] (.)*? [\r\n] '"""' ('\r\n'|'\r'|'\n'|EOF)
  ;


// Pipe whitespace to the HIDDEN channel to support retrieving source text through the parser.
WS
    : [ \r\n\t]+ -> channel(HIDDEN)
    ;

COMMENT
        : '/*' .*? '*/' -> channel(HIDDEN)
        ;

METADATA_PREFIX
      : '/// '
      ;

LINE_COMMENT
        : '//' ~[/] ~[\r\n]* -> channel(HIDDEN)
        ;

// INLINE_COMMENT
//   : [ \t]* C_STYLE_COMMENT
//   | [ \t]* LINE_COMMENT
//   ;

fragment ESC
        : '\\' ([`"'\\/fnrt] | UNICODE)    // allow \", \', \\, \/, \f, etc. and \uXXX
        ;

fragment UNICODE
        : 'u' HEX HEX HEX HEX
        ;

fragment HEX
        : [0-9a-fA-F]
        ;