Move pkg/eval/resolve.go to pkg/edit, and add more introspection methods on *Ns.

This commit is contained in:
Qi Xiao 2021-01-05 15:29:27 +00:00
parent 5c55e72e78
commit af0c22c47a
6 changed files with 109 additions and 99 deletions

View File

@ -513,11 +513,11 @@ func (pureEvaler) EachSpecial(f func(string)) {
}
func (pe pureEvaler) EachNs(f func(string)) {
eval.EachNsInTop(pe.ev.Builtin(), pe.ev.Global(), f)
eachNsInTop(pe.ev.Builtin(), pe.ev.Global(), f)
}
func (pe pureEvaler) EachVariableInNs(ns string, f func(string)) {
eval.EachVariableInTop(pe.ev.Builtin(), pe.ev.Global(), ns, f)
eachVariableInTop(pe.ev.Builtin(), pe.ev.Global(), ns, f)
}
func (pe pureEvaler) PurelyEvalPrimary(pn *parse.Primary) interface{} {

66
pkg/edit/ns_helper.go Normal file
View File

@ -0,0 +1,66 @@
package edit
import (
"os"
"strings"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/fsutil"
)
// Calls the passed function for each variable name in namespace ns that can be
// found from the top context.
func eachVariableInTop(builtin, global *eval.Ns, ns string, f func(s string)) {
switch ns {
case "builtin:":
builtin.IterateNames(f)
case "", ":":
global.IterateNames(f)
builtin.IterateNames(f)
case "e:":
fsutil.EachExternal(func(cmd string) {
f(cmd + eval.FnSuffix)
})
case "E:":
for _, s := range os.Environ() {
if i := strings.IndexByte(s, '='); i > 0 {
f(s[:i])
}
}
default:
segs := eval.SplitQNameSegs(ns)
mod := global.IndexName(segs[0])
if mod == nil {
mod = builtin.IndexName(segs[0])
}
for _, seg := range segs[1:] {
if mod == nil {
return
}
mod = mod.Get().(*eval.Ns).IndexName(seg)
}
if mod != nil {
mod.Get().(*eval.Ns).IterateNames(f)
}
}
}
// Calls the passed function for each namespace that can be used from the top
// context.
func eachNsInTop(builtin, global *eval.Ns, f func(s string)) {
f("builtin:")
f("e:")
f("E:")
global.IterateNames(func(name string) {
if strings.HasSuffix(name, eval.NsSuffix) {
f(name)
}
})
builtin.IterateNames(func(name string) {
if strings.HasSuffix(name, eval.NsSuffix) {
f(name)
}
})
}

View File

@ -1,43 +1,44 @@
package eval
package edit
import (
"reflect"
"sort"
"testing"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/eval/vars"
)
var testVar = vars.NewReadOnly("")
var eachVariableInTopTests = []struct {
builtin *Ns
global *Ns
builtin *eval.Ns
global *eval.Ns
ns string
wantNames []string
}{
{
builtin: NsBuilder{"foo": testVar, "bar": testVar}.Ns(),
global: NsBuilder{"lorem": testVar, "ipsum": testVar}.Ns(),
builtin: eval.NsBuilder{"foo": testVar, "bar": testVar}.Ns(),
global: eval.NsBuilder{"lorem": testVar, "ipsum": testVar}.Ns(),
ns: "builtin:",
wantNames: []string{"bar", "foo"},
},
{
builtin: NsBuilder{"foo": testVar, "bar": testVar}.Ns(),
global: NsBuilder{"lorem": testVar, "ipsum": testVar}.Ns(),
builtin: eval.NsBuilder{"foo": testVar, "bar": testVar}.Ns(),
global: eval.NsBuilder{"lorem": testVar, "ipsum": testVar}.Ns(),
ns: "",
wantNames: []string{"bar", "foo", "ipsum", "lorem"},
},
{
builtin: NsBuilder{
"mod:": vars.NewReadOnly(NsBuilder{"a": testVar, "b": testVar}.Ns()),
builtin: eval.NsBuilder{
"mod:": vars.NewReadOnly(eval.NsBuilder{"a": testVar, "b": testVar}.Ns()),
}.Ns(),
ns: "mod:",
wantNames: []string{"a", "b"},
},
{
global: NsBuilder{
"mod:": vars.NewReadOnly(NsBuilder{"a": testVar, "b": testVar}.Ns()),
global: eval.NsBuilder{
"mod:": vars.NewReadOnly(eval.NsBuilder{"a": testVar, "b": testVar}.Ns()),
}.Ns(),
ns: "mod:",
wantNames: []string{"a", "b"},
@ -54,7 +55,7 @@ func TestEachVariableInTop(t *testing.T) {
global := getNs(test.global)
var names []string
EachVariableInTop(builtin, global, test.ns,
eachVariableInTop(builtin, global, test.ns,
func(s string) { names = append(names, s) })
sort.Strings(names)
@ -65,24 +66,24 @@ func TestEachVariableInTop(t *testing.T) {
}
var eachNsInTopTests = []struct {
builtin *Ns
global *Ns
builtin *eval.Ns
global *eval.Ns
wantNames []string
}{
{
wantNames: []string{"E:", "builtin:", "e:"},
},
{
builtin: NsBuilder{"foo:": testVar}.Ns(),
builtin: eval.NsBuilder{"foo:": testVar}.Ns(),
wantNames: []string{"E:", "builtin:", "e:", "foo:"},
},
{
global: NsBuilder{"foo:": testVar}.Ns(),
global: eval.NsBuilder{"foo:": testVar}.Ns(),
wantNames: []string{"E:", "builtin:", "e:", "foo:"},
},
{
builtin: NsBuilder{"foo:": testVar}.Ns(),
global: NsBuilder{"bar:": testVar}.Ns(),
builtin: eval.NsBuilder{"foo:": testVar}.Ns(),
global: eval.NsBuilder{"bar:": testVar}.Ns(),
wantNames: []string{"E:", "bar:", "builtin:", "e:", "foo:"},
},
}
@ -93,7 +94,7 @@ func TestEachNsInTop(t *testing.T) {
global := getNs(test.global)
var names []string
EachNsInTop(builtin, global, func(s string) { names = append(names, s) })
eachNsInTop(builtin, global, func(s string) { names = append(names, s) })
sort.Strings(names)
if !reflect.DeepEqual(names, test.wantNames) {
@ -102,9 +103,9 @@ func TestEachNsInTop(t *testing.T) {
}
}
func getNs(ns *Ns) *Ns {
func getNs(ns *eval.Ns) *eval.Ns {
if ns == nil {
return new(Ns)
return new(eval.Ns)
}
return ns
}

View File

@ -65,10 +65,11 @@ func (ns *Ns) Repr(int) string {
}
// Index looks up a variable with the given name, and returns its value if it
// exists. This is only used for introspection.
// exists. This is only used for introspection from Elvish code; for
// introspection from Go code, use IndexName.
func (ns *Ns) Index(k interface{}) (interface{}, bool) {
if ks, ok := k.(string); ok {
variable := ns.indexInner(ks)
variable := ns.IndexName(ks)
if variable == nil {
return nil, false
}
@ -77,7 +78,10 @@ func (ns *Ns) Index(k interface{}) (interface{}, bool) {
return nil, false
}
func (ns *Ns) indexInner(k string) vars.Var {
// Index looks up a variable with the given name, and returns its value if it
// exists, or nil if it does not. This is the type-safe version of Index and is
// useful for introspection from Go code.
func (ns *Ns) IndexName(k string) vars.Var {
i := ns.lookup(k)
if i != -1 {
return ns.slots[i]
@ -106,6 +110,17 @@ func (ns *Ns) IterateKeys(f func(interface{}) bool) {
}
}
// IterateNames produces the names of all variables in the Ns. It is the
// type-safe version of IterateKeys and is useful for introspection from Go
// code. It doesn't support breaking early.
func (ns *Ns) IterateNames(f func(string)) {
for i, name := range ns.names {
if ns.slots[i] != nil {
f(name)
}
}
}
// HasName reports whether the Ns has a variable with the given name.
func (ns *Ns) HasName(k string) bool {
for i, name := range ns.names {

View File

@ -1,72 +0,0 @@
package eval
import (
"os"
"strings"
"github.com/elves/elvish/pkg/fsutil"
)
// EachVariableInTop calls the passed function for each variable name in
// namespace ns that can be found from the top context.
func EachVariableInTop(builtin, global *Ns, ns string, f func(s string)) {
switch ns {
case "builtin:":
for _, name := range builtin.names {
f(name)
}
case "", ":":
for _, name := range global.names {
f(name)
}
for _, name := range builtin.names {
f(name)
}
case "e:":
fsutil.EachExternal(func(cmd string) {
f(cmd + FnSuffix)
})
case "E:":
for _, s := range os.Environ() {
if i := strings.IndexByte(s, '='); i > 0 {
f(s[:i])
}
}
default:
segs := SplitQNameSegs(ns)
mod := global.indexInner(segs[0])
if mod == nil {
mod = builtin.indexInner(segs[0])
}
for _, seg := range segs[1:] {
if mod == nil {
return
}
mod = mod.Get().(*Ns).indexInner(seg)
}
if mod != nil {
for _, name := range mod.Get().(*Ns).names {
f(name)
}
}
}
}
// EachNsInTop calls the passed function for each namespace that can be used
// from the top context.
func EachNsInTop(builtin, global *Ns, f func(s string)) {
f("builtin:")
f("e:")
f("E:")
for _, name := range global.names {
if strings.HasSuffix(name, NsSuffix) {
f(name)
}
}
for _, name := range builtin.names {
if strings.HasSuffix(name, NsSuffix) {
f(name)
}
}
}

View File

@ -134,7 +134,7 @@ func deref(fm *Frame, ref *varRef) vars.Var {
if !ok {
return nil
}
variable = ns.indexInner(subName)
variable = ns.IndexName(subName)
if variable == nil {
return nil
}