import { type Value, type FunctionDef, toValue } from "./value" import { OpCode } from "./opcode" export type Bytecode = { instructions: Instruction[] constants: Constant[] } export type Instruction = { op: OpCode operand?: number | string } export type Constant = | Value | FunctionDef // // Parse bytecode from human-readable string format. // Operand types are determined by prefix/literal: // #42 -> immediate number (e.g., JUMP #5, MAKE_ARRAY #3) // name -> variable/function name (e.g., LOAD x, CALL_NATIVE add) // 42 -> number constant (e.g., PUSH 42) // "str" -> string constant (e.g., PUSH "hello") // 'str' -> string constant (e.g., PUSH 'hello') // true -> boolean constant (e.g., PUSH true) // false -> boolean constant (e.g., PUSH false) // null -> null constant (e.g., PUSH null) // // Function definitions: // MAKE_FUNCTION (x y) #7 -> basic function // MAKE_FUNCTION (x y=42) #7 -> with defaults // MAKE_FUNCTION (x ...rest) #7 -> variadic // MAKE_FUNCTION (x @named) #7 -> named // function parseFunctionParams(paramStr: string, constants: Constant[]): { params: string[] defaults: Record variadic: boolean named: boolean } { const params: string[] = [] const defaults: Record = {} let variadic = false let named = false // Remove parens and split by whitespace const paramList = paramStr.slice(1, -1).trim() if (!paramList) { return { params, defaults, variadic, named: named } } const parts = paramList.split(/\s+/) for (const part of parts) { // Check for named args (@name) if (part.startsWith('@')) { named = true params.push(part.slice(1)) } else if (part.startsWith('...')) { // Check for variadic (...name) variadic = true params.push(part.slice(3)) } else if (part.includes('=')) { // Check for default value (name=value) const [name, defaultValue] = part.split('=').map(s => s.trim()) params.push(name!) // Parse default value and add to constants if (/^-?\d+(\.\d+)?$/.test(defaultValue!)) { constants.push(toValue(parseFloat(defaultValue!))) } else if (/^['\"].*['\"]$/.test(defaultValue!)) { constants.push(toValue(defaultValue!.slice(1, -1))) } else if (defaultValue === 'true') { constants.push(toValue(true)) } else if (defaultValue === 'false') { constants.push(toValue(false)) } else if (defaultValue === 'null') { constants.push(toValue(null)) } else { throw new Error(`Invalid default value: ${defaultValue}`) } defaults[name!] = constants.length - 1 } else { params.push(part) } } return { params, defaults, variadic, named: named } } export function toBytecode(str: string): Bytecode /* throws */ { const lines = str.trim().split("\n") const bytecode: Bytecode = { instructions: [], constants: [] } for (let line of lines) { // Strip semicolon comments const commentIndex = line.indexOf(';') if (commentIndex !== -1) { line = line.slice(0, commentIndex) } const trimmed = line.trim() if (!trimmed) continue const [op, ...rest] = trimmed.split(/\s+/) const opCode = OpCode[op as keyof typeof OpCode] if (opCode === undefined) { throw new Error(`Unknown opcode: ${op}`) } let operandValue: number | string | undefined = undefined if (rest.length > 0) { const operand = rest.join(' ') // Special handling for MAKE_FUNCTION with paren syntax if (opCode === OpCode.MAKE_FUNCTION && operand.startsWith('(')) { // Parse: MAKE_FUNCTION (params) #body const match = operand.match(/^(\(.*?\))\s+(#-?\d+)$/) if (!match) { throw new Error(`Invalid MAKE_FUNCTION syntax: ${operand}`) } const paramStr = match[1]! const bodyStr = match[2]! const body = parseInt(bodyStr.slice(1)) const { params, defaults, variadic, named } = parseFunctionParams(paramStr, bytecode.constants) // Add function definition to constants bytecode.constants.push({ type: 'function_def', params, defaults, body, variadic, named }) operandValue = bytecode.constants.length - 1 } else if (operand.startsWith('#')) { // immediate number operandValue = parseInt(operand.slice(1)) } else if (/^['"].*['"]$/.test(operand)) { // string const stringValue = operand.slice(1, operand.length - 1) bytecode.constants.push(toValue(stringValue)) operandValue = bytecode.constants.length - 1 } else if (/^-?\d+(\.\d+)?$/.test(operand)) { // number bytecode.constants.push(toValue(parseFloat(operand))) operandValue = bytecode.constants.length - 1 } else if (operand === 'true' || operand === 'false') { // boolean bytecode.constants.push(toValue(operand === 'true')) operandValue = bytecode.constants.length - 1 } else if (operand === 'null') { // null bytecode.constants.push(toValue(null)) operandValue = bytecode.constants.length - 1 } else if (/^[a-zA-Z_].*$/.test(operand)) { // variable operandValue = operand } else { throw new Error(`Invalid operand: ${operand}`) } } bytecode.instructions.push({ op: opCode, operand: operandValue }) } return bytecode }