Skip to content

Add string interpolation support in codegen #1587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<CgExpression>) : CgElement

// Enum constant

data class CgEnumConstantAccess(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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) {
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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("(")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -223,6 +224,9 @@ interface CgVisitor<R> {
// Spread operator
fun visit(element: CgSpread): R

// Formatted string
fun visit(element: CgFormattedString): R

// Enum constant
fun visit(element: CgEnumConstantAccess): R

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down