Skip to content

FHIRPath Parser & Lexer

FHIRPath lexer and parser components

FhirPathLexer

Path: fhircraft.fhir.path.lexer.FhirPathLexer

FhirPathLexer(debug=False)

A Lexical analyzer for JsonPath.

Methods:

Name Description
tokenize

Maps a string to an iterator over tokens. In other words: [char] -> [token]

t_ignore_WHITESPACE

[\s]

t_ignore_COMMENT

\/*([\s\S]?)*\/|\/\/(.)

t_CHOICE_ELEMENT

(?:`[a-zA-Z][a-zA-Z0-9-][^`][x]`)|(?:(?:|[a-zA-Z])[a-zA-Z0-9][x])

t_ENVIRONMENTAL_VARIABLE

(?:\%[a-zA-Z][a-zA-Z0-9-]|\%`[a-zA-Z][a-zA-Z0-9-][^`]`)

t_CONTEXTUAL_OPERATOR

\$(\w*)?

t_DATETIME

@\d{4}(?:-\d{2}(?:-\d{2})?)?T(?:\d{2}(?::\d{2}(?::\d{2}(?:.\d{3}(?:[+|-]\d{2}(?::\d{2})?)?)?)?)?)?

t_DATE

@\d{4}(?:-\d{2}(?:-\d{2})?)?

t_TIME

\@T\d{2}(?::\d{2}(?::\d{2}(?:.\d{3}(?:[+|-]\d{2}(?::\d{2})?)?)?)?)?

t_NUMBER

-?\d+(.\d+)?

t_STRING

\'((?:[^\'\]|\.)*)\'

t_IDENTIFIER

(?:`[a-zA-Z][a-zA-Z0-9-][^`]`)|(?:(?:|[a-zA-Z])[a-zA-Z0-9])

t_NOT_EQUAL

!=

t_NOT_EQUIVALENT

!~

t_GREATER_EQUAL_THAN

=

t_LESS_EQUAL_THAN

<=

t_LESS_THAN

<

t_GREATER_THAN
t_EQUAL

=

t_EQUIVALENT

~

t_error_invalid_function

[a-zA-Z][a-zA-Z_0-9]((?:.)?)

t_error_doublequote_string

\"([^\"]*)?\"

Source code in fhircraft/fhir/path/lexer.py
def __init__(self, debug=False):
    self.debug = debug
    if self.__doc__ is None:
        raise FhirPathLexerError("Docstrings have been removed by design of PLY.")
    # Create the lexer once during initialization for better performance
    self.lexer = ply.lex.lex(module=self)

tokenize

tokenize(string)

Maps a string to an iterator over tokens. In other words: [char] -> [token]

Source code in fhircraft/fhir/path/lexer.py
def tokenize(self, string):
    """
    Maps a string to an iterator over tokens. In other words: [char] -> [token]
    """

    # Reuse the existing lexer instead of creating a new one each time
    self.lexer.latest_newline = 0
    self.lexer.string_value = None
    self.lexer.input(string)

    while True:
        t = self.lexer.token()
        if t is None:
            break
        t.col = t.lexpos - self.lexer.latest_newline
        yield t

    if self.lexer.string_value is not None:
        raise FhirPathLexerError("Unexpected EOF in string literal or identifier")

t_ignore_WHITESPACE

t_ignore_WHITESPACE(t)

[\s]

Source code in fhircraft/fhir/path/lexer.py
def t_ignore_WHITESPACE(self, t):
    # Whitespace (http://hl7.org/fhirpath/N1/#whitespace)
    # -------------------------------------------------------------------------------
    # FHIRPath defines tab (\t), space ( ), line feed (\n) and carriage return (\r) as whitespace,
    # meaning they are only used to separate other tokens within the language. Any number
    # of whitespace characters can appear, and the language does not use whitespace for
    # anything other than delimiting tokens.
    r"[\s]"
    if t.value == "\n":
        t.lexer.lineno += 1
        t.lexer.latest_newline = t.lexpos

t_ignore_COMMENT

t_ignore_COMMENT(t)

\/*([\s\S]?)*\/|\/\/(.)

Source code in fhircraft/fhir/path/lexer.py
def t_ignore_COMMENT(self, t):
    # Comments (http://hl7.org/fhirpath/N1/#comments)
    # -------------------------------------------------------------------------------
    # FHIRPath defines two styles of comments, single-line, and multi-line.
    # - A single-line comment consists of two forward slashes, followed by any
    #   text up to the end of the line
    # - To begin a multi-line comment, the typical forward slash-asterisk token
    #   is used. The comment is closed with an asterisk-forward slash, and everything enclosed is ignored
    r"\/\*([\s\S]*?)\*\/|\/\/(.*)"
    for substring in ["//", "/*", "*/"]:
        t.value = t.value.replace(substring, "")
    t.value = t.value.strip()

t_CHOICE_ELEMENT

t_CHOICE_ELEMENT(t)

