diff --git a/env/env.go b/env/env.go index d8faa70f..7af9dbb4 100644 --- a/env/env.go +++ b/env/env.go @@ -21,6 +21,8 @@ type ( parent *Env values map[string]reflect.Value types map[string]reflect.Type + packages map[string]map[string]reflect.Value + packageTypes map[string]map[string]reflect.Type externalLookup ExternalLookup } ) diff --git a/env/envPackage.go b/env/envPackage.go new file mode 100644 index 00000000..7bd48c16 --- /dev/null +++ b/env/envPackage.go @@ -0,0 +1,76 @@ +package env + +import "reflect" + +// Package returns the methods for the package defined by name. +// If no package is found by this name in the environment, +// the globally defined package with this name is returned. +func (e *Env) Package(name string) map[string]reflect.Value { + e.rwMutex.RLock() + pkg, ok := e.packages[name] + e.rwMutex.RUnlock() + + if ok { + return pkg + } + + if e.parent == nil { + pkg, ok := Packages[name] + if ok { + return pkg + } + } + + return nil +} + +// DefinePackage defines methods for the package name. +func (e *Env) DefinePackage(name string, values map[string]reflect.Value) { + e.rwMutex.RLock() + defer e.rwMutex.RUnlock() + + if e.packages == nil { + e.packages = map[string]map[string]reflect.Value{} + } + e.packages[name] = make(map[string]reflect.Value, len(values)) + for k, v := range values { + e.packages[name][k] = v + } +} + +// PackageTypes returns the types for the package defined by name. +// If no package is found by this name in the environment, +// the globally defined package with this name is returned +func (e *Env) PackageTypes(name string) map[string]reflect.Type { + e.rwMutex.RLock() + pkg, ok := e.packageTypes[name] + e.rwMutex.RUnlock() + + if ok { + return pkg + } + + if e.parent == nil { + pkg, ok := PackageTypes[name] + if ok { + return pkg + } + } + + return nil +} + +// DefinePackageTypes defines types for the package name. +func (e *Env) DefinePackageTypes(name string, types map[string]reflect.Type) { + e.rwMutex.RLock() + defer e.rwMutex.RUnlock() + + if e.packageTypes == nil { + e.packageTypes = map[string]map[string]reflect.Type{} + } + + e.packageTypes[name] = make(map[string]reflect.Type, len(types)) + for k, v := range types { + e.packageTypes[name][k] = v + } +} diff --git a/env/envPackage_test.go b/env/envPackage_test.go new file mode 100644 index 00000000..383c046f --- /dev/null +++ b/env/envPackage_test.go @@ -0,0 +1,92 @@ +package env + +import ( + "reflect" + "testing" +) + +func TestDefinePackage(t *testing.T) { + e := NewEnv() + + if custom := e.Package("custom"); custom != nil { + t.Fatal("unexpected \"custom\" package", custom) + } + + // Define the method "hello" for the package "custom" using the global variable. + Packages["custom"] = map[string]reflect.Value{ + "hello": reflect.ValueOf(func() string { return "hello" }), + } + + if custom := e.Package("custom"); custom == nil { + t.Fatal("expected \"custom\" package") + } else if hello, ok := custom["hello"]; !ok { + t.Fatal("expected method definition \"hello\" in package \"custom\"") + } else if fn, ok := hello.Interface().(func() string); !ok { + t.Fatal("expected method func() string {}") + } else if res := fn(); res != "hello" { + t.Fatalf("expected return value \"hello\" but got %v", res) + } + + // Re-define the method for the package "custom" at the environment level. + e.DefinePackage("custom", map[string]reflect.Value{ + "hello": reflect.ValueOf(func() string { return "hallo" }), + }) + + if custom := e.Package("custom"); custom == nil { + t.Fatal("expected \"custom\" package") + } else if hello, ok := custom["hello"]; !ok { + t.Fatal("expected method definition \"hello\" in package \"custom\"") + } else if fn, ok := hello.Interface().(func() string); !ok { + t.Fatal("expected method func() string {}") + } else if res := fn(); res != "hallo" { + t.Fatalf("expected return value \"hallo\" but got %v", res) + } +} + +func TestDefinePackageTypes(t *testing.T) { + e := NewEnv() + + if custom := e.PackageTypes("custom"); custom != nil { + t.Fatal("unexpected \"custom\" package", custom) + } + + type A struct { + a string + } + + // Define the type "A" for the package "custom" using the global variable. + PackageTypes["custom"] = map[string]reflect.Type{ + "A": reflect.TypeOf(A{}), + } + + if custom := e.PackageTypes("custom"); custom == nil { + t.Fatal("expected \"custom\" package") + } else if typeA, ok := custom["A"]; !ok { + t.Fatal("expected type definition \"A\" in package \"custom\"") + } else { + a := reflect.New(typeA).Interface() + if is, want := a, (&A{}); !reflect.DeepEqual(is, want) { + t.Fatalf("%T != %T", is, want) + } + } + + type B struct { + b int + } + + // Re-define the type for the package "custom" at the environment level. + e.DefinePackageTypes("custom", map[string]reflect.Type{ + "A": reflect.TypeOf(B{}), + }) + + if custom := e.PackageTypes("custom"); custom == nil { + t.Fatal("expected \"custom\" package") + } else if typeA, ok := custom["A"]; !ok { + t.Fatal("expected type definition \"A\" in package \"custom\"") + } else { + a := reflect.New(typeA).Interface() + if is, want := a, (&B{}); !reflect.DeepEqual(is, want) { + t.Fatalf("%T != %T", is, want) + } + } +} diff --git a/vm/vmExpr.go b/vm/vmExpr.go index bf8f1f39..a5d55ba2 100644 --- a/vm/vmExpr.go +++ b/vm/vmExpr.go @@ -522,8 +522,8 @@ func (runInfo *runInfoStruct) invokeExpr() { name := runInfo.rv.String() runInfo.rv = nilValue - methods, ok := env.Packages[name] - if !ok { + methods := runInfo.env.Package(name) + if methods == nil { runInfo.err = newStringError(expr, "package not found: "+name) return } @@ -537,8 +537,8 @@ func (runInfo *runInfoStruct) invokeExpr() { } } - types, ok := env.PackageTypes[name] - if ok { + types := runInfo.env.PackageTypes(name) + if types != nil { for typeName, typeValue := range types { err = pack.DefineReflectType(typeName, typeValue) if err != nil {