Compare commits

..

No commits in common. "210c0249faa1e50cee2365d4863e6cb82e06f234" and "2855b4fbe36a1f034a100de37b2e566e0f0ad96b" have entirely different histories.

6 changed files with 0 additions and 410 deletions

View File

@ -7,21 +7,6 @@ It's where Shrimp live.
bun install
bun test
## Use it
Execute text bytecode:
./bin/reef examples/loop.reef
Validate bytecode:
./bin/validate examples/loop.reef
Run the simple debugger to see what the instructions are doing:
./bin/debug examples/loop.reef
./bin/debug -h examples/loop.reef
## Features
- Stack operations (PUSH, POP, DUP)

355
bin/debug
View File

@ -1,355 +0,0 @@
#!/usr/bin/env bun
import { VM, toBytecode } from "../src/index"
import { OpCode } from "../src/opcode"
import type { Value } from "../src/value"
// ANSI color codes
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
cyan: '\x1b[36m',
yellow: '\x1b[33m',
green: '\x1b[32m',
red: '\x1b[31m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
}
type DebugOptions = {
delay: number
step: boolean
noClear: boolean
}
async function parseArgs(): Promise<{ bytecode: string, options: DebugOptions }> {
const args = process.argv.slice(2)
const options: DebugOptions = {
delay: 1000,
step: false,
noClear: false,
}
let bytecodeArg: string | undefined
for (let i = 0; i < args.length; i++) {
const arg = args[i]!
if (arg === '--delay' || arg === '-d') {
options.delay = parseInt(args[++i]!)
} else if (arg === '--step' || arg === '-s') {
options.step = true
} else if (arg === '--no-clear') {
options.noClear = true
} else if (arg === '--help' || arg === '-h') {
console.log(`
${colors.bright}ReefVM Debugger${colors.reset}
Usage: bun bin/debug [options] <bytecode-file>
Options:
-d, --delay <ms> Delay between instructions (default: 300ms)
-s, --step Step through one instruction at a time (press Enter)
-h, --help Show this help message
--no-clear Don't clear screen between instructions
Examples:
bun bin/debug program.reef
bun bin/debug --delay 500 program.reef
bun bin/debug --step program.reef
`)
process.exit(0)
} else {
bytecodeArg = arg
}
}
if (!bytecodeArg) {
console.error(`${colors.red}Error: No bytecode file specified${colors.reset}`)
console.error(`Run with --help for usage information`)
process.exit(1)
}
// Read bytecode file
const file = Bun.file(bytecodeArg)
if (!file.exists()) {
console.error(`${colors.red}Error: File not found: ${bytecodeArg}${colors.reset}`)
process.exit(1)
}
const bytecode = file.text().then(text => text)
return { bytecode: await bytecode, options }
}
function formatValue(value: Value): string {
if (value.type === 'string') {
return `${colors.green}"${value.value}"${colors.reset}`
} else if (value.type === 'number') {
return `${colors.cyan}${value.value}${colors.reset}`
} else if (value.type === 'boolean') {
return `${colors.yellow}${value.value}${colors.reset}`
} else if (value.type === 'null') {
return `${colors.dim}null${colors.reset}`
} else if (value.type === 'array') {
const items = value.value.map(v => formatValue(v)).join(', ')
return `${colors.blue}[${items}]${colors.reset}`
} else if (value.type === 'dict') {
const entries = Array.from(value.value.entries())
.map(([k, v]) => `${k}: ${formatValue(v)}`)
.join(', ')
return `${colors.magenta}{${entries}}${colors.reset}`
} else if (value.type === 'function') {
const params = value.params.join(', ')
return `${colors.dim}<fn(${params})>${colors.reset}`
}
return String(value)
}
function formatStack(stack: Value[]): string {
if (stack.length === 0) {
return `${colors.dim}[empty]${colors.reset}`
}
return stack.map((v, i) => ` ${colors.dim}[${i}]${colors.reset} ${formatValue(v)}`).join('\n')
}
function formatVariables(scope: any): string {
const vars: string[] = []
function collectVars(s: any, depth = 0) {
if (!s) return
const prefix = depth > 0 ? `${colors.dim}(parent)${colors.reset} ` : ''
for (const [name, value] of s.locals.entries()) {
vars.push(` ${prefix}${colors.bright}${name}${colors.reset} = ${formatValue(value)}`)
}
if (s.parent) {
collectVars(s.parent, depth + 1)
}
}
collectVars(scope)
if (vars.length === 0) {
return `${colors.dim}[no variables]${colors.reset}`
}
return vars.join('\n')
}
function getOpcodeName(op: OpCode): string {
return OpCode[op] || `UNKNOWN(${op})`
}
function formatInstructionAt(vm: VM, pc: number): string {
const instruction = vm.instructions[pc]
if (!instruction) {
return `${colors.dim}[END]${colors.reset}`
}
const opName = getOpcodeName(instruction.op)
let operandStr = ''
if (instruction.operand !== undefined) {
const operand = instruction.operand
// Format operand based on type
if (typeof operand === 'number') {
// Check if it's a constant reference for PUSH
if (instruction.op === OpCode.PUSH) {
const constant = vm.constants[operand]
if (constant && constant.type !== 'function_def') {
operandStr = ` ${formatValue(constant)}`
}
} else if (instruction.op === OpCode.MAKE_FUNCTION) {
const fnDef = vm.constants[operand]
if (fnDef && fnDef.type === 'function_def') {
const params = fnDef.params.join(' ')
// Check if body address has a label
const bodyLabel = vm.labels.get(fnDef.body)
const bodyStr = bodyLabel ? `.${bodyLabel}` : `#${fnDef.body}`
operandStr = ` ${colors.dim}(${params}) ${bodyStr}${colors.reset}`
}
} else if (instruction.op === OpCode.JUMP || instruction.op === OpCode.JUMP_IF_FALSE || instruction.op === OpCode.JUMP_IF_TRUE) {
// Relative jump - calculate target PC
const targetPC = pc + 1 + operand
const targetLabel = vm.labels.get(targetPC)
if (targetLabel) {
operandStr = ` ${colors.cyan}.${targetLabel}${colors.reset}`
} else {
operandStr = ` ${colors.cyan}${operand}${colors.reset}`
}
} else if (instruction.op === OpCode.PUSH_TRY || instruction.op === OpCode.PUSH_FINALLY) {
// Absolute address
const targetLabel = vm.labels.get(operand)
if (targetLabel) {
operandStr = ` ${colors.cyan}.${targetLabel}${colors.reset}`
} else {
operandStr = ` ${colors.cyan}${operand}${colors.reset}`
}
} else {
operandStr = ` ${colors.cyan}${operand}${colors.reset}`
}
} else {
operandStr = ` ${colors.yellow}${operand}${colors.reset}`
}
}
return `${colors.bright}${opName}${colors.reset}${operandStr}`
}
function formatInstructionWindow(vm: VM, highlightPC: number, contextLines: number = 5): string {
const lines: string[] = []
const totalInstructions = vm.instructions.length
// Calculate window bounds
const start = Math.max(0, highlightPC - contextLines)
const end = Math.min(totalInstructions, highlightPC + contextLines + 1)
for (let i = start; i < end; i++) {
const isCurrent = i === highlightPC
const pcStr = `${i}`.padStart(4, ' ')
const instruction = formatInstructionAt(vm, i)
// Check if there's a label at this instruction
const label = vm.labels.get(i)
const labelStr = label ? `${colors.dim}.${label}:${colors.reset}` : ''
if (isCurrent) {
// Current instruction with arrow
if (label) {
lines.push(`${colors.bright}${colors.blue} ${pcStr} → ${labelStr} ${instruction}${colors.reset}`)
} else {
lines.push(`${colors.bright}${colors.blue} ${pcStr} → ${instruction}${colors.reset}`)
}
} else {
// Other instructions
if (label) {
lines.push(`${colors.dim} ${pcStr} ${labelStr} ${instruction}${colors.reset}`)
} else {
lines.push(`${colors.dim} ${pcStr} ${instruction}${colors.reset}`)
}
}
}
return lines.join('\n')
}
function clearScreen() {
console.write('\x1b[2J\x1b[H')
}
function displayState(vm: VM, options: DebugOptions, stepNum: number, highlightPC: number) {
if (!options.noClear) {
clearScreen()
}
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log(`${colors.bright}🪸 ReefVM Debugger${colors.reset} ${colors.dim}(Step ${stepNum})${colors.reset}`)
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log()
// Instruction window
console.log(`${colors.bright}Program:${colors.reset}`)
console.log(formatInstructionWindow(vm, highlightPC, 5))
console.log()
// Stack
console.log(`${colors.bright}Stack:${colors.reset}`)
console.log(formatStack(vm.stack))
console.log()
// Variables
console.log(`${colors.bright}Variables:${colors.reset}`)
console.log(formatVariables(vm.scope))
console.log()
// Call stack
console.log(`${colors.bright}Call Stack Depth:${colors.reset} ${colors.cyan}${vm.callStack.length}${colors.reset}`)
// Exception handlers
if (vm.exceptionHandlers.length > 0) {
console.log(`${colors.bright}Exception Handlers:${colors.reset} ${colors.cyan}${vm.exceptionHandlers.length}${colors.reset}`)
}
console.log()
console.log(`${colors.dim}${'─'.repeat(63)}${colors.reset}`)
if (options.step) {
console.log(`${colors.dim}Press Enter to continue...${colors.reset}`)
}
}
async function waitForInput(): Promise<void> {
return new Promise((resolve) => {
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.once('data', (data) => {
process.stdin.setRawMode(false)
process.stdin.pause()
// Handle Ctrl+C
if (data[0] === 3) {
console.log(`\n${colors.yellow}Interrupted${colors.reset}`)
process.exit(0)
}
resolve()
})
})
}
async function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function debug(bytecodeStr: string, options: DebugOptions) {
const bytecode = toBytecode(bytecodeStr)
const vm = new VM(bytecode)
let stepNum = 0
// Execute step by step
while (!vm.stopped && vm.pc < vm.instructions.length) {
const instruction = vm.instructions[vm.pc]!
const executedPC = vm.pc // Save PC of instruction we're about to execute
try {
await vm.execute(instruction)
vm.pc++
stepNum++
// Display state showing the instruction we just executed
displayState(vm, options, stepNum, executedPC)
if (options.step) {
await waitForInput()
} else {
await sleep(options.delay)
}
} catch (error) {
console.log()
console.log(`${colors.red}${colors.bright}ERROR:${colors.reset} ${colors.red}${error}${colors.reset}`)
process.exit(1)
}
}
// Final result
console.log()
console.log(`${colors.bright}${colors.green}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log(`${colors.bright}${colors.green}Execution Complete${colors.reset}`)
console.log(`${colors.bright}${colors.green}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log()
const result = vm.stack[vm.stack.length - 1] || { type: 'null', value: null } as Value
console.log(`${colors.bright}Final Result:${colors.reset} ${formatValue(result)}`)
console.log()
}
// Main
const { bytecode, options } = await parseArgs()
await debug(bytecode, options)

View File

@ -1,23 +0,0 @@
; Simple counter example
PUSH 0
STORE counter
PUSH 10
STORE max
.loop:
LOAD counter
LOAD max
LT
JUMP_IF_FALSE .end
LOAD counter
PUSH 1
ADD
STORE counter
JUMP .loop
.end:
LOAD counter
HALT

View File

@ -1,7 +0,0 @@
; Simple arithmetic
PUSH 5
PUSH 10
ADD
STORE result
LOAD result
HALT

View File

@ -4,7 +4,6 @@ import { OpCode } from "./opcode"
export type Bytecode = {
instructions: Instruction[]
constants: Constant[]
labels?: Map<number, string> // Maps instruction index to label name
}
export type Instruction = {
@ -241,12 +240,5 @@ export function toBytecode(str: string): Bytecode /* throws */ {
})
}
// Invert labels map: name->index becomes index->name for debugger display
const labelsByIndex = new Map<number, string>()
for (const [name, index] of labels.entries()) {
labelsByIndex.set(index, name)
}
bytecode.labels = labelsByIndex
return bytecode
}

View File

@ -16,13 +16,11 @@ export class VM {
scope: Scope
constants: Constant[] = []
instructions: Instruction[] = []
labels: Map<number, string> = new Map()
nativeFunctions: Map<string, NativeFunction> = new Map()
constructor(bytecode: Bytecode) {
this.instructions = bytecode.instructions
this.constants = bytecode.constants
this.labels = bytecode.labels || new Map()
this.scope = new Scope()
}