Przeglądaj źródła

Merge remote-tracking branch 'origin/master'

simon 3 lat temu
rodzic
commit
cdcf2d2356
7 zmienionych plików z 774 dodań i 9 usunięć
  1. 146 0
      code/code.go
  2. 79 0
      code/code_test.go
  3. 108 0
      compiler/compiler.go
  4. 181 0
      compiler/compiler_test.go
  5. 39 9
      repl/repl.go
  6. 126 0
      vm/vm.go
  7. 95 0
      vm/vm_test.go

+ 146 - 0
code/code.go

@@ -0,0 +1,146 @@
+package code
+
+// Author: simon
+// Author: ynwdlxm@163.com
+// Date: 2022/10/19 13:12
+// Desc: bytecode
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+)
+
+type Instructions []byte
+
+// String MiniDisassembler
+func (ins Instructions) String() string {
+	var out bytes.Buffer
+
+	i := 0
+	for i < len(ins) {
+		def, err := Lookup(ins[i])
+		if err != nil {
+			_, err := fmt.Fprintf(&out, "ERROR: %s\n", err)
+			if err != nil {
+				return ""
+			}
+			continue
+		}
+
+		operands, read := ReadOperands(def, ins[i+1:])
+
+		_, err = fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstructions(def, operands))
+		if err != nil {
+			return ""
+		}
+
+		i += 1 + read
+	}
+
+	return out.String()
+}
+
+func (ins Instructions) fmtInstructions(def *Definition, operands []int) string {
+	operandCount := len(def.OperandWidths)
+
+	if len(operands) != operandCount {
+		return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", len(operands), operandCount)
+	}
+
+	switch operandCount {
+	case 0:
+		return def.Name
+	case 1:
+		return fmt.Sprintf("%s %d", def.Name, operands[0])
+	}
+
+	return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
+}
+
+type Opcode byte
+
+const (
+	OpConstant Opcode = iota
+	OpAdd
+	OpSub
+	OpMul
+	OpDiv
+	OpPop
+)
+
+// Definition For debugging and testing purposes
+//
+// it’s handy being able to look up how many operands an opcode has and what its human-readable name is.
+// In order to achieve that, we’ll add proper definitions and some tooling
+//
+// Name helps to make an opcode readable
+// OperandWidths contains the number of bytes each operand takes up
+type Definition struct {
+	Name          string
+	OperandWidths []int
+}
+
+var definitions = map[Opcode]*Definition{
+	OpConstant: {"OpConstant", []int{2}},
+	OpAdd:      {"OpAdd", []int{}}, // doesn't have any operands
+	OpSub:      {"OpSub", []int{}},
+	OpMul:      {"OpMul", []int{}},
+	OpDiv:      {"OpDiv", []int{}},
+	OpPop:      {"OpPop", []int{}},
+}
+
+func Lookup(op byte) (*Definition, error) {
+	def, ok := definitions[Opcode(op)]
+	if !ok {
+		return nil, fmt.Errorf("opcode %d undefined", op)
+	}
+	return def, nil
+}
+
+func Make(op Opcode, operands ...int) []byte {
+	def, ok := definitions[op]
+	if !ok {
+		return []byte{}
+	}
+
+	instructionLen := 1
+	for _, w := range def.OperandWidths {
+		instructionLen += w
+	}
+
+	instruction := make([]byte, instructionLen) // allocate the byte slice
+	instruction[0] = byte(op)
+	offset := 1
+	for i, o := range operands {
+		width := def.OperandWidths[i]
+		switch width {
+		case 2:
+			binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
+		}
+		offset += width
+	}
+
+	return instruction
+}
+
+// ReadOperands reverses everything Make did
+func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
+	operands := make([]int, len(def.OperandWidths))
+	offset := 0
+
+	for i, width := range def.OperandWidths {
+		switch width {
+		case 2:
+			operands[i] = int(ReadUint16(ins[offset:]))
+		}
+
+		offset += width
+	}
+
+	return operands, offset
+}
+
+func ReadUint16(ins Instructions) uint16 {
+	return binary.BigEndian.Uint16(ins)
+}

+ 79 - 0
code/code_test.go

