Apply better names for function and files. Add images for doc
This commit is contained in:
22
README.md
22
README.md
@@ -49,3 +49,25 @@ Our AST is represented with a tree of objects of all the same type Node, each no
|
||||
This structure allows a simpler implementation of recursive tree walk, and makes possible an faster and more efficient non-recursive tree walk.
|
||||
|
||||
The tree walk allows callback both for pre-order and post-order processing.
|
||||
|
||||
## Example
|
||||
|
||||
Consider the following source:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
func main() {
|
||||
for a := 0; a < 10000; a++ {
|
||||
if (a & 0x8ff) == 0x800 {
|
||||
println(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
AST: 
|
||||
|
||||
CFG: 
|
||||
|
||||
In CFG, the node labels correspond to the number in left of AST nodes
|
||||
|
||||
1
gi.go
1
gi.go
@@ -9,7 +9,6 @@ func main() {
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println(1)
|
||||
for a := 0; a < 10000; a++ {
|
||||
if (a & 0x8ff) == 0x800 {
|
||||
println(a)
|
||||
|
||||
BIN
images/ast.jpg
Normal file
BIN
images/ast.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
BIN
images/cfg.jpg
Normal file
BIN
images/cfg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -6,8 +6,9 @@ import (
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// Parse src string containing go code and generate AST. Returns the root node.
|
||||
func SrcToAst(src string) *Node {
|
||||
// Ast(src) parses src string containing Go code and generates the corresponding AST.
|
||||
// The AST root node is returned.
|
||||
func Ast(src string) *Node {
|
||||
fset := token.NewFileSet() // positions are relative to fset
|
||||
f, err := parser.ParseFile(fset, "sample.go", src, 0)
|
||||
if err != nil {
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSrcToAst(t *testing.T) {
|
||||
func TestAst(t *testing.T) {
|
||||
src := `
|
||||
package main
|
||||
|
||||
@@ -13,6 +13,6 @@ func main() {
|
||||
println(1)
|
||||
}
|
||||
`
|
||||
root := SrcToAst(src)
|
||||
root := Ast(src)
|
||||
fmt.Println(root.index)
|
||||
}
|
||||
@@ -7,16 +7,17 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// TODO: remove coupling with go/ast and go/token. This should be handled only in SrcToAst
|
||||
// TODO: remove coupling with go/ast and go/token. This should be handled only in Ast()
|
||||
|
||||
// Generate a CFG from AST (wiring successors in AST)
|
||||
func (e *Node) AstToCfg() {
|
||||
// n.Cfg() generates a control flow graph (CFG) from AST (wiring successors in AST)
|
||||
func (e *Node) Cfg() {
|
||||
//var findex int
|
||||
e.Walk(nil, func(n *Node) {
|
||||
switch x := (*n.anode).(type) {
|
||||
case *ast.BlockStmt:
|
||||
wire_child(n)
|
||||
// FIXME: could bypass this node at CFG and wire directly last child
|
||||
n.isnop = true
|
||||
n.isNop = true
|
||||
n.run = nop
|
||||
n.val = n.Child[len(n.Child)-1].val
|
||||
case *ast.IncDecStmt:
|
||||
@@ -31,13 +32,13 @@ func (e *Node) AstToCfg() {
|
||||
case *ast.ExprStmt:
|
||||
wire_child(n)
|
||||
// FIXME: could bypass this node at CFG and wire directly last child
|
||||
n.isnop = true
|
||||
n.isNop = true
|
||||
n.run = nop
|
||||
n.val = n.Child[len(n.Child)-1].val
|
||||
case *ast.ParenExpr:
|
||||
wire_child(n)
|
||||
// FIXME: could bypass this node at CFG and wire directly last child
|
||||
n.isnop = true
|
||||
n.isNop = true
|
||||
n.run = nop
|
||||
n.val = n.Child[len(n.Child)-1].val
|
||||
case *ast.BinaryExpr:
|
||||
@@ -54,7 +55,7 @@ func (e *Node) AstToCfg() {
|
||||
wire_child(n)
|
||||
n.run = call
|
||||
case *ast.IfStmt:
|
||||
n.isnop = true
|
||||
n.isNop = true
|
||||
n.run = nop
|
||||
n.Start = n.Child[0].Start
|
||||
n.Child[1].snext = n
|
||||
@@ -68,7 +69,7 @@ func (e *Node) AstToCfg() {
|
||||
n.Child[0].next[0] = n
|
||||
}
|
||||
case *ast.ForStmt:
|
||||
n.isnop = true
|
||||
n.isNop = true
|
||||
n.run = nop
|
||||
// FIXME: works only if for node has 4 children
|
||||
n.Start = n.Child[0].Start
|
||||
@@ -78,6 +79,7 @@ func (e *Node) AstToCfg() {
|
||||
n.Child[3].snext = n.Child[2].Start
|
||||
n.Child[2].snext = n.Child[1].Start
|
||||
case *ast.BasicLit:
|
||||
n.isConst = true
|
||||
// FIXME: values must be converted to int or float if possible
|
||||
if v, err := strconv.ParseInt(x.Value, 0, 0); err == nil {
|
||||
*n.val = v
|
||||
@@ -10,57 +10,68 @@ type RunFun func(n *Node, i *Interpreter)
|
||||
|
||||
// Structure for AST and CFG
|
||||
type Node struct {
|
||||
Child []*Node // child subtrees
|
||||
anc *Node // ancestor
|
||||
Start *Node // entry point in subtree (CFG)
|
||||
snext *Node // successor (CFG)
|
||||
next [2]*Node // conditional successors, for false and for true (CFG)
|
||||
index int // node index (dot display)
|
||||
run RunFun // function to run at CFG execution
|
||||
val *interface{} // pointer on generic value (CFG execution)
|
||||
ident string // set if node is a var or func
|
||||
isnop bool // node is a no op
|
||||
anode *ast.Node // original ast node (temporary, will be removed)
|
||||
Child []*Node // child subtrees
|
||||
anc *Node // ancestor
|
||||
Start *Node // entry point in subtree (CFG)
|
||||
snext *Node // successor (CFG)
|
||||
next [2]*Node // conditional successors, for false and for true (CFG)
|
||||
index int // node index (dot display)
|
||||
findex int // index of value in frame
|
||||
run RunFun // function to run at CFG execution
|
||||
val *interface{} // pointer on generic value (CFG execution)
|
||||
ident string // set if node is a var or func
|
||||
isNop bool // node run function us a no-op
|
||||
isConst bool // node value is a constant
|
||||
anode *ast.Node // original ast node (temporary, will be removed)
|
||||
}
|
||||
|
||||
// Interpreter execution context
|
||||
// A Frame contains values for the current execution level
|
||||
type Frame struct {
|
||||
up *Frame // up level in call stack
|
||||
down *Frame // down in call stack
|
||||
val []interface{} // array of values
|
||||
}
|
||||
|
||||
// Interpreter contains global resources and state
|
||||
type Interpreter struct {
|
||||
sym map[string]*interface{}
|
||||
res interface{}
|
||||
sym map[string]*interface{}
|
||||
constant map[string]*interface{}
|
||||
frame *Frame
|
||||
out interface{}
|
||||
}
|
||||
|
||||
// Returns true if node is a leaf in the AST
|
||||
// n.isLeaf() returns true if Node n is a leaf in the AST
|
||||
func (n *Node) isLeaf() bool {
|
||||
return len((*n).Child) == 0
|
||||
}
|
||||
|
||||
// Walk AST in depth first order, call 'in' function at node entry and
|
||||
// 'out' function at node exit.
|
||||
func (n *Node) Walk(in func(n *Node), out func(n *Node)) {
|
||||
if in != nil {
|
||||
in(n)
|
||||
// n.Walk(cbin, cbout) traverses AST n in depth first order, call cbin function
|
||||
// at node entry and cbout function at node exit.
|
||||
func (n *Node) Walk(cbin func(n *Node), cbout func(n *Node)) {
|
||||
if cbin != nil {
|
||||
cbin(n)
|
||||
}
|
||||
for _, Child := range n.Child {
|
||||
Child.Walk(in, out)
|
||||
Child.Walk(cbin, cbout)
|
||||
}
|
||||
if out != nil {
|
||||
out(n)
|
||||
if cbout != nil {
|
||||
cbout(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and return a new interpreter object
|
||||
// NewInterpreter()creates and returns a new interpreter object
|
||||
func NewInterpreter() *Interpreter {
|
||||
return &Interpreter{sym: make(map[string]*interface{})}
|
||||
}
|
||||
|
||||
//
|
||||
// i.Eval(s) evaluates Go code represented as a string
|
||||
func (i *Interpreter) Eval(src string) interface{} {
|
||||
root := SrcToAst(src)
|
||||
root.AstDot()
|
||||
cfg_entry := root.Child[1].Child[2] // FIXME: entry point should be resolved from 'main' name
|
||||
cfg_entry.AstToCfg()
|
||||
cfg_entry.OptimCfg()
|
||||
//cfg_entry.CfgDot()
|
||||
i.RunCfg(cfg_entry.Start)
|
||||
return i.res
|
||||
root := Ast(src)
|
||||
//root.AstDot()
|
||||
entry := root.Child[1].Child[2] // FIXME: entry point should be resolved from 'main' name
|
||||
entry.Cfg()
|
||||
entry.OptimCfg()
|
||||
//entry.CfgDot()
|
||||
i.Run(entry.Start)
|
||||
return i.out
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package interp
|
||||
|
||||
func ExampleRunCfg_1() {
|
||||
func ExampleEval_1() {
|
||||
src := `
|
||||
package main
|
||||
|
||||
@@ -8,15 +8,12 @@ func main() {
|
||||
println(1)
|
||||
}
|
||||
`
|
||||
root := SrcToAst(src)
|
||||
cfg_entry := root.Child[1].Child[2] // FIXME: entry point should be resolved from 'main' name
|
||||
AstToCfg(cfg_entry)
|
||||
RunCfg(cfg_entry.Start)
|
||||
NewInterpreter().Eval(src)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleRunCfg_2() {
|
||||
func ExampleEval_2() {
|
||||
src := `
|
||||
package main
|
||||
|
||||
@@ -30,10 +27,7 @@ func main() {
|
||||
}
|
||||
`
|
||||
|
||||
root := SrcToAst(src)
|
||||
cfg_entry := root.Child[1].Child[2] // FIXME: entry point should be resolved from 'main' name
|
||||
AstToCfg(cfg_entry)
|
||||
RunCfg(cfg_entry.Start)
|
||||
NewInterpreter().Eval(src)
|
||||
// Output:
|
||||
// 1
|
||||
// 2048
|
||||
@@ -2,7 +2,7 @@ package interp
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (i *Interpreter) RunCfg(entry *Node) {
|
||||
func (i *Interpreter) Run(entry *Node) {
|
||||
for n := entry; n != nil; {
|
||||
n.run(n, i)
|
||||
if n.snext != nil {
|
||||
@@ -28,14 +28,6 @@ func assign(n *Node, i *Interpreter) {
|
||||
n.val = i.sym[name]
|
||||
}
|
||||
|
||||
func cond_branch(n *Node, i *Interpreter) {
|
||||
if (*n.val).(bool) {
|
||||
n.snext = n.next[1]
|
||||
} else {
|
||||
n.snext = n.next[0]
|
||||
}
|
||||
}
|
||||
|
||||
func and(n *Node, i *Interpreter) {
|
||||
for _, child := range n.Child {
|
||||
if child.ident != "" {
|
||||
Reference in New Issue
Block a user