Skip to content

CompletionCorpus.kt

src/jvmMain/kotlin/afield/runtime/completion/CompletionCorpus.kt · 400 lines · cited by What a document is on disk

package and imports — 45 lines, from line 1
package afield.runtime.completion

import afield.app.diagnostics.named
import afield.app.protocol.Completion
import afield.data.table.div
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.document.workspace.metadata
import afield.runtime.execution.plus
import afield.runtime.kotlin.exported
import afield.runtime.kotlin.importStatementFrom
import afield.runtime.lifecycle.forever
import afield.runtime.lifecycle.loop
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.with
import afield.ui.output.histogram
import afield.ui.output.md
import afield.ui.output.templates.templates

import afield.app.protocol.put
import afield.app.server.documents
import afield.audio.synth.*
import afield.data.table.get
import afield.document.graph.Box
import afield.app.server.SessionInformation
import afield.document.graph.box
import afield.document.graph.doResync
import afield.document.graph.preamble
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.kotlin.ImportResolution
import afield.runtime.kotlin.KotlinInterface2
import afield.runtime.kotlin.isDefaultImportedTypeName
import afield.runtime.kotlin.kotlinImportStatementFor
import afield.runtime.lifecycle.map
import afield.tools.docs.format
import afield.ui.output.templates.filter
import afield.runtime.macros.gatherPreambleUpwards
import afield.graphics.shader.normalizePreambleText
import java.io.File
import java.security.MessageDigest

A reproducible corpus of real boxes for measuring Kotlin completion, harvested with their macros already expanded so each box sees the preamble it has at run time.

CompletionCorpus reads the bundled starting points and templates, expands each box's macros without evaluating its code, and checks the result in as fixtures that the completion probes measure against.

object CompletionCorpus

The completion corpus: real boxes, with the preamble they have AT RUNTIME.

Completion resolves a box against its inherited preamble, and in Field a large part of that preamble is not authored — it is written by macro expansion. prop<Int>(sgL) becomes var Box.sgL by Inheritance<Int>(), moduleHere { } becomes the /*supermap*/ class MappedStructure + fun Mesh.set(…, builder: MappedStructure.() -> Unit) pair, @exported republishes a box's own values. None of that exists in a document as materialised on disk; all of it exists the moment the box runs.

So a corpus read straight off disk measures a context no user ever has. CompletionLoweringProbe's first run said as much: its most-unresolved names were renderer (33), window (28) and the sg* family — every one of them a macro expansion that hadn't happened.

Harvesting fixes that without needing to run anything: harvest expands each box's macros through the engine's own applyMacros(previewOnly = false), which is the same pass a real run makes and which writes the generated segments onto the box, but does not evaluate a line of the box's code. The result is checked in (writeFixtures) so the measurement is reproducible, reviewable as a diff, and independent of macro expansion at measure time — and readFixtures verifies each entry still belongs to the code it was harvested from, so a corpus edit can't silently invalidate the numbers.

What this deliberately does NOT capture: @exported republication, which only happens after real evaluation of a parent box. Those names stay unresolved, and the probe's snippet-region error histogram is where that shows up.

object CompletionCorpus {

const val ROOT_ID = "root"

The id a document's root box has; document.md lists it as a parent without a record of its own.

val defaultDirs: List<String> = listOf("public/docs/assets/agent_transcripts", "templates")

Where the bundled starting points and templates live, relative to the repo root.

const val FIXTURE_DIR = "tools/completion-corpus/boxes"

Checked-in harvest output.

data class BoxRec

One box, as stored in a document.md: its parents and its named text properties. childLanguage is the language this box confers on its children when it is a language root (mainLanguage, or the legacy isPythonRoot / isRemoteJSRoot flags), lowercased; null for an ordinary box.

    data class BoxRec(
        val id: String,
        val parents: List<String>,
        val props: MutableMap<String, String> = mutableMapOf(),
        val childLanguage: String? = null,
    )

data class DocRec(val name: String, val dir: File, val boxes: LinkedHashMap<String, BoxRec>)

data class Case

One corpus entry: a box's code, and the preamble it has once its macros have expanded.

