tmp change - LOAD_NATIVE
This commit is contained in:
parent
62f890e59d
commit
4d2ae1c9fe
|
|
@ -74,7 +74,7 @@ type InstructionTuple =
|
|||
| ["STR_CONCAT", number]
|
||||
|
||||
// Native
|
||||
| ["CALL_NATIVE", string]
|
||||
| ["LOAD_NATIVE", string]
|
||||
|
||||
// Special
|
||||
| ["HALT"]
|
||||
|
|
@ -88,7 +88,7 @@ export type ProgramItem = InstructionTuple | LabelDefinition
|
|||
// Operand types are determined by prefix/literal:
|
||||
// #42 -> immediate number (e.g., JUMP #5, MAKE_ARRAY #3)
|
||||
// .label -> label reference (e.g., JUMP .loop_start, MAKE_FUNCTION (x y) .body)
|
||||
// name -> variable/function name (e.g., LOAD x, CALL_NATIVE add)
|
||||
// name -> variable/function name (e.g., LOAD x, LOAD_NATIVE add)
|
||||
// 42 -> number constant (e.g., PUSH 42)
|
||||
// "str" -> string constant (e.g., PUSH "hello")
|
||||
// 'str' -> string constant (e.g., PUSH 'hello')
|
||||
|
|
@ -336,7 +336,7 @@ function toBytecodeFromArray(program: ProgramItem[]): Bytecode /* throws */ {
|
|||
case "STORE":
|
||||
case "TRY_LOAD":
|
||||
case "TRY_CALL":
|
||||
case "CALL_NATIVE":
|
||||
case "LOAD_NATIVE":
|
||||
operandValue = operand as string
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export enum OpCode {
|
|||
STR_CONCAT, // operand: value count (number) | stack: [val1, ..., valN] → [string] | concatenate N values
|
||||
|
||||
// typescript interop
|
||||
CALL_NATIVE, // operand: function name (identifier) | stack: [...args] → [result] | consumes entire stack
|
||||
LOAD_NATIVE, // operand: function name (identifier) | stack: [] → [function] | load native function
|
||||
|
||||
// special
|
||||
HALT // operand: none | stop execution
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const OPCODES_WITH_OPERANDS = new Set([
|
|||
OpCode.MAKE_DICT,
|
||||
OpCode.STR_CONCAT,
|
||||
OpCode.MAKE_FUNCTION,
|
||||
OpCode.CALL_NATIVE,
|
||||
OpCode.LOAD_NATIVE,
|
||||
])
|
||||
|
||||
const OPCODES_WITHOUT_OPERANDS = new Set([
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Scope } from "./scope"
|
||||
|
||||
type NativeFunction = (...args: Value[]) => Promise<Value> | Value
|
||||
|
||||
export type Value =
|
||||
| { type: 'null', value: null }
|
||||
| { type: 'boolean', value: boolean }
|
||||
|
|
@ -18,6 +20,7 @@ export type Value =
|
|||
named: boolean,
|
||||
value: '<function>'
|
||||
}
|
||||
| { type: 'native_function', fn: NativeFunction, value: '<native>' }
|
||||
|
||||
export type Dict = Map<string, Value>
|
||||
|
||||
|
|
@ -101,6 +104,8 @@ export function toString(v: Value): string {
|
|||
return 'null'
|
||||
case 'function':
|
||||
return '<function>'
|
||||
case 'native_function':
|
||||
return '<native>'
|
||||
case 'array':
|
||||
return `[${v.value.map(toString).join(', ')}]`
|
||||
case 'dict': {
|
||||
|
|
@ -145,6 +150,8 @@ export function isEqual(a: Value, b: Value): boolean {
|
|||
}
|
||||
case 'function':
|
||||
return false // functions never equal
|
||||
case 'native_function':
|
||||
return false // native functions never equal
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
@ -168,6 +175,8 @@ export function fromValue(v: Value): any {
|
|||
return v.value
|
||||
case 'function':
|
||||
return '<function>'
|
||||
case 'native_function':
|
||||
return '<native>'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
38
src/vm.ts
38
src/vm.ts
|
|
@ -430,6 +430,21 @@ export class VM {
|
|||
|
||||
const fn = this.stack.pop()!
|
||||
|
||||
// Handle native functions
|
||||
if (fn.type === 'native_function') {
|
||||
if (namedCount > 0)
|
||||
throw new Error('CALL: native functions do not support named arguments')
|
||||
|
||||
// Mark current frame as break target (like regular CALL does)
|
||||
if (this.callStack.length > 0)
|
||||
this.callStack[this.callStack.length - 1]!.isBreakTarget = true
|
||||
|
||||
// Call the native function with positional args
|
||||
const result = await fn.fn(...positionalArgs)
|
||||
this.stack.push(result)
|
||||
break
|
||||
}
|
||||
|
||||
if (fn.type !== 'function')
|
||||
throw new Error('CALL: not a function')
|
||||
|
||||
|
|
@ -591,27 +606,16 @@ export class VM {
|
|||
this.stack.push(returnValue)
|
||||
break
|
||||
|
||||
case OpCode.CALL_NATIVE:
|
||||
case OpCode.LOAD_NATIVE: {
|
||||
const functionName = instruction.operand as string
|
||||
const tsFunction = this.nativeFunctions.get(functionName)
|
||||
const nativeFunc = this.nativeFunctions.get(functionName)
|
||||
|
||||
if (!tsFunction)
|
||||
throw new Error(`CALL_NATIVE: function not found: ${functionName}`)
|
||||
if (!nativeFunc)
|
||||
throw new Error(`LOAD_NATIVE: function not found: ${functionName}`)
|
||||
|
||||
// Mark current frame as break target (like CALL does)
|
||||
if (this.callStack.length > 0)
|
||||
this.callStack[this.callStack.length - 1]!.isBreakTarget = true
|
||||
|
||||
// Pop all arguments from stack (TypeScript function consumes entire stack)
|
||||
const tsArgs = [...this.stack]
|
||||
this.stack = []
|
||||
|
||||
// Call the TypeScript function and await if necessary
|
||||
const tsResult = await tsFunction(...tsArgs)
|
||||
|
||||
// Push result back onto stack
|
||||
this.stack.push(tsResult)
|
||||
this.stack.push({ type: 'native_function', fn: nativeFunc, value: '<native>' })
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
throw `Unknown op: ${instruction.op}`
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ import { toBytecode } from "#bytecode"
|
|||
describe("functions parameter", () => {
|
||||
test("pass functions to run()", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE add
|
||||
PUSH 5
|
||||
PUSH 3
|
||||
CALL_NATIVE add
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -20,9 +23,12 @@ describe("functions parameter", () => {
|
|||
|
||||
test("pass functions to VM constructor", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE multiply
|
||||
PUSH 10
|
||||
PUSH 2
|
||||
CALL_NATIVE multiply
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -36,11 +42,19 @@ describe("functions parameter", () => {
|
|||
|
||||
test("pass multiple functions", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE add
|
||||
PUSH 10
|
||||
PUSH 5
|
||||
CALL_NATIVE add
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE sum
|
||||
LOAD_NATIVE multiply
|
||||
LOAD sum
|
||||
PUSH 3
|
||||
CALL_NATIVE multiply
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -54,9 +68,12 @@ describe("functions parameter", () => {
|
|||
|
||||
test("auto-wraps native functions", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE concat
|
||||
PUSH "hello"
|
||||
PUSH "world"
|
||||
CALL_NATIVE concat
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -69,8 +86,11 @@ describe("functions parameter", () => {
|
|||
|
||||
test("works with async functions", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE delay
|
||||
PUSH 100
|
||||
CALL_NATIVE delay
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -86,11 +106,19 @@ describe("functions parameter", () => {
|
|||
|
||||
test("can combine with manual registerFunction", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE add
|
||||
PUSH 5
|
||||
PUSH 3
|
||||
CALL_NATIVE add
|
||||
PUSH 2
|
||||
CALL_NATIVE subtract
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE sum
|
||||
LOAD_NATIVE subtract
|
||||
LOAD sum
|
||||
PUSH 2
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -127,8 +155,11 @@ describe("functions parameter", () => {
|
|||
|
||||
test("function throws error", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE divide
|
||||
PUSH 0
|
||||
CALL_NATIVE divide
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -147,16 +178,25 @@ describe("functions parameter", () => {
|
|||
|
||||
test("complex workflow with multiple function calls", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE add
|
||||
PUSH 5
|
||||
PUSH 3
|
||||
CALL_NATIVE add
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE result
|
||||
LOAD_NATIVE multiply
|
||||
LOAD result
|
||||
PUSH 2
|
||||
CALL_NATIVE multiply
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE final
|
||||
LOAD_NATIVE format
|
||||
LOAD final
|
||||
CALL_NATIVE format
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -171,8 +211,11 @@ describe("functions parameter", () => {
|
|||
|
||||
test("function overriding - later registration wins", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE getValue
|
||||
PUSH 5
|
||||
CALL_NATIVE getValue
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ import { VM } from "#vm"
|
|||
import { toBytecode } from "#bytecode"
|
||||
import { toValue, toNumber, toString } from "#value"
|
||||
|
||||
test("CALL_NATIVE - basic function call", async () => {
|
||||
test("LOAD_NATIVE - basic function call", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE add
|
||||
PUSH 5
|
||||
PUSH 10
|
||||
CALL_NATIVE add
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -21,11 +24,14 @@ test("CALL_NATIVE - basic function call", async () => {
|
|||
expect(result).toEqual({ type: 'number', value: 15 })
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - function with string manipulation", async () => {
|
||||
test("LOAD_NATIVE - function with string manipulation", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE concat
|
||||
PUSH "hello"
|
||||
PUSH "world"
|
||||
CALL_NATIVE concat
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -40,10 +46,13 @@ test("CALL_NATIVE - function with string manipulation", async () => {
|
|||
expect(result).toEqual({ type: 'string', value: 'hello world' })
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - async function", async () => {
|
||||
test("LOAD_NATIVE - async function", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE asyncDouble
|
||||
PUSH 42
|
||||
CALL_NATIVE asyncDouble
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -58,9 +67,12 @@ test("CALL_NATIVE - async function", async () => {
|
|||
expect(result).toEqual({ type: 'number', value: 84 })
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - function with no arguments", async () => {
|
||||
test("LOAD_NATIVE - function with no arguments", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
CALL_NATIVE getAnswer
|
||||
LOAD_NATIVE getAnswer
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -73,12 +85,15 @@ test("CALL_NATIVE - function with no arguments", async () => {
|
|||
expect(result).toEqual({ type: 'number', value: 42 })
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - function with multiple arguments", async () => {
|
||||
test("LOAD_NATIVE - function with multiple arguments", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE sum
|
||||
PUSH 2
|
||||
PUSH 3
|
||||
PUSH 4
|
||||
CALL_NATIVE sum
|
||||
PUSH 3
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -92,10 +107,13 @@ test("CALL_NATIVE - function with multiple arguments", async () => {
|
|||
expect(result).toEqual({ type: 'number', value: 9 })
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - function returns array", async () => {
|
||||
test("LOAD_NATIVE - function returns array", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE makeRange
|
||||
PUSH 3
|
||||
CALL_NATIVE makeRange
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -121,20 +139,26 @@ test("CALL_NATIVE - function returns array", async () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - function not found", async () => {
|
||||
test("LOAD_NATIVE - function not found", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
CALL_NATIVE nonexistent
|
||||
LOAD_NATIVE nonexistent
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
expect(vm.run()).rejects.toThrow('CALL_NATIVE: function not found: nonexistent')
|
||||
expect(vm.run()).rejects.toThrow('LOAD_NATIVE: function not found: nonexistent')
|
||||
})
|
||||
|
||||
test("CALL_NATIVE - using result in subsequent operations", async () => {
|
||||
test("LOAD_NATIVE - using result in subsequent operations", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE triple
|
||||
PUSH 5
|
||||
CALL_NATIVE triple
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
PUSH 10
|
||||
ADD
|
||||
`)
|
||||
|
|
@ -151,9 +175,12 @@ test("CALL_NATIVE - using result in subsequent operations", async () => {
|
|||
|
||||
test("Native function wrapping - basic sync function with native types", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE add
|
||||
PUSH 5
|
||||
PUSH 10
|
||||
CALL_NATIVE add
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -169,8 +196,11 @@ test("Native function wrapping - basic sync function with native types", async (
|
|||
|
||||
test("Native function wrapping - async function with native types", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE asyncDouble
|
||||
PUSH 42
|
||||
CALL_NATIVE asyncDouble
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -187,9 +217,12 @@ test("Native function wrapping - async function with native types", async () =>
|
|||
|
||||
test("Native function wrapping - string manipulation", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE concat
|
||||
PUSH "hello"
|
||||
PUSH "world"
|
||||
CALL_NATIVE concat
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -205,8 +238,11 @@ test("Native function wrapping - string manipulation", async () => {
|
|||
|
||||
test("Native function wrapping - with default parameters", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE ls
|
||||
PUSH "/home/user"
|
||||
CALL_NATIVE ls
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -222,8 +258,11 @@ test("Native function wrapping - with default parameters", async () => {
|
|||
|
||||
test("Native function wrapping - returns array", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE makeRange
|
||||
PUSH 3
|
||||
CALL_NATIVE makeRange
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -247,9 +286,12 @@ test("Native function wrapping - returns array", async () => {
|
|||
|
||||
test("Native function wrapping - returns object (becomes dict)", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE makeUser
|
||||
PUSH "Alice"
|
||||
PUSH 30
|
||||
CALL_NATIVE makeUser
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
@ -269,9 +311,17 @@ test("Native function wrapping - returns object (becomes dict)", async () => {
|
|||
|
||||
test("Native function wrapping - mixed with manual Value functions", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE nativeAdd
|
||||
PUSH 5
|
||||
CALL_NATIVE nativeAdd
|
||||
CALL_NATIVE manualDouble
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE sum
|
||||
LOAD_NATIVE manualDouble
|
||||
LOAD sum
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
|
|
|||
|
|
@ -387,9 +387,12 @@ describe("RegExp", () => {
|
|||
test("with native functions", async () => {
|
||||
const { VM } = await import("#vm")
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE match
|
||||
PUSH "hello world"
|
||||
PUSH /world/
|
||||
CALL_NATIVE match
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -407,10 +410,13 @@ describe("RegExp", () => {
|
|||
test("native function with regex replacement", async () => {
|
||||
const { VM } = await import("#vm")
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE replace
|
||||
PUSH "hello world"
|
||||
PUSH /o/g
|
||||
PUSH "0"
|
||||
CALL_NATIVE replace
|
||||
PUSH 3
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
@ -427,9 +433,12 @@ describe("RegExp", () => {
|
|||
test("native function extracting matches", async () => {
|
||||
const { VM } = await import("#vm")
|
||||
const bytecode = toBytecode(`
|
||||
LOAD_NATIVE extractNumbers
|
||||
PUSH "test123abc456"
|
||||
PUSH /\\d+/g
|
||||
CALL_NATIVE extractNumbers
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user