(?:`[a-zA-Z][a-zA-Z0-9-][^`][x]`)|(?:(?:|[a-zA-Z])[a-zA-Z0-9][x])

Source code in fhircraft/fhir/path/lexer.py
def t_CHOICE_ELEMENT(self, t):
    r"(?:\`[a-zA-Z][a-zA-Z0-9\-][^\`]*\[x\]\`)|(?:(?:_|[a-zA-Z])[a-zA-Z0-9_]*\[x\])"
    t.value = t.value.replace("[x]", "")
    return t

t_ENVIRONMENTAL_VARIABLE

t_ENVIRONMENTAL_VARIABLE(t)

(?:\%[a-zA-Z][a-zA-Z0-9-]|\%`[a-zA-Z][a-zA-Z0-9-][^`]`)

Source code in fhircraft/fhir/path/lexer.py
def t_ENVIRONMENTAL_VARIABLE(self, t):
    # Environmental variable (http://hl7.org/fhirpath/N1/#environment-variables)
    # -------------------------------------------------------------------------------
    # A token introduced by a % refers to a value that is passed into the evaluation
    # engine by the calling environment.
    r"(?:\%[a-zA-Z][a-zA-Z0-9\-]*|\%\`[a-zA-Z][a-zA-Z0-9\-][^\`]*\`)"
    return t

t_CONTEXTUAL_OPERATOR

t_CONTEXTUAL_OPERATOR(t)

\$(\w*)?

Source code in fhircraft/fhir/path/lexer.py
def t_CONTEXTUAL_OPERATOR(self, t):
    # Contextual Operators (http://hl7.org/fhirpath/N1/#functions)
    # -------------------------------------------------------------------------------
    # Special elements within a funciton that refere to the input collection under evalution
    r"\$(\w*)?"
    return t

t_DATETIME

t_DATETIME(t)

@\d{4}(?:-\d{2}(?:-\d{2})?)?T(?:\d{2}(?::\d{2}(?::\d{2}(?:.\d{3}(?:[+|-]\d{2}(?::\d{2})?)?)?)?)?)?

Source code in fhircraft/fhir/path/lexer.py
def t_DATETIME(self, t):
    # DateTime (http://hl7.org/fhirpath/N1/#datetime)
    # -------------------------------------------------------------------------------
    # The Date type represents date and partial date values.
    # - The date literal is a subset of [ISO8601]:
    # - A date being with a @
    # - It uses the format YYYY-MM-DD format, though month and day parts are optional
    # - Months must be present if a day is present
    # The Time type represents time-of-day and partial time-of-day values.
    # - A time begins with a @T
    # - It uses the Thh:mm:ss.fff format
    r"@\d{4}(?:-\d{2}(?:-\d{2})?)?T(?:\d{2}(?:\:\d{2}(?:\:\d{2}(?:.\d{3}(?:[\+|\-]\d{2}(?:\:\d{2})?)?)?)?)?)?"
    return t

t_DATE

t_DATE(t)

@\d{4}(?:-\d{2}(?:-\d{2})?)?

Source code in fhircraft/fhir/path/lexer.py
def t_DATE(self, t):
    # Date (http://hl7.org/fhirpath/N1/#date)
    # -------------------------------------------------------------------------------
    # The Date type represents date and partial date values.
    # - The date literal is a subset of [ISO8601]:
    # - A date being with a @
    # - It uses the format YYYY-MM-DD format, though month and day parts are optional
    # - Months must be present if a day is present
    r"@\d{4}(?:-\d{2}(?:-\d{2})?)?"
    return t

t_TIME

t_TIME(t)

\@T\d{2}(?::\d{2}(?::\d{2}(?:.\d{3}(?:[+|-]\d{2}(?::\d{2})?)?)?)?)?

Source code in fhircraft/fhir/path/lexer.py
def t_TIME(self, t):
    # Time (http://hl7.org/fhirpath/N1/#time)
    # -------------------------------------------------------------------------------
    # The Time type represents time-of-day and partial time-of-day values.
    # - A time begins with a @T
    # - It uses the Thh:mm:ss.fff format
    r"\@T\d{2}(?:\:\d{2}(?:\:\d{2}(?:\.\d{3}(?:[+|-]\d{2}(?:\:\d{2})?)?)?)?)?"
    return t

t_NUMBER

t_NUMBER(t)

-?\d+(.\d+)?

Source code in fhircraft/fhir/path/lexer.py
def t_NUMBER(self, t):
    r"-?\d+(\.\d+)?"
    if "." in t.value:
        # Decimal (http://hl7.org/fhirpath/N1/#decimal)
        # -------------------------------------------------------------------------------
        t.value = float(t.value)
        t.type = "DECIMAL"
    else:
        # Integer (http://hl7.org/fhirpath/N1/#integer)
        # -------------------------------------------------------------------------------
        t.value = int(t.value)
        t.type = "INTEGER"
    return t

t_STRING

t_STRING(t)

\'((?:[^\'\]|\.)*)\'

Source code in fhircraft/fhir/path/lexer.py
def t_STRING(self, t):
    # String (http://hl7.org/fhirpath/N1/#string)
    # -------------------------------------------------------------------------------
    # The String type represents string values up to 231-1 characters in length. String
    # literals are surrounded by single-quotes and may use \-escapes to escape quotes
    # and represent Unicode characters
    r"\'((?:[^\'\\]|\\.)*)\'"
    t.value = t.value.strip("'")
    return t

