#!/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}${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. Use ${colors.bright}Tab${colors.reset} for autocomplete.`) console.log(`\nCommands:`) 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() { // Get all opcode names for autocomplete const opcodes = Object.keys(OpCode) .filter(key => isNaN(Number(key))) // Filter out numeric keys (enum values) const commands = ['clear', 'reset', 'exit', 'quit'] const completions = [...opcodes, ...commands] // Autocomplete function function completer(line: string) { const hits = completions.filter(c => c.toLowerCase().startsWith(line.toLowerCase())) return [hits.length ? hits : completions, line] } const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: `${colors.green}reef>${colors.reset} `, completer, }) 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()