import type { Bytecode, Constant } from "./bytecode" import { OpCode } from "./opcode" import type { Value, FunctionDef } from "./value" /** * Converts a Bytecode object back to human-readable string format. * This is the inverse of toBytecode(). */ export const bytecodeToString = (bytecode: Bytecode): string => { const lines: string[] = [] const { instructions, constants, labels } = bytecode for (let i = 0; i < instructions.length; i++) { // Check if there's a label at this position if (labels?.has(i)) { lines.push(`.${labels.get(i)}:`) } const instr = instructions[i]! const opName = OpCode[instr.op] // Get string name from enum // Format based on whether operand exists if (instr.operand === undefined) { lines.push(opName) } else { const operandStr = formatOperand(instr.op, instr.operand, constants, labels, i) lines.push(`${opName} ${operandStr}`) } } return lines.join('\n') } /** * Format an operand based on the opcode type */ const formatOperand = ( op: OpCode, operand: number | string, constants: Constant[], labels: Map | undefined, currentIndex: number ): string => { // Handle string operands (variable names) if (typeof operand === 'string') { return operand } // Handle numeric operands based on opcode switch (op) { case OpCode.PUSH: { // Look up constant value const value = constants[operand] if (!value) { throw new Error(`Invalid constant index: ${operand}`) } return formatConstant(value) } case OpCode.MAKE_FUNCTION: { // Look up function definition and format as (params) .label const funcDef = constants[operand] if (!funcDef || !('type' in funcDef) || funcDef.type !== 'function_def') { throw new Error(`Invalid function definition at constant index: ${operand}`) } return formatFunctionDef(funcDef as FunctionDef, labels, constants) } case OpCode.JUMP: case OpCode.JUMP_IF_FALSE: case OpCode.JUMP_IF_TRUE: { // Convert relative offset to absolute position const targetIndex = currentIndex + 1 + operand const labelName = labels?.get(targetIndex) return labelName ? `.${labelName}` : `#${operand}` } case OpCode.PUSH_TRY: case OpCode.PUSH_FINALLY: { // These use absolute positions const labelName = labels?.get(operand) return labelName ? `.${labelName}` : `#${operand}` } case OpCode.MAKE_ARRAY: case OpCode.MAKE_DICT: case OpCode.STR_CONCAT: // These are just counts return `#${operand}` default: return `#${operand}` } } /** * Format a constant value (from constants pool) */ const formatConstant = (constant: Constant): string => { // Handle function definitions (shouldn't happen in PUSH, but be safe) if ('type' in constant && constant.type === 'function_def') { return '' } // Handle Value types const value = constant as Value switch (value.type) { case 'null': return 'null' case 'boolean': return value.value.toString() case 'number': return value.value.toString() case 'string': // Use single quotes and escape special characters return `'${escapeString(value.value)}'` case 'regex': { // Format as /pattern/flags const pattern = value.value.source const flags = value.value.flags return `/${pattern}/${flags}` } case 'array': { // Format as [item1, item2, ...] const items = value.value.map(formatConstant).join(', ') return `[${items}]` } case 'dict': { // Format as {key1: value1, key2: value2} const entries = Array.from(value.value.entries()) .map(([k, v]) => `${k}: ${formatConstant(v)}`) .join(', ') return `{${entries}}` } case 'function': case 'native': return '' default: return '' } } /** * Escape special characters in strings for output */ const escapeString = (str: string): string => { return str .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') .replace(/\r/g, '\\r') .replace(/\$/g, '\\$') } /** * Format a function definition as (params) .label */ const formatFunctionDef = ( funcDef: FunctionDef, labels: Map | undefined, constants: Constant[] ): string => { const params: string[] = [] for (let i = 0; i < funcDef.params.length; i++) { const paramName = funcDef.params[i]! const defaultIndex = funcDef.defaults[paramName] if (defaultIndex !== undefined) { // Parameter has a default value const defaultValue = constants[defaultIndex] if (!defaultValue) { throw new Error(`Invalid default value index: ${defaultIndex}`) } params.push(`${paramName}=${formatConstant(defaultValue)}`) } else if (i === funcDef.params.length - 1 && funcDef.variadic) { // Last parameter and function is variadic params.push(`...${paramName}`) } else if (i === funcDef.params.length - 1 && funcDef.named) { // Last parameter and function accepts named args params.push(`@${paramName}`) } else { // Regular parameter params.push(paramName) } } // Format body address (prefer label name if available) const bodyLabel = labels?.get(funcDef.body) const bodyStr = bodyLabel ? `.${bodyLabel}` : `#${funcDef.body}` return `(${params.join(' ')}) ${bodyStr}` }