diff --git a/_test/var12.go b/_test/var12.go new file mode 100644 index 000000000..01f2d91a2 --- /dev/null +++ b/_test/var12.go @@ -0,0 +1,13 @@ +package main + +var ( + a = b + b = "hello" +) + +func main() { + println(a) +} + +// Output: +// hello diff --git a/_test/var13.go b/_test/var13.go new file mode 100644 index 000000000..3550d0a56 --- /dev/null +++ b/_test/var13.go @@ -0,0 +1,23 @@ +package main + +var ( + a = concat("hello", b) + b = concat(" ", c, "!") + c = d + d = "world" +) + +func concat(a ...string) string { + var s string + for _, ss := range a { + s += ss + } + return s +} + +func main() { + println(a) +} + +// Output: +// hello world! diff --git a/_test/var14.go b/_test/var14.go new file mode 100644 index 000000000..7abe0499a --- /dev/null +++ b/_test/var14.go @@ -0,0 +1,10 @@ +package main + +import "github.com/containous/yaegi/_test/vars" + +func main() { + println(vars.A) +} + +// Output: +// hello world! diff --git a/_test/vars/first.go b/_test/vars/first.go new file mode 100644 index 000000000..0ccb7442e --- /dev/null +++ b/_test/vars/first.go @@ -0,0 +1,14 @@ +package vars + +var ( + A = concat("hello", B) + C = D +) + +func concat(a ...string) string { + var s string + for _, ss := range a { + s += ss + } + return s +} diff --git a/_test/vars/second.go b/_test/vars/second.go new file mode 100644 index 000000000..fff3aded2 --- /dev/null +++ b/_test/vars/second.go @@ -0,0 +1,6 @@ +package vars + +var ( + B = concat(" ", C, "!") + D = "world" +) diff --git a/interp/cfg.go b/interp/cfg.go index fdfefee8e..b4e93d684 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -845,7 +845,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) { } sc = sc.pop() - case constDecl, varDecl: + case constDecl: + wireChild(n) + + case varDecl: + // Global varDecl do not need to be wired as this + // will be handled after cfg. + if n.anc.kind == fileStmt { + break + } wireChild(n) case declStmt, exprStmt, sendStmt: @@ -1033,7 +1041,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) { } case fileStmt: - wireChild(n) + wireChild(n, varDecl) sc = sc.pop() n.findex = -1 @@ -1941,6 +1949,90 @@ func genRun(nod *node) error { return err } +func genGlobalVars(roots []*node, sc *scope) (*node, error) { + var vars []*node + for _, n := range roots { + vars = append(vars, getVars(n)...) + } + + if len(vars) == 0 { + return nil, nil + } + + varNode, err := genGlobalVarDecl(vars, sc) + if err != nil { + return nil, err + } + setExec(varNode.start) + return varNode, nil +} + +func getVars(n *node) (vars []*node) { + for _, child := range n.child { + if child.kind == varDecl { + vars = append(vars, child.child...) + } + } + return vars +} + +func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) { + varNode := &node{kind: varDecl, action: aNop, gen: nop} + + deps := map[*node][]*node{} + for _, n := range nodes { + deps[n] = getVarDependencies(n, sc) + } + + inited := map[*node]bool{} + revisit := []*node{} + for { + for _, n := range nodes { + canInit := true + for _, d := range deps[n] { + if !inited[d] { + canInit = false + } + } + if !canInit { + revisit = append(revisit, n) + continue + } + + varNode.child = append(varNode.child, n) + inited[n] = true + } + + if len(revisit) == 0 || equalNodes(nodes, revisit) { + break + } + + nodes = revisit + revisit = []*node{} + } + + if len(revisit) > 0 { + return nil, revisit[0].cfgErrorf("variable definition loop") + } + wireChild(varNode) + return varNode, nil +} + +func getVarDependencies(nod *node, sc *scope) (deps []*node) { + nod.Walk(func(n *node) bool { + if n.kind == identExpr { + if sym, _, ok := sc.lookup(n.ident); ok { + if sym.kind != varSym || !sym.global || sym.node == nod { + return false + } + deps = append(deps, sym.node) + } + } + return true + }, nil) + return deps +} + // setFnext sets the cond fnext field to next, propagates it for parenthesis blocks // and sets the action to branch. func setFNext(cond, next *node) { @@ -2000,47 +2092,68 @@ func (n *node) isType(sc *scope) bool { } // wireChild wires AST nodes for CFG in subtree. -func wireChild(n *node) { +func wireChild(n *node, exclude ...nkind) { + child := excludeNodeKind(n.child, exclude) + // Set start node, in subtree (propagated to ancestors by post-order processing) - for _, child := range n.child { - switch child.kind { + for _, c := range child { + switch c.kind { case arrayType, chanType, chanTypeRecv, chanTypeSend, funcDecl, importDecl, mapType, basicLit, identExpr, typeDecl: continue default: - n.start = child.start + n.start = c.start } break } // Chain sequential operations inside a block (next is right sibling) - for i := 1; i < len(n.child); i++ { - switch n.child[i].kind { + for i := 1; i < len(child); i++ { + switch child[i].kind { case funcDecl: - n.child[i-1].tnext = n.child[i] + child[i-1].tnext = child[i] default: - switch n.child[i-1].kind { + switch child[i-1].kind { case breakStmt, continueStmt, gotoStmt, returnStmt: // tnext is already computed, no change default: - n.child[i-1].tnext = n.child[i].start + child[i-1].tnext = child[i].start } } } // Chain subtree next to self - for i := len(n.child) - 1; i >= 0; i-- { - switch n.child[i].kind { + for i := len(child) - 1; i >= 0; i-- { + switch child[i].kind { case arrayType, chanType, chanTypeRecv, chanTypeSend, importDecl, mapType, funcDecl, basicLit, identExpr, typeDecl: continue case breakStmt, continueStmt, gotoStmt, returnStmt: // tnext is already computed, no change default: - n.child[i].tnext = n + child[i].tnext = n } break } } +func excludeNodeKind(child []*node, kinds []nkind) []*node { + if len(kinds) == 0 { + return child + } + var res []*node + for _, c := range child { + exclude := false + for _, k := range kinds { + if c.kind == k { + exclude = true + } + } + if !exclude { + res = append(res, c) + } + } + return res +} + func (n *node) name() (s string) { switch { case n.ident != "": diff --git a/interp/gta.go b/interp/gta.go index 2cedd2054..6dc955e27 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -78,8 +78,8 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error) if typ.isBinMethod { typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true, scope: sc} } - if sc.sym[dest.ident] == nil { - sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val} + if sc.sym[dest.ident] == nil || sc.sym[dest.ident].typ.incomplete { + sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n} } if n.anc.kind == constDecl { sc.sym[dest.ident].kind = constSym @@ -112,7 +112,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error) sym1, exists1 := sc.sym[asImportName] sym2, exists2 := sc.sym[c.ident] if !exists1 && !exists2 { - sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ} + sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n} continue } diff --git a/interp/interp.go b/interp/interp.go index c5ff461b7..c9fe2fa3d 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -405,6 +405,13 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) { // Execute node closures interp.run(root, nil) + // Wire and execute global vars + n, err := genGlobalVars([]*node{root}, interp.scopes[interp.Name]) + if err != nil { + return res, err + } + interp.run(n, nil) + for _, n := range initNodes { interp.run(n, interp.frame) } diff --git a/interp/run.go b/interp/run.go index 7d4a8e764..9db530aad 100644 --- a/interp/run.go +++ b/interp/run.go @@ -84,6 +84,9 @@ func init() { } func (interp *Interpreter) run(n *node, cf *frame) { + if n == nil { + return + } var f *frame if cf == nil { f = interp.frame diff --git a/interp/src.go b/interp/src.go index c6cd46794..7985ce50c 100644 --- a/interp/src.go +++ b/interp/src.go @@ -132,6 +132,13 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) { interp.run(n, nil) } + // Wire and execute global vars + n, err := genGlobalVars(rootNodes, interp.scopes[path]) + if err != nil { + return "", err + } + interp.run(n, nil) + // Add main to list of functions to run, after all inits if m := interp.main(); m != nil { initNodes = append(initNodes, m)