Skip to content

source_block_macro.kt

src/jvmMain/kotlin/afield/runtime/macros/source_block_macro.kt · 70 lines

package afield.runtime.macros

import afield.document.graph.Box

fun Box.macro_sourceBlock(args: List<String>): Insert

body — 65 lines, from line 5
fun Box.macro_sourceBlock(args: List<String>): Insert {
    val raw = args.joinToString("")
    val body = extractSourceBlockBody(raw)
    val sourceLiteral = kotlinStringLiteral(body)
    val boxLiteral = kotlinStringLiteral(id)
    return Insert(
        here = "afield.study.source.SourceBackedBlock(boxId = $boxLiteral, source = $sourceLiteral) {\n$body\n}"
    )
}

private fun extractSourceBlockBody(rawArgs: String): String {
    val trimmed = rawArgs.trim()
    require(trimmed.isNotEmpty()) { "sourceBlock expects a trailing lambda, e.g. sourceBlock { value }" }

    val unwrapped = stripOuterParens(trimmed).trim()
    val open = unwrapped.indexOf('{')
    val close = unwrapped.lastIndexOf('}')
    require(open >= 0 && close > open) { "sourceBlock expects a lambda body, e.g. sourceBlock { value }" }

    return unwrapped.substring(open + 1, close).trim()
}

private fun stripOuterParens(text: String): String {
    if (!text.startsWith("(") || !text.endsWith(")")) return text

    var depth = 0
    var inString = false
    var escaped = false
    for (i in text.indices) {
        val c = text[i]
        if (inString) {
            if (escaped) escaped = false
            else if (c == '\\') escaped = true
            else if (c == '"') inString = false
            continue
        }

        when (c) {
            '"' -> inString = true
            '(' -> depth++
            ')' -> {
                depth--
                if (depth == 0 && i != text.lastIndex) return text
            }
        }
    }

    return if (depth == 0) text.substring(1, text.lastIndex) else text
}

private fun kotlinStringLiteral(text: String): String = buildString(text.length + 16) {
    append('"')
    text.forEach { c ->
        when (c) {
            '\\' -> append("\\\\")
            '"' -> append("\\\"")
            '\n' -> append("\\n")
            '\r' -> append("\\r")
            '\t' -> append("\\t")
            '$' -> append("\\$")
            else -> append(c)
        }
    }
    append('"')
}