From 9d06c90e417b6c962ab2817252c7929f395642ce Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Mon, 4 Mar 2024 16:11:25 -0500 Subject: [PATCH] run functions --- vm/vm.go | 115 +++++++++++++++++++++++++++++++++++++++++--------- vm/vm_test.go | 90 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 21 deletions(-) diff --git a/vm/vm.go b/vm/vm.go index a9c1989..3b835b7 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -9,30 +9,54 @@ import ( const StackSize = 2048 const GlobalsSize = 65536 +const MaxFrames = 1024 var Null = &object.Null{} var True = &object.Boolean{Value: true} var False = &object.Boolean{Value: false} +type Frame struct { + fn *object.CompiledFunction + ip int +} + +func NewFrame(fn *object.CompiledFunction) *Frame { + return &Frame{fn: fn, ip: -1} +} + +func (f *Frame) Instructions() code.Instructions { + return f.fn.Instructions +} + type VM struct { - constants []object.Object - instructions code.Instructions + constants []object.Object stack []object.Object sp int // Always points to the next value. Top of stack is stack[sp-1] globals []object.Object + + frames []*Frame + framesIndex int } func New(bytecode *compiler.Bytecode) *VM { + mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} + mainFrame := NewFrame(mainFn) + + frames := make([]*Frame, MaxFrames) + frames[0] = mainFrame + return &VM{ - constants: bytecode.Constants, - instructions: bytecode.Instructions, + constants: bytecode.Constants, stack: make([]object.Object, StackSize), sp: 0, globals: make([]object.Object, GlobalsSize), + + frames: frames, + framesIndex: 1, } } @@ -42,18 +66,40 @@ func NewWithGlobalState(bytecode *compiler.Bytecode, s []object.Object) *VM { return vm } +func (vm *VM) currentFrame() *Frame { + return vm.frames[vm.framesIndex-1] +} + +func (vm *VM) pushFrame(f *Frame) { + vm.frames[vm.framesIndex] = f + vm.framesIndex++ +} + +func (vm *VM) popFrame() *Frame { + vm.framesIndex-- + return vm.frames[vm.framesIndex] +} + func (vm *VM) LastPoppedStackElem() object.Object { return vm.stack[vm.sp] } func (vm *VM) Run() error { - for ip := 0; ip < len(vm.instructions); ip++ { - op := code.Opcode(vm.instructions[ip]) + var ip int + var ins code.Instructions + var op code.Opcode + + for vm.currentFrame().ip < len(vm.currentFrame().Instructions())-1 { + vm.currentFrame().ip++ + + ip = vm.currentFrame().ip + ins = vm.currentFrame().Instructions() + op = code.Opcode(ins[ip]) switch op { case code.OpConstant: - constIndex := code.ReadUint16(vm.instructions[ip+1:]) - ip += 2 + constIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 err := vm.push(vm.constants[constIndex]) if err != nil { @@ -100,16 +146,16 @@ func (vm *VM) Run() error { } case code.OpJump: - pos := int(code.ReadUint16(vm.instructions[ip+1:])) - ip = pos - 1 + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip = pos - 1 case code.OpJumpNotTruthy: - pos := int(code.ReadUint16(vm.instructions[ip+1:])) - ip += 2 + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 condition := vm.pop() if !isTruthy(condition) { - ip = pos - 1 + vm.currentFrame().ip = pos - 1 } case code.OpNull: @@ -119,14 +165,14 @@ func (vm *VM) Run() error { } case code.OpSetGlobal: - globalIndex := code.ReadUint16(vm.instructions[ip+1:]) - ip += 2 + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 vm.globals[globalIndex] = vm.pop() case code.OpGetGlobal: - globalIndex := code.ReadUint16(vm.instructions[ip+1:]) - ip += 2 + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 err := vm.push(vm.globals[globalIndex]) if err != nil { @@ -134,8 +180,8 @@ func (vm *VM) Run() error { } case code.OpArray: - numElements := int(code.ReadUint16(vm.instructions[ip+1:])) - ip += 2 + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 array := vm.buildArray(vm.sp-numElements, vm.sp) vm.sp = vm.sp - numElements @@ -146,8 +192,8 @@ func (vm *VM) Run() error { } case code.OpHash: - numElements := int(code.ReadUint16(vm.instructions[ip+1:])) - ip += 2 + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 hash, err := vm.buildHash(vm.sp-numElements, vm.sp) if err != nil { @@ -169,6 +215,33 @@ func (vm *VM) Run() error { return err } + case code.OpCall: + fn, ok := vm.stack[vm.sp-1].(*object.CompiledFunction) + if !ok { + return fmt.Errorf("calling non-function") + } + frame := NewFrame(fn) + vm.pushFrame(frame) + + case code.OpReturnValue: + returnValue := vm.pop() + vm.popFrame() + vm.pop() + + err := vm.push(returnValue) + if err != nil { + return err + } + + case code.OpReturn: + vm.popFrame() + vm.pop() + + err := vm.push(Null) + if err != nil { + return err + } + } } diff --git a/vm/vm_test.go b/vm/vm_test.go index dca7e02..adba853 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -296,3 +296,93 @@ func TestIndexExpressions(t *testing.T) { runVmTests(t, tests) } + +func TestCallingFunctionsWithoutArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let fivePlusTen = fn() { 5 + 10; }; + fivePlusTen(); + `, + expected: 15, + }, + { + input: ` + let one = fn() { 1; }; + let two = fn() { 2; }; + one() + two() + `, + expected: 3, + }, + { + input: ` + let a = fn() { 1 }; + let b = fn() { a() + 1 }; + let c = fn() { b() + 1 }; + c(); + `, + expected: 3, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithReturnStatements(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let earlyExit = fn() { return 99; 100; }; + earlyExit(); + `, + expected: 99, + }, + { + input: ` + let earlyExit = fn() { return 99; return 100; }; + earlyExit(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let noReturn = fn() { }; + noReturn(); + `, + expected: Null, + }, + { + input: ` + let noReturn = fn() { }; + let noReturnTwo = fn() { noReturn(); }; + noReturn(); + noReturnTwo(); + `, + expected: Null, + }, + } + + runVmTests(t, tests) +} + +func TestFirstClassFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let returnsOne = fn() { 1; }; + let returnsOneReturner = fn() { returnsOne; }; + returnsOneReturner()(); + `, + expected: 1, + }, + } + + runVmTests(t, tests) +}