Skip to content

NanoInterface.kt

src/jvmMain/kotlin/afield/lang/nano/NanoInterface.kt · 735 lines · cited by What a document is on disk

package and imports — 52 lines, from line 1
package afield.lang.nano

import afield.ai.agent.*
import afield.ai.conversation.*
import afield.ai.tools.*
import afield.animation.gesture.*
import afield.animation.interpolation.*
import afield.animation.keyframes.*
import afield.app.diagnostics.*
import afield.app.notifications.*
import afield.app.protocol.*
import afield.app.server.*
import afield.app.settings.*
import afield.audio.synth.*
import afield.data.table.*
import afield.document.context.*
import afield.document.graph.*
import afield.document.history.*
import afield.document.persistence.*
import afield.document.properties.*
import afield.document.workspace.*
import afield.graphics.color.*
import afield.graphics.paint.*
import afield.graphics.vulkan.*
import afield.math.functions.*
import afield.media.capture.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.completion.*
import afield.runtime.execution.*
import afield.runtime.kotlin.*
import afield.runtime.lifecycle.*
import afield.tools.automation.*
import afield.tools.docs.*
import afield.ui.controls.*
import afield.ui.layout.*
import afield.ui.notation.*
import afield.ui.output.*
import afield.ui.output.templates.*
import afield.ui.slides.*
import afield.ui.visualization.*
import afield.app.diagnostics.logging.lprintln
import afield.runtime.TopologyWatcher
import afield.runtime.MainLanguageRuntime
import afield.runtime.applyRuntimeToggle
import afield.runtime.isMainLanguage
import afield.runtime.registerMainLanguageRuntime
import afield.document.properties.Delegation
import afield.document.properties.Inheritance
import io.ktor.websocket.WebSocketSession
import kotlinx.coroutines.delay
import kotlinx.coroutines.yield

Field4 language adapter for the Nano 8-bit assembly language.

Nano is a compact virtual machine: 4 registers (r0–r3), 32 bytes of RAM with address wrap, a small call stack, and an instruction set sized for emergence and artificial-life grid experiments. NanoExecution is the BoxExecution implementation; setting a parent box's isNanoRoot to true turns its children into Nano assembly boxes.

installNano registers the runtime with Field's language dispatch; installNanoPlugin starts the topology watcher that activates it when the box tree changes. nanoAssemble and Box.nanoDisassemble are available to neighbouring Kotlin boxes that need to inspect or construct the VM's byte representation directly.

class NanoExecution(val inside: Box) : BoxExecution

Field4 BoxExecution implementation for the Nano micro-computer language.

Nano is a compact 8-bit virtual machine with 4 registers (r0–r3), 32 bytes of RAM (address wrap), a small call stack, and an assembly language designed for emergence and artificial-life grid simulations.

Feature highlights inside Field4: - executeFragment / executeAll: assemble and run Nano code, display register state and any out rX output in the box's output area. - launchAll: run the VM in a cooperative per-tick loop (for animation / ALife step). - complete: offer mnemonic, register, and label completion. - disassemble: Box extension function to disassemble raw bytes.

To enable Nano for a box hierarchy, set the parent box's isNanoRoot property to true. Child boxes then become Nano boxes (green border) whose code property is Nano assembly.

Quick example:

; Fibonacci up to ~250
ldi r0, 0
ldi r1, 1
loop:
    out r0          ; print current value
    mov r2, r1
    add r1, r0
    mov r0, r2
    cmp r1, r0      ; compare to detect overflow
    jlt  loop       ; loop while r1 < 255
hlt

class NanoExecution(val inside: Box) : BoxExecution {

    private val assembler = NanoAssembler()
    private val vm = NanoVM()
    private val stepDecorationKey = "__nano_step_line__"
    private val stepDelayMs = 220L

Output wiring

    private fun configureOutput(
        inside: Box,
        executeLine: Int,
        reply: (Message) -> Unit,
        originSession: WebSocketSession?
    ) {
        configureOutputProxies(inside, reply, originSession)
        vm.onOutput = { value ->
            inside.output.proxy(value.toString(), true, executeLine)
        }
        vm.onError = { msg, pcVal ->
            inside.displayError(msg, line = executeLine, property = "code")
            lprintln("error,nano", "Nano VM error at PC=0x${pcVal.toString(16)}: $msg")
        }
    }

