Chassis DSL

Specifying what to generate

The Chassis DSL for generating Code

One of Kotlin’s nice language features that makes it ideal for creating pure Kotlin DSLs (Domain Specific Languages) are trailing lambda parameters, which allow to pass the lambda implementation outside the brackets when calling a function whose last parameter is a lambda. For a better hands-on explanation see e.g. Baeldung Kotlin DSL.

To be crystal clear here: the Chassis DSL is 100% pure Kotlin source-code

Chassis DSL elaborates on this and adds a few nice features to make it more usable and flexible.

  • using Kotlin’s’ context() feature for dsl related objects and functions (have a look at e.g. youtube)
  • especially we use the context to “pass” the class DslCtx through all of our DSL parsing. (DslCtx is gathering all information the Chassis DSL contains)
  • Multiple parsing PASSes of our DSL (see class DslCtx)
    • multiple parses are necessary as we want to “use” parsed information of other models for the current model, but the other model might not have been parsed yet at all.
  • via class DslRef (see class DslRef) we’re able to “reference” any other defined (sub)level element
    • for mor in detail on DslRef see (TODO TBD)
    • this enables us to do a lot of things
      • specifying other models as to be extended super-classes/interface
      • gather properties of some other model
      • constrain Fillers or CRUD operations on other models or model-properties
      • etc.

One drawback of the DSL being pure kotlin sourcecode is security, as the DSL may contain arbitrary harmful code also.
But as Chassis and its DSL solely is used at development time (and CICD) this disadvantage is no issue for our use-case: generating code.

At the moment there is implemented only one toplevel DSL method:

continue with Chassis top-level modelgroup

DslCtx PASS’es

Here’s an example of a DSL function implementation, defining the DSL showcase("someName" { ... showcase Sub(DSL) } sub-DSL (see class DslShowcase)youtube)

As you can see any DSL function implementation should decide what to do in which DSL PASS (be careful though to still decent the DSL tree as inner sub-DSL might also want to do something in that PASS)

context(DslCtxWrapper)
class DslShowcaseDelegateImpl(simpleNameOfDelegator: String, delegatorRef: IDslRef)
  : ADslDelegateClass(simpleNameOfDelegator, delegatorRef), IDslImplShowcaseDelegate
{
    ...
  
    override fun showcase(simpleName: String, block: IDslApiShowcaseBlock.() -> Unit) {
        log.info("fun {}(\"{}\") { ... } in PASS {}", object{}.javaClass.enclosingMethod.name, simpleName, dslCtx.currentPASS)
        when (dslCtx.currentPASS) {
            dslCtx.PASS_ERROR -> TODO()
            dslCtx.PASS_FINISH -> { /* TODO implement me! */ }
            dslCtx.PASS_1_BASEMODELS -> {
                val dslImpl = theShowcaseBlocks.getOrPut(simpleName) { DslShowcaseBlockImpl(simpleName, selfDslRef) }
                dslImpl.apply(block)
            }
            else -> {}
        }
    }

back to root