t_IDENTIFIER

t_IDENTIFIER(t)

(?:`[a-zA-Z][a-zA-Z0-9-][^`]`)|(?:(?:|[a-zA-Z])[a-zA-Z0-9])

Source code in fhircraft/fhir/path/lexer.py
def t_IDENTIFIER(self, t):
    # Identifiers (http://hl7.org/fhirpath/N1/#identifiers)
    # -------------------------------------------------------------------------------
    # Identifiers are used as labels to allow expressions to reference elements such
    # as model types and properties. FHIRPath supports two types of identifiers, simple
    # and delimited.
    # - A simple identifier is any alphabetical character or an underscore, followed by
    #   any number of alpha-numeric characters or underscores
    # - A delimited identifier is any sequence of characters enclosed in backticks ( ` ):
    r"(?:\`[a-zA-Z][a-zA-Z0-9\-][^\`]*\`)|(?:(?:_|[a-zA-Z])[a-zA-Z0-9_]*)"
    if t.value.startswith("`") and t.value.endswith("`"):
        t.value = t.value.strip("`")
        t.type = "IDENTIFIER"
    else:
        t.type = self.reserved_words.get(t.value, "IDENTIFIER")
    return t

t_NOT_EQUAL

t_NOT_EQUAL(t)

!=

Source code in fhircraft/fhir/path/lexer.py
def t_NOT_EQUAL(self, t):
    r"!="
    return t

t_NOT_EQUIVALENT

t_NOT_EQUIVALENT(t)

!~

Source code in fhircraft/fhir/path/lexer.py
def t_NOT_EQUIVALENT(self, t):
    r"!~"
    return t

t_GREATER_EQUAL_THAN

t_GREATER_EQUAL_THAN(t)

=

Source code in fhircraft/fhir/path/lexer.py
def t_GREATER_EQUAL_THAN(self, t):
    r">="
    return t

t_LESS_EQUAL_THAN

t_LESS_EQUAL_THAN(t)

<=

Source code in fhircraft/fhir/path/lexer.py
def t_LESS_EQUAL_THAN(self, t):
    r"<="
    return t

t_LESS_THAN

t_LESS_THAN(t)

<

Source code in fhircraft/fhir/path/lexer.py
def t_LESS_THAN(self, t):
    r"<"
    return t

t_GREATER_THAN

t_GREATER_THAN(t)
Source code in fhircraft/fhir/path/lexer.py
def t_GREATER_THAN(self, t):
    r">"
    return t

t_EQUAL

t_EQUAL(t)

=

Source code in fhircraft/fhir/path/lexer.py
def t_EQUAL(self, t):
    r"="
    return t

t_EQUIVALENT

t_EQUIVALENT(t)

~

Source code in fhircraft/fhir/path/lexer.py
def t_EQUIVALENT(self, t):
    r"~"
    return t

t_error_invalid_function

t_error_invalid_function(t)

[a-zA-Z][a-zA-Z_0-9]((?:.)?)

Source code in fhircraft/fhir/path/lexer.py
def t_error_invalid_function(self, t):
    r"[a-zA-Z][a-zA-Z_0-9]*\((?:.*)?\)"
    t.value = t.value.split("(")[0]
    pos = t.lexpos - t.lexer.latest_newline
    raise FhirPathLexerError(
        f'FHIRPath lexer error at {t.lexer.lineno}:{pos} - Invalid function: "{t.value}".\n{_underline_error_in_fhir_path(t.lexer.lexdata, t.value, pos)}'
    )

t_error_doublequote_string

t_error_doublequote_string(t)

\"([^\"]*)?\"

Source code in fhircraft/fhir/path/lexer.py
def t_error_doublequote_string(self, t):
    r"\"([^\"]*)?\" "
    pos = t.lexpos - t.lexer.latest_newline
    raise FhirPathLexerError(
        f"FHIRPath lexer error at {t.lexer.lineno}:{pos} - Double-quoted strings are not valid in FHIRPath: {t.value}\n{_underline_error_in_fhir_path(t.lexer.lexdata, t.value, pos)}"
    )

MergeLexerMetaclass

Path: fhircraft.fhir.path.lexer.MergeLexerMetaclass

Bases: type

FhirPathParser

Path: fhircraft.fhir.path.parser.FhirPathParser

FhirPathParser(debug=False, lexer_class=None)

An LALR-parser for FHIRPath

Methods:

Name Description
p_fhirpath_term_expression

expression : term

p_fhirpath_invocation_expression

expression : expression '.' invocation

p_fhirpath_indexer_expression

expression : expression '[' expression ']'

p_fhirpath_multiplicative_operation

expression : expression '*' expression

p_fhirpath_additive_operation

expression : expression '+' expression

p_fhirpath_type_operation

expression : expression IS type_specifier

p_fhirpath_union_operation

expression : expression '|' expression

p_fhirpath_equality_operation

expression : expression EQUAL expression

p_fhirpath_inequality_operation

expression : expression GREATER_THAN expression

p_fhirpath_membership_fhirpath_operation

expression : expression IN expression

