Compare commits
3 Commits
fa55eb7170
...
a885a59140
| Author | SHA1 | Date | |
|---|---|---|---|
| a885a59140 | |||
| 47f829fcad | |||
| 7f4f73dd41 |
202
src/format.ts
Normal file
202
src/format.ts
Normal file
|
|
@ -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<number, string> | 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 '<function_def>'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 '<function>'
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '<unknown>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<number, string> | 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}`
|
||||||
|
}
|
||||||
|
|
@ -11,3 +11,4 @@ export { type Bytecode, toBytecode, type ProgramItem } from "./bytecode"
|
||||||
export { wrapNative } from "./function"
|
export { wrapNative } from "./function"
|
||||||
export { type Value, toValue, toString, toNumber, fromValue, toNull } from "./value"
|
export { type Value, toValue, toString, toNumber, fromValue, toNull } from "./value"
|
||||||
export { VM } from "./vm"
|
export { VM } from "./vm"
|
||||||
|
export { bytecodeToString } from "./format"
|
||||||
|
|
@ -386,7 +386,7 @@ export class VM {
|
||||||
const varName = instruction.operand as string
|
const varName = instruction.operand as string
|
||||||
const value = this.scope.get(varName)
|
const value = this.scope.get(varName)
|
||||||
|
|
||||||
if (value?.type === 'function') {
|
if (value?.type === 'function' || value?.type === 'native') {
|
||||||
this.stack.push(value)
|
this.stack.push(value)
|
||||||
this.stack.push(toValue(0))
|
this.stack.push(toValue(0))
|
||||||
this.stack.push(toValue(0))
|
this.stack.push(toValue(0))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user