diff --git a/src/programmatic.ts b/src/programmatic.ts deleted file mode 100644 index 969b017..0000000 --- a/src/programmatic.ts +++ /dev/null @@ -1,279 +0,0 @@ -import type { Bytecode, Constant } from "./bytecode" -import { OpCode } from "./opcode" -import { toValue } from "./value" - -// Instruction types -type PrimitiveValue = number | string | boolean | null - -type InstructionTuple = - // Stack - | ["PUSH", PrimitiveValue] - | ["POP"] - | ["DUP"] - - // Variables - | ["LOAD", string] - | ["STORE", string] - - // Arithmetic - | ["ADD"] | ["SUB"] | ["MUL"] | ["DIV"] | ["MOD"] - - // Comparison - | ["EQ"] | ["NEQ"] | ["LT"] | ["GT"] | ["LTE"] | ["GTE"] - - // Logical - | ["NOT"] - - // Control flow - | ["JUMP", string | number] - | ["JUMP_IF_FALSE", string | number] - | ["JUMP_IF_TRUE", string | number] - | ["BREAK"] - - // Exception handling - | ["PUSH_TRY", string | number] - | ["PUSH_FINALLY", string | number] - | ["POP_TRY"] - | ["THROW"] - - // Functions - | ["MAKE_FUNCTION", string[], string | number] - | ["CALL"] - | ["TAIL_CALL"] - | ["RETURN"] - - // Arrays - | ["MAKE_ARRAY", number] - | ["ARRAY_GET"] - | ["ARRAY_SET"] - | ["ARRAY_PUSH"] - | ["ARRAY_LEN"] - - // Dicts - | ["MAKE_DICT", number] - | ["DICT_GET"] - | ["DICT_SET"] - | ["DICT_HAS"] - - // Native - | ["CALL_NATIVE", string] - - // Special - | ["HALT"] - -type LabelDefinition = [string] // Just ".label_name" - -type ProgramItem = InstructionTuple | LabelDefinition - -function isLabelDefinition(item: ProgramItem): item is LabelDefinition { - return item.length === 1 && typeof item[0] === "string" && item[0].startsWith(".") -} - -function isLabelReference(value: string | number): value is string { - return typeof value === "string" && value.startsWith(".") -} - -function parseFunctionParams(params: string[]): { - params: string[] - defaults: Record - variadic: boolean - named: boolean - defaultConstants: Constant[] -} { - const resultParams: string[] = [] - const defaults: Record = {} - const defaultConstants: Constant[] = [] - let variadic = false - let named = false - - for (const param of params) { - if (param.startsWith("@")) { - // Named parameter - named = true - resultParams.push(param.slice(1)) - } else if (param.startsWith("...")) { - // Variadic parameter - variadic = true - resultParams.push(param.slice(3)) - } else if (param.includes("=")) { - // Default parameter - const [name, defaultValue] = param.split("=").map(s => s.trim()) - resultParams.push(name!) - - // Parse default value - if (/^-?\d+(\.\d+)?$/.test(defaultValue!)) { - defaultConstants.push(toValue(parseFloat(defaultValue!))) - } else if (defaultValue === "true") { - defaultConstants.push(toValue(true)) - } else if (defaultValue === "false") { - defaultConstants.push(toValue(false)) - } else if (defaultValue === "null") { - defaultConstants.push(toValue(null)) - } else if (/^['"].*['"]$/.test(defaultValue!)) { - defaultConstants.push(toValue(defaultValue!.slice(1, -1))) - } else { - throw new Error(`Invalid default value: ${defaultValue}`) - } - - defaults[name!] = -1 // Will be fixed after we know constant indices - } else { - // Regular parameter - resultParams.push(param) - } - } - - return { params: resultParams, defaults, variadic, named, defaultConstants } -} - -export function fromInstructions(program: ProgramItem[]): Bytecode { - const constants: Constant[] = [] - const instructions: any[] = [] - const labels = new Map() - - // First pass: collect labels and their positions - const filteredProgram: InstructionTuple[] = [] - for (const item of program) { - if (isLabelDefinition(item)) { - const labelName = item[0].slice(1) // Remove leading "." - labels.set(labelName, filteredProgram.length) - } else { - filteredProgram.push(item as InstructionTuple) - } - } - - // Second pass: build instructions - for (let i = 0; i < filteredProgram.length; i++) { - const item = filteredProgram[i]! - const op = item[0] as string - const opCode = OpCode[op as keyof typeof OpCode] - - if (opCode === undefined) { - throw new Error(`Unknown opcode: ${op}`) - } - - let operandValue: number | string | undefined = undefined - - if (item.length > 1) { - const operand = item[1] - - switch (op) { - case "PUSH": - // Add to constants pool - constants.push(toValue(operand as PrimitiveValue)) - operandValue = constants.length - 1 - break - - case "MAKE_FUNCTION": { - const params = operand as string[] - const body = item[2] - - if (body === undefined) { - throw new Error("MAKE_FUNCTION requires body address") - } - - const { params: resultParams, defaults, variadic, named, defaultConstants } = parseFunctionParams(params) - - // Add default constants to pool and update indices - const defaultIndices: Record = {} - for (const [paramName, _] of Object.entries(defaults)) { - const defaultConst = defaultConstants.shift()! - constants.push(defaultConst) - defaultIndices[paramName] = constants.length - 1 - } - - // Resolve body label or use numeric value - let bodyAddress: number - if (isLabelReference(body)) { - const labelName = body.slice(1) - const labelPos = labels.get(labelName) - if (labelPos === undefined) { - throw new Error(`Undefined label: ${labelName}`) - } - bodyAddress = labelPos - } else { - bodyAddress = body as number - } - - // Add function definition to constants - constants.push({ - type: "function_def", - params: resultParams, - defaults: defaultIndices, - body: bodyAddress, - variadic, - named - }) - - operandValue = constants.length - 1 - break - } - - case "JUMP": - case "JUMP_IF_FALSE": - case "JUMP_IF_TRUE": { - // Relative jump - if (isLabelReference(operand as string | number)) { - const labelName = (operand as string).slice(1) - const labelPos = labels.get(labelName) - if (labelPos === undefined) { - throw new Error(`Undefined label: ${labelName}`) - } - operandValue = labelPos - (i + 1) // Relative offset - } else { - operandValue = operand as number - } - break - } - - case "PUSH_TRY": - case "PUSH_FINALLY": { - // Absolute address - if (isLabelReference(operand as string | number)) { - const labelName = (operand as string).slice(1) - const labelPos = labels.get(labelName) - if (labelPos === undefined) { - throw new Error(`Undefined label: ${labelName}`) - } - operandValue = labelPos - } else { - operandValue = operand as number - } - break - } - - case "LOAD": - case "STORE": - case "CALL_NATIVE": - // String operand - operandValue = operand as string - break - - case "MAKE_ARRAY": - case "MAKE_DICT": - // Numeric operand - operandValue = operand as number - break - - default: - throw new Error(`Unexpected operand for ${op}`) - } - } - - instructions.push({ - op: opCode, - operand: operandValue - }) - } - - // Build labels map for debugger (instruction index -> label name) - const labelsByIndex = new Map() - for (const [name, index] of labels.entries()) { - labelsByIndex.set(index, name) - } - - return { - instructions, - constants, - labels: labelsByIndex - } -}