Ver código fonte

Functions in the VM

runningwater 3 anos atrás
pai
commit
fbb5327378
3 arquivos alterados com 151 adições e 17 exclusões
  1. 23 0
      vm/frame.go
  2. 78 17
      vm/vm.go
  3. 50 0
      vm/vm_test.go

+ 23 - 0
vm/frame.go

@@ -0,0 +1,23 @@
+// Author: simon
+// Author: ynwdlxm@163.com
+// Date: 2022/11/15 10:57
+// Desc:
+
+package vm
+
+import (
+	"github/runnignwater/monkey/code"
+	"github/runnignwater/monkey/object"
+)
+
+type Frame struct {
+	fn *object.CompileFunction // points to the compiled function
+	ip int                     // the instruction pointer in this frame
+}
+
+func NewFrame(fn *object.CompileFunction) *Frame {
+	return &Frame{fn: fn, ip: -1}
+}
+func (f *Frame) Instructions() code.Instructions {
+	return f.fn.Instructions
+}

+ 78 - 17
vm/vm.go

@@ -14,6 +14,7 @@ import (
 
 const StackSize = 2048
 const GlobalSize = 65535
+const MaxFrames = 1024
 
 var True = &object.Boolean{Value: true}
 var False = &object.Boolean{Value: false}
@@ -27,9 +28,18 @@ type VM struct {
 	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.CompileFunction{Instructions: byteCode.Instructions}
+	mainFrame := NewFrame(mainFn)
+
+	frames := make([]*Frame, MaxFrames)
+	frames[0] = mainFrame
+
 	return &VM{
 		instructions: byteCode.Instructions,
 		constants:    byteCode.Constants,
@@ -38,6 +48,9 @@ func New(byteCode *compiler.ByteCode) *VM {
 		sp:    0,
 
 		globals: make([]object.Object, GlobalSize),
+
+		frames:      frames,
+		framesIndex: 1,
 	}
 }
 
@@ -55,13 +68,21 @@ func NewWithGlobalsStore(byteCode *compiler.ByteCode, s []object.Object) *VM {
 // }
 
 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])
@@ -101,15 +122,15 @@ func (vm *VM) Run() error {
 		case code.OpPop:
 			vm.pop()
 		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:
 			err := vm.push(Null)
@@ -117,13 +138,13 @@ func (vm *VM) Run() error {
 				return err
 			}
 		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 {
@@ -131,8 +152,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 -= numElements
@@ -142,8 +163,8 @@ func (vm *VM) Run() error {
 				return err
 			}
 		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 {
@@ -163,6 +184,31 @@ func (vm *VM) Run() error {
 			if err != nil {
 				return err
 			}
+		case code.OpCall:
+			fn, ok := vm.stack[vm.sp-1].(*object.CompileFunction)
+			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
+			}
 		}
 	}
 
@@ -179,6 +225,21 @@ func isTruthy(obj object.Object) bool {
 		return true
 	}
 }
+
+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) push(o object.Object) error {
 	if vm.sp >= StackSize {
 		return fmt.Errorf("stack overflow")

+ 50 - 0
vm/vm_test.go

@@ -139,6 +139,56 @@ 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 earlyExit = fn () {return 99; 100;};
+              earlyExit();
+			`,
+			expected: 99,
+		},
+	}
+
+	runVmTests(t, tests)
+}
+
+func TestFunctionWithoutReturnValue(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)
+}
+
 func runVmTests(t *testing.T, tests []vmTestCase) {
 	t.Helper()