runningwater 3 лет назад
Родитель
Сommit
7a67feb4ee
4 измененных файлов с 430 добавлено и 0 удалено
  1. 134 0
      code/code.go
  2. 78 0
      code/code_test.go
  3. 93 0
      compiler/compiler.go
  4. 125 0
      compiler/compiler_test.go

+ 134 - 0
code/code.go

@@ -0,0 +1,134 @@
+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 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
+)
+
+// 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: {Name: "OpConstant", OperandWidths: []int{2}},
+}
+
+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)
+}

+ 78 - 0
code/code_test.go

@@ -0,0 +1,78 @@
+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}},
+	}
+
+	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(OpConstant, 1),
+		Make(OpConstant, 2),
+		Make(OpConstant, 65535),
+	}
+
+	expected := `0000 OpConstant 1
+0003 OpConstant 2
+0006 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])
+			}
+		}
+	}
+}

+ 93 - 0
compiler/compiler.go

@@ -0,0 +1,93 @@
+// Author: simon
+// Author: ynwdlxm@163.com
+// Date: 2022/10/19 14:39
+// Desc: Compiler
+
+package compiler
+
+import (
+	"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
+		}
+
+	case *ast.InfixExpression:
+		err := c.Compile(node.Left)
+		if err != nil {
+			return err
+		}
+		err = c.Compile(node.Right)
+		if err != nil {
+			return err
+		}
+
+	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
+}

+ 125 - 0
compiler/compiler_test.go

@@ -0,0 +1,125 @@
+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),
+			},
+		},
+	}
+
+	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 {
+	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()
+}