p_fhirpath_and_operation

expression : expression AND expression

p_fhirpath_or_operation

expression : expression OR expression

p_fhirpath_implies_operation

expression : expression IMPLIES expression

p_fhirpath_term

term : invocation

p_fhirpath_parenthesized_expression

parenthesized_expression : '(' expression ')'

p_fhirpath_invocation

invocation : element

p_fhirpath_root

root : ROOT_NODE

p_fhirpath_element

element : identifier

p_fhirpath_typechoice_invocation

type_choice : CHOICE_ELEMENT

p_fhirpath_constant

constant : ENVIRONMENTAL_VARIABLE

p_fhirpath_contextual

contextual : CONTEXTUAL_OPERATOR

p_fhirpath_type_specifier

type_specifier : identifier

p_fhirpath_type_specifier_context

type_specifier : identifier '.' identifier

p_fhirpath_function

function : function_name '(' arguments ')'

p_fhirpath_type_function

function : OFTYPE '(' type_specifier ')'

p_fhirpath_function_name

function_name : identifier

p_fhirpath_function_arguments

arguments : arguments ',' argument

p_fhirpath_function_argument

argument : expression

p_fhirpath_identifier

identifier : IDENTIFIER

p_fhirpath_literal

literal : number

p_fhirpath_literal_empty

literal : '{' '}'

p_fhirpath_boolean

boolean : BOOLEAN

p_fhirpath_datetime

datetime : DATETIME

p_fhirpath_time

time : TIME

p_fhirpath_date

date : DATE

p_fhirpath_quantity

quantity : number unit

p_fhirpath_unit

unit : STRING

p_fhirpath_number

number : INTEGER

p_fhirpath_empty

empty :

Source code in fhircraft/fhir/path/parser.py
def __init__(self, debug=False, lexer_class=None):
    if self.__doc__ is None:
        raise FhirPathParserError(
            "Docstrings have been removed! By design of PLY, "
        )

    self.debug = debug
    self.lexer_class = (
        lexer_class or FhirPathLexer
    )  # Crufty but works around statefulness in PLY
    self.lexer = self.lexer_class()

    # Since PLY has some crufty aspects and dumps files, we try to keep them local
    # However, we need to derive the name of the output Python file :-/
    output_directory = os.path.dirname(__file__)
    try:
        module_name = os.path.splitext(os.path.split(__file__)[1])[0]
    except:
        module_name = __name__

    start_symbol = "expression"
    parsing_table_module = "_".join([module_name, start_symbol, "parsetab"])

    # Generate the parse table
    self.parser = ply.yacc.yacc(
        module=self,
        debug=self.debug,
        tabmodule=parsing_table_module,
        outputdir=output_directory,
        write_tables=False,
        start=start_symbol,
        errorlog=logger,
    )

p_fhirpath_term_expression

p_fhirpath_term_expression(p)

expression : term

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_term_expression(self, p):
    """expression : term"""
    p[0] = p[1]

p_fhirpath_invocation_expression

p_fhirpath_invocation_expression(p)

expression : expression '.' invocation

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_invocation_expression(self, p):
    "expression : expression '.' invocation"
    p[0] = Invocation(p[1], p[3])

p_fhirpath_indexer_expression

p_fhirpath_indexer_expression(p)

expression : expression '[' expression ']'

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_indexer_expression(self, p):
    "expression : expression '[' expression ']'"
    p[0] = Invocation(p[1], subsetting.Index(p[3]))

p_fhirpath_multiplicative_operation

p_fhirpath_multiplicative_operation(p)

expression : expression '*' expression | expression '/' expression | expression DIV expression | expression MOD expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_multiplicative_operation(self, p):
    """expression : expression '*' expression
    | expression '/' expression
    | expression DIV expression
    | expression MOD expression"""
    op = p[2]
    if op == "*":
        p[0] = math.Multiplication(p[1], p[3])
    elif op == "/":
        p[0] = math.Division(p[1], p[3])
    elif op == "div":
        p[0] = math.Div(p[1], p[3])
    elif op == "mod":
        p[0] = math.Mod(p[1], p[3])

p_fhirpath_additive_operation

p_fhirpath_additive_operation(p)

expression : expression '+' expression | expression '-' expression | expression '&' expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_additive_operation(self, p):
    """expression : expression '+' expression
    | expression '-' expression
    | expression '&' expression"""
    op = p[2]
    if op == "+":
        p[0] = math.Addition(p[1], p[3])
    elif op == "-":
        p[0] = math.Subtraction(p[1], p[3])
    elif op == "&":
        p[0] = strings.Concatenation(p[1], p[3])

p_fhirpath_type_operation

p_fhirpath_type_operation(p)

expression : expression IS type_specifier | expression AS type_specifier

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_type_operation(self, p):
    """expression : expression IS type_specifier
    | expression AS type_specifier"""
    op = p[2]
    if op == "is":
        p[0] = types.Is(p[1], p[3])
    elif op == "as":
        p[0] = types.As(p[1], p[3])

p_fhirpath_union_operation

p_fhirpath_union_operation(p)

expression : expression '|' expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_union_operation(self, p):
    """expression : expression '|' expression"""
    p[0] = collection.Union(p[1], p[3])