    private fun clearErrorStatus(reply: (Message) -> Unit, originSession: WebSocketSession?) {
        dispatchExecutionOutput(
            inside,
            reply,
            Output(
                uid = inside.id,
                line = -1,
                kind = "err",
                property = "code",
                html = "",
                frame = Runloop.runLoop.frame.toInt()
            ),
            originSession = originSession
        )
    }

BoxExecution

override fun executeFragment(inside: Box, execute: Execute, reply: (Message) -> Unit) {

    override fun executeFragment(inside: Box, execute: Execute, reply: (Message) -> Unit) {
        runOnce(inside, execute, "fragment", reply)
    }

One assemble-and-run of the program, reported under mode. See DatalogExecution.runOnce.

    private fun runOnce(inside: Box, execute: Execute, mode: String, reply: (Message) -> Unit) {
        val originSession = thisSession.get()
        val trace = ExecutionTrace.forEngine(inside, execute, mode, reply)
        inMain {
            val wm = ++inside.fragmentGeneration
            send(Message("is-running").payload(IsRunning(execute.uid, true, wm, "__fragment__")))
            clearStepDecoration()
            configureOutput(inside, execute.line, trace.reply, originSession)
            var ok = false
            try {
                runNano(execute.code, execute.line, trace.reply, originSession, maxSteps = 100_000)
                ok = true
            } finally {
                trace.finish(ok)
                send(Message("is-running").payload(IsRunning(execute.uid, false, wm, "__fragment__")))
            }
        }
    }

override fun executeAll(inside: Box, execute: Execute, reply: (Message) -> Unit) {

    override fun executeAll(inside: Box, execute: Execute, reply: (Message) -> Unit) {
        runOnce(inside, execute, "run", reply)
    }

override fun launchAll

body — 114 lines, from line 171
    override fun launchAll(
        inside: Box, execute: Execute, code: String, strand: String, reply: (Message) -> Unit
    ) {
        val originSession = thisSession.get()
        val trace = ExecutionTrace.forEngine(inside, execute, "launch", reply)
        inMain {
            val wm = ++inside.fragmentGeneration

            configureOutputProxies(inside, trace.reply, originSession)
            val outputs = mutableListOf<Int>()
            var runtimeError: String? = null
            vm.onOutput = { value ->
                outputs += value and 0xFF
                inside.output.proxy(value.toString(), true, execute.line)
            }
            vm.onError = { msg, pcVal ->
                runtimeError = "Nano VM error at PC=0x${pcVal.toString(16).padStart(2, '0')}: $msg"
                inside.displayError(msg, line = execute.line, property = "code")
                lprintln("error,nano", runtimeError)
            }

            val result = assembler.assembleWithSourceMap(code)
            if (result is NanoAssembler.AssemblyDebugResult.Err) {
                clearStepDecoration()
                result.errors.forEach { e ->
                    inside.displayError(e.message, line = execute.line + e.line - 1, property = "code")
                }
                trace.finish(false, "the program did not assemble")
                send(Message("is-running").payload(IsRunning(execute.uid, false, wm, strand)))
                return@inMain
            }

            result as NanoAssembler.AssemblyDebugResult.Ok
            val assembly = result.info
            val program = assembly.bytes
            clearErrorStatus(reply, originSession)
            clearStepDecoration()

            val generation = inside.runGeneration.getOrPut(strand) { -1 } + 1
            inside.runGeneration[strand] = generation
            send(Message("is-running").payload(IsRunning(execute.uid, true, generation, strand)))

            val memoryBefore = loadProgramIntoVm(vm, program)
            inside.displayOpen("nano")

            var steps = 0
            var lastFocusedLine: Int? = null

            fun publishState(focusPc: Int, changedRegisters: Set<Int>, changedRam: Set<Int>) {
                val focusLine = assembly.pcToLine.getOrNull(focusPc)
                setStepDecoration(code, focusLine)
                if (focusLine != null && focusLine != lastFocusedLine) {
                    focusEditorLine(focusLine, originSession)
                    lastFocusedLine = focusLine
                }
                inside.nanoMemoryBefore = memoryBefore
                inside.nanoMemoryAfter = vm.ram.toList()
                send(
                    Message("set-output").payload(
                        Set_output(
                            inside.id,
                            "outputarea",
                            "nano",
                            vm.toNanoRunReport(
                                program = program,
                                source = code,
                                steps = steps,
                                outputs = outputs,
                                focusPc = focusPc,
                                focusLine = focusLine,
                                changedRegisters = changedRegisters,
                                changedRam = changedRam,
                                runtimeError = runtimeError
                            ).toHtml(
                                title = "Nano state",
                                includeSource = false,
                                includeDisassembly = true,
                                includeProgramBytes = true
                            )
                        )
                    )
                )
            }

            publishState(vm.pc, emptySet(), emptySet())

            // The program assembled, loaded and published its first state; the stepping loop below
            // is the run, not the setup. Same boundary Kotlin's launch receipt draws.
            trace.finish(runtimeError == null)

            inMain {
                try {
                    while (inside.runGeneration[strand] == generation && !vm.halted) {
                        delay(configuredStepDelayMs())
                        if (inside.runGeneration[strand] != generation || vm.halted) break

                        val previousRegisters = vm.reg.copyOf()
                        val previousRam = vm.ram.copyOf()
                        val stepResult = vm.stepOnce(program)
                        if (!stepResult.executed) break

                        steps++
                        publishState(
                            focusPc = stepResult.executedPc,
                            changedRegisters = diffChangedIndices(previousRegisters, vm.reg),
                            changedRam = diffChangedIndices(previousRam, vm.ram)
                        )
                    }
                } finally {
                    send(Message("is-running").payload(IsRunning(execute.uid, false, generation, strand)))
                }
            }
        }
    }

override fun stop(inside: Box)

    override fun stop(inside: Box) {
        vm.halted = true
        inside.runGeneration.keys.toList().forEach { strand ->
            inside.runGeneration[strand] = (inside.runGeneration[strand] ?: 0) + 1
        }
    }

override fun complete(inside: Box, completionRequest: CompletionRequest, reply: (Message) -> Unit) {

body — 64 lines, from line 293
    override fun complete(inside: Box, completionRequest: CompletionRequest, reply: (Message) -> Unit) {
        val code = completionRequest.allCode
        val cursor = completionRequest.position.coerceIn(0, code.length)

        // Find the token being typed
        var start = cursor
        while (start > 0 && !code[start - 1].isWhitespace() && code[start - 1] != ',') start--
        val prefix = code.substring(start, cursor).lowercase()

        val completions = mutableListOf<Metadata>()

        // Mnemonics
        for ((mnemonic, info) in MNEMONIC_HELP) {
            if (mnemonic.startsWith(prefix)) {
                completions += Metadata(
                    icon = "keyword",
                    tail = "nano",
                    documentation = info,
                    text = mnemonic,
                    displayText = mnemonic
                )
            }
        }

        // Registers
        for (r in listOf("r0", "r1", "r2", "r3")) {
            if (r.startsWith(prefix)) {
                completions += Metadata(
                    icon = "variable",
                    tail = "reg",
                    documentation = "Nano 8-bit register $r (0–255)",
                    text = r,
                    displayText = r
                )
            }
        }

        // Labels from current code
        if (prefix.isNotEmpty() && prefix[0].isLetter()) {
            for (label in assembler.extractLabels(code)) {
                if (label.lowercase().startsWith(prefix)) {
                    completions += Metadata(
                        icon = "symbol",
                        tail = "label",
                        documentation = "Label '$label' defined in this program",
                        text = label,
                        displayText = label
                    )
                }
            }
        }

        if (completions.isNotEmpty()) {
            reply(
                Message("complete").payload(
                    Completion(
                        boundsStart = start,
                        boundsEnd = cursor,
                        metadata = completions
                    )
                )
            )
        }
    }

override fun executeSymbol(inside: Box, executeSymbol: ExecuteSymbol, reply: (Message) -> Unit) {

    override fun executeSymbol(inside: Box, executeSymbol: ExecuteSymbol, reply: (Message) -> Unit) {
        // For Nano, find the label and run the program up to that label
        val exec = Execute(
            uid = executeSymbol.uid,
            type = "fragment",
            line = executeSymbol.line,
            code = executeSymbol.symbol,
            singleLine = true
        )
        executeFragment(inside, exec, reply)
    }

override val languageName: String get() = "nano"

Internal: assemble + run

source — 190 lines, from line 374
    private fun runNano(
        code: String,
        executeLine: Int,
        reply: (Message) -> Unit,
        originSession: WebSocketSession?,
        maxSteps: Int = 100_000
    ) {
        val result = assembler.assembleWithSourceMap(code)
        when (result) {
            is NanoAssembler.AssemblyDebugResult.Err -> {
                result.errors.forEach { e ->
                    inside.displayError(e.message, line = executeLine + e.line - 1, property = "code")
                }
                result.errors.firstOrNull()?.let { e ->
                    LastRunNotes.record(inside, "code", code, executeLine, emptyList(), stoppedAt = e.line, stopReason = e.message)
                }
            }
            is NanoAssembler.AssemblyDebugResult.Ok -> {
                clearErrorStatus(reply, originSession)
                val assembly = result.info
                val program = assembly.bytes
                val freshVm = NanoVM(maxSteps = maxSteps)
                val outputs = mutableListOf<Int>()
                var runtimeError: String? = null
                freshVm.onOutput = { value ->
                    outputs += value and 0xFF
                    vm.onOutput(value)
                }
                freshVm.onError = { msg, pc ->
                    runtimeError = "Nano VM error at PC=0x${pc.toString(16).padStart(2, '0')}: $msg"
                    vm.onError(msg, pc)
                }
                val memoryBefore = loadProgramIntoVm(freshVm, program)
                val trace = freshVm.runTracingLines(program, assembly.pcToLine)
                val steps = trace.steps
                LastRunNotes.record(inside, "code", code, executeLine, nanoLineNotes(assembly.pcToLine, trace),
                    stoppedAt = runtimeError?.let { assembly.pcToLine.getOrNull(freshVm.pc) }, stopReason = runtimeError)
                inside.nanoMemoryBefore = memoryBefore
                inside.nanoMemoryAfter = freshVm.ram.toList()
                val html = freshVm
                    .toNanoRunReport(
                        program = program,
                        source = code,
                        steps = steps,
                        outputs = outputs,
                        focusPc = freshVm.pc,
                        focusLine = assembly.pcToLine.getOrNull(freshVm.pc),
                        runtimeError = runtimeError
                    )
                    .toHtml(
                        title = "Nano state",
                        includeSource = false,
                        includeDisassembly = false,
                        includeProgramBytes = false
                    )
                dispatchExecutionOutput(
                    inside,
                    reply,
                    Output(
                        uid = inside.id,
                        line = executeLine + code.lines().size,
                        kind = "ok",
                        html = html,
                        frame = Runloop.runLoop.frame.toInt()
                    ),
                    originSession = originSession
                )
            }
        }
    }

    private fun diffChangedIndices(previous: IntArray, current: IntArray): Set<Int> {
        return previous.indices.filterTo(linkedSetOf()) { index -> previous[index] != current[index] }
    }

    private fun configuredStepDelayMs(): Long {
        val raw = runCatching { inside.nanoLaunchDelayMs }.getOrNull()
        return raw?.toLong()?.coerceAtLeast(0L) ?: stepDelayMs
    }

    private fun loadProgramIntoVm(machine: NanoVM, program: ByteArray): List<Int> {
        machine.load(program)
        applyConfiguredMemory(machine.ram, runCatching { inside.nanoMemoryInit }.getOrNull())
        return machine.ram.toList()
    }

    private fun applyConfiguredMemory(memory: IntArray, spec: Any?) {
        when (spec) {
            null -> return
            is ByteArray -> spec.forEachIndexed { index, value -> writeMemoryValue(memory, index, value.toInt()) }
            is ShortArray -> spec.forEachIndexed { index, value -> writeMemoryValue(memory, index, value.toInt()) }
            is IntArray -> spec.forEachIndexed { index, value -> writeMemoryValue(memory, index, value) }
            is LongArray -> spec.forEachIndexed { index, value -> writeMemoryValue(memory, index, value.toInt()) }
            is FloatArray -> spec.forEachIndexed { index, value -> writeMemoryValue(memory, index, value.toInt()) }
            is DoubleArray -> spec.forEachIndexed { index, value -> writeMemoryValue(memory, index, value.toInt()) }
            is Array<*> -> spec.forEachIndexed { index, value -> value.asNanoByteOrNull()?.let { writeMemoryValue(memory, index, it) } }
            is Iterable<*> -> spec.forEachIndexed { index, value -> value.asNanoByteOrNull()?.let { writeMemoryValue(memory, index, it) } }
            is Map<*, *> -> spec.forEach { (address, value) ->
                val addr = address.asNanoIndexOrNull() ?: return@forEach
                val byte = value.asNanoByteOrNull() ?: return@forEach
                writeMemoryValue(memory, addr, byte)
            }
            else -> lprintln("warning,nano", "Ignoring unsupported nanoMemoryInit value of type ${spec::class.simpleName}")
        }
    }

    private fun writeMemoryValue(memory: IntArray, index: Int, value: Int) {
        if (memory.isEmpty()) return
        memory[index and (memory.size - 1)] = value and 0xFF
    }

    private fun Any?.asNanoIndexOrNull(): Int? {
        return when (this) {
            is Number -> this.toInt()
            is String -> this.trim().toIntOrNull()
            else -> null
        }
    }

    private fun Any?.asNanoByteOrNull(): Int? {
        return when (this) {
            is Number -> this.toInt() and 0xFF
            is String -> parseNanoStringNumber(this)
            else -> null
        }
    }

    private fun parseNanoStringNumber(text: String): Int? {
        val trimmed = text.trim()
        if (trimmed.isEmpty()) return null
        val parsed = when {
            trimmed.startsWith("-0x", ignoreCase = true) ->
                trimmed.substring(3).toIntOrNull(16)?.let { -it }
            trimmed.startsWith("0x", ignoreCase = true) ->
                trimmed.substring(2).toIntOrNull(16)
            else -> trimmed.toIntOrNull()
        } ?: return null
        return parsed and 0xFF
    }

    private fun clearStepDecoration() {
        inside.decorationState.remove(stepDecorationKey)
    }

    private fun setStepDecoration(code: String, focusLine: Int?) {
        if (focusLine == null) {
            clearStepDecoration()
            return
        }
        val lines = code.lines()
        if (focusLine !in 1..lines.size) {
            clearStepDecoration()
            return
        }
        val lineText = lines[focusLine - 1]
        val endColumn = maxOf(1, lineText.length + 1)
        val className = "nanoStep_${inside.id}".map { if (it.isLetterOrDigit()) it else '_' }.joinToString("")
        val css = """
            .${className} {
                background: rgba(217, 115, 38, 0.10);
                border-bottom: 1px solid rgba(217, 115, 38, 0.95);
                border-radius: 2px;
            }
        """.trimIndent().replace("\n", " ")
        inside.decorationState[stepDecorationKey] = RawDecoration(
            lineStart = focusLine,
            colStart = 1,
            lineEnd = focusLine,
            colEnd = endColumn,
            classname = className,
            classDef = mapOf(className to css)
        )
    }

    private fun focusEditorLine(line: Int, originSession: WebSocketSession?) {
        val message = Message("select-and-edit-box").payload(
            SelectAndEditBox(
                uid = inside.id,
                property = "code",
                scrollToLine = line,
                highlightLine = line
            )
        )
        if (originSession != null) {
            _send(originSession, message)
        } else {
            send(message)
        }
    }
}

Box property for root detection

var Box.isNanoRoot by Delegation<Boolean?>()

var Box.nanoLaunchDelayMs by Inheritance<Number?>()

var Box.nanoMemoryInit by Inheritance<Any?>()

var Box.nanoMemoryBefore by Delegation<List<Int>?>()

var Box.nanoMemoryAfter by Delegation<List<Int>?>()

internal class NanoLineTrace(val steps: Int, val ranAt: Map<Int, Int>, val registersAfter: Map<Int, IntArray>, val outputAt: Map<Int, List<Int>>)

What each source line of a Nano run did: how often it ran, the registers after it last did, what it output.

internal class NanoLineTrace(val steps: Int, val ranAt: Map<Int, Int>, val registersAfter: Map<Int, IntArray>, val outputAt: Map<Int, List<Int>>)

internal fun NanoVM.runTracingLines(program: ByteArray, pcToLine: IntArray): NanoLineTrace

NanoVM.run, one instruction at a time so that each can be traced to its source line through pcToLine (the assembler's source map, one 1-based line per emitted byte).

internal fun NanoVM.runTracingLines(program: ByteArray, pcToLine: IntArray): NanoLineTrace {
    val ranAt = HashMap<Int, Int>()
    val registersAfter = HashMap<Int, IntArray>()
    val outputAt = HashMap<Int, MutableList<Int>>()
    var line = 0
    val prior = onOutput
    onOutput = { value ->
        outputAt.getOrPut(line) { mutableListOf() } += value and 0xFF
        prior(value)
    }
    var steps = 0
    try {
        while (program.isNotEmpty() && !halted && steps < maxSteps) {
            line = pcToLine.getOrElse(pc) { 0 }
            if (!stepOnce(program).executed) break
            steps++
            ranAt.merge(line, 1, Int::plus)
            registersAfter[line] = reg.copyOf()
        }
    } finally {
        onOutput = prior
    }
    return NanoLineTrace(steps, ranAt, registersAfter, outputAt)
}

internal fun nanoLineNotes(pcToLine: IntArray, trace: NanoLineTrace): List<LastRunNotes.Produced>

A run's line notes: each instruction line says how many times it ran and what followed; one that never ran says so.

internal fun nanoLineNotes(pcToLine: IntArray, trace: NanoLineTrace): List<LastRunNotes.Produced> =
    pcToLine.filter { it > 0 }.distinct().sorted().map { line ->
        val count = trace.ranAt[line] ?: 0
        val caption = if (count == 0) "never ran" else buildString {
            append(if (count == 1) "ran once" else "ran $count times")
            trace.registersAfter[line]?.let { regs -> append(", then ").append(regs.withIndex().joinToString(" ") { (i, v) -> "r$i=$v" }) }
            trace.outputAt[line]?.takeIf { it.isNotEmpty() }?.let { out ->
                append(", output ").append(out.take(16).joinToString(" ")).append(if (out.size > 16) " …" else "")
            }
        }
        LastRunNotes.Produced(line, line, caption)
    }

fun installNano()

@RunsAt(early)
fun installNano() {
    Box.ioPersistentProperties.addAll(setOf("isNanoRoot", "nanoLaunchDelayMs", "nanoMemoryInit"))
    registerMainLanguageRuntime(
        MainLanguageRuntime(
            id = "nano",
            label = "Nano",
            runtimeClass = NanoExecution::class,
            setLegacyRoot = { box, enabled -> box.isNanoRoot = enabled },
            applyTopology = ::nanoTopologyNeedsUpdating,
        ),
    )
}

fun installNanoPlugin()

@RunsAt(plugins)
fun installNanoPlugin() {
    TopologyWatcher("nano") { nanoTopologyNeedsUpdating(it) }.start()
}

fun nanoTopologyNeedsUpdating(values: Collection<Box>)

fun nanoTopologyNeedsUpdating(values: Collection<Box>) {
    applyRuntimeToggle(
        boxes = values,
        isRoot = { it.isNanoRoot == true || it.isMainLanguage("nano") },
        runtimeClass = NanoExecution::class,
        newExecution = { child -> NanoExecution(child) },
        language = "Nano",
        customization = "",
        notifyCategory = "nano"
    )
}

Disassembler helper accessible from Kotlin live code

fun Box.nanoDisassemble(bytes: ByteArray)

Disassemble Nano bytecode bytes and display the result in this box's output area.

val prog = nanoAssemble("""
    ldi r0, 10
    loop:
        dec r0
        jnz loop
    hlt
""")
box.nanoDisassemble(prog)
fun Box.nanoDisassemble(bytes: ByteArray) {
    val text = NanoDisassembler().disassembleToString(bytes)
    output.display("disasm", "<pre style='font-family:monospace;font-size:12px'>$text</pre>")
}

fun nanoAssemble(source: String): ByteArray

Assemble Nano source and return the bytecode, or throw NanoAssemblyException on error. Convenient from Kotlin live-coding boxes:

val prog = nanoAssemble("ldi r0, 42\nhlt")

fun nanoAssemble(source: String): ByteArray {
    val result = NanoAssembler().assemble(source)
    return when (result) {
        is NanoAssembler.AssemblyResult.Ok -> result.bytes
        is NanoAssembler.AssemblyResult.Err ->
            throw NanoAssemblyException(result.errors.joinToString("\n") { "line ${it.line}: ${it.message}" })
    }
}

class NanoAssemblyException(message: String) : RuntimeException(message)

Completion documentation

source — 44 lines, from line 691
private val MNEMONIC_HELP: Map<String, String> = mapOf(
    "nop"   to "No operation",
    "hlt"   to "Halt the VM",
    "ret"   to "Return from CALL (pop return address)",
    "inc"   to "inc rX  — increment register rX (wraps mod 256)",
    "dec"   to "dec rX  — decrement register rX (wraps mod 256)",
    "not"   to "not rX  — bitwise NOT of rX",
    "neg"   to "neg rX  — two's-complement negate of rX",
    "clr"   to "clr rX  — clear register rX (set to 0)",
    "out"   to "out rX  — print value of rX as decimal",
    "rng"   to "rng rX  — fill rX with a pseudorandom byte (0–255)",
    "mov"   to "mov rD, rS  — rD = rS",
    "add"   to "add rD, rS  — rD = rD + rS  (wraps mod 256; sets Z/C/S)",
    "sub"   to "sub rD, rS  — rD = rD - rS  (wraps; sets Z/C/S)",
    "mul"   to "mul rD, rS  — rD = (rD * rS) & 0xFF  (sets Z/C)",
    "and"   to "and rD, rS  — rD = rD & rS  (bitwise AND)",
    "or"    to "or  rD, rS  — rD = rD | rS  (bitwise OR)",
    "xor"   to "xor rD, rS  — rD = rD ^ rS  (bitwise XOR)",
    "cmp"   to "cmp rD, rS  — set flags from (rD - rS); registers unchanged",
    "ldr"   to "ldr rD, rS  — rD = RAM[rS]  (load indirect)",
    "str"   to "str rD, rS  — RAM[rD] = rS  (store indirect)",
    "div"   to "div rD, rS  — rD = rD / rS  (integer; rS=0 → rD=0xFF)",
    "ldi"   to "ldi rX, N   — rX = N  (load immediate byte, 0–255)",
    "lda"   to "lda rX, A   — rX = RAM[A]  (load from absolute address 0–31)",
    "sta"   to "sta rX, A   — RAM[A] = rX  (store to absolute address 0–31)",
    "addi"  to "addi rX, N  — rX += N  (signed immediate, -128..127)",
    "ldrr"  to "ldrr rX, N  — rX = RAM[PC + N]  (PC-relative load, N signed)",
    "strr"  to "strr rX, N  — RAM[PC + N] = rX  (PC-relative store, N signed)",
    "shl"   to "shl rX, N   — rX <<= N  (logical shift left)",
    "shr"   to "shr rX, N   — rX >>= N  (logical shift right)",
    "jmp"   to "jmp label   — unconditional relative jump",
    "jz"    to "jz  label   — jump if Zero flag (last result == 0)",
    "jnz"   to "jnz label   — jump if not Zero",
    "jgt"   to "jgt label   — jump if > 0 (not zero and not negative)",
    "jlt"   to "jlt label   — jump if < 0 (negative / sign flag set)",
    "jge"   to "jge label   — jump if >= 0 (not negative)",
    "jle"   to "jle label   — jump if <= 0 (zero or negative)",
    "jc"    to "jc  label   — jump if Carry flag set",
    "jnc"   to "jnc label   — jump if Carry flag clear",
    "call"  to "call label  — relative call (push return address, jump)",
    "jmpa"  to "jmpa label  — absolute jump (0–255)",
    "calla" to "calla label — absolute call (push return address, absolute jump)",
    ".data" to ".data b0 b1 …  — emit raw bytes into the program stream"
)