@@ -0,0 +1,79 @@
+package code
+
+import "testing"
+
+func TestMake(t *testing.T) {
+	tests := []struct {
+		op       Opcode
+		operands []int
+		expected []byte
+	}{
+		{OpConstant, []int{65534}, []byte{byte(OpConstant), 0xFF, 0xFE}},
+		{OpAdd, []int{}, []byte{byte(OpAdd)}},
+	}
+
+	for _, tt := range tests {
+		instruction := Make(tt.op, tt.operands...)
+
+		if len(instruction) != len(tt.expected) {
+			t.Errorf("instruction has wrong length. want=%d, got=%d", len(tt.expected), len(instruction))
+		}
+
+		for i, b := range tt.expected {
+			if instruction[i] != tt.expected[i] {
+				t.Errorf("wrong byte at pos %d. want=%d, got=%d", i, b, instruction[i])
+			}
+		}
+	}
+}
+
+func TestInstructionsString(t *testing.T) {
+	instructions := []Instructions{
+		Make(OpAdd),
+		Make(OpConstant, 2),
+		Make(OpConstant, 65535),
+	}
+
+	expected := `0000 OpAdd
+0001 OpConstant 2
+0004 OpConstant 65535
+`
+
+	concatted := Instructions{}
+	for _, ins := range instructions {
+		concatted = append(concatted, ins...)
+	}
+	if concatted.String() != expected {
+		t.Errorf("instructions wrong formatted.\nwant=%q\n got=%q", expected, concatted.String())
+	}
+}
+
+func TestReadOperands(t *testing.T) {
+	tests := []struct {
+		op        Opcode
+		operands  []int
+		bytesRead int
+	}{
+		{OpConstant, []int{65535}, 2},
+	}
+
+	for _, tt := range tests {
+		instruction := Make(tt.op, tt.operands...)
+
+		def, err := Lookup(byte(tt.op))
+		if err != nil {
+			t.Fatalf("definition not found: %g\n", err)
+		}
+
+		operandsRead, n := ReadOperands(def, instruction[1:])
+		if n != tt.bytesRead {
+			t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
+		}
+
+		for i, want := range tt.operands {
+			if operandsRead[i] != want {
+				t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
+			}
+		}
+	}
+}

+ 108 - 0
compiler/compiler.go

@@ -0,0 +1,108 @@
+// Author: simon
+// Author: ynwdlxm@163.com
+// Date: 2022/10/19 14:39
+// Desc: Compiler
+
+package compiler
+
+import (
+	"fmt"
+	"github/runnignwater/monkey/ast"
+	"github/runnignwater/monkey/code"
+	"github/runnignwater/monkey/object"
+)
+
+type Compiler struct {
+	instructions code.Instructions // hold the generated bytecode
+	constants    []object.Object   // slice that serves as our constant pool
+}
+
+func New() *Compiler {
+	return &Compiler{
+		instructions: code.Instructions{},
+		constants:    []object.Object{},
+	}
+}
+
+func (c *Compiler) Compile(node ast.Node) error {
+	switch node := node.(type) {
+	case *ast.Program:
+		for _, s := range node.Statements {
+			err := c.Compile(s)
+			if err != nil {
+				return err
+			}
+		}
+
+	case *ast.ExpressionStatement:
+		err := c.Compile(node.Expression)
+		if err != nil {
+			return err
+		}
+		c.emit(code.OpPop)
+
+	case *ast.InfixExpression:
+		err := c.Compile(node.Left)
+		if err != nil {
+			return err
+		}
+		err = c.Compile(node.Right)
+		if err != nil {
+			return err
+		}
+
+		switch node.Operator {
+		case "+":
+			c.emit(code.OpAdd)
+		case "-":
+			c.emit(code.OpSub)
+		case "*":
+			c.emit(code.OpMul)
+		case "/":
+			c.emit(code.OpDiv)
+		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))
+	}
+	return nil
+}
+
+func (c *Compiler) addConstant(obj object.Object) int {
+	c.constants = append(c.constants, obj)
+	return len(c.constants) - 1
+}
+
+func (c *Compiler) addInstruction(ins []byte) int {
+	posNewInstruction := len(c.instructions)
+	c.instructions = append(c.instructions, ins...)
+	return posNewInstruction
+}
+
+// emit generate an instruction and add it to the results,
+// either by printing it, writing it to a file or
+// by adding it to a collection in memory
+//
+// op code.Opcode
+//
+// operands [operand]int
+func (c *Compiler) emit(op code.Opcode, operands ...int) int {
+	ins := code.Make(op, operands...)
+	pos := c.addInstruction(ins)
+	return pos
+}
+
+func (c *Compiler) ByteCode() *ByteCode {
+	return &ByteCode{
+		Instructions: c.instructions,
+		Constants:    c.constants,
+	}
+}
+
+type ByteCode struct {
+	Instructions code.Instructions
+	Constants    []object.Object
+}

+ 181 - 0
compiler/compiler_test.go

