From 92ac1679faa641aaab0f3ed1c25d0a2c162d21fb Mon Sep 17 00:00:00 2001 From: xubo Date: Mon, 9 Apr 2018 16:22:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0:=20=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v2/exprvm/cmd.go | 64 ++++++++++++++++++++++++++++++++ v2/exprvm/compiler.go | 75 ++++++++++++++++++++++++++++++++++++++ v2/exprvm/compiler_test.go | 23 ++++++++++++ v2/exprvm/error.go | 8 ++++ v2/exprvm/opcode.go | 37 +++++++++++++++++++ v2/exprvm/stack.go | 65 +++++++++++++++++++++++++++++++++ v2/exprvm/vm.go | 71 ++++++++++++++++++++++++++++++++++++ 7 files changed, 343 insertions(+) create mode 100644 v2/exprvm/cmd.go create mode 100644 v2/exprvm/compiler.go create mode 100644 v2/exprvm/compiler_test.go create mode 100644 v2/exprvm/error.go create mode 100644 v2/exprvm/opcode.go create mode 100644 v2/exprvm/stack.go create mode 100644 v2/exprvm/vm.go diff --git a/v2/exprvm/cmd.go b/v2/exprvm/cmd.go new file mode 100644 index 00000000..7283c6c6 --- /dev/null +++ b/v2/exprvm/cmd.go @@ -0,0 +1,64 @@ +package exprvm + +import ( + "fmt" + "strings" +) + +type Command struct { + Type Opcode + + Operand []interface{} +} + +func (self *Command) String() string { + + var sb strings.Builder + + sb.WriteString(self.Type.String()) + + if len(self.Operand) > 0 { + sb.WriteString(" ") + for index, operand := range self.Operand { + if index > 0 { + sb.WriteString(", ") + } + sb.WriteString(fmt.Sprintf("%v", operand)) + } + } + + return sb.String() +} + +type Chunk struct { + Commands []Command +} + +func (self *Chunk) String() string { + + var sb strings.Builder + + for _, cmd := range self.Commands { + + sb.WriteString(fmt.Sprintf("%s\n", cmd.String())) + } + + return sb.String() +} + +func (self *Chunk) AddCode(t Opcode) { + + self.Commands = append(self.Commands, Command{Type: t}) +} + +func (self *Chunk) AddCodeOperand(t Opcode, operand ...interface{}) { + + self.Commands = append(self.Commands, Command{ + Type: t, + Operand: operand, + }) +} + +func newChunk() *Chunk { + return &Chunk{} +} diff --git a/v2/exprvm/compiler.go b/v2/exprvm/compiler.go new file mode 100644 index 00000000..482c3b49 --- /dev/null +++ b/v2/exprvm/compiler.go @@ -0,0 +1,75 @@ +package exprvm + +import ( + "go/ast" + "go/parser" + "go/token" + "strconv" +) + +func Compile(src string) (*Chunk, error) { + + node, err := parser.ParseExpr(src) + if err != nil { + return nil, err + } + + ast.Print(nil, node) + + ck := newChunk() + + if err := walkTree(ck, node); err != nil { + return nil, err + } + + return ck, nil +} + +func walkTree(ck *Chunk, node ast.Node) (err error) { + + switch thisNode := node.(type) { + case *ast.BinaryExpr: + if err = walkTree(ck, thisNode.X); err != nil { + return + } + + if err = walkTree(ck, thisNode.Y); err != nil { + return + } + + switch thisNode.Op { + case token.ADD: + ck.AddCode(Opcode_Add) + case token.SUB: + ck.AddCode(Opcode_Sub) + case token.MUL: + ck.AddCode(Opcode_Mul) + case token.QUO: + ck.AddCode(Opcode_Div) + } + case *ast.BasicLit: // 字面量/常数 + + switch thisNode.Kind { + case token.INT: + + v, err := strconv.Atoi(thisNode.Value) + if err != nil { + return err + } + + ck.AddCodeOperand(Opcode_Push, v) + default: + return ErrUnknownOperandType + } + case *ast.ParenExpr: + return walkTree(ck, thisNode.X) + case *ast.UnaryExpr: + walkTree(ck, thisNode.X) + ck.AddCode(Opcode_Minus) + case *ast.Ident: // 变量/常量 + default: + return ErrUnknownExpression + } + + return nil +} diff --git a/v2/exprvm/compiler_test.go b/v2/exprvm/compiler_test.go new file mode 100644 index 00000000..810fbc8c --- /dev/null +++ b/v2/exprvm/compiler_test.go @@ -0,0 +1,23 @@ +package exprvm + +import ( + "testing" +) + +func TestCompiler(t *testing.T) { + + code := `-2+1` + + ck, err := Compile(code) + if err != nil { + t.Log(err) + t.FailNow() + } + + t.Log(ck) + + vm := NewMachine() + vm.Run(ck) + + t.Log(vm.DataStack.String()) +} diff --git a/v2/exprvm/error.go b/v2/exprvm/error.go new file mode 100644 index 00000000..615fbaa9 --- /dev/null +++ b/v2/exprvm/error.go @@ -0,0 +1,8 @@ +package exprvm + +import "github.com/pkg/errors" + +var ( + ErrUnknownExpression = errors.New("Unknown Expression") + ErrUnknownOperandType = errors.New("Unknown Operand Type") +) diff --git a/v2/exprvm/opcode.go b/v2/exprvm/opcode.go new file mode 100644 index 00000000..f099853a --- /dev/null +++ b/v2/exprvm/opcode.go @@ -0,0 +1,37 @@ +package exprvm + +type Opcode int + +const ( + Opcode_Nop Opcode = iota + Opcode_Push + Opcode_Add + Opcode_Sub + Opcode_Mul + Opcode_Div + Opcode_Minus + Opcode_Exit +) + +func (self Opcode) String() string { + switch self { + case Opcode_Nop: + return "Nop" + case Opcode_Push: + return "Push" + case Opcode_Add: + return "Add" + case Opcode_Sub: + return "Sub" + case Opcode_Mul: + return "Mul" + case Opcode_Div: + return "Div" + case Opcode_Minus: + return "Minus" + case Opcode_Exit: + return "Exit" + } + + return "Unknown" +} diff --git a/v2/exprvm/stack.go b/v2/exprvm/stack.go new file mode 100644 index 00000000..117c0107 --- /dev/null +++ b/v2/exprvm/stack.go @@ -0,0 +1,65 @@ +package exprvm + +import ( + "fmt" + "strings" +) + +type node struct { + value interface{} + prev *node +} +type Stack struct { + top *node + length int +} + +func (self *Stack) String() string { + + var sb strings.Builder + + index := 0 + for i := self.top; i != nil; i = i.prev { + sb.WriteString(fmt.Sprintf("[%d] %v\n", index, i.value)) + index++ + } + + return sb.String() +} + +// Create a new stack +func NewStack() *Stack { + return &Stack{nil, 0} +} + +// Return the number of items in the stack +func (this *Stack) Len() int { + return this.length +} + +// View the top item on the stack +func (this *Stack) Peek() interface{} { + if this.length == 0 { + return nil + } + return this.top.value +} + +// Pop the top item of the stack and return it +func (this *Stack) Pop() interface{} { + if this.length == 0 { + return nil + } + + n := this.top + this.top = n.prev + this.length-- + return n.value +} + +// Push a value onto the top of the stack +func (this *Stack) Push(value interface{}) { + n := &node{value, this.top} + this.top = n + this.length++ +} diff --git a/v2/exprvm/vm.go b/v2/exprvm/vm.go new file mode 100644 index 00000000..85147a96 --- /dev/null +++ b/v2/exprvm/vm.go @@ -0,0 +1,71 @@ +package exprvm + +type Machine struct { + DataStack *Stack +} + +func (self *Machine) Run(chunk *Chunk) { + + for pc := 0; pc < len(chunk.Commands); { + + cmd := chunk.Commands[pc] + + if cmd.Type == Opcode_Exit { + break + } + + self.execute(cmd) + + pc++ + } + +} + +func (self *Machine) execute(cmd Command) (err error) { + + switch cmd.Type { + case Opcode_Push: + self.DataStack.Push(cmd.Operand[0]) + case Opcode_Add, Opcode_Sub, Opcode_Div, Opcode_Mul: + v1 := self.DataStack.Pop() + v2 := self.DataStack.Pop() + + result := arithOp(cmd.Type, v2, v1) + + self.DataStack.Push(result) + case Opcode_Minus: + v := self.DataStack.Pop() + + self.DataStack.Push(-v.(int)) + + default: + panic("Unknown opcode") + } + + return nil +} + +func arithOp(op Opcode, a, b interface{}) interface{} { + + an := a.(int) + bn := b.(int) + + switch op { + case Opcode_Add: + return an + bn + case Opcode_Sub: + return an - bn + case Opcode_Mul: + return an * bn + case Opcode_Div: + return an / bn + } + + return nil +} + +func NewMachine() *Machine { + return &Machine{ + DataStack: NewStack(), + } +}