Repository: traefik/yaegi (Go)

Issue:
Add call to expose variables declared with Eval
### Proposal

I want a function that lists all of the global variables in `package main`. Specifically, I want a list of variables declared with `Eval("var x = 1")` and `Eval("y := 1")`.

```go
// Globals returns a map of global variables and constants in the main package.
func (*Interpreter) Globals() map[string]reflect.Value
```

### Background

I have developed [an extension](https://gitlab.com/ethan.reesor/vscode-notebooks/go-playbooks) that implements Yaegi-based notebooks for Visual Studio Code. I want to add a variables view, like the Jupyter extension has for their notebooks.

### Workarounds

I don't think there are any. I was going to show a horrible hack using the debugging interface, but I don't think that exposes global variables. The Symbols function only returns exported values.


Discussion:

Patch:
diff --git a/interp/scope.go b/interp/scope.go
index 6f362ac2b..b0c0c76bb 100644
--- a/interp/scope.go
+++ b/interp/scope.go
@@ -241,3 +241,26 @@ func (interp *Interpreter) initScopePkg(pkgID, pkgName string) *scope {
 	interp.mutex.Unlock()
 	return sc
 }
+
+// Globals returns a map of global variables and constants in the main package.
+func (interp *Interpreter) Globals() map[string]reflect.Value {
+	syms := map[string]reflect.Value{}
+	interp.mutex.RLock()
+	defer interp.mutex.RUnlock()
+
+	v, ok := interp.srcPkg["main"]
+	if !ok {
+		return syms
+	}
+
+	for n, s := range v {
+		switch s.kind {
+		case constSym:
+			syms[n] = s.rval
+		case varSym:
+			syms[n] = interp.frame.data[s.index]
+		}
+	}
+
+	return syms
+}


Test patch:
diff --git a/interp/interp_test.go b/interp/interp_test.go
index 7e331b01e..3492e28b9 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -1,9 +1,12 @@
 package interp
 
 import (
+	"go/constant"
 	"log"
 	"reflect"
 	"testing"
+
+	"github.com/traefik/yaegi/stdlib"
 )
 
 func init() { log.SetFlags(log.Lshortfile) }
@@ -19,13 +22,13 @@ func TestIsNatural(t *testing.T) {
 			n: &node{
 				typ: &itype{
 					rtype: func() reflect.Type {
-						var x uint = 3
-						return reflect.TypeOf(x)
+						var a uint = 3
+						return reflect.TypeOf(a)
 					}(),
 				},
 				rval: func() reflect.Value {
-					var x uint = 3
-					return reflect.ValueOf(x)
+					var a uint = 3
+					return reflect.ValueOf(a)
 				}(),
 			},
 			expected: true,
@@ -35,13 +38,13 @@ func TestIsNatural(t *testing.T) {
 			n: &node{
 				typ: &itype{
 					rtype: func() reflect.Type {
-						x := 3
-						return reflect.TypeOf(x)
+						a := 3
+						return reflect.TypeOf(a)
 					}(),
 				},
 				rval: func() reflect.Value {
-					x := 3
-					return reflect.ValueOf(x)
+					a := 3
+					return reflect.ValueOf(a)
 				}(),
 			},
 			expected: true,
@@ -51,13 +54,13 @@ func TestIsNatural(t *testing.T) {
 			n: &node{
 				typ: &itype{
 					rtype: func() reflect.Type {
-						var x int = 3
-						return reflect.TypeOf(x)
+						var a int = 3
+						return reflect.TypeOf(a)
 					}(),
 				},
 				rval: func() reflect.Value {
-					var x int = 3
-					return reflect.ValueOf(x)
+					var a int = 3
+					return reflect.ValueOf(a)
 				}(),
 			},
 			expected: true,
@@ -67,13 +70,13 @@ func TestIsNatural(t *testing.T) {
 			n: &node{
 				typ: &itype{
 					rtype: func() reflect.Type {
-						var x float64 = 3.0
-						return reflect.TypeOf(x)
+						var a float64 = 3.0
+						return reflect.TypeOf(a)
 					}(),
 				},
 				rval: func() reflect.Value {
-					var x float64 = 3.0
-					return reflect.ValueOf(x)
+					var a float64 = 3.0
+					return reflect.ValueOf(a)
 				}(),
 			},
 			expected: true,
@@ -83,13 +86,13 @@ func TestIsNatural(t *testing.T) {
 			n: &node{
 				typ: &itype{
 					rtype: func() reflect.Type {
-						var x float64 = 3.14
-						return reflect.TypeOf(x)
+						var a float64 = 3.14
+						return reflect.TypeOf(a)
 					}(),
 				},
 				rval: func() reflect.Value {
-					var x float64 = 3.14
-					return reflect.ValueOf(x)
+					var a float64 = 3.14
+					return reflect.ValueOf(a)
 				}(),
 			},
 			expected: false,
@@ -99,13 +102,13 @@ func TestIsNatural(t *testing.T) {
 			n: &node{
 				typ: &itype{
 					rtype: func() reflect.Type {
-						var x int = -3
-						return reflect.TypeOf(x)
+						var a int = -3
+						return reflect.TypeOf(a)
 					}(),
 				},
 				rval: func() reflect.Value {
-					var x int = -3
-					return reflect.ValueOf(x)
+					var a int = -3
+					return reflect.ValueOf(a)
 				}(),
 			},
 			expected: false,
@@ -188,3 +191,42 @@ func TestIsNatural(t *testing.T) {
 		}
 	}
 }
+
+func TestGlobals(t *testing.T) {
+	i := New(Options{})
+	if err := i.Use(stdlib.Symbols); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := i.Eval("var a = 1"); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := i.Eval("b := 2"); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := i.Eval("const c = 3"); err != nil {
+		t.Fatal(err)
+	}
+
+	g := i.Globals()
+	a := g["a"]
+	if !a.IsValid() {
+		t.Fatal("a not found")
+	}
+	if a := a.Interface(); a != 1 {
+		t.Fatalf("wrong a: want (%[1]T) %[1]v, have (%[2]T) %[2]v", 1, a)
+	}
+	b := g["b"]
+	if !b.IsValid() {
+		t.Fatal("b not found")
+	}
+	if b := b.Interface(); b != 2 {
+		t.Fatalf("wrong b: want (%[1]T) %[1]v, have (%[2]T) %[2]v", 2, b)
+	}
+	c := g["c"]
+	if !c.IsValid() {
+		t.Fatal("c not found")
+	}
+	if cc, ok := c.Interface().(constant.Value); ok && constant.MakeInt64(3) != cc {
+		t.Fatalf("wrong c: want (%[1]T) %[1]v, have (%[2]T) %[2]v", constant.MakeInt64(3), cc)
+	}
+}