p_fhirpath_equality_operation

p_fhirpath_equality_operation(p)

expression : expression EQUAL expression | expression EQUIVALENT expression | expression NOT_EQUAL expression | expression NOT_EQUIVALENT expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_equality_operation(self, p):
    """expression : expression EQUAL expression
    | expression EQUIVALENT expression
    | expression NOT_EQUAL expression
    | expression NOT_EQUIVALENT expression"""
    op = p[2]
    if op == "=":
        p[0] = equality.Equals(p[1], p[3])
    elif op == "~":
        p[0] = equality.Equivalent(p[1], p[3])
    elif op == "!=":
        p[0] = equality.NotEquals(p[1], p[3])
    elif op == "!~":
        p[0] = equality.NotEquivalent(p[1], p[3])

p_fhirpath_inequality_operation

p_fhirpath_inequality_operation(p)

expression : expression GREATER_THAN expression | expression GREATER_EQUAL_THAN expression | expression LESS_THAN expression | expression LESS_EQUAL_THAN expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_inequality_operation(self, p):
    """expression : expression GREATER_THAN expression
    | expression GREATER_EQUAL_THAN expression
    | expression LESS_THAN expression
    | expression LESS_EQUAL_THAN expression"""
    op = p[2]
    if op == ">":
        p[0] = comparison.GreaterThan(p[1], p[3])
    elif op == ">=":
        p[0] = comparison.GreaterEqualThan(p[1], p[3])
    elif op == "<":
        p[0] = comparison.LessThan(p[1], p[3])
    elif op == "<=":
        p[0] = comparison.LessEqualThan(p[1], p[3])

p_fhirpath_membership_fhirpath_operation

p_fhirpath_membership_fhirpath_operation(p)

expression : expression IN expression | expression CONTAINS expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_membership_fhirpath_operation(self, p):
    """expression : expression IN expression
    | expression CONTAINS expression"""
    op = p[2]
    if op == "in":
        p[0] = collection.In(p[1], p[3])
    elif op == "contains":
        p[0] = collection.Contains(p[1], p[3])

p_fhirpath_and_operation

p_fhirpath_and_operation(p)

expression : expression AND expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_and_operation(self, p):
    """expression : expression AND expression"""
    p[0] = boolean.And(p[1], p[3])

p_fhirpath_or_operation

p_fhirpath_or_operation(p)

expression : expression OR expression | expression XOR expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_or_operation(self, p):
    """expression : expression OR expression
    | expression XOR expression"""
    op = p[2]
    if op == "or":
        p[0] = boolean.Or(p[1], p[3])
    elif op == "xor":
        p[0] = boolean.Xor(p[1], p[3])

p_fhirpath_implies_operation

p_fhirpath_implies_operation(p)

expression : expression IMPLIES expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_implies_operation(self, p):
    """expression : expression IMPLIES expression"""
    p[0] = boolean.Implies(p[1], p[3])

p_fhirpath_term

p_fhirpath_term(p)

term : invocation | literal | constant | parenthesized_expression

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_term(self, p):
    """term : invocation
    | literal
    | constant
    | parenthesized_expression"""
    p[0] = p[1]

p_fhirpath_parenthesized_expression

p_fhirpath_parenthesized_expression(p)

parenthesized_expression : '(' expression ')'

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_parenthesized_expression(self, p):
    """parenthesized_expression : '(' expression ')'"""
    p[0] = p[2]

p_fhirpath_invocation

p_fhirpath_invocation(p)

invocation : element | root | type_choice | function | contextual

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_invocation(self, p):
    """invocation : element
    | root
    | type_choice
    | function
    | contextual"""
    p[0] = p[1]

p_fhirpath_root

p_fhirpath_root(p)

root : ROOT_NODE

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_root(self, p):
    """root : ROOT_NODE"""
    p[0] = RootElement(p[1])

p_fhirpath_element

p_fhirpath_element(p)

element : identifier

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_element(self, p):
    """element : identifier"""
    p[0] = Element(p[1])

p_fhirpath_typechoice_invocation

p_fhirpath_typechoice_invocation(p)

type_choice : CHOICE_ELEMENT

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_typechoice_invocation(self, p):
    "type_choice : CHOICE_ELEMENT"
    p[0] = additional.TypeChoice(p[1])

p_fhirpath_constant

p_fhirpath_constant(p)

constant : ENVIRONMENTAL_VARIABLE

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_constant(self, p):
    """constant : ENVIRONMENTAL_VARIABLE"""
    p[0] = environment.EnvironmentVariable(p[1])

p_fhirpath_contextual

p_fhirpath_contextual(p)

contextual : CONTEXTUAL_OPERATOR

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_contextual(self, p):
    """contextual : CONTEXTUAL_OPERATOR"""
    if p[1] == "$this":
        p[0] = environment.ContextualThis()
    elif p[1] == "$index":
        p[0] = environment.ContextualIndex()
    elif p[1] == "$total":
        p[0] = environment.ContextualTotal()
    else:
        raise FhirPathParserError(
            f'FHIRPath parser error at {p.lineno(1)}:{p.lexpos(1)}: Invalid contextual operator "{p[1]}".\n{_underline_error_in_fhir_path(self.string, p[1], p.lexpos(1))}'
        )

