Skip to content
Open
Show file tree
Hide file tree
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 @@ -91,7 +91,7 @@ internal class TestCqlEngineRelatedContextSupport : FhirExecutionTestBase() {
private fun evaluate(
cqlEngine: CqlEngine,
expression: String,
initialContext: Pair<String, Any>?,
initialContext: Pair<String, Any?>?,
): Any? {
val evaluateResult =
cqlEngine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ object ExpressionRefEvaluator {
try {
val def =
Libraries.resolveExpressionRef(expressionRef.name, state.getCurrentLibrary()!!)
state.pushActivationFrame(def, def.context)
state.pushActivationFrame(def, def.context!!)
try {
return visitor.visitExpressionDef(def, state)
val result = visitor.visitExpressionDef(def, state)
state.storeIntermediateResultForTracing(result)
return result
} finally {
state.popActivationFrame()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ object FunctionRefEvaluator {
} else {
// Establish activation frame with the function
// definition being evaluated.
state.pushActivationFrame(functionDef, functionDef.context)
state.pushActivationFrame(functionDef, functionDef.context!!)
try {
for (i in arguments.indices) {
state.push(Variable(functionDef.operand[i].name!!).withValue(arguments[i]))
}
return visitor.visitExpression(functionDef.expression!!, state)
val result = visitor.visitExpression(functionDef.expression!!, state)
state.storeIntermediateResultForTracing(result)
return result
} finally {
state.popActivationFrame()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,189 +1,62 @@
package org.opencds.cqf.cql.engine.exception

import java.util.Deque
import java.util.LinkedList
import java.util.function.Consumer
import java.util.function.Function
import org.hl7.elm.r1.Expression
import org.hl7.elm.r1.ExpressionDef
import org.hl7.elm.r1.FunctionDef
import org.hl7.elm.r1.OperandDef
import org.hl7.elm.r1.VersionedIdentifier
import org.opencds.cqf.cql.engine.execution.State.ActivationFrame
import org.opencds.cqf.cql.engine.execution.Variable
import org.opencds.cqf.cql.engine.execution.trace.ExpressionDefTraceFrame
import org.opencds.cqf.cql.engine.execution.trace.TraceFrame

/**
* A backtrace represents a stack of call-like frames from the root of an evaluation to a particular
* sub-expression, commonly a sub-expression in which an exception was thrown.
*
* @author Jan Moringen
* A backtrace represents a series of trace frames from the root of an evaluation to the
* sub-expression in which an exception was thrown.
*/
class Backtrace {
/**
* Represents the evaluation of a CQL expression.
*
* @see FunctionoidFrame Subclass for functions and expression definitions.
*/
open class Frame(
/**
* Returns the expression that was being evaluated when the backtrace frame was captured.
*
* @return The expression.
*/
val expression: Expression?
)
class Backtrace(
/** The root of the trace frame series. */
val frame: TraceFrame
) {
override fun toString(): String {
return frame.toIndentedString(0, false)
}

/**
* Instances of this subclass of [Frame] represent the invocation of a function with specific
* arguments or the evaluation of an expression definition.
*
* In addition to the (sub)expression being evaluated, this class captures the surrounding
* function or expression definition, function arguments, local variables as well as the name
* and value of the CQL context.
*/
@Suppress("LongParameterList")
class FunctionoidFrame(
expression: Expression?,
/**
* Returns the definition in which the current expression was being evaluated when the
* backtrace frame was captured.
*
* Definitions are either direct instances of ExpressionDef which correspond to define foo:
* ... in CQL libraries or instances of FunctionDef which correspond to define function
* foo(...): ... in CQL libraries. In either case, getExpression() returns the
* sub-expression within the definition expression that was being evaluated.
*
* @return The containing definition expression.
*/
val definition: ExpressionDef?,
/**
* Returns the arguments with which the [FunctionDef] expression was invoked.
*
* Returns an empty list if the surrounding expression was an [ExpressionDef] but not a
* FunctionDef.
*
* @return A list of the [Variable]s that correspond to function call arguments.
*/
val arguments: MutableList<Variable?>?,
/**
* Returns the local variable bindings in the scope of expression evaluation of the frame.
*
* @return A list of the [Variable]s that correspond to local variable bindings.
*/
val localVariables: MutableList<Variable?>?,
/**
* Returns the name of the Library that was current during the evaluation represented by the
* frame.
*
* @return The name of the Library in context.
*/
val libraryIdentifier: VersionedIdentifier?,
companion object {
/**
* Returns the name of the CQL context that was current during the evaluation represented by
* the frame.
*
* @return The name of the CQL context.
* Creates a [Backtrace] from a stack of activation frames and the expression in which the
* exception was thrown.
*/
val contextName: String?,
/**
* Returns the value of the CQL context that was current during the evaluation represented
* by the frame.
*
* @return The value of the CQL context.
*/
val contextValue: Any?,
) : Frame(expression)

val frames: MutableList<Frame?> = LinkedList<Frame?>()
fun fromActivationFrames(
activationFrameStack: Deque<ActivationFrame>,
expression: Expression,
contextValues: Map<String, Any?>,
): Backtrace {
val topActivationFrame = activationFrameStack.peek()

fun addFrame(frame: Frame?) {
this.frames.add(frame)
}
var frame =
TraceFrame(
topActivationFrame.library,
expression,
topActivationFrame.variables.toList().reversed(),
topActivationFrame.contextName!! to
contextValues[topActivationFrame.contextName],
)

@Suppress("LongParameterList")
fun maybeAddFrame(
containingDefinition: ExpressionDef?,
definitionFrame: ActivationFrame?,
stack: Deque<ActivationFrame>,
contextName: String?,
contextValue: Any?,
libraryIdentifier: VersionedIdentifier?,
expression: Expression?,
) {
// When the EvaluationVisitor unwinds through
// EvaluationVisitor.visitExpression calls, every call has a
// unique expression that is being evaluated but not every
// call has a unique ActivationFrame since each
// ActivationFrame is associated with the respective innermost
// expression definition or function definition enclosing the
// expression which is being evaluated in the call. For that
// reason, this method is called for multiple expressions with
// the same surrounding ActivationFrame. If the frame at the
// end of our frame list already describes the definition or
// function that surrounds the expression for which the
// visitExpression is being unwound, everything has already
// been recorded: the innermost expression, the containing
// definition or function and all local variables and
// arguments.
if (!frames.isEmpty()) {
val currentFrame = this.frames.get(frames.size - 1)
if (currentFrame is FunctionoidFrame) {
if (currentFrame.definition === containingDefinition) {
return
for (activationFrame in activationFrameStack) {
val element = activationFrame.element
if (element is ExpressionDef) {
frame =
ExpressionDefTraceFrame(
activationFrame.library,
element,
activationFrame.variables.toList().reversed(),
activationFrame.contextName!! to
contextValues[activationFrame.contextName],
activationFrame.result,
listOf(frame),
)
}
}

return Backtrace(frame)
}
// Walk the ActivationFrames from the top of the stack to the
// innermost ActivationFrame which corresponds to an
// expression or function definition which is definitionFrame.
// Variable bindings in frames closer to the top of the stack
// than definitionFrame correspond to local variables which
// where established in the scope of the top-level expression
// or function. Each variable binding in definitionFrame
// itself corresponds to either an invocation argument, if the
// frame represents a function invocation, or more local
// variables. Classify all relevant bindings into those two
// categories, mainly for more informative presentation in
// backtraces.
val arguments = mutableListOf<Variable?>()
val localVariables = mutableListOf<Variable?>()
for (frame in stack) {
if (frame === definitionFrame) {
val parameterNames: List<String?>
if (containingDefinition is FunctionDef) {
parameterNames = containingDefinition.operand.map(OperandDef::name)
} else {
parameterNames = listOf()
}
frame.variables.forEach(
Consumer { variable ->
if (parameterNames.contains(variable!!.name)) {
arguments.add(variable)
} else {
localVariables.add(variable)
}
}
)
arguments.sortWith(
Comparator.comparing(
Function { argument -> parameterNames.indexOf(argument!!.name) }
)
)
break
} else {
localVariables.addAll(frame.variables)
}
}
addFrame(
FunctionoidFrame(
expression,
containingDefinition,
arguments,
localVariables,
libraryIdentifier,
contextName,
contextValue,
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ constructor(

@Transient var sourceLocator: SourceLocator? = null

val backtrace: Backtrace = Backtrace()
var backtrace: Backtrace? = null

init {
this.sourceLocator = sourceLocator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ constructor(val environment: Environment, engineOptions: MutableSet<Options>? =
// definitions and retrieves.
EnableProfiling,

// Collect trace information during evaluation (expressions and function
// calls with intermediate results). Trace data can be exported after evaluation.
EnableTracing,

// Collect coverage information during execution. Coverage
// data can be exported in LCOV format after execution.
EnableCoverageCollection,
Expand Down Expand Up @@ -209,7 +213,7 @@ constructor(val environment: Environment, engineOptions: MutableSet<Options>? =
}
}
} finally {
this.state.endEvaluation()
result.trace = this.state.endEvaluation()
// We are moving the evaluated resources off the stack so we can work on the next ones
this.state.clearEvaluatedResources()
// We are moving the library off the stack so we can work on the next one
Expand All @@ -229,11 +233,12 @@ constructor(val environment: Environment, engineOptions: MutableSet<Options>? =
) {
try {
val action = this.state.shouldDebug(def)
state.pushActivationFrame(def, def.context)
state.pushActivationFrame(def, def.context!!)
try {
val value = eval()
result.results[expression] = ExpressionResult(value, this.state.evaluatedResources)
this.state.logDebugResult(def, value, action)
state.storeIntermediateResultForTracing(value)
} finally {
this.state.popActivationFrame()
// this avoids spill over of evaluatedResources from previous/next
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import org.opencds.cqf.cql.engine.debug.DebugMap
*/
class EvaluationParams(
val expressions: Map<VersionedIdentifier, List<EvaluationExpressionRef>?>,
val contextParameter: Pair<String, Any>? = null,
val contextParameter: Pair<String, Any?>? = null,
val parameters: Map<String, Any?>? = null,
val debugMap: DebugMap? = null,
val evaluationDateTime: ZonedDateTime? = null,
Expand All @@ -26,7 +26,7 @@ class EvaluationParams(
private val expressions =
mutableMapOf<VersionedIdentifier, List<EvaluationExpressionRef>?>()

var contextParameter: Pair<String, Any>? = null
var contextParameter: Pair<String, Any?>? = null
var parameters: Map<String, Any?>? = null
var debugMap: DebugMap? = null
var evaluationDateTime: ZonedDateTime? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.opencds.cqf.cql.engine.execution

import org.opencds.cqf.cql.engine.debug.DebugResult
import org.opencds.cqf.cql.engine.execution.trace.Trace

class EvaluationResult {
/** Includes both expression results and function evaluation results. */
Expand Down Expand Up @@ -30,4 +31,7 @@ class EvaluationResult {
}

var debugResult: DebugResult? = null

/** Trace information collected during evaluation. Only used when tracing is enabled. */
var trace: Trace? = null
}
Loading
Loading