This commit is contained in:
Chris Wanstrath 2025-10-07 21:53:28 -07:00
parent fd447abea8
commit ec53a6420e
3 changed files with 299 additions and 0 deletions

View File

@ -23,6 +23,14 @@ bun test <file> # Run specific test file
bun test --watch # Watch mode
```
### Tools
```bash
./bin/reef <file.reef> # Execute bytecode file
./bin/validate <file.reef> # Validate bytecode
./bin/debug <file.reef> # Step-by-step debugger
./bin/repl # Interactive REPL
```
### Building
No build step required - Bun runs TypeScript directly.

View File

@ -22,6 +22,14 @@ Run the simple debugger to see what the instructions are doing:
./bin/debug examples/loop.reef
./bin/debug -h examples/loop.reef
Interactive REPL for exploring instructions:
./bin/repl
Type opcodes interactively and see the stack and variables update in real-time.
Commands: `clear`, `reset`, `exit`.
## Features
- Stack operations (PUSH, POP, DUP)

283
bin/repl Executable file
View File

@ -0,0 +1,283 @@
#!/usr/bin/env bun
import { VM, toBytecode } from "../src/index"
import { OpCode } from "../src/opcode"
import type { Value } from "../src/value"
import * as readline from 'node:readline'
// 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',
}
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(' ')
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) {
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) {
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 displayHistory(instructions: string[], vm: VM) {
console.log(`\n${colors.bright}Instructions:${colors.reset}`)
if (instructions.length === 0) {
console.log(` ${colors.dim}[none yet]${colors.reset}`)
} else {
instructions.forEach((_, i) => {
console.log(` ${colors.dim}[${i}]${colors.reset} ${formatInstructionAt(vm, i)}`)
})
}
}
function displayState(vm: VM) {
console.log(`\n${colors.bright}Stack:${colors.reset}`)
console.log(formatStack(vm.stack))
console.log(`\n${colors.bright}Variables:${colors.reset}`)
console.log(formatVariables(vm.scope))
if (vm.callStack.length > 0) {
console.log(`\n${colors.bright}Call Stack Depth:${colors.reset} ${colors.cyan}${vm.callStack.length}${colors.reset}`)
}
if (vm.exceptionHandlers.length > 0) {
console.log(`${colors.bright}Exception Handlers:${colors.reset} ${colors.cyan}${vm.exceptionHandlers.length}${colors.reset}`)
}
}
function clearScreen() {
console.write('\x1b[2J\x1b[H')
}
function showWelcome() {
clearScreen()
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log(`${colors.bright}🪸 ReefVM REPL${colors.reset}`)
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log(`\nType instructions one at a time. Commands:`)
console.log(` ${colors.bright}clear${colors.reset} - Clear screen and reset state`)
console.log(` ${colors.bright}reset${colors.reset} - Reset VM state (keep history visible)`)
console.log(` ${colors.bright}exit${colors.reset} - Quit REPL`)
console.log(`\nExamples:`)
console.log(` ${colors.cyan}PUSH 42${colors.reset}`)
console.log(` ${colors.cyan}PUSH 10${colors.reset}`)
console.log(` ${colors.cyan}ADD${colors.reset}`)
console.log(` ${colors.cyan}STORE result${colors.reset}`)
console.log()
}
async function repl() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: `${colors.green}reef>${colors.reset} `,
})
let instructions: string[] = []
let vm: VM | null = null
showWelcome()
rl.prompt()
rl.on('line', async (line: string) => {
let trimmed = line.trim()
// Handle empty lines
if (!trimmed) {
rl.prompt()
return
}
// Handle commands
if (trimmed === 'exit' || trimmed === 'quit') {
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
process.exit(0)
}
if (trimmed === 'clear') {
instructions = []
vm = null
showWelcome()
rl.prompt()
return
}
if (trimmed === 'reset') {
instructions = []
vm = null
console.log(`\n${colors.yellow}VM reset${colors.reset}`)
rl.prompt()
return
}
// lowercase -> uppercase (cuz we're lazy)
if (/^[a-z]\s*/.test(trimmed)) {
const parts = trimmed.split(' ')
trimmed = [parts[0].toUpperCase(), ...parts.slice(1)].join(' ')
}
// Add instruction and execute
instructions.push(trimmed)
try {
// Rebuild bytecode with all instructions
const bytecodeStr = instructions.join('\n')
const bytecode = toBytecode(bytecodeStr)
vm = new VM(bytecode)
// Execute all instructions
while (!vm.stopped && vm.pc < vm.instructions.length) {
const instruction = vm.instructions[vm.pc]!
await vm.execute(instruction)
vm.pc++
}
// Display current state
clearScreen()
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
console.log(`${colors.bright}🪸 ReefVM REPL${colors.reset}`)
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
displayHistory(instructions, vm)
displayState(vm)
console.log(`\n${colors.dim}${'─'.repeat(63)}${colors.reset}`)
} catch (error: any) {
console.log(`\n${colors.red}Error: ${error.message}${colors.reset}`)
// Remove the failed instruction
instructions.pop()
}
rl.prompt()
})
rl.on('close', () => {
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
process.exit(0)
})
// Handle Ctrl+C gracefully
rl.on('SIGINT', () => {
console.log(`\n${colors.yellow}Use 'exit' to quit${colors.reset}`)
rl.prompt()
})
}
// Main
await repl()