@@ -0,0 +1,181 @@
+package compiler
+
+import (
+	"fmt"
+	"github/runnignwater/monkey/ast"
+	"github/runnignwater/monkey/code"
+	"github/runnignwater/monkey/lexer"
+	"github/runnignwater/monkey/object"
+	"github/runnignwater/monkey/parser"
+	"testing"
+)
+
+type compilerTestCase struct {
+	input                string
+	expectedConstants    []interface{}
+	expectedInstructions []code.Instructions
+}
+
+func TestIntegerArithmetic(t *testing.T) {
+	tests := []compilerTestCase{
+		{
+			input:             "1 + 2",
+			expectedConstants: []interface{}{1, 2},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpAdd),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "1;2",
+			expectedConstants: []interface{}{1, 2},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpPop),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "1 - 2",
+			expectedConstants: []interface{}{1, 2},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpSub),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "1 * 2",
+			expectedConstants: []interface{}{1, 2},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpMul),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "2 / 1",
+			expectedConstants: []interface{}{2, 1},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpDiv),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "5 * (2 + 10)",
+			expectedConstants: []interface{}{5, 2, 10},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpConstant, 2),
+				code.Make(code.OpAdd),
+				code.Make(code.OpMul),
+				code.Make(code.OpPop),
+			},
+		},
+	}
+
+	runCompilerTests(t, tests)
+}
+
+func runCompilerTests(t *testing.T, tests []compilerTestCase) {
+	t.Helper()
+
+	for _, tt := range tests {
+		program := parse(tt.input)
+
+		compiler := New()
+		err := compiler.Compile(program)
+		if err != nil {
+			t.Fatalf("compiler error: %s", err)
+		}
+
+		bytecode := compiler.ByteCode()
+		err = testInstructions(tt.expectedInstructions, bytecode.Instructions)
+		if err != nil {
+			t.Fatalf("testInstructions failed: %s", err)
+		}
+
+		err = testConstants(t, tt.expectedConstants, bytecode.Constants)
+		if err != nil {
+			t.Fatalf("testConstants failed: %s", err)
+		}
+	}
+}
+
+func testConstants(
+	t *testing.T,
+	expected []interface{},
+	actual []object.Object,
+) error {
+	t.Helper()
+
+	if len(expected) != len(actual) {
+		return fmt.Errorf("wrong number of constants. got=%d, want=%d", len(actual), len(expected))
+	}
+
+	for i, constant := range expected {
+		switch constant := constant.(type) {
+		case int:
+			err := testIntegerObject(int64(constant), actual[i])
+			if err != nil {
+				return fmt.Errorf("constant %d -- testIntegerObject failed: %s",
+					i, err)
+			}
+		}
+	}
+	return nil
+}
+
+func testIntegerObject(expected int64, actual object.Object) error {
+	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 testInstructions(
+	expected []code.Instructions,
+	actual code.Instructions,
+) error {
+	concatted := concatInstructions(expected)
+
+	if len(actual) != len(concatted) {
+		return fmt.Errorf("wrong instructions length.\nwant=%q\ngot=%q", concatted, actual)
+	}
+
+	for i, ins := range concatted {
+		if actual[i] != ins {
+			return fmt.Errorf("wrong instructions at %d.\nwant=%q\ngot=%q", i, concatted, actual)
+		}
+	}
+	return nil
+}
+
+func concatInstructions(s []code.Instructions) code.Instructions {
+	out := code.Instructions{}
+
+	for _, ins := range s {
+		out = append(out, ins...)
+	}
+	return out
+}
+
+func parse(input string) *ast.Program {
+	l := lexer.New(input)
+	p := parser.New(l)
+	return p.ParseProgram()
+}

+ 39 - 9
repl/repl.go

@@ -3,10 +3,10 @@ package repl
 import (
 	"bufio"
 	"fmt"
-	"github/runnignwater/monkey/evaluator"
+	"github/runnignwater/monkey/compiler"
 	"github/runnignwater/monkey/lexer"
-	"github/runnignwater/monkey/object"
 	"github/runnignwater/monkey/parser"
+	"github/runnignwater/monkey/vm"
 	"io"
 	"log"
 )
@@ -22,7 +22,7 @@ const PROMPT = ">>> "
 
 func Start(in io.Reader, out io.Writer) {
 	scanner := bufio.NewScanner(in)
-	env := object.NewEnvironment()
+	// env := object.NewEnvironment()
 
 	for {
 		fmt.Printf(PROMPT)
@@ -40,14 +40,44 @@ func Start(in io.Reader, out io.Writer) {
 			continue
 		}
 
-		evaluated := evaluator.Eval(program, env)
-		if evaluated != nil {
-			if _, err := io.WriteString(out, evaluated.Inspect()); err != nil {
-				panic(err)
+		// evaluated := evaluator.Eval(program, env)
+		// if evaluated != nil {
+		// 	if _, err := io.WriteString(out, evaluated.Inspect()); err != nil {
+		// 		panic(err)
+		// 	}
+		// 	if _, err := io.WriteString(out, "\n"); err != nil {
+		// 		panic(err)
+		// 	}
+		// }
+
+		comp := compiler.New()
+		err := comp.Compile(program)
+		if err != nil {
+			_, err := fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err)
+			if err != nil {
+				return
 			}
-			if _, err := io.WriteString(out, "\n"); err != nil {
-				panic(err)
+			continue
+		}
+
+		machine := vm.New(comp.ByteCode())
+		err = machine.Run()
+		if err != nil {
+			_, err := fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err)
+			if err != nil {
+				return
 			}
+			continue
+		}
+
+		lastPopped := machine.LastPopStackElem()
+		_, err = io.WriteString(out, lastPopped.Inspect())
+		if err != nil {
+			return
+		}
+		_, err = io.WriteString(out, "\n")
+		if err != nil {
+			return
 		}
 	}
 }

+ 126 - 0
vm/vm.go

@@ -0,0 +1,126 @@
+// Author: simon
+// Author: ynwdlxm@163.com
+// Date: 2022/10/22 13:55
+// Desc: Stack VM implement
+
+package vm
+
+import (
+	"fmt"
+	"github/runnignwater/monkey/code"
+	"github/runnignwater/monkey/compiler"
+	"github/runnignwater/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{
+		instructions: byteCode.Instructions,
+		constants:    byteCode.Constants,
+
+		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, code.OpSub, code.OpMul, code.OpDiv:
+			err := vm.executeBinaryOperation(op)
+			if err != nil {
+				return err
+			}
+		case code.OpPop:
+			vm.pop()
+		}
+	}
+
+	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
+}
+
+func (vm *VM) executeBinaryOperation(op code.Opcode) error {
+	right := vm.pop()
+	left := vm.pop()
+
+	leftType := left.Type()
+	rightType := right.Type()
+	if leftType == object.IntegerObj && rightType == object.IntegerObj {
+		return vm.executeBinaryIntegerOperation(op, left, right)
+	}
+
+	return fmt.Errorf("unsupported types of binary operation: %s %s", leftType, rightType)
+}
+
+func (vm *VM) LastPopStackElem() object.Object {
+	return vm.stack[vm.sp]
+}
+
+func (vm *VM) executeBinaryIntegerOperation(
+	op code.Opcode,
+	left, right object.Object,
+) error {
+	leftValue := left.(*object.Integer).Value
+	rightValue := right.(*object.Integer).Value
+
+	var result int64
+
+	switch op {
+	case code.OpAdd:
+		result = leftValue + rightValue
+	case code.OpSub:
+		result = leftValue - rightValue
+	case code.OpMul:
+		result = leftValue * rightValue
+	case code.OpDiv:
+		result = leftValue / rightValue
+	default:
+		return fmt.Errorf("unknown integer operator: %d", op)
+	}
+
+	return vm.push(&object.Integer{Value: result})
+}

