kotlinadvanced

Type-Safe DSL Builder

Build domain-specific languages with Kotlin: receiver lambdas, @DslMarker, and nested builders.

kotlin
@DslMarker
annotation class HtmlDsl

// HTML DSL
@HtmlDsl
class HTML {
    private val children = mutableListOf<String>()

    fun head(block: Head.() -> Unit) {
        val head = Head().apply(block)
        children.add("<head>${head.render()}</head>")
    }

    fun body(block: Body.() -> Unit) {
        val body = Body().apply(block)
        children.add("<body>${body.render()}</body>")
    }

    fun render() = "<html>${children.joinToString("\n")}</html>"
}

@HtmlDsl
class Head {
    private val elements = mutableListOf<String>()
    fun title(text: String) { elements.add("<title>$text</title>") }
    fun meta(name: String, content: String) {
        elements.add("""<meta name="$name" content="$content">""")
    }
    fun render() = elements.joinToString("\n")
}

@HtmlDsl
class Body {
    private val elements = mutableListOf<String>()

    fun h1(text: String) { elements.add("<h1>$text</h1>") }
    fun p(text: String) { elements.add("<p>$text</p>") }
    fun div(cssClass: String = "", block: Body.() -> Unit) {
        val inner = Body().apply(block)
        val cls = if (cssClass.isNotEmpty()) """ class="$cssClass""" else ""
        elements.add("<div$cls>${inner.render()}</div>")
    }
    fun ul(block: UL.() -> Unit) {
        val ul = UL().apply(block)
        elements.add("<ul>${ul.render()}</ul>")
    }
    fun render() = elements.joinToString("\n")
}

@HtmlDsl
class UL {
    private val items = mutableListOf<String>()
    fun li(text: String) { items.add("<li>$text</li>") }
    fun render() = items.joinToString("\n")
}

fun html(block: HTML.() -> Unit): String = HTML().apply(block).render()

// Query DSL
@DslMarker
annotation class QueryDsl

@QueryDsl
class Query {
    var table: String = ""
    private val columns = mutableListOf<String>()
    private val conditions = mutableListOf<String>()
    private var orderBy: String? = null
    private var limit: Int? = null

    fun select(vararg cols: String) { columns.addAll(cols) }
    fun where(condition: String) { conditions.add(condition) }
    fun orderBy(column: String, desc: Boolean = false) {
        orderBy = "$column${if (desc) " DESC" else ""}"
    }
    fun limit(n: Int) { limit = n }

    fun build(): String {
        val cols = if (columns.isEmpty()) "*" else columns.joinToString(", ")
        var sql = "SELECT $cols FROM $table"
        if (conditions.isNotEmpty()) sql += " WHERE ${conditions.joinToString(" AND ")}"
        orderBy?.let { sql += " ORDER BY $it" }
        limit?.let { sql += " LIMIT $it" }
        return sql
    }
}

fun query(block: Query.() -> Unit): String = Query().apply(block).build()

fun main() {
    // HTML DSL
    val page = html {
        head {
            title("My Page")
            meta("description", "A Kotlin DSL example")
        }
        body {
            h1("Welcome")
            div("container") {
                p("This is built with a type-safe DSL")
                ul {
                    li("Item 1")
                    li("Item 2")
                    li("Item 3")
                }
            }
        }
    }
    println(page)

    // Query DSL
    val sql = query {
        table = "users"
        select("name", "email", "age")
        where("age > 18")
        where("active = true")
        orderBy("name")
        limit(10)
    }
    println(sql)
}

Use Cases

  • Configuration DSLs for frameworks
  • Type-safe HTML/SQL builders
  • Domain-specific API abstractions

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.