forked from defunkt/ReefVM
add MAKE_FUNCTION to bytecode text format
This commit is contained in:
parent
45e4c29df4
commit
8198c555ac
|
|
@ -23,6 +23,74 @@ export type Constant =
|
||||||
// 42 -> number constant (e.g., PUSH 42)
|
// 42 -> number constant (e.g., PUSH 42)
|
||||||
// "str" -> string constant (e.g., PUSH "hello")
|
// "str" -> string constant (e.g., PUSH "hello")
|
||||||
// 'str' -> string constant (e.g., PUSH 'hello')
|
// 'str' -> string constant (e.g., PUSH 'hello')
|
||||||
|
//
|
||||||
|
// 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 -> kwargs
|
||||||
|
//
|
||||||
|
|
||||||
|
function parseFunctionParams(paramStr: string, constants: Constant[]): {
|
||||||
|
params: string[]
|
||||||
|
defaults: Record<string, number>
|
||||||
|
variadic: boolean
|
||||||
|
named: boolean
|
||||||
|
} {
|
||||||
|
const params: string[] = []
|
||||||
|
const defaults: Record<string, number> = {}
|
||||||
|
let variadic = false
|
||||||
|
let kwargs = false
|
||||||
|
|
||||||
|
// Remove parens and split by whitespace
|
||||||
|
const paramList = paramStr.slice(1, -1).trim()
|
||||||
|
if (!paramList) {
|
||||||
|
return { params, defaults, variadic, named: kwargs }
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = paramList.split(/\s+/)
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
// Check for named args (@name)
|
||||||
|
if (part.startsWith('@')) {
|
||||||
|
kwargs = 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: kwargs }
|
||||||
|
}
|
||||||
|
|
||||||
export function toBytecode(str: string): Bytecode /* throws */ {
|
export function toBytecode(str: string): Bytecode /* throws */ {
|
||||||
const lines = str.trim().split("\n")
|
const lines = str.trim().split("\n")
|
||||||
|
|
||||||
|
|
@ -47,7 +115,33 @@ export function toBytecode(str: string): Bytecode /* throws */ {
|
||||||
if (rest.length > 0) {
|
if (rest.length > 0) {
|
||||||
const operand = rest.join(' ')
|
const operand = rest.join(' ')
|
||||||
|
|
||||||
if (operand.startsWith('#')) {
|
// 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: kwargs } = parseFunctionParams(paramStr, bytecode.constants)
|
||||||
|
|
||||||
|
// Add function definition to constants
|
||||||
|
bytecode.constants.push({
|
||||||
|
type: 'function_def',
|
||||||
|
params,
|
||||||
|
defaults,
|
||||||
|
body,
|
||||||
|
variadic,
|
||||||
|
kwargs
|
||||||
|
})
|
||||||
|
|
||||||
|
operandValue = bytecode.constants.length - 1
|
||||||
|
}
|
||||||
|
else if (operand.startsWith('#')) {
|
||||||
// immediate number
|
// immediate number
|
||||||
operandValue = parseInt(operand.slice(1))
|
operandValue = parseInt(operand.slice(1))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { test, expect } from "bun:test"
|
import { test, expect } from "bun:test"
|
||||||
import { toBytecode } from "#bytecode"
|
import { toBytecode } from "#bytecode"
|
||||||
import { OpCode } from "#opcode"
|
import { OpCode } from "#opcode"
|
||||||
|
import { VM } from "#vm"
|
||||||
|
|
||||||
test("string compilation", () => {
|
test("string compilation", () => {
|
||||||
const str = `
|
const str = `
|
||||||
|
|
@ -21,3 +22,111 @@ test("string compilation", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("MAKE_FUNCTION - basic function", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_FUNCTION () #3
|
||||||
|
CALL #0
|
||||||
|
HALT
|
||||||
|
PUSH 42
|
||||||
|
RETURN
|
||||||
|
`)
|
||||||
|
|
||||||
|
const vm = new VM(bytecode)
|
||||||
|
const result = await vm.run()
|
||||||
|
expect(result).toEqual({ type: 'number', value: 42 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("MAKE_FUNCTION - function with parameters", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_FUNCTION (x y) #5
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
CALL #2
|
||||||
|
HALT
|
||||||
|
LOAD x
|
||||||
|
LOAD y
|
||||||
|
ADD
|
||||||
|
RETURN
|
||||||
|
`)
|
||||||
|
|
||||||
|
const vm = new VM(bytecode)
|
||||||
|
const result = await vm.run()
|
||||||
|
expect(result).toEqual({ type: 'number', value: 30 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("MAKE_FUNCTION - function with default parameters", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_FUNCTION (x y=100) #4
|
||||||
|
PUSH 10
|
||||||
|
CALL #1
|
||||||
|
HALT
|
||||||
|
LOAD x
|
||||||
|
LOAD y
|
||||||
|
ADD
|
||||||
|
RETURN
|
||||||
|
`)
|
||||||
|
|
||||||
|
const vm = new VM(bytecode)
|
||||||
|
const result = await vm.run()
|
||||||
|
expect(result).toEqual({ type: 'number', value: 110 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("MAKE_FUNCTION - tail recursive countdown", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_FUNCTION (n) #6
|
||||||
|
STORE countdown
|
||||||
|
LOAD countdown
|
||||||
|
PUSH 5
|
||||||
|
CALL #1
|
||||||
|
HALT
|
||||||
|
LOAD n
|
||||||
|
PUSH 0
|
||||||
|
EQ
|
||||||
|
JUMP_IF_FALSE #2
|
||||||
|
PUSH "done"
|
||||||
|
RETURN
|
||||||
|
LOAD countdown
|
||||||
|
LOAD n
|
||||||
|
PUSH 1
|
||||||
|
SUB
|
||||||
|
TAIL_CALL #1
|
||||||
|
`)
|
||||||
|
|
||||||
|
const vm = new VM(bytecode)
|
||||||
|
const result = await vm.run()
|
||||||
|
expect(result).toEqual({ type: 'string', value: 'done' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("MAKE_FUNCTION - multiple default values", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_FUNCTION (a=1 b=2 c=3) #3
|
||||||
|
CALL #0
|
||||||
|
HALT
|
||||||
|
LOAD a
|
||||||
|
LOAD b
|
||||||
|
LOAD c
|
||||||
|
ADD
|
||||||
|
ADD
|
||||||
|
RETURN
|
||||||
|
`)
|
||||||
|
|
||||||
|
const vm = new VM(bytecode)
|
||||||
|
const result = await vm.run()
|
||||||
|
expect(result).toEqual({ type: 'number', value: 6 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("MAKE_FUNCTION - default with string", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_FUNCTION (name="World") #3
|
||||||
|
CALL #0
|
||||||
|
HALT
|
||||||
|
LOAD name
|
||||||
|
RETURN
|
||||||
|
`)
|
||||||
|
|
||||||
|
const vm = new VM(bytecode)
|
||||||
|
const result = await vm.run()
|
||||||
|
expect(result).toEqual({ type: 'string', value: 'World' })
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user