Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class FhirPathEngine(
) {
private val evaluator = FhirPathEvaluator(fhirPathTypeResolver, fhirModelNavigator, strictMode)

// Parse trees are immutable and reused across resources, so cache them by expression string.
// Unbounded cache, sized by the caller's distinct expressions (a fixed search-parameter set here).
private val parsedExpressionCache = HashMap<String, fhirpathParser.ExpressionContext>()

val traces: Map<String, List<TraceEntry>>
get() = evaluator.traces

Expand All @@ -48,6 +52,22 @@ class FhirPathEngine(
base: Any?,
variables: Map<String, Any?> = emptyMap(),
): Collection<Any> {
val parsedExpression = parsedExpressionCache.getOrPut(expression) { parseExpression(expression) }

evaluator.initialize(context = base, variables = variables)

// Convert the items in the result collection from FHIR types to FHIRPath types if it has not
// occurred in FHIRPath evaluation. Without this conversion, `Patient.name.given` would return
// results of type FHIR.string but `Patient.name.given.select(substring(0))` would return
// results of type FHIRPath.string. With this conversion, both expressions would return
// FHIRPath.string. This is necessary because we lazily convert FHIR types to FHIRPath types in
// the evaluation in order to preserve data elements such as `id` and `extension` in case they
// are needed.
val result = evaluator.visit(parsedExpression).map { it.toFhirPathType(fhirPathTypeResolver) }
return result
}

private fun parseExpression(expression: String): fhirpathParser.ExpressionContext {
val lexer = fhirpathLexer(CharStreams.fromString(expression))
val tokenStream = CommonTokenStream(lexer)
val parser =
Expand All @@ -57,26 +77,13 @@ class FhirPathEngine(
}

val parsedExpression = parser.expression()
// ANTLR attempts to parse the entire expression but does not throw an error when it cannot. In
// such cases, explicitly check that the entire expression has been consumed to ensure that the
// expression is valid.
// ANTLR may not error on incomplete parsing; verify the whole expression was consumed (EOF).
if (tokenStream.LA(1) != Token.EOF) {
error(
"Expression contains extraneous input that could not be parsed: '${tokenStream[parser.currentToken!!.tokenIndex + 1].text}'"
)
}

evaluator.initialize(context = base, variables = variables)

// Convert the items in the result collection from FHIR types to FHIRPath types if it has not
// occurred in FHIRPath evaluation. Without this conversion, `Patient.name.given` would return
// results of type FHIR.string but `Patient.name.given.select(substring(0))` would return
// results of type FHIRPath.string. With this conversion, both expressions would return
// FHIRPath.string. This is necessary because we lazily convert FHIR types to FHIRPath types in
// the evaluation in order to preserve data elements such as `id` and `extension` in case they
// are needed.
val result = evaluator.visit(parsedExpression).map { it.toFhirPathType(fhirPathTypeResolver) }
return result
return parsedExpression
}

companion object
Expand Down
Loading