+ 95 - 0
vm/vm_test.go

@@ -0,0 +1,95 @@
+package vm
+
+import (
+	"fmt"
+	"github/runnignwater/monkey/ast"
+	"github/runnignwater/monkey/compiler"
+	"github/runnignwater/monkey/lexer"
+	"github/runnignwater/monkey/object"
+	"github/runnignwater/monkey/parser"
+	"testing"
+)
+
+type vmTestCase struct {
+	input    string
+	expected interface{}
+}
+
+func TestIntegerArithmetic(t *testing.T) {
+	tests := []vmTestCase{
+		{"1", 1},
+		{"2", 2},
+		{"1 + 2", 3},
+		{"1 - 2", -1},
+		{"1 * 2", 2},
+		{"4 / 2", 2},
+		{"50 / 2 * 2 + 10 - 5", 55},
+		{"5 * (2 + 10)", 60},
+		{"5 + 5 + 5 + 5 - 10", 10},
+		{"2 * 2 * 2 * 2 * 2", 32},
+		{"5 * 2 + 10", 20},
+		{"5 + 2 * 10", 25},
+		{"5 * (2 + 10)", 60},
+	}
+
+	runVmTests(t, tests)
+}
+
+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("compler error: %s", err)
+		}
+
+		vm := New(comp.ByteCode())
+		err = vm.Run()
+		if err != nil {
+			t.Fatalf("vm error: %s", err)
+		}
+
+		stackElem := vm.LastPopStackElem()
+
+		testExpectedObject(t, tt.expected, stackElem)
+	}
+}
+
+func parse(input string) *ast.Program {
+	l := lexer.New(input)
+	p := parser.New(l)
+	return p.ParseProgram()
+}
+
+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 testIntegerObject(expected int64, actual object.Object) error {
+	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
+}