-
Notifications
You must be signed in to change notification settings - Fork 423
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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++ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
} | ||
} |