From 0c6ed4a88332e52664839d44be4d2368d49b41d8 Mon Sep 17 00:00:00 2001 From: Giorgio Garofalo Date: Mon, 24 Jun 2024 15:38:19 +0200 Subject: [PATCH] Fix value + iterable append --- .../visitor/AppendExpressionVisitor.kt | 30 +++++++++++++------ .../value/output/NodeOutputValueVisitor.kt | 17 +++++++---- .../iamgio/quarkdown/test/FullPipelineTest.kt | 11 +++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/function/expression/visitor/AppendExpressionVisitor.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/function/expression/visitor/AppendExpressionVisitor.kt index a0fc2548..0abf45ca 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/function/expression/visitor/AppendExpressionVisitor.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/function/expression/visitor/AppendExpressionVisitor.kt @@ -13,6 +13,8 @@ import eu.iamgio.quarkdown.function.value.DynamicValue import eu.iamgio.quarkdown.function.value.EnumValue import eu.iamgio.quarkdown.function.value.GeneralCollectionValue import eu.iamgio.quarkdown.function.value.InlineMarkdownContentValue +import eu.iamgio.quarkdown.function.value.InputValue +import eu.iamgio.quarkdown.function.value.IterableValue import eu.iamgio.quarkdown.function.value.LambdaValue import eu.iamgio.quarkdown.function.value.MarkdownContentValue import eu.iamgio.quarkdown.function.value.NumberValue @@ -46,22 +48,32 @@ class AppendExpressionVisitor(private val other: Expression) : ExpressionVisitor /** * @return string result of the concatenation between [this] and [other] */ - private fun Value<*>.concatenate(): String { + private fun Value<*>.concatenate(): InputValue<*> { + val otherEval = other.eval() // Evaluate the next expression. + + // If the other value is a collection, add the current value to it as the first element. + if (otherEval is IterableValue<*> && this is OutputValue<*>) { + return GeneralCollectionValue(listOf(this, *otherEval.unwrappedValue.toList().toTypedArray())) + } + + // Concatenate the string representation of the two values. + fun stringify(value: Value<*>) = when (value) { is VoidValue -> "" else -> value.unwrappedValue.toString() } - return stringify(this) + stringify(other.eval()) + + return StringValue(stringify(this) + stringify(otherEval)) } // "abc" "def" -> "abcdef" // "abc" .sum {2} {3} -> "abc5" - override fun visit(value: StringValue) = StringValue(value.concatenate()) + override fun visit(value: StringValue) = value.concatenate() // 15 "abc" -> "15abc" // 15 8 -> "158" - override fun visit(value: NumberValue) = StringValue(value.concatenate()) + override fun visit(value: NumberValue) = value.concatenate() // true false -> false // false true -> false @@ -71,7 +83,7 @@ class AppendExpressionVisitor(private val other: Expression) : ExpressionVisitor when (other) { // Logic AND between values. is BooleanValue -> BooleanValue(value.unwrappedValue && other.unwrappedValue) - else -> StringValue(value.concatenate()) + else -> value.concatenate() } // [a, b, c] "abc" -> [a, b, c, "abc"] @@ -95,10 +107,10 @@ class AppendExpressionVisitor(private val other: Expression) : ExpressionVisitor // CENTER "abc" -> "CENTERabc" // CENTER CENTER -> "CENTERCENTER" // CENTER 15 -> "CENTER15" - override fun visit(value: EnumValue) = StringValue(value.concatenate()) + override fun visit(value: EnumValue) = value.concatenate() // obj "abc" -> "objabc" - override fun visit(value: ObjectValue<*>) = StringValue(value.concatenate()) + override fun visit(value: ObjectValue<*>) = value.concatenate() private fun concatenateAsNodes(root: Node): List { val nodes = mutableListOf(root) @@ -127,7 +139,7 @@ class AppendExpressionVisitor(private val other: Expression) : ExpressionVisitor } // Like visit(StringValue) - override fun visit(value: DynamicValue): Expression = DynamicValue(value.concatenate()) + override fun visit(value: DynamicValue): Expression = DynamicValue(value.concatenate().unwrappedValue) // TODO append lambda override fun visit(value: LambdaValue): Expression = throw UnsupportedOperationException() @@ -136,7 +148,7 @@ class AppendExpressionVisitor(private val other: Expression) : ExpressionVisitor override fun visit(expression: FunctionCall<*>): Expression = when (val result = expression.eval()) { is Expression -> result.append(other) - else -> StringValue(result.concatenate()) + else -> result.concatenate() } /** diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/output/NodeOutputValueVisitor.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/output/NodeOutputValueVisitor.kt index 06004a54..0282e74c 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/output/NodeOutputValueVisitor.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/output/NodeOutputValueVisitor.kt @@ -18,6 +18,7 @@ import eu.iamgio.quarkdown.function.value.NodeValue import eu.iamgio.quarkdown.function.value.NumberValue import eu.iamgio.quarkdown.function.value.ObjectValue import eu.iamgio.quarkdown.function.value.OrderedCollectionValue +import eu.iamgio.quarkdown.function.value.OutputValue import eu.iamgio.quarkdown.function.value.StringValue import eu.iamgio.quarkdown.function.value.UnorderedCollectionValue import eu.iamgio.quarkdown.function.value.ValueFactory @@ -66,10 +67,16 @@ class NodeOutputValueVisitor(private val context: Context) : OutputValueVisitor< override fun visit(value: VoidValue) = BlockText() - // Dynamic output (e.g. produced by the stdlib function `.function`) is treated as Markdown by default. - override fun visit(value: DynamicValue) = - when (val raw = value.unwrappedValue) { - is Node -> raw - else -> this.visit(ValueFactory.blockMarkdown(raw.toString(), context).asNodeValue()) + // Dynamic output (e.g. produced by the stdlib function `.function`) is treated: + // - If it is a suitable output value: its content is visited again with this visitor. + // - If it is a collection: its items are wrapped in a GeneralCollectionValue and visited. + // - Otherwise: its string content is parsed as Markdown. + @Suppress("UNCHECKED_CAST") + override fun visit(value: DynamicValue): Node { + return when (value.unwrappedValue) { + is OutputValue<*> -> value.unwrappedValue.accept(this) + is Iterable<*> -> GeneralCollectionValue(value.unwrappedValue as Iterable>).accept(this) + else -> this.visit(ValueFactory.blockMarkdown(value.unwrappedValue.toString(), context).asNodeValue()) } + } } diff --git a/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt b/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt index 2e1fa971..edfc8d10 100644 --- a/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt +++ b/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt @@ -192,6 +192,17 @@ class FullPipelineTest { assertEquals("

Title

Hi 0

Hi 1

", it) } + execute( + """ + .foreach {..2} + # Hello + .foreach {..1} + **Hi**! + """.trimIndent(), + ) { + assertEquals("

Hello

Hi!

Hello

Hi!

", it) + } + execute( """ .foreach {..2}