diff --git a/code/code.go b/code/code.go index 3d6997b..5c72efe 100644 --- a/code/code.go +++ b/code/code.go @@ -12,6 +12,7 @@ type Opcode byte const ( OpConstant Opcode = iota + OpAdd ) type Definition struct { @@ -21,6 +22,7 @@ type Definition struct { var definitions = map[Opcode]*Definition{ OpConstant: {"OpConstant", []int{2}}, + OpAdd: {"OpAdd", []int{}}, } func Lookup(op byte) (*Definition, error) { @@ -88,6 +90,8 @@ func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { } switch operandCount { + case 0: + return def.Name case 1: return fmt.Sprintf("%s %d", def.Name, operands[0]) } diff --git a/code/code_test.go b/code/code_test.go index 0d1a6e4..47d8246 100644 --- a/code/code_test.go +++ b/code/code_test.go @@ -9,6 +9,7 @@ func TestMake(t *testing.T) { expected []byte }{ {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, } for _, tt := range test { @@ -28,14 +29,14 @@ func TestMake(t *testing.T) { func TestInstructions(t *testing.T) { instructions := []Instructions{ - Make(OpConstant, 1), + Make(OpAdd), Make(OpConstant, 2), Make(OpConstant, 65535), } - expected := `0000 OpConstant 1 -0003 OpConstant 2 -0006 OpConstant 65535 + expected := `0000 OpAdd +0001 OpConstant 2 +0004 OpConstant 65535 ` concatted := Instructions{} diff --git a/compiler/compiler.go b/compiler/compiler.go index 3475874..b47d10f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1,6 +1,7 @@ package compiler import ( + "fmt" "monkey/ast" "monkey/code" "monkey/object" @@ -45,6 +46,13 @@ func (c *Compiler) Compile(node ast.Node) error { return err } + switch node.Operator { + case "+": + c.emit(code.OpAdd) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + case *ast.IntegerLiteral: integer := &object.Integer{Value: node.Value} c.emit(code.OpConstant, c.addConstant(integer)) diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 3a50232..8ce7939 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -24,6 +24,7 @@ func TestIntegerArithmetic(t *testing.T) { expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), }, }, } diff --git a/vm/vm.go b/vm/vm.go new file mode 100644 index 0000000..b3e1a5b --- /dev/null +++ b/vm/vm.go @@ -0,0 +1,80 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 + +type VM struct { + constants []object.Object + instructions code.Instructions + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] +} + +func New(bytecode *compiler.Bytecode) *VM { + return &VM{ + constants: bytecode.Constants, + instructions: bytecode.Instructions, + + stack: make([]object.Object, StackSize), + sp: 0, + } +} + +func (vm *VM) StackTop() object.Object { + if vm.sp == 0 { + return nil + } + return vm.stack[vm.sp-1] +} + +func (vm *VM) Run() error { + for ip := 0; ip < len(vm.instructions); ip++ { + op := code.Opcode(vm.instructions[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpAdd: + right := vm.pop() + left := vm.pop() + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + result := leftValue + rightValue + vm.push(&object.Integer{Value: result}) + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} diff --git a/vm/vm_test.go b/vm/vm_test.go new file mode 100644 index 0000000..7ef09e1 --- /dev/null +++ b/vm/vm_test.go @@ -0,0 +1,81 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.StackTop() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func testExpectedObject(t *testing.T, expected interface{}, actual object.Object) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testIntegerObject(expected int64, actual object.Object) interface{} { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v", actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected) + } + + return nil +} + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + } + + runVmTests(t, tests) +}