p_fhirpath_type_specifier

p_fhirpath_type_specifier(p)

type_specifier : identifier | ROOT_NODE

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_type_specifier(self, p):
    """type_specifier : identifier
    | ROOT_NODE"""
    p[0] = TypeSpecifier(p[1])

p_fhirpath_type_specifier_context

p_fhirpath_type_specifier_context(p)
identifier '.' identifier

| identifier '.' ROOT_NODE

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_type_specifier_context(self, p):
    """
    type_specifier : identifier '.' identifier
                   | identifier '.' ROOT_NODE
    """
    p[0] = TypeSpecifier(f"{p[1]}.{p[3]}")

p_fhirpath_function

p_fhirpath_function(p)

function : function_name '(' arguments ')'

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_function(self, p):
    """function : function_name '(' arguments ')'"""

    def check(args, function, nargs):
        if args[1] == function:
            params = ensure_list(args[3] or [])
            params = [param for param in params if param is not None]
            nprovided = len(params)
            if nprovided not in ensure_list(nargs):
                raise FhirPathParserError(
                    f"FHIRPath parser error at {p.lineno(1)}:{p.lexpos(1)}: Function {function}() requires {nargs} arguments, but {nprovided} were provided.\n{_underline_error_in_fhir_path(self.string, function, p.lexpos(1))}"
                )
            return True
        return False

    # -------------------------------------------------------------------------------
    # Existence
    # -------------------------------------------------------------------------------
    if check(p, "empty", nargs=0):
        p[0] = existence.Empty()
    elif check(p, "exists", nargs=[0, 1]):
        p[0] = existence.Exists(*p[3])
    elif check(p, "all", nargs=[0, 1]):
        p[0] = existence.All(*p[3])
    elif check(p, "allTrue", nargs=0):
        p[0] = existence.AllTrue()
    elif check(p, "anyTrue", nargs=0):
        p[0] = existence.AnyTrue()
    elif check(p, "allFalse", nargs=0):
        p[0] = existence.AllFalse()
    elif check(p, "anyFalse", nargs=0):
        p[0] = existence.AnyFalse()
    elif check(p, "subsetOf", nargs=1):
        p[0] = existence.SubsetOf(*p[3])
    elif check(p, "supersetOf", nargs=1):
        p[0] = existence.SupersetOf(*p[3])
    elif check(p, "count", nargs=0):
        p[0] = existence.Count()
    elif check(p, "distinct", nargs=0):
        p[0] = existence.Distinct()
    elif check(p, "isDistinct", nargs=0):
        p[0] = existence.IsDistinct()
    # -------------------------------------------------------------------------------
    # Subsetting
    # -------------------------------------------------------------------------------
    elif check(p, "where", nargs=1):
        p[0] = filtering.Where(*p[3])
    elif check(p, "select", nargs=1):
        p[0] = filtering.Select(*p[3])
    elif check(p, "repeat", nargs=1):
        p[0] = filtering.Repeat(*p[3])
    # -------------------------------------------------------------------------------
    # Additional functions
    # -------------------------------------------------------------------------------
    elif check(p, "extension", nargs=1):
        p[0] = additional.Extension(*p[3])
    elif check(p, "resolve", nargs=0):
        p[0] = additional.Resolve()
    elif check(p, "hasValue", nargs=0):
        p[0] = additional.HasValue()
    elif check(p, "getValue", nargs=0):
        p[0] = additional.GetValue()
    elif check(p, "htmlChecks", nargs=0):
        p[0] = additional.HtmlChecks()
    elif check(p, "lowBoundary", nargs=0):
        p[0] = additional.LowBoundary()
    elif check(p, "highBoundary", nargs=0):
        p[0] = additional.HighBoundary()
    elif check(p, "elementDefinition", nargs=0):
        p[0] = additional.ElementDefinition()
    elif check(p, "slice", nargs=2):
        p[0] = additional.Slice(*p[3])
    elif check(p, "checkModifiers", nargs=1):
        p[0] = additional.CheckModifiers(*p[3])
    elif check(p, "conformsTo", nargs=1):
        p[0] = additional.ConformsTo(*p[3])
    elif check(p, "memberOf", nargs=1):
        p[0] = additional.MemberOf(*p[3])
    elif check(p, "subsumes", nargs=1):
        p[0] = additional.Subsumes(*p[3])
    elif check(p, "subsumedBy", nargs=1):
        p[0] = additional.SubsumedBy(*p[3])
    elif check(p, "comparable", nargs=1):
        p[0] = additional.Comparable(*p[3])
    # -------------------------------------------------------------------------------
    # Subsetting
    # -------------------------------------------------------------------------------
    elif check(p, "single", nargs=0):
        p[0] = subsetting.Single()
    elif check(p, "first", nargs=0):
        p[0] = subsetting.First()
    elif check(p, "last", nargs=0):
        p[0] = subsetting.Last()
    elif check(p, "tail", nargs=0):
        p[0] = subsetting.Tail()
    elif check(p, "skip", nargs=1):
        p[0] = subsetting.Skip(*p[3])
    elif check(p, "take", nargs=1):
        p[0] = subsetting.Take(*p[3])
    elif check(p, "intersect", nargs=1):
        p[0] = subsetting.Intersect(*p[3])
    elif check(p, "exclude", nargs=1):
        p[0] = subsetting.Exclude(*p[3])
    # -------------------------------------------------------------------------------
    # Combining
    # -------------------------------------------------------------------------------
    elif check(p, "union", nargs=1):
        p[0] = combining.Union(*p[3])
    elif check(p, "combine", nargs=1):
        p[0] = combining.Combine(*p[3])
    # -------------------------------------------------------------------------------
    # Conversion
    # -------------------------------------------------------------------------------
    elif check(p, "iif", nargs=[2, 3]):
        p[0] = conversion.Iif(*p[3])
    elif check(p, "toBoolean", nargs=0):
        p[0] = conversion.ToBoolean()
    elif check(p, "convertsToBoolean", nargs=0):
        p[0] = conversion.ConvertsToBoolean()
    elif check(p, "toInteger", nargs=0):
        p[0] = conversion.ToInteger()
    elif check(p, "convertsToInteger", nargs=0):
        p[0] = conversion.ConvertsToInteger()
    elif check(p, "toDate", nargs=0):
        p[0] = conversion.ToDate()
    elif check(p, "convertsToDate", nargs=0):
        p[0] = conversion.ConvertsToDate()
    elif check(p, "toDateTime", nargs=0):
        p[0] = conversion.ToDateTime()
    elif check(p, "convertsToDateTime", nargs=0):
        p[0] = conversion.ConvertsToDateTime()
    elif check(p, "toDecimal", nargs=0):
        p[0] = conversion.ToDecimal()
    elif check(p, "convertsToDecimal", nargs=0):
        p[0] = conversion.ConvertsToDecimal()
    elif check(p, "toQuantity", nargs=[0, 1]):
        p[0] = conversion.ToQuantity()
    elif check(p, "convertsToQuantity", nargs=[0, 1]):
        p[0] = conversion.ConvertsToQuantity()
    elif check(p, "toString", nargs=0):
        p[0] = conversion.ToString()
    elif check(p, "convertsToString", nargs=0):
        p[0] = conversion.ConvertsToString()
    elif check(p, "toTime", nargs=0):
        p[0] = conversion.ToTime()
    elif check(p, "convertsToTime", nargs=0):
        p[0] = conversion.ConvertsToTime()
    # -------------------------------------------------------------------------------
    # String manipulation
    # -------------------------------------------------------------------------------
    elif check(p, "indexOf", nargs=1):
        p[0] = strings.IndexOf(*p[3])
    elif check(p, "substring", nargs=[1, 2]):
        p[0] = strings.Substring(*p[3])
    elif check(p, "startsWith", nargs=1):
        p[0] = strings.StartsWith(*p[3])
    elif check(p, "endsWith", nargs=1):
        p[0] = strings.EndsWith(*p[3])
    elif check(p, "contains", nargs=1):
        p[0] = strings.Contains(*p[3])
    elif check(p, "upper", nargs=0):
        p[0] = strings.Upper()
    elif check(p, "lower", nargs=0):
        p[0] = strings.Lower()
    elif check(p, "replace", nargs=2):
        p[0] = strings.Replace(*p[3])
    elif check(p, "matches", nargs=1):
        p[0] = strings.Matches(*p[3])
    elif check(p, "replaceMatches", nargs=2):
        p[0] = strings.ReplaceMatches(*p[3])
    elif check(p, "length", nargs=0):
        p[0] = strings.Length()
    elif check(p, "toChars", nargs=0):
        p[0] = strings.ToChars()
    # -------------------------------------------------------------------------------
    # Math
    # -------------------------------------------------------------------------------
    elif check(p, "abs", nargs=0):
        p[0] = math.Abs()
    elif check(p, "ceiling", nargs=0):
        p[0] = math.Ceiling()
    elif check(p, "exp", nargs=0):
        p[0] = math.Exp()
    elif check(p, "floor", nargs=0):
        p[0] = math.Floor()
    elif check(p, "ln", nargs=0):
        p[0] = math.Ln()
    elif check(p, "log", nargs=1):
        p[0] = math.Log(*p[3])
    elif check(p, "power", nargs=1):
        p[0] = math.Power(*p[3])
    elif check(p, "round", nargs=1):
        p[0] = math.Round(*p[3])
    elif check(p, "sqrt", nargs=0):
        p[0] = math.Sqrt()
    elif check(p, "truncate", nargs=0):
        p[0] = math.Truncate()
    # -------------------------------------------------------------------------------
    # Tree navigation
    # -------------------------------------------------------------------------------
    elif check(p, "children", nargs=0):
        p[0] = navigation.Children()
    elif check(p, "descendants", nargs=0):
        p[0] = navigation.Descendants()
    # -------------------------------------------------------------------------------
    # Boolean functions
    # -------------------------------------------------------------------------------
    elif check(p, "not", nargs=0):
        p[0] = boolean.Not()
    # -------------------------------------------------------------------------------
    # Utility functions
    # -------------------------------------------------------------------------------
    elif check(p, "trace", nargs=[1, 2]):
        p[0] = utility.Trace(*p[3])
    elif check(p, "now", nargs=0):
        p[0] = utility.Now()
    elif check(p, "timeOfDay", nargs=0):
        p[0] = utility.TimeOfDay()
    elif check(p, "today", nargs=0):
        p[0] = utility.Today()
    # -------------------------------------------------------------------------------
    # Aggregation functions
    # -------------------------------------------------------------------------------
    elif check(p, "aggregate", nargs=[1, 2]):
        p[0] = aggregates.Aggregate(*p[3])
    else:
        pos = self.string.find(str(p[1]))
        raise FhirPathParserError(
            f'FHIRPath parser error at {p.lineno(1)}:{pos}: Invalid function "{p[1]}".\n{_underline_error_in_fhir_path(self.string,p[1], pos)}'
        )

