Skip to content

Commit

Permalink
Implement .let function
Browse files Browse the repository at this point in the history
  • Loading branch information
iamgio committed Jul 28, 2024
1 parent 906f123 commit 6149e42
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ class KFunctionAdapter<T : OutputValue<*>>(private val function: KFunction<T>) :
// Corresponding KParameter.
val param = function.parameters[parameter.index]

param to argument.value.unwrappedValue
// The argument is unwrapped unless the value class specifies not to.
// An example of a disabled unwrapping is DynamicValue, which is used to pass dynamically typed values as-is.
val arg =
argument.value.let {
if (it::class.hasAnnotation<NoAutoArgumentUnwrapping>()) it else it.unwrappedValue
}

param to arg
}

// Call the KFunction.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package eu.iamgio.quarkdown.function.reflect

import eu.iamgio.quarkdown.function.value.Value

/**
* When invoking a function via [KFunctionAdapter], [Value] arguments are automatically unwrapped to their raw value,
* unless this annotation is present on the [Value] subclass.
*/
@Target(AnnotationTarget.CLASS)
annotation class NoAutoArgumentUnwrapping
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package eu.iamgio.quarkdown.function.value

import eu.iamgio.quarkdown.function.expression.visitor.ExpressionVisitor
import eu.iamgio.quarkdown.function.reflect.NoAutoArgumentUnwrapping
import eu.iamgio.quarkdown.function.value.output.OutputValueVisitor

/**
Expand All @@ -13,6 +14,7 @@ import eu.iamgio.quarkdown.function.value.output.OutputValueVisitor
* or simply an opaque wrapper for a generic value (e.g. a `Node`)
* @see eu.iamgio.quarkdown.function.reflect.DynamicValueConverter
*/
@NoAutoArgumentUnwrapping
data class DynamicValue(override val unwrappedValue: Any?) : InputValue<Any?>, OutputValue<Any?> {
override fun <T> accept(visitor: ExpressionVisitor<T>): T = visitor.visit(this)

Expand Down
17 changes: 17 additions & 0 deletions stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Flow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ val Flow: Module =
::forEach,
::function,
::variable,
::let,
::node,
)

Expand Down Expand Up @@ -161,6 +162,22 @@ fun variable(
)
}

/**
* Defines a temporary variable that can be used in the lambda body.
* Example:
* ```
* .let {world}
* Hello, **.1**!
* ```
* @param value value to use as a temporary variable
* @param body content to evaluate with the temporary variable. Accepts 1 parameter ([value] itself)
* @return the evaluation of [body] with [value] as a parameter
*/
fun let(
value: DynamicValue,
body: Lambda,
): OutputValue<*> = body.invokeDynamic(value)

/**
* Creates a null invisible node that forces the expression it lies in to be evaluated as Markdown content.
* This is a workaround that can be used at the beginning of lambda blocks (e.g. in a `.function`, `.if` or `.foreach` call)
Expand Down
44 changes: 44 additions & 0 deletions test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,50 @@ class FullPipelineTest {
) {
assertEquals("<h4>Hello, <em>world</em>!</h4><p>Hello, <em>iamgio</em>!</p>", it)
}

execute(
"""
.let {world}
Hello, **.1**!
""".trimIndent(),
) {
assertEquals("<p>Hello, <strong>world</strong>!</p>", it)
}

execute(
"""
.foreach {..3}
.let {.1}
x:
.sum {3} {.x}
""".trimIndent(),
) {
assertEquals("456", it)
}

execute(
"""
.foreach {..3}
.node
.let {.1}
x:
.sum {3} {.x}
""".trimIndent(),
) {
assertEquals("<p>4</p><p>5</p><p>6</p>", it)
}

execute(
"""
.let {code.txt}
file:
.let {.read {.file} {..2}}
.code
.1
""".trimIndent(),
) {
assertEquals("<pre><code>Line 1\nLine 2</code></pre>", it)
}
}

@Test
Expand Down

0 comments on commit 6149e42

Please sign in to comment.