diff --git a/README.md b/README.md index 9fe04594..315ea852 100644 --- a/README.md +++ b/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: ![ast](images/ast.jpg) + +CFG: ![cfg](images/cfg.jpg) + +In CFG, the node labels correspond to the number in left of AST nodes diff --git a/gi.go b/gi.go index ae97950f..149d731e 100644 --- a/gi.go +++ b/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) diff --git a/images/ast.jpg b/images/ast.jpg new file mode 100644 index 00000000..2d6a982b Binary files /dev/null and b/images/ast.jpg differ diff --git a/images/cfg.jpg b/images/cfg.jpg new file mode 100644 index 00000000..163f4d3f Binary files /dev/null and b/images/cfg.jpg differ diff --git a/interp/src_to_ast.go b/interp/ast.go similarity index 87% rename from interp/src_to_ast.go rename to interp/ast.go index 1fa23290..973a62ae 100644 --- a/interp/src_to_ast.go +++ b/interp/ast.go @@ -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 { diff --git a/interp/src_to_ast_test.go b/interp/ast_test.go similarity index 69% rename from interp/src_to_ast_test.go rename to interp/ast_test.go index 74c88da6..aa99d38d 100644 --- a/interp/src_to_ast_test.go +++ b/interp/ast_test.go @@ -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) } diff --git a/interp/ast_to_cfg.go b/interp/cfg.go similarity index 91% rename from interp/ast_to_cfg.go rename to interp/cfg.go index 281103de..29ba67a1 100644 --- a/interp/ast_to_cfg.go +++ b/interp/cfg.go @@ -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 diff --git a/interp/ast_dot.go b/interp/dot.go similarity index 100% rename from interp/ast_dot.go rename to interp/dot.go diff --git a/interp/interp.go b/interp/interp.go index d069b3f0..57233c1f 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -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 } diff --git a/interp/run_cfg_test.go b/interp/interp_test.go similarity index 50% rename from interp/run_cfg_test.go rename to interp/interp_test.go index 8e9cea9c..c67b50b0 100644 --- a/interp/run_cfg_test.go +++ b/interp/interp_test.go @@ -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 diff --git a/interp/run_cfg.go b/interp/run.go similarity index 91% rename from interp/run_cfg.go rename to interp/run.go index 6036e274..0f1dab0d 100644 --- a/interp/run_cfg.go +++ b/interp/run.go @@ -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 != "" {