Skip to content

Commit

Permalink
添加: 表达式计算
Browse files Browse the repository at this point in the history
  • Loading branch information
davyxu committed Apr 9, 2018
1 parent e0f4079 commit 92ac167
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 0 deletions.
64 changes: 64 additions & 0 deletions v2/exprvm/cmd.go
Original file line number Diff line number Diff line change
@@ -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{}
}
75 changes: 75 additions & 0 deletions v2/exprvm/compiler.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions v2/exprvm/compiler_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
8 changes: 8 additions & 0 deletions v2/exprvm/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package exprvm

import "github.com/pkg/errors"

var (
ErrUnknownExpression = errors.New("Unknown Expression")
ErrUnknownOperandType = errors.New("Unknown Operand Type")
)
37 changes: 37 additions & 0 deletions v2/exprvm/opcode.go
Original file line number Diff line number Diff line change
@@ -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"
}
65 changes: 65 additions & 0 deletions v2/exprvm/stack.go
Original file line number Diff line number Diff line change
@@ -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++
}
71 changes: 71 additions & 0 deletions v2/exprvm/vm.go
Original file line number Diff line number Diff line change
@@ -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(),
}
}

0 comments on commit 92ac167

Please sign in to comment.