Skip to content

Commit

Permalink
Immediately compute parameter arguments when declared
Browse files Browse the repository at this point in the history
This will make it possible to avoid re-calculating independent parameters, enable captured parameters be used only after an iteration completes, and iterate arguments closer to their declaration. See issue #11 for context.

The code was minimally modified to change the behavior, and tests were changed to account for arguments iteration in declaration order instead of usage order (since that's now the order arguments are calculated and depend on each other).
  • Loading branch information
BenWoodworth committed Nov 20, 2023
1 parent 70788a1 commit 1d8cc7f
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 162 deletions.
33 changes: 20 additions & 13 deletions src/commonMain/kotlin/ParameterState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,14 @@ internal class ParameterState<@Suppress("unused") out T> internal constructor()


/**
* Returns a string representation of the current argument, or a "not initialized" message.
*
* Useful while debugging, e.g. inline hints that show property values:
* ```
* val letter by parameter { 'a'..'z' } //letter$delegate: b
* ```
* Returns a string representation of the current argument, or a "not declared" message.
*/
override fun toString(): String =
argument.toString()
if (property == null) {
"Parameter not declared yet."
} else {
argument.toString()
}

/**
* Set up the delegate for a parameter [property] with the given [arguments].
Expand All @@ -95,11 +94,13 @@ internal class ParameterState<@Suppress("unused") out T> internal constructor()
* The new [arguments] will be ignored in favor of re-using the existing arguments, under the assumption that they're equal.
*
* @throws ParameterizeException if already declared for a different [property].
* @throws ParameterizeContinue if [arguments] is empty.
*/
internal fun <T> declare(property: KProperty<T>, arguments: Iterable<T>) {
val declaredProperty = this.property

if (declaredProperty == null) {
initialize(arguments) // Before any state gets changed, in case arguments is empty
this.property = property
this.arguments = arguments
} else if (!property.equalsProperty(declaredProperty)) {
Expand All @@ -109,6 +110,8 @@ internal class ParameterState<@Suppress("unused") out T> internal constructor()

/**
* Initialize and return the argument.
*
* @throws ParameterizeContinue if [arguments] is empty.
*/
private fun <T> initialize(arguments: Iterable<T>): T {
val iterator = arguments.iterator()
Expand Down Expand Up @@ -184,14 +187,18 @@ internal class ParameterState<@Suppress("unused") out T> internal constructor()
}

/**
* Returns the property and argument if initialized, or `null` otherwise.
* Returns the property and argument.
*
* @throws IllegalStateException if this parameter is not declared.
*/
internal fun getFailureArgumentOrNull(): ParameterizeFailure.Argument<*>? {
val argument = argument
if (argument === Uninitialized) return null

internal fun getFailureArgument(): ParameterizeFailure.Argument<*> {
val property = checkNotNull(property) {
"Parameter argument is initialized, but ${::property.name} is null"
"Cannot get failure argument before parameter has been declared"
}

val argument = argument
check (argument !== Uninitialized) {
"Parameter delegate is declared with ${property.name}, but ${::argument.name} is uninitialized"
}

return ParameterizeFailure.Argument(property, argument)
Expand Down
8 changes: 8 additions & 0 deletions src/commonMain/kotlin/Parameterize.kt
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ public class ParameterizeScope internal constructor(
public class ParameterDelegate<out T> internal constructor(
internal val parameterState: ParameterState<T>
) {
/**
* Returns a string representation of the current argument.
*
* Useful while debugging, e.g. inline hints that show property values:
* ```
* val letter by parameter { 'a'..'z' } //letter$delegate: b
* ```
*/
override fun toString(): String =
parameterState.toString()
}
Expand Down
38 changes: 14 additions & 24 deletions src/commonMain/kotlin/ParameterizeState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class ParameterizeState {
if (it != null) throw ParameterizeException("Nesting parameters is not currently supported: `${property.name}` was declared within `${it.name}`'s arguments")
}

val parameterIndex = parameterCount++
val parameterIndex = parameterCount

val parameter = if (parameterIndex in parameters.indices) {
parameters[parameterIndex]
Expand All @@ -54,7 +54,10 @@ internal class ParameterizeState {
.also { parameters += it }
}

parameter.parameterState.declare(property, arguments)
property.trackNestedUsage {
parameter.parameterState.declare(property, arguments)
parameterCount++ // After declaring, since the parameter shouldn't count if declare throws
}

return parameter
}
Expand All @@ -73,10 +76,7 @@ internal class ParameterizeState {
fun <T> getParameterArgument(parameter: ParameterDelegate<*>, property: KProperty<T>): T {
val isFirstUse = !parameter.parameterState.hasBeenUsed

return property
.trackNestedUsage {
parameter.parameterState.getArgument(property)
}
return parameter.parameterState.getArgument(property)
.also {
if (isFirstUse) trackUsedParameter(parameter)
}
Expand All @@ -91,40 +91,30 @@ internal class ParameterizeState {
}

/**
* Iterate the last parameter (by the order they're first used) that has a
* next argument, and reset all parameters that were first used after it
* (since they may depend on the now changed value, and may be computed
* differently).
* Iterate the last parameter that has a next argument (in order of when their arguments were calculated), and reset
* all parameters that were first used after it (since they may depend on the now changed value, and may be computed
* differently now that a previous argument changed).
*
* Returns `true` if the arguments are at a new permutation.
*/
private fun nextArgumentPermutationOrFalse(): Boolean {
var iterated = false

val usedParameterIterator = parametersUsed
.listIterator(parametersUsed.lastIndex + 1)
val declaredParameterIterator = parameters
.listIterator(parameterCount)

while (usedParameterIterator.hasPrevious()) {
val parameter = usedParameterIterator.previous()
while (declaredParameterIterator.hasPrevious()) {
val parameter = declaredParameterIterator.previous()

if (!parameter.parameterState.isLastArgument) {
parameter.parameterState.nextArgument()
iterated = true
break
}

usedParameterIterator.remove()
parameter.parameterState.reset()
}

for (i in parameterCountAfterAllUsed..<parameterCount) {
val parameter = parameters[i]

if (!parameter.parameterState.hasBeenUsed) {
parameter.parameterState.reset()
}
}

parameterCount = 0
parameterCountAfterAllUsed = 0

Expand All @@ -137,7 +127,7 @@ internal class ParameterizeState {
fun getFailureArguments(): List<ParameterizeFailure.Argument<*>> =
parameters.take(parameterCount)
.filter { it.parameterState.hasBeenUsed }
.mapNotNull { it.parameterState.getFailureArgumentOrNull() }
.map { it.parameterState.getFailureArgument() }

fun handleFailure(onFailure: OnFailureScope.(Throwable) -> Unit, failure: Throwable) {
failureCount++
Expand Down
Loading

0 comments on commit 1d8cc7f

Please sign in to comment.