p_fhirpath_type_function

p_fhirpath_type_function(p)

function : OFTYPE '(' type_specifier ')' | IS '(' type_specifier ')' | AS '(' type_specifier ')'

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_type_function(self, p):
    """function : OFTYPE '(' type_specifier ')'
    | IS '(' type_specifier ')'
    | AS '(' type_specifier ')'"""
    # -------------------------------------------------------------------------------
    # Type functions
    # -------------------------------------------------------------------------------
    if p[1] == "ofType":
        p[0] = filtering.OfType(p[3])
    elif p[1] == "is":
        p[0] = types.LegacyIs(p[3])
    elif p[1] == "as":
        p[0] = types.LegacyAs(p[3])

p_fhirpath_function_name

p_fhirpath_function_name(p)

function_name : identifier | CONTAINS | IN

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_function_name(self, p):
    """function_name : identifier
    | CONTAINS
    | IN
    """
    p[0] = p[1]

p_fhirpath_function_arguments

p_fhirpath_function_arguments(p)
arguments ',' argument

| argument

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_function_arguments(self, p):
    """
    arguments : arguments ',' argument
              | argument
    """
    if len(p) == 2:
        p[0] = [p[1]]
    else:
        p[0] = p[1]
        p[0].append(p[3])

p_fhirpath_function_argument

p_fhirpath_function_argument(p)

argument : expression | empty

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_function_argument(self, p):
    """argument : expression
    | empty"""
    p[0] = p[1]

p_fhirpath_identifier

p_fhirpath_identifier(p)

identifier : IDENTIFIER

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_identifier(self, p):
    """identifier : IDENTIFIER"""
    p[0] = p[1]

p_fhirpath_literal

p_fhirpath_literal(p)

literal : number | boolean | STRING | date | time | datetime | quantity

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_literal(self, p):
    """literal : number
    | boolean
    | STRING
    | date
    | time
    | datetime
    | quantity
    """
    p[0] = Literal(p[1])

p_fhirpath_literal_empty

p_fhirpath_literal_empty(p)

literal : '{' '}'

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_literal_empty(self, p):
    """literal : '{' '}'"""
    p[0] = Literal([])

p_fhirpath_boolean

p_fhirpath_boolean(p)

boolean : BOOLEAN

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_boolean(self, p):
    "boolean : BOOLEAN"
    p[0] = True if p[1] == "true" else False

p_fhirpath_datetime

p_fhirpath_datetime(p)

datetime : DATETIME

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_datetime(self, p):
    "datetime : DATETIME"
    p[0] = literals.DateTime(p[1])

p_fhirpath_time

p_fhirpath_time(p)

time : TIME

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_time(self, p):
    "time : TIME"
    p[0] = literals.Time(p[1])

p_fhirpath_date

p_fhirpath_date(p)

date : DATE

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_date(self, p):
    "date : DATE"
    p[0] = literals.Date(p[1])

p_fhirpath_quantity

p_fhirpath_quantity(p)

quantity : number unit

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_quantity(self, p):
    """quantity : number unit"""
    p[0] = literals.Quantity(p[1], p[2])

p_fhirpath_unit

p_fhirpath_unit(p)

unit : STRING | CALENDAR_DURATION

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_unit(self, p):
    """unit : STRING
    | CALENDAR_DURATION"""
    p[0] = p[1]

p_fhirpath_number

p_fhirpath_number(p)

number : INTEGER | DECIMAL

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_number(self, p):
    """number : INTEGER
    | DECIMAL"""
    p[0] = p[1]

p_fhirpath_empty

p_fhirpath_empty(p)

empty :

Source code in fhircraft/fhir/path/parser.py
def p_fhirpath_empty(self, p):
    """empty :"""
    p[0] = None

IteratorToTokenStream

Path: fhircraft.fhir.path.parser.IteratorToTokenStream

IteratorToTokenStream(iterator)
Source code in fhircraft/fhir/path/parser.py
def __init__(self, iterator):
    self.iterator = iterator

fhirpath module-attribute

fhirpath = FhirPathParser()

parse

parse(string)
Source code in fhircraft/fhir/path/parser.py
def parse(string):
    return FhirPathParser().parse(string)