From 47f829fcada71655f0d40ec363b5bcc844af8856 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Mon, 20 Oct 2025 09:31:19 -0700 Subject: [PATCH] A 100% AI written debug function --- src/format.ts | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 +- 2 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/format.ts diff --git a/src/format.ts b/src/format.ts new file mode 100644 index 0000000..7d0158b --- /dev/null +++ b/src/format.ts @@ -0,0 +1,202 @@ +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}` +} diff --git a/src/index.ts b/src/index.ts index 99c2a72..0c23d00 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,4 +10,5 @@ export async function run(bytecode: Bytecode, functions?: Record