    data class Case(
        val doc: String,
        val box: String,
        val code: String,
        val preamble: String,
        val receivers: List<String> = listOf("afield.document.graph.Box"),
    ) {

val label: String get() = "$doc/$box"

val stem: String get() = "${doc}__${box}".replace(Regex("""[^A-Za-z0-9_.-]"""), "_")

Stable file-name stem for this box's fixture files.

    private val FENCE = Regex("""^```(\w*)\s*\[field-(box|property)\s+([^]]*)]\s*$""")
    private val ATTR = Regex("""(\w+)="([^"]*)"""")
    private val PARENTS = Regex(""""parents"\s*\[([^]]*)]""")
    private val MAIN_LANGUAGE = Regex(""""mainLanguage"\s+"([^"]*)"""")
    private val LEGACY_LANGUAGE_ROOT = Regex(""""(isPythonRoot|isRemoteJSRoot)"\s+true\b""")

Languages whose boxes are still Kotlin source. A Kotlin/JS head completes as Kotlin.

    private val KOTLIN_LANGUAGES = setOf("kotlin", "kotlinjs")

fun sha256(s: String): String

    fun sha256(s: String): String = MessageDigest.getInstance("SHA-256")
        .digest(s.toByteArray()).joinToString("") { "%02x".format(it) }.take(16)

fun parseDocument(file: File): DocRec?

Parse a document.md — the machine-readable mirror Field writes beside metadata.edn. Only what this corpus needs is read: each box's parent list (for gatherPreambleUpwards) and its property texts. Not an EDN parser: the mirror exists so tools don't need one.

    fun parseDocument(file: File): DocRec? {
        val lines = runCatching { file.readLines() }.getOrNull() ?: return null
        val boxes = LinkedHashMap<String, BoxRec>()
        var i = 0
        while (i < lines.size) {
            val m = FENCE.find(lines[i])
            if (m == null) { i++; continue }
            val kind = m.groupValues[2]
            val attrs = ATTR.findAll(m.groupValues[3]).associate { it.groupValues[1] to it.groupValues[2] }
            val body = StringBuilder()
            i++
            while (i < lines.size && !lines[i].startsWith("```")) { body.append(lines[i]).append('\n'); i++ }
            i++ // closing fence
            when (kind) {
                "box" -> {
                    val id = attrs["id"] ?: continue
                    val parents = PARENTS.find(body)?.groupValues?.get(1).orEmpty()
                        .split('"').map { it.trim() }.filter { it.isNotEmpty() && it != "#afield/box" }
                    val childLanguage = MAIN_LANGUAGE.find(body)?.groupValues?.get(1)?.trim()?.lowercase()
                        ?.ifEmpty { null }
                        ?: LEGACY_LANGUAGE_ROOT.find(body)?.groupValues?.get(1)
                            ?.let { if (it == "isPythonRoot") "python" else "remotejs" }
                    boxes[id] = BoxRec(id, parents, childLanguage = childLanguage)
                }
                "property" -> {
                    val id = attrs["box"] ?: continue
                    val name = attrs["name"] ?: continue
                    boxes[id]?.props?.put(name, body.toString().trimEnd('\n'))
                }
            }
        }
        return if (boxes.isEmpty()) null else DocRec(file.parentFile.name, file.parentFile, boxes)
    }

fun documents(dirs: List<String> = defaultDirs): List<DocRec>

Every document under dirs (repo-relative or absolute), sorted, for determinism.

    fun documents(dirs: List<String> = defaultDirs): List<DocRec> =
        dirs.map(::File).filter { it.isDirectory }
            .flatMap { d -> d.listFiles()?.toList().orEmpty() }
            .filter { it.isDirectory }.sortedBy { it.name }
            .mapNotNull { parseDocument(File(it, "document.md")) }

fun boxGraph(doc: DocRec): Map<String, Box>

Reconstruct a document's box graph as detached Boxes carrying its text properties. Enough for gatherPreambleUpwards (which walks parents and reads preamble) and for macro expansion (which reads vertex/fragment/… and writes preamble). Nothing is registered anywhere, so these boxes are garbage once the caller drops them.

document.md names "root" as a parent but carries no record for it, and none of these boxes was in a session, so Box.root (inside.root) threw for any box using rootProp<T>(name), whose Insert targets the root: demo_document/demo_timeline was harvested with no preamble at all. The graph now has the root a live document has, and every box shares one ephemeral session.

    fun boxGraph(doc: DocRec): Map<String, Box> {
        val boxes = doc.boxes.mapValues { (id, _) -> Box(id) }.toMutableMap()
        val root = boxes.getOrPut(ROOT_ID) { Box(ROOT_ID) }
        val session = SessionInformation(filename = "<corpus-${doc.name}>", documentName = doc.name, root = root, ephemeral = true)
        boxes.forEach { (id, box) ->
            box.inside = session
            session.boxes[id] = box
        }
        doc.boxes.forEach { (id, rec) ->
            rec.parents.forEach { p -> boxes[p]?.let { boxes[id]!!.parents.add(it) } }
        }
        doc.boxes.forEach { (id, rec) ->
            val box = boxes[id]!!
            // Text properties only — that is all a `document.md` fence carries, and all macro expansion
            // reads. Typed properties (`frame`, `children`) live in the EDN and matter to neither.
            rec.props.forEach { (name, value) -> box.setPropertyRaw(name, value, doResync = false) }
        }
        return boxes
    }

fun kotlinBoxes(doc: DocRec): List<BoxRec>

Boxes whose code is Kotlin — the only ones this corpus is about.

A box's language is conferred by its parents, the way applyRuntimeToggle does it: a child of any language root runs in that root's language. document.md records the root's mainLanguage, never a code_language property, so a check on code_language alone let 24 Lua, Python, Csound and Pd boxes in as Kotlin. Their import numpy as np became 5 injected-region errors, and cv2.‸cvtColor was scored as a Kotlin package path.

    fun kotlinBoxes(doc: DocRec): List<BoxRec> = doc.boxes.values.filter { rec ->
        (rec.props["code_language"] ?: "kotlin") == "kotlin" && !rec.props["code"].isNullOrBlank() &&
            rec.parents.none { p -> doc.boxes[p]?.childLanguage?.let { it !in KOTLIN_LANGUAGES } == true }
    }

fun harvest

Expand every box's macros and read back its inherited preamble.

applyMacros with previewOnly = false is the engine's own expansion pass: it rewrites the code (return value discarded — the corpus keeps the code the author wrote, which is what an editor holds) and, as a side effect, merges each macro's generated segments into the box's preamble through applyInsertToPreamble. Nothing is evaluated, so this needs no window, no GPU and no runloop, and a box that would loop forever at runtime harvests in milliseconds.

@param onProblem called with (label, throwable) for a box whose expansion throws; harvesting continues, and that box keeps whatever preamble it had on disk.

    fun harvest(
        ki: KotlinInterface2,
        dirs: List<String> = defaultDirs,
        onProblem: (String, Throwable) -> Unit = { _, _ -> },
    ): List<Case> {
        val out = mutableListOf<Case>()
        for (doc in documents(dirs)) {
            val boxes = boxGraph(doc)
            for (rec in kotlinBoxes(doc)) {
                val box = boxes[rec.id]!!
                val code = rec.props["code"]!!
                runCatching { ki.applyMacros(box, code, emptySet(), false) }
                    .onFailure { onProblem("${doc.name}/${rec.id}", it) }
                val gathered = runCatching { normalizePreambleText(gatherPreambleUpwards(box)) }.getOrDefault("")
                out += Case(doc.name, rec.id, code, withImportsForDeclaredTypes(gathered))
            }
        }
        return out
    }

self-consistency

internal fun withImportsForDeclaredTypes(preamble: String): String

Restore an import for any type the preamble declares a property of and does not bring into scope, when this classpath can resolve it unambiguously.

typesDeclaredWithoutAnImport asserts the result should always be empty because the macro writes T's import alongside by Inheritance<T>(). A preamble is persisted per box, though, so it records whatever the macro could resolve on the machine that authored it — and a contested simple name makes importStatementFrom emit nothing at all. That is the Window failure the import helper is written around, and optical_flow_pingpong arrived carrying it for HasImageView: the box code imports afield.graphics.texture.HasImageView on its own line 7, the saved preamble declares Inheritance<HasImageView> and imports nothing, and the fixture it harvests into cannot compile as written.

Resolving again here, at harvest, is not a second opinion — it is the same kotlinImportStatementFor the macro uses, asked on the machine doing the harvesting, which for a hermetic harvest is the clean-checkout answer. Types it cannot resolve are left alone, so a genuinely ambiguous name still reaches the checker and is still reported rather than papered over with a guess.

    internal fun withImportsForDeclaredTypes(preamble: String): String {
        val missing = typesDeclaredWithoutAnImport(preamble)
        if (missing.isEmpty()) return preamble
        val added = missing.mapNotNull { kotlinImportStatementFor(it) }
        if (added.isEmpty()) return preamble
        return (added.sorted() + preamble.lines()).joinToString("\n")
    }

var Box.window by Inheritance<Window>() — the declaration a prop<T>(name) macro writes.

    private val PROP_DECL = Regex("""\bby\s+(?:Inheritance|Onceing)<([A-Za-z_][A-Za-z0-9_.]*)""")

The receiver of a preamble's own extension declaration: Box in var Box.petals by ….

    private val PREAMBLE_RECEIVER = Regex("""\b(?:val|var)\s+([A-Z]\w*)\.\w+\s+by\b""")

fun typesDeclaredWithoutAnImport(preamble: String): List<String>

The types this preamble declares a property of but never brings into scope.

Should always be empty, because the macro that writes by Inheritance<T>() writes T's import in the same afield.runtime.macros.Insert. It is checked anyway because the two halves are separable by hand — twice now a fixture has been "fixed" by deleting the import line and leaving the declaration, which produces a preamble in which T does not resolve, and a completion corpus whose whole purpose is measuring what resolves cannot afford to be measuring that.

A type counts as in scope if it is fully qualified, default-imported, imported explicitly, declared in the preamble itself (the /*supermap*/ and /*graphics-structure-type*/ forms declare classes inline), or lives in a package the preamble star-imports — the last resolved through ImportResolution's class index rather than guessed, so import afield.graphics.vulkan.buffers.* excuses FrameBufferGroup and does not excuse Window.

    fun typesDeclaredWithoutAnImport(preamble: String): List<String> {
        val importLines = preamble.lines().map { it.trim() }.filter { it.startsWith("import ") }
            .map { it.removePrefix("import ").trim() }
        val explicit = importLines.filterNot { it.endsWith(".*") }.map { it.substringAfterLast('.') }.toSet()
        val wildcards = importLines.filter { it.endsWith(".*") }.map { it.removeSuffix(".*") }.toSet()
        // `var Box.petals`, `val Once.petals`: every prop preamble declares on these receivers without
        // importing them, so they are in scope wherever a preamble compiles. `rootProp<Box>(execution)`
        // is the first corpus box whose *type argument* is one of them.
        val receivers = PREAMBLE_RECEIVER.findAll(preamble).map { it.groupValues[1] }.toSet()
        return PROP_DECL.findAll(preamble).map { it.groupValues[1] }
            .distinct()
            .filterNot { declared ->
                val t = declared.substringAfterLast('.')
                '.' in declared ||
                    isDefaultImportedTypeName(t) ||
                    t in receivers ||
                    t in explicit ||
                    Regex("""\bclass\s+$t\b""").containsMatchIn(preamble) ||
                    // Guarded: resolving a star-import means a whole-classpath scan, and a preamble
                    // with no star import needs no scan to be judged.
                    (wildcards.isNotEmpty() &&
                        ImportResolution.index()[t].orEmpty().any { fq -> fq.substringBeforeLast('.') in wildcards })
            }
            .map { it.substringAfterLast('.') }
            .sorted()
            .toList()
    }

fixtures

fun writeFixtures(cases: List<Case>, dir: File = File(FIXTURE_DIR)): Int

Write cases as reviewable fixtures: one manifest line per box, and one .preamble.kt file per box that has a preamble. The preamble is the harvested part — the part no static read of the corpus can produce — so it is what gets checked in; the code is left where it already lives and referenced by a content hash, so there is exactly one copy of it in the repo and a corpus edit is detectable.

    fun writeFixtures(cases: List<Case>, dir: File = File(FIXTURE_DIR)): Int {
        dir.mkdirs()
        dir.listFiles()?.filter { it.name.endsWith(".preamble.kt") }?.forEach { it.delete() }
        var written = 0
        val manifest = StringBuilder("[\n")
        cases.sortedBy { it.label }.forEachIndexed { index, c ->
            val preambleFile = if (c.preamble.isBlank()) "" else "${c.stem}.preamble.kt"
            if (preambleFile.isNotEmpty()) {
                File(dir, preambleFile).writeText(c.preamble.trimEnd() + "\n")
                written++
            }
            if (index > 0) manifest.append(",\n")
            manifest.append("""  {"doc": "${c.doc}", "box": "${c.box}", "codeSha256": "${sha256(c.code)}", """)
                .append(""""codeLines": ${c.code.lines().size}, "receivers": ["${c.receivers.joinToString("\", \"")}"], """)
                .append(""""preamble": "$preambleFile"}""")
        }
        manifest.append("\n]\n")
        File(dir, "manifest.json").writeText(manifest.toString())
        return written
    }

data class Drift(val label: String, val what: String)

A fixture that no longer matches the corpus it was harvested from.

fun readFixtures(dir: File = File(FIXTURE_DIR), dirs: List<String> = defaultDirs): Pair<List<Case>, List<Drift>>

Read the checked-in fixtures back, joined to the code they were harvested from. The join is verified: a box that has moved, vanished or been edited shows up in Drift rather than being measured against a preamble that belongs to different code.

    fun readFixtures(dir: File = File(FIXTURE_DIR), dirs: List<String> = defaultDirs): Pair<List<Case>, List<Drift>> {
        val manifest = File(dir, "manifest.json")
        if (!manifest.isFile) return emptyList<Case>() to listOf(Drift(dir.path, "no manifest.json — run the harvest"))
        val entry = Regex(
            """\{"doc": "([^"]*)", "box": "([^"]*)", "codeSha256": "([^"]*)", "codeLines": (\d+), """ +
                """"receivers": \[([^]]*)], "preamble": "([^"]*)"}"""
        )
        val onDisk = documents(dirs).associate { doc ->
            doc.name to kotlinBoxes(doc).associate { it.id to it.props["code"]!! }
        }
        val cases = mutableListOf<Case>()
        val drift = mutableListOf<Drift>()
        val seen = mutableSetOf<String>()
        for (m in entry.findAll(manifest.readText())) {
            val (doc, box, sha, _, recvRaw, preambleFile) = m.destructured
            val label = "$doc/$box"
            seen += label
            val code = onDisk[doc]?.get(box)
            if (code == null) { drift += Drift(label, "in the fixtures but not in the corpus"); continue }
            if (sha256(code) != sha) { drift += Drift(label, "code changed since it was harvested"); continue }
            val preamble = if (preambleFile.isBlank()) "" else
                File(dir, preambleFile).takeIf { it.isFile }?.readText().orEmpty()
                    .ifBlank { drift += Drift(label, "preamble file $preambleFile is missing"); "" }
            cases += Case(doc, box, code, preamble, recvRaw.split(',').map { it.trim().trim('"') }.filter { it.isNotEmpty() })
        }
        onDisk.forEach { (doc, boxes) ->
            boxes.keys.forEach { box ->
                if ("$doc/$box" !in seen) drift += Drift("$doc/$box", "in the corpus but not in the fixtures")
            }
        }
        return cases to drift
    }
}