![JAR search and dependency download from the Maven repository](/logo.png)
org.partiql.lang.eval.Thunk.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of partiql-lang-kotlin Show documentation
Show all versions of partiql-lang-kotlin Show documentation
An implementation of PartiQL for the JVM written in Kotlin.
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at:
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package org.partiql.lang.eval
import com.amazon.ionelement.api.MetaContainer
import org.partiql.lang.ast.SourceLocationMeta
import org.partiql.lang.ast.StaticTypeMeta
import org.partiql.lang.domains.staticType
import org.partiql.lang.errors.ErrorBehaviorInPermissiveMode
import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.errors.Property
/**
* A thunk with no parameters other than the current environment.
*
* See https://en.wikipedia.org/wiki/Thunk
*
* @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use
* different types here.
*/
internal typealias Thunk = (TEnv) -> ExprValue
/**
* A thunk taking a single argument and the current environment.
*
* See https://en.wikipedia.org/wiki/Thunk
*
* @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use
* different types here.
* @param TArg The type of the additional argument.
*/
internal typealias ThunkValue = (TEnv, TArg) -> ExprValue
/**
* A type alias for an exception handler which always throws(primarily used for [TypingMode.LEGACY]).
*/
internal typealias ThunkExceptionHandlerForLegacyMode = (Throwable, SourceLocationMeta?) -> Nothing
/**
* A type alias for an exception handler which does not always throw(primarily used for [TypingMode.PERMISSIVE]).
*/
internal typealias ThunkExceptionHandlerForPermissiveMode = (Throwable, SourceLocationMeta?) -> Unit
/**
* Options for thunk construction.
*
* - [handleExceptionForLegacyMode] will be called when in [TypingMode.LEGACY] mode
* - [handleExceptionForPermissiveMode] will be called when in [TypingMode.PERMISSIVE] mode
* - [thunkReturnTypeAssertions] is intended for testing only, and ensures that the return value of every expression
* conforms to its `StaticType` meta. This has negative performance implications so should be avoided in production
* environments. This only be used for testing and diagnostic purposes only.
* The default exception handler wraps any [Throwable] exception and throws [EvaluationException]
*/
data class ThunkOptions private constructor(
val handleExceptionForLegacyMode: ThunkExceptionHandlerForLegacyMode = DEFAULT_EXCEPTION_HANDLER_FOR_LEGACY_MODE,
val handleExceptionForPermissiveMode: ThunkExceptionHandlerForPermissiveMode = DEFAULT_EXCEPTION_HANDLER_FOR_PERMISSIVE_MODE,
val thunkReturnTypeAssertions: ThunkReturnTypeAssertions = ThunkReturnTypeAssertions.DISABLED,
) {
companion object {
/**
* Creates a java style builder that will choose the default values for any unspecified options.
*/
@JvmStatic
fun builder() = Builder()
/**
* Kotlin style builder that will choose the default values for any unspecified options.
*/
fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
/**
* Creates a [ThunkOptions] instance with the standard values.
*/
@JvmStatic
fun standard() = Builder().build()
}
/**
* Builds a [ThunkOptions] instance.
*/
class Builder {
private var options = ThunkOptions()
fun handleExceptionForLegacyMode(value: ThunkExceptionHandlerForLegacyMode) = set { copy(handleExceptionForLegacyMode = value) }
fun handleExceptionForPermissiveMode(value: ThunkExceptionHandlerForPermissiveMode) = set { copy(handleExceptionForPermissiveMode = value) }
fun evaluationTimeTypeChecks(value: ThunkReturnTypeAssertions) = set { copy(thunkReturnTypeAssertions = value) }
private inline fun set(block: ThunkOptions.() -> ThunkOptions): Builder {
options = block(options)
return this
}
fun build() = options
}
}
internal val DEFAULT_EXCEPTION_HANDLER_FOR_LEGACY_MODE: ThunkExceptionHandlerForLegacyMode = { e, sourceLocation ->
val message = e.message ?: ""
throw EvaluationException(
"Internal error, $message",
errorCode = (e as? EvaluationException)?.errorCode ?: ErrorCode.EVALUATOR_GENERIC_EXCEPTION,
errorContext = errorContextFrom(sourceLocation),
cause = e,
internal = true
)
}
internal val DEFAULT_EXCEPTION_HANDLER_FOR_PERMISSIVE_MODE: ThunkExceptionHandlerForPermissiveMode = { _, _ -> }
/**
* An extension method for creating [ThunkFactory] based on the type of [TypingMode]
* - when [TypingMode] is [TypingMode.LEGACY], creates [LegacyThunkFactory]
* - when [TypingMode] is [TypingMode.PERMISSIVE], creates [PermissiveThunkFactory]
*/
internal fun TypingMode.createThunkFactory(
thunkOptions: ThunkOptions,
valueFactory: ExprValueFactory
): ThunkFactory = when (this) {
TypingMode.LEGACY -> LegacyThunkFactory(thunkOptions, valueFactory)
TypingMode.PERMISSIVE -> PermissiveThunkFactory(thunkOptions, valueFactory)
}
/**
* Provides methods for constructing new thunks according to the specified [CompileOptions].
*/
internal abstract class ThunkFactory(
val thunkOptions: ThunkOptions,
val valueFactory: ExprValueFactory
) {
private fun checkEvaluationTimeType(thunkResult: ExprValue, metas: MetaContainer): ExprValue {
// When this check is enabled we throw an exception the [MetaContainer] does not have a
// [StaticTypeMeta]. This indicates a bug or unimplemented support for an AST node in
// [StaticTypeInferenceVisitorTransform].
val staticType = metas.staticType?.type ?: error("Metas collection does not have a StaticTypeMeta")
if (!staticType.isInstance(thunkResult)) {
throw EvaluationException(
"Runtime type does not match the expected StaticType",
ErrorCode.EVALUATOR_VALUE_NOT_INSTANCE_OF_EXPECTED_TYPE,
errorContext = errorContextFrom(metas).apply {
this[Property.EXPECTED_STATIC_TYPE] = staticType.toString()
},
internal = true
)
}
return thunkResult
}
/**
* If [ThunkReturnTypeAssertions.ENABLED] is set, wraps the receiver thunk in another thunk
* that verifies that the value returned from the receiver thunk matches the type found in the [StaticTypeMeta]
* contained within [metas].
*
* If [metas] contains does not contain [StaticTypeMeta], an [IllegalStateException] is thrown. This is to prevent
* confusion in the case [StaticTypeInferenceVisitorTransform] has a bug which prevents it from assigning a
* [StaticTypeMeta] or in case it is not run at all.
*/
protected fun Thunk.typeCheck(metas: MetaContainer): Thunk =
when (thunkOptions.thunkReturnTypeAssertions) {
ThunkReturnTypeAssertions.DISABLED -> this
ThunkReturnTypeAssertions.ENABLED -> {
val wrapper = { env: TEnv ->
val thunkResult: ExprValue = this(env)
checkEvaluationTimeType(thunkResult, metas)
}
wrapper
}
}
/** Same as [typeCheck] but works on a [ThunkEnvValue] instead of a [Thunk]. */
protected fun ThunkValue.typeCheckEnvValue(metas: MetaContainer): ThunkValue =
when (thunkOptions.thunkReturnTypeAssertions) {
ThunkReturnTypeAssertions.DISABLED -> this
ThunkReturnTypeAssertions.ENABLED -> {
val wrapper = { env: TEnv, value: ExprValue ->
val thunkResult: ExprValue = this(env, value)
checkEvaluationTimeType(thunkResult, metas)
}
wrapper
}
}
/** Same as [typeCheck] but works on a [ThunkEnvValue>] instead of a [Thunk]. */
protected fun ThunkValue>.typeCheckEnvValueList(metas: MetaContainer): ThunkValue> =
when (thunkOptions.thunkReturnTypeAssertions) {
ThunkReturnTypeAssertions.DISABLED -> this
ThunkReturnTypeAssertions.ENABLED -> {
val wrapper = { env: TEnv, value: List ->
val thunkResult: ExprValue = this(env, value)
checkEvaluationTimeType(thunkResult, metas)
}
wrapper
}
}
/**
* Creates a [Thunk] which handles exceptions by wrapping them into an [EvaluationException] which uses
* [handleException] to handle exceptions appropriately.
*
* Literal lambdas passed to this function as [t] are inlined into the body of the function being returned, which
* reduces the need to create additional call contexts. The lambdas passed as [t] may not contain non-local returns
* (`crossinline`).
*/
internal inline fun thunkEnv(metas: MetaContainer, crossinline t: Thunk): Thunk {
val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta
return { env: TEnv ->
handleException(sourceLocationMeta) {
t(env)
}
}.typeCheck(metas)
}
/**
* Defines the strategy for unknown propagation of 1-3 operands.
*
* This is the [TypingMode] specific implementation of unknown-propagation, used by the [thunkEnvOperands]
* functions. [getVal1], [getVal2] and [getVal2] are lambdas to allow for differences in short-circuiting.
*
* For all [TypingMode]s, if the values returned by [getVal1], [getVal2] and [getVal2] are all known,
* [compute] is invoked to perform the operation-specific computation.
*
* Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625.
* This shouldn't matter though because this class is still `internal`.
*/
abstract fun propagateUnknowns(
getVal1: () -> ExprValue,
getVal2: (() -> ExprValue)?,
getVal3: (() -> ExprValue)?,
compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue
): ExprValue
/**
* Similar to the other [propagateUnknowns] overload, performs unknown propagation for a variadic sequence of
* operations.
*
* Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625.
* This shouldn't matter though because this class is still `internal`.
*/
abstract fun propagateUnknowns(
operands: Sequence,
compute: (List) -> ExprValue
): ExprValue
/**
* Creates a thunk that accepts three [Thunk] operands ([t1], [t2], and [t3]), evaluates them and propagates
* unknowns according to the current [TypingMode]. When possible, use this function or one of its overloads
* instead of [thunkEnv] when the operation requires propagation of unknown values.
*
* [t1], [t2] and [t3] are each evaluated in with short circuiting depending on the current [TypingMode]:
*
* - In [TypingMode.PERMISSIVE] mode, the first `MISSING` returned from one of the thunks causes a short-circuit,
* and `MISSING` is returned immediately without evaluating the remaining thunks. If none of the thunks return
* `MISSING`, if any of them has returned `NULL`, `NULL` is returned.
* - In [TypingMode.LEGACY] mode, the first `NULL` or `MISSING` returned from one of the thunks causes a
* short-circuit, and returns `NULL` without evaluating the remaining thunks.
*
* In both modes, if none of the thunks returns `MISSING` or `NULL`, [compute] is invoked to perform the final
* computation on values of the operands which are guaranteed to be known.
*
* Overloads of this function exist that accept 1 and 2 arguments. We do not make [t2] and [t3] nullable with a
* default value of `null` instead of supplying those overloads primarily because [compute] has a different
* signature for each, but also because that would prevent [thunkEnvOperands] from being `inline`.
*/
internal inline fun thunkEnvOperands(
metas: MetaContainer,
crossinline t1: Thunk,
crossinline t2: Thunk,
crossinline t3: Thunk,
crossinline compute: (TEnv, ExprValue, ExprValue, ExprValue) -> ExprValue
): Thunk =
thunkEnv(metas) { env ->
propagateUnknowns({ t1(env) }, { t2(env) }, { t3(env) }) { v1, v2, v3 ->
compute(env, v1, v2!!, v3!!)
}
}.typeCheck(metas)
/** See the [thunkEnvOperands] with three [Thunk] operands. */
internal inline fun thunkEnvOperands(
metas: MetaContainer,
crossinline t1: Thunk,
crossinline t2: Thunk,
crossinline compute: (TEnv, ExprValue, ExprValue) -> ExprValue
): Thunk =
this.thunkEnv(metas) { env ->
propagateUnknowns({ t1(env) }, { t2(env) }, null) { v1, v2, _ ->
compute(env, v1, v2!!)
}
}.typeCheck(metas)
/** See the [thunkEnvOperands] with three [Thunk] operands. */
internal inline fun thunkEnvOperands(
metas: MetaContainer,
crossinline t1: Thunk,
crossinline compute: (TEnv, ExprValue) -> ExprValue
): Thunk =
this.thunkEnv(metas) { env ->
propagateUnknowns({ t1(env) }, null, null) { v1, _, _ ->
compute(env, v1)
}
}.typeCheck(metas)
/** See the [thunkEnvOperands] with a variadic list of [Thunk] operands. */
internal inline fun thunkEnvOperands(
metas: MetaContainer,
operandThunks: List>,
crossinline compute: (TEnv, List) -> ExprValue
): Thunk {
return this.thunkEnv(metas) { env ->
val operandSeq = sequence { operandThunks.forEach { yield(it(env)) } }
propagateUnknowns(operandSeq) { values ->
compute(env, values)
}
}.typeCheck(metas)
}
/** Similar to [thunkEnv], but creates a [ThunkEnvValue] instead. */
internal inline fun thunkEnvValue(
metas: MetaContainer,
crossinline t: ThunkValue
): ThunkValue {
val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta
return { env: TEnv, arg1: ExprValue ->
handleException(sourceLocationMeta) {
t(env, arg1)
}
}.typeCheckEnvValue(metas)
}
/** Similar to [thunkEnv], but creates a [ThunkEnvValue>] instead. */
internal inline fun thunkEnvValueList(
metas: MetaContainer,
crossinline t: ThunkValue>
): ThunkValue> {
val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta
return { env: TEnv, arg1: List ->
handleException(sourceLocationMeta) {
t(env, arg1)
}
}.typeCheckEnvValueList(metas)
}
/**
* Similar to [thunkEnv] but evaluates all [argThunks] and performs a fold using [op] as the operation.
*
* Also handles null propagation appropriately for [NAryOp] arithmetic operations. Each thunk in [argThunks]
* is evaluated in turn and:
*
* - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`.
* - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one
* of the operands returned `NULL`, `NULL` is returned.
*
* For both modes, if all of the operands are known, performs a fold over them with [op].
*/
internal abstract fun thunkFold(
metas: MetaContainer,
argThunks: List>,
op: (ExprValue, ExprValue) -> ExprValue
): Thunk
/**
* Similar to [thunkFold] but intended for comparison operators, i.e. `=`, `>`, `>=`, `<`, `<=`.
*
* The first argument of [op] is always the value of `argThunks[n]` and
* the second is always `argThunks[n + 1]` where `n` is 0 to `argThunks.size - 2`.
*
* - If [op] returns false, the thunk short circuits and the result of the thunk becomes `false`.
* - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`.
* - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one
* of the operands returned `NULL`, `NULL` is returned.
*
* If [op] is true for all invocations then the result of the thunk becomes `true`, otherwise the reuslt is `false`.
*
* The name of this function was inspired by Racket's `andmap` procedure.
*/
internal abstract fun thunkAndMap(
metas: MetaContainer,
argThunks: List>,
op: (ExprValue, ExprValue) -> Boolean
): Thunk
/** Populates [exception] with the line & column from the specified [SourceLocationMeta]. */
protected fun populateErrorContext(
exception: EvaluationException,
sourceLocation: SourceLocationMeta?
): EvaluationException {
// Only add source location data to the error context if it doesn't already exist
// in [errorContext].
if (!exception.errorContext.hasProperty(Property.LINE_NUMBER)) {
sourceLocation?.let { fillErrorContext(exception.errorContext, sourceLocation) }
}
return exception
}
/**
* Handles exceptions appropriately for a run-time [Thunk].
*
* - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that
* is thrown, if present.
* - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present.
* - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown
* with the original exception as the cause.
*/
abstract fun handleException(
sourceLocation: SourceLocationMeta?,
block: () -> ExprValue
): ExprValue
}
/**
* Provides methods for constructing new thunks according to the specified [CompileOptions] for [TypingMode.LEGACY] behaviour.
*/
internal class LegacyThunkFactory(
thunkOptions: ThunkOptions,
valueFactory: ExprValueFactory
) : ThunkFactory(thunkOptions, valueFactory) {
override fun propagateUnknowns(
getVal1: () -> ExprValue,
getVal2: (() -> ExprValue)?,
getVal3: (() -> ExprValue)?,
compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue
): ExprValue {
val val1 = getVal1()
return when {
val1.isUnknown() -> valueFactory.nullValue
else -> {
val val2 = getVal2?.let { it() }
when {
val2 == null -> compute(val1, null, null)
val2.isUnknown() -> valueFactory.nullValue
else -> {
val val3 = getVal3?.let { it() }
when {
val3 == null -> compute(val1, val2, null)
val3.isUnknown() -> valueFactory.nullValue
else -> compute(val1, val2, val3)
}
}
}
}
}
}
override fun propagateUnknowns(
operands: Sequence,
compute: (List) -> ExprValue
): ExprValue {
// Because we need to short-circuit on the first unknown value and [operands] is a sequence,
// we can't use .map here. (non-local returns on `.map` are not allowed)
val argValues = mutableListOf()
operands.forEach {
when {
it.isUnknown() -> return valueFactory.nullValue
else -> argValues.add(it)
}
}
return compute(argValues)
}
/** See [ThunkFactory.thunkFold]. */
override fun thunkFold(
metas: MetaContainer,
argThunks: List>,
op: (ExprValue, ExprValue) -> ExprValue
): Thunk {
require(argThunks.isNotEmpty()) { "argThunks must not be empty" }
val firstThunk = argThunks.first()
val otherThunks = argThunks.drop(1)
return thunkEnv(metas) thunkBlock@{ env ->
val firstValue = firstThunk(env)
when {
// Short-circuit at first NULL or MISSING value and return NULL.
firstValue.isUnknown() -> valueFactory.nullValue
else -> {
otherThunks.fold(firstValue) { acc, curr ->
val currValue = curr(env)
if (currValue.type.isUnknown) {
return@thunkBlock valueFactory.nullValue
}
op(acc, currValue)
}
}
}
}.typeCheck(metas)
}
/** See [ThunkFactory.thunkAndMap]. */
override fun thunkAndMap(
metas: MetaContainer,
argThunks: List>,
op: (ExprValue, ExprValue) -> Boolean
): Thunk {
require(argThunks.size >= 2) { "argThunks must have at least two elements" }
val firstThunk = argThunks.first()
val otherThunks = argThunks.drop(1)
return thunkEnv(metas) thunkBlock@{ env ->
val firstValue = firstThunk(env)
when {
// If the first value is unknown, short circuit returning null.
firstValue.isUnknown() -> valueFactory.nullValue
else -> {
otherThunks.fold(firstValue) { lastValue, currentThunk ->
val currentValue = currentThunk(env)
if (currentValue.isUnknown()) {
return@thunkBlock valueFactory.nullValue
}
val result = op(lastValue, currentValue)
if (!result) {
return@thunkBlock valueFactory.newBoolean(false)
}
currentValue
}
valueFactory.newBoolean(true)
}
}
}
}
/**
* Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.LEGACY] behaviour.
*
* - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that
* is thrown, if present.
* - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present.
* - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown
* with the original exception as the cause.
*/
override fun handleException(
sourceLocation: SourceLocationMeta?,
block: () -> ExprValue
): ExprValue =
try {
block()
} catch (e: EvaluationException) {
throw populateErrorContext(e, sourceLocation)
} catch (e: Exception) {
thunkOptions.handleExceptionForLegacyMode(e, sourceLocation)
}
}
/**
* Provides methods for constructing new thunks according to the specified [CompileOptions] and for
* [TypingMode.PERMISSIVE] behaviour.
*/
internal class PermissiveThunkFactory(
thunkOptions: ThunkOptions,
valueFactory: ExprValueFactory
) : ThunkFactory(thunkOptions, valueFactory) {
override fun propagateUnknowns(
getVal1: () -> ExprValue,
getVal2: (() -> ExprValue)?,
getVal3: (() -> ExprValue)?,
compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue
): ExprValue {
val val1 = getVal1()
return when (val1.type) {
ExprValueType.MISSING -> valueFactory.missingValue
else -> {
val val2 = getVal2?.let { it() }
when {
val2 == null -> nullOrCompute(val1, null, null, compute)
val2.type == ExprValueType.MISSING -> valueFactory.missingValue
else -> {
val val3 = getVal3?.let { it() }
when {
val3 == null -> nullOrCompute(val1, val2, null, compute)
val3.type == ExprValueType.MISSING -> valueFactory.missingValue
else -> nullOrCompute(val1, val2, val3, compute)
}
}
}
}
}
}
override fun propagateUnknowns(
operands: Sequence,
compute: (List) -> ExprValue
): ExprValue {
// Because we need to short-circuit on the first MISSING value and [operands] is a sequence,
// we can't use .map here. (non-local returns on `.map` are not allowed)
val argValues = mutableListOf()
operands.forEach {
when (it.type) {
ExprValueType.MISSING -> return valueFactory.missingValue
else -> argValues.add(it)
}
}
return when {
// if any result is `NULL`, propagate return null instead.
argValues.any { it.type == ExprValueType.NULL } -> valueFactory.nullValue
else -> compute(argValues)
}
}
private fun nullOrCompute(
v1: ExprValue,
v2: ExprValue?,
v3: ExprValue?,
compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue
): ExprValue =
when {
v1.type == ExprValueType.NULL ||
(v2?.let { it.type == ExprValueType.NULL }) ?: false ||
(v3?.let { it.type == ExprValueType.NULL }) ?: false -> valueFactory.nullValue
else -> compute(v1, v2, v3)
}
/** See [ThunkFactory.thunkFold]. */
override fun thunkFold(
metas: MetaContainer,
argThunks: List>,
op: (ExprValue, ExprValue) -> ExprValue
): Thunk {
require(argThunks.isNotEmpty()) { "argThunks must not be empty" }
return thunkEnv(metas) { env ->
val values = argThunks.map {
val v = it(env)
when (v.type) {
// Short-circuit at first detected MISSING value.
ExprValueType.MISSING -> return@thunkEnv valueFactory.missingValue
else -> v
}
}
when {
// Propagate NULL if any operand is NULL.
values.any { it.type == ExprValueType.NULL } -> valueFactory.nullValue
// compute the final value.
else -> values.reduce { first, second -> op(first, second) }
}
}.typeCheck(metas)
}
/** See [ThunkFactory.thunkAndMap]. */
override fun thunkAndMap(
metas: MetaContainer,
argThunks: List>,
op: (ExprValue, ExprValue) -> Boolean
): Thunk {
require(argThunks.size >= 2) { "argThunks must have at least two elements" }
return thunkEnv(metas) thunkBlock@{ env ->
val values = argThunks.map {
val v = it(env)
when (v.type) {
// Short-circuit at first detected MISSING value.
ExprValueType.MISSING -> return@thunkBlock valueFactory.missingValue
else -> v
}
}
when {
// Propagate NULL if any operand is NULL.
values.any { it.type == ExprValueType.NULL } -> valueFactory.nullValue
else -> {
(0..(values.size - 2)).forEach { i ->
if (!op(values[i], values[i + 1]))
return@thunkBlock valueFactory.newBoolean(false)
}
return@thunkBlock valueFactory.newBoolean(true)
}
}
}
}
/**
* Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.PERMISSIVE] behaviour.
*
* - Exceptions thrown by [block] that are [EvaluationException] are caught and [MissingExprValue] is returned.
* - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown
* with the original exception as the cause.
*/
override fun handleException(
sourceLocation: SourceLocationMeta?,
block: () -> ExprValue
): ExprValue =
try {
block()
} catch (e: EvaluationException) {
thunkOptions.handleExceptionForPermissiveMode(e, sourceLocation)
when (e.errorCode.errorBehaviorInPermissiveMode) {
// Rethrows the exception as it does in LEGACY mode.
ErrorBehaviorInPermissiveMode.THROW_EXCEPTION -> throw populateErrorContext(e, sourceLocation)
ErrorBehaviorInPermissiveMode.RETURN_MISSING -> valueFactory.missingValue
}
} catch (e: Exception) {
thunkOptions.handleExceptionForLegacyMode(e, sourceLocation)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy