kotlinpoet icon indicating copy to clipboard operation
kotlinpoet copied to clipboard

DSL for Kotlin generation

Open Egorand opened this issue 8 years ago • 12 comments

In addition to the APIs, it would be great to have a fluent DSL for defining Kotlin files.

Egorand avatar May 16 '17 17:05 Egorand

I had written a DSL for javapoet, it was nice until it wasn't. I'm not too much a fan of it.

burntcookie90 avatar May 16 '17 18:05 burntcookie90

I think we can do a few niceties like #39 and then maybe .apply and .let will get us 90% of what a DSL would without the overhead trying to project the API into it.

JakeWharton avatar May 16 '17 18:05 JakeWharton

a fluent DSL for defining Kotlin files

I chuckled a little at this since I imagine most would just end up re-inventing Kotlin.

EddieRingle avatar May 16 '17 19:05 EddieRingle

90% of what a DSL would

Minus the readability

By overloading the methods in the builder, the example from the Readme could look like this :

val greeterClass = ClassName.get("", "Greeter")
val kotlinFile = KotlinFile.builder("", "HelloWorld") {
    addType("Greeter") {
        primaryConstructor() {
            addParameter("name", String::class)
        }
        addProperty("name", String::class) {
            initializer("name")
        }
        addFun("greet") {
            addStatement("println(%S)", "Hello, \$name")
        }
    }
    addFun("main") {
        addParameter("args", TypeName.get(Array<String>::class))
        addStatement("%T(args[0]).greet()", greeterClass)
    }
}

kotlinFile.writeTo(System.out)

which, i think, is much less "dense"

Of course, it would require to overloading the methods to match the builder of the parameter with something like this (in KotlinFile.kt):

fun addType(name: String, block: TypeSpec.Builder.() -> Unit) = addType(
        TypeSpec.classBuilder(name).apply(block).build())

max4t avatar Jun 02 '17 22:06 max4t

Maybe you can add the function

infix fun String.of(clazz: KClazz) = pairOf(this, clazz)

and make addFun accept a vararg of these pairs, then you can call the method like:

AddFun("main", "args" of Array<String>::class){ //code }

Almost like I did here: https://github.com/tieskedh/KotlinPoetDSL

But I made it more advanced, too also include

AddFun("main", "args" varArg String::class){ //code }

tieskedh avatar Jun 19 '17 07:06 tieskedh

I have started implementing a dsl wrapper for poet in my project. If I get it working, I will open a PR to contribute it upstream here.

wakingrufus avatar May 04 '18 13:05 wakingrufus

Yet another comment from #435, answering also to @breandan request ;) I was doing Kotlin code generation with DSL some time ago. The idea was to make the DSL code look as close as possible to the Kotlin code it generates. That makes some sense and simplifies the way one generates the code. Of course, the syntax of the language does not allow the idea to be implemented fully. The DSL should use callable references to extract types and names from all references one uses in generated code.

I do not have full examples of that, the only thing I blogged was about Gradle. You may see how the idea is implemented there https://jonnyzzz.com/blog/2017/11/02/gradle-dsl/. Few places of an incomplete experiment are also on my GitHub at https://github.com/jonnyzzz/kotlin.generator/

jonnyzzz avatar Aug 06 '18 08:08 jonnyzzz

I just added a pull request #496, where extension functions for most builders are provided. It follows @tieskedh 's approach:

fun addType(name: String, block: TypeSpec.Builder.() -> Unit) = addType( TypeSpec.classBuilder(name).apply(block).build())

I used these functions for my own project and it worked very well. One great advantage is that you'll get less indentation in comparison with a builder chain, which saves you a lot horizontal space. Also, I find the code much more natural.

friedrich-goetze avatar Oct 15 '18 18:10 friedrich-goetze

I think you mean @max4t. My DSL - I need to continue to work on it... - is way more advanced at the moment. Also, it uses custom builders, as my wrapper uses interfaces for adding functions to objects and this repo does not provide these interfaces.

Ps. If anyone can help with the licenses, I would be very happy, as I don't know almost nothing about them...

tieskedh avatar Oct 15 '18 18:10 tieskedh

I added a fourth approach for using DSL-marker. I created a PR to @friedrich-goetze . Having said that, I think annotating the builder-classes is better because:

  • It's cleaner: you have to add the annotation only a couple of times.
  • It can break code of users, when they don't use the code well. (breaking code -> instable -> before official release?)
  • If users use their own extension-lambda's, they are immediately safer. (If they don't use wrappers of course)

tieskedh avatar Oct 31 '18 12:10 tieskedh

If anyone is still interested, I've created a project that contains a bunch of dsl extension functions in the way descrived earlier, for kotlin poet: https://github.com/enjoydambience/kotlinbard

It uses reflection+codegen to covers every builder function. (Yes, it uses itself to generate itself!)

GlassBricks avatar Jul 29 '20 23:07 GlassBricks

Hello, a very interesting thing has just been introduced to the DSL support library I wrote for kotlinpoet. Generate DSL source code directly through Kotlin PSI Elements via reading kotlinpoet's source code. Currently, I have only implemented part of it for experimentation. https://github.com/Omico/Elucidator/commit/042ca38c6c7de665cb202f001ac11b21b34de04a

Omico avatar Jun 12 '23 07:06 Omico

We've implemented a generator for a Kotlinpoet DSL here and would love to see your feedback on it, it is currently published here

DRSchlaubi avatar Dec 15 '23 19:12 DRSchlaubi