Skip to content

Consider adding in-place formatted code blocks #877

Closed

Description

The issue I have is positional arguments are sometimes hard to understand and maintain.
On the other hand, Kotlin has string templates, so why don't we use it for templating purposes?

For instance:

val hello = """Hello, "world", \n test"""
val j = FieldSpec.builder(Int::class.typeName, "j").build()

val codeBlock = codeBlock { "println(${hello.S} + ${j.N})" }

println(codeBlock.toString())

==>

println("Hello, \"world\", \\n test" + j)

One of my use cases:

codeBlock {
    """
    ${config.tokenTypeName.T} oldToken = ${token.N};
    ${config.tokenTypeName.T} nextToken = oldToken.next;
    if (nextToken == null) {
      nextToken = ${token_source.N}.${lexer.generateMethodName.N}(kind);
      ${jj_tokenCount.N}++;
      if (${tokenOutput.N} != null) {
          ${tokenOutput.N}.accept(nextToken);
      }
    }
    ${token.N} = nextToken;
    oldToken.next = null; // Stop nepotism
    return nextToken;

    """.trimIndent()
}

Here's my implementation (it is for JavaPoet's API, however, it should be more or less the same for KotlinPoet):

typealias StringTemplateMethod = StringTemplate.() -> String

class StringTemplate {
    val args = mutableListOf<Any?>()

    private fun arg(mask: String, v: Any?) = args.run {
        add(v)
        "$$size$mask"
    }

    val TypeName.T: String get() = arg("T", this)
    val FieldSpec.T: String get() = arg("T", this.type)
    val MethodSpec.T: String get() = arg("T", this.returnType)

    val FieldSpec.N: String get() = arg("N", this)
    val MethodSpec.N: String get() = arg("N", this)
    val TypeSpec.N: String get() = arg("N", this)

    val String.N: String get() = arg("N", this)

    val Any?.L: String get() = arg("L", this)
    val Any?.S: String get() = arg("S", this)
}

inline fun CodeBlock.Builder.add(template: StringTemplate.() -> String) = run {
    val t = StringTemplate()
    val format = t.template()
    add(format, *t.args.toTypedArray())!!
}

inline fun codeBlock(template: StringTemplate.() -> String) =
    CodeBlock.builder().add(template).build()!!

It might be convenient to have methods like beginControlFlow(template: StringTemplateMethod)

Basically every time there's (String, vararg args) method, it might be better written as (StringTemplateMethod)

For instance, the following sample from readme

.addStatement("if (ingredient is %T) taco %M ingredient", meat, minusAssign)

becomes

.addStatement { "if (ingredient is ${meat.T}) taco ${minusAssign.M} ingredient" }

See square/javapoet#761 (comment)
See tieskedh/KotlinPoetDSL#55

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions