Skip to content

Commit

Permalink
fix: execute global variables in the correct order
Browse files Browse the repository at this point in the history
* fix: constant definition loop on out of order vars

* fix: do not wire global varDecl

* fix: wire and execute global vars

* chore: add tests

* fix: refactor and lint
  • Loading branch information
nrwiersma committed Jul 9, 2020
1 parent 16ff52a commit 98eacf3
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 17 deletions.
13 changes: 13 additions & 0 deletions _test/var12.go
@@ -0,0 +1,13 @@
package main

var (
a = b
b = "hello"
)

func main() {
println(a)
}

// Output:
// hello
23 changes: 23 additions & 0 deletions _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!
10 changes: 10 additions & 0 deletions _test/var14.go
@@ -0,0 +1,10 @@
package main

import "github.com/containous/yaegi/_test/vars"

func main() {
println(vars.A)
}

// Output:
// hello world!
14 changes: 14 additions & 0 deletions _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
}
6 changes: 6 additions & 0 deletions _test/vars/second.go
@@ -0,0 +1,6 @@
package vars

var (
B = concat(" ", C, "!")
D = "world"
)
141 changes: 127 additions & 14 deletions interp/cfg.go
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 != "":
Expand Down
6 changes: 3 additions & 3 deletions interp/gta.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
7 changes: 7 additions & 0 deletions interp/interp.go
Expand Up @@ -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)
}
Expand Down
3 changes: 3 additions & 0 deletions interp/run.go
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions interp/src.go
Expand Up @@ -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)
Expand Down

0 comments on commit 98eacf3

Please sign in to comment.