diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt index ebcda00076..3f937e68b1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt @@ -87,6 +87,7 @@ interface CgElement { is CgNotNullAssertion -> visit(element) is CgVariable -> visit(element) is CgParameterDeclaration -> visit(element) + is CgFormattedString -> visit(element) is CgLiteral -> visit(element) is CgNonStaticRunnable -> visit(element) is CgStaticRunnable -> visit(element) @@ -823,6 +824,11 @@ class CgArrayInitializer(val arrayType: ClassId, val elementType: ClassId, val v class CgSpread(override val type: ClassId, val array: CgExpression) : CgExpression +// Interpolated string +// e.g. String.format() for Java, "${}" for Kotlin + +class CgFormattedString(val array: List) : CgElement + // Enum constant data class CgEnumConstantAccess( diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt index eaaa186bb3..561a619f23 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt @@ -595,7 +595,11 @@ abstract class CgAbstractRenderer( // Primitive and String literals override fun visit(element: CgLiteral) { - val value = with(element.value) { + print(element.toStringConstant()) + } + + protected fun CgLiteral.toStringConstant(asRawString: Boolean = false) = + with(this.value) { when (this) { is Byte -> toStringConstant() is Char -> toStringConstant() @@ -605,12 +609,11 @@ abstract class CgAbstractRenderer( is Float -> toStringConstant() is Double -> toStringConstant() is Boolean -> toStringConstant() - is String -> toStringConstant() + // String is "\"" + "str" + "\"", RawString is "str" + is String -> if (asRawString) "$this".escapeCharacters() else toStringConstant() else -> "$this" } } - print(value) - } // Non-static runnable like this::toString or (new Object())::toString etc override fun visit(element: CgNonStaticRunnable) { @@ -924,7 +927,7 @@ abstract class CgAbstractRenderer( private fun Boolean.toStringConstant() = if (this) "true" else "false" - private fun String.toStringConstant(): String = "\"" + escapeCharacters() + "\"" + protected fun String.toStringConstant(): String = "\"" + escapeCharacters() + "\"" protected abstract fun String.escapeCharacters(): String diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt index 80a6630243..f6b4ccdd02 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt @@ -32,6 +32,8 @@ import org.utbot.framework.codegen.domain.models.CgSwitchCase import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel import org.utbot.framework.codegen.domain.models.CgClass import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgTestMethod import org.utbot.framework.codegen.domain.models.CgTypeCast import org.utbot.framework.codegen.domain.models.CgVariable @@ -320,6 +322,27 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C println("}") } + override fun visit(element: CgFormattedString) { + val nonLiteralElements = element.array.filterNot { it is CgLiteral } + + print("String.format(") + val constructedMsg = buildString { + element.array.forEachIndexed { index, cgElement -> + if (cgElement is CgLiteral) append( + cgElement.toStringConstant(asRawString = true) + ) else append("%s") + if (index < element.array.lastIndex) append(" ") + } + } + + print(constructedMsg.toStringConstant()) + + // Comma to separate msg from variables + if (nonLiteralElements.isNotEmpty()) print(", ") + nonLiteralElements.renderSeparated(newLines = false) + print(")") + } + override fun toStringConstantImpl(byte: Byte): String = "(byte) $byte" override fun toStringConstantImpl(short: Short): String = "(short) $short" diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt index 70531d610e..98a7f32ee0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt @@ -38,8 +38,11 @@ import org.utbot.framework.codegen.domain.models.CgSwitchCase import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel import org.utbot.framework.codegen.domain.models.CgClass import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgTestMethod import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgValue import org.utbot.framework.codegen.domain.models.CgVariable import org.utbot.framework.codegen.util.nullLiteral import org.utbot.framework.plugin.api.BuiltinClassId @@ -480,6 +483,29 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = println("}") } + override fun visit(element: CgFormattedString) { + print("\"") + element.array.forEachIndexed { index, cgElement -> + if (cgElement is CgLiteral) + print(cgElement.toStringConstant(asRawString = true)) + else { + print("$") + when (cgElement) { + // It is not necessary to wrap variables with curly brackets + is CgVariable -> cgElement.accept(this) + else -> { + print("{") + cgElement.accept(this) + print("}") + } + } + } + + if (index < element.array.lastIndex) print(" ") + } + print("\"") + } + override fun toStringConstantImpl(byte: Byte): String = buildString { if (byte < 0) { append("(") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt index de08aebf31..40e80e565c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt @@ -76,6 +76,7 @@ import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel import org.utbot.framework.codegen.domain.models.CgClass import org.utbot.framework.codegen.domain.models.CgClassBody import org.utbot.framework.codegen.domain.models.CgDocRegularLineStmt +import org.utbot.framework.codegen.domain.models.CgFormattedString import org.utbot.framework.codegen.domain.models.CgNestedClassesRegion import org.utbot.framework.codegen.domain.models.CgTestMethod import org.utbot.framework.codegen.domain.models.CgTestMethodCluster @@ -223,6 +224,9 @@ interface CgVisitor { // Spread operator fun visit(element: CgSpread): R + // Formatted string + fun visit(element: CgFormattedString): R + // Enum constant fun visit(element: CgEnumConstantAccess): R diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt index f4ae3f0b7c..7063f54b43 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -21,6 +21,7 @@ import org.utbot.framework.codegen.domain.models.CgExecutableCall import org.utbot.framework.codegen.domain.models.CgExpression import org.utbot.framework.codegen.domain.models.CgFieldAccess import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgFormattedString import org.utbot.framework.codegen.domain.models.CgGetJavaClass import org.utbot.framework.codegen.domain.models.CgGetKotlinClass import org.utbot.framework.codegen.domain.models.CgGetLength @@ -340,6 +341,10 @@ internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgP } } + override fun visit(element: CgFormattedString) { + throw NotImplementedError("String interpolation is not supported in JavaScript renderer") + } + //TODO MINOR: check override fun renderForLoopVarControl(element: CgForLoop) { print("for (") diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index 5bd1141d11..7dc3fed93a 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -28,6 +28,7 @@ import org.utbot.framework.codegen.domain.models.CgExecutableCall import org.utbot.framework.codegen.domain.models.CgExpression import org.utbot.framework.codegen.domain.models.CgForEachLoop import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgFormattedString import org.utbot.framework.codegen.domain.models.CgGetJavaClass import org.utbot.framework.codegen.domain.models.CgGetKotlinClass import org.utbot.framework.codegen.domain.models.CgGetLength @@ -530,6 +531,10 @@ internal class CgPythonRenderer( print(element.value.toString()) } + override fun visit(element: CgFormattedString) { + throw NotImplementedError("String interpolation is not supported in Python renderer") + } + override fun String.escapeCharacters(): String = StringEscapeUtils .escapeJava(this)