Repository: d5/tengo (go)

Issue:
Symbol Table and Closure
Hi,

I encountered following behavior at v2.6
```go
f := undefined
if true {
    a := 10
    f = func() {
        a = 20
    }
}
b := 5
f()
fmt := import("fmt")
fmt.println(b)
```
Output
```
20
```
Expected
```
5
```
Symbol table is forked before If statement and symbols next index is reset to the state before forking after If statement finished in compiler, because of that variable `a` and variable  `b` has same symbol index and captured variable in `f` refers to wrong variable. What do you think?

Pull request description:
fix issues with symbols in global scope blocks
Fixes #317 .


Patch:
diff --git a/symbol_table.go b/symbol_table.go
index a1f25c7..73aaad3 100644
--- a/symbol_table.go
+++ b/symbol_table.go
@@ -44,6 +44,17 @@ func (t *SymbolTable) Define(name string) *Symbol {
 
 	if t.Parent(true) == nil {
 		symbol.Scope = ScopeGlobal
+
+		// if symbol is defined in a block of global scope, symbol index must
+		// be tracked at the root-level table instead.
+		if p := t.parent; p != nil {
+			for p.parent != nil {
+				p = p.parent
+			}
+			t.numDefinition--
+			p.numDefinition++
+		}
+
 	} else {
 		symbol.Scope = ScopeLocal
 	}


Test patch:
diff --git a/compiler_test.go b/compiler_test.go
index 55801a4..0fe9312 100644
--- a/compiler_test.go
+++ b/compiler_test.go
@@ -1180,7 +1180,7 @@ if a := 1; a {
 			tengo.MakeInstruction(parser.OpConstant, 2),
 			tengo.MakeInstruction(parser.OpSetGlobal, 0),
 			tengo.MakeInstruction(parser.OpGetGlobal, 0),
-			tengo.MakeInstruction(parser.OpSetGlobal, 1),
+			tengo.MakeInstruction(parser.OpSetGlobal, 2),
 			tengo.MakeInstruction(parser.OpSuspend)),
 		objectsArray(
 			intObject(1),
diff --git a/vm_test.go b/vm_test.go
index d5437a5..feb91f4 100644
--- a/vm_test.go
+++ b/vm_test.go
@@ -1804,7 +1804,93 @@ out = (func() {
 	`, nil, 2)
 }
 
+func TestBlocksInGlobalScope(t *testing.T) {
+	expectRun(t, `
+f := undefined
+if true {
+	a := 1
+	f = func() {
+		a = 2
+	}
+}
+b := 3
+f()
+out = b`,
+		nil, 3)
+
+	expectRun(t, `
+func() {
+	f := undefined
+	if true {
+		a := 10
+		f = func() {
+			a = 20
+		}
+	}
+	b := 5
+	f()
+	out = b
+}()
+	`,
+		nil, 5)
+
+	expectRun(t, `
+f := undefined
+if true {
+	a := 1
+	b := 2
+	f = func() {
+		a = 3
+		b = 4
+	}
+}
+c := 5
+d := 6
+f()
+out = c + d`,
+		nil, 11)
+
+	expectRun(t, `
+fn := undefined
+if true {
+	a := 1
+	b := 2
+	if true {
+		c := 3
+		d := 4
+		fn = func() {
+			a = 5
+			b = 6
+			c = 7
+			d = 8
+		}
+	}
+}
+e := 9
+f := 10
+fn()
+out = e + f`,
+		nil, 19)
+
+	expectRun(t, `
+out = 0
+func() {
+	for x in [1, 2, 3] {
+		out += x
+	}
+}()`,
+		nil, 6)
+
+	expectRun(t, `
+out = 0
+for x in [1, 2, 3] {
+	out += x
+}`,
+		nil, 6)
+}
+
 func TestIf(t *testing.T) {
+
 	expectRun(t, `if (true) { out = 10 }`, nil, 10)
 	expectRun(t, `if (false) { out = 10 }`, nil, tengo.UndefinedValue)
 	expectRun(t, `if (false) { out = 10 } else { out = 20 }`, nil, 20)