New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope introspection builtins #492

Closed
xiaq opened this Issue Sep 30, 2017 · 5 comments

Comments

Projects
None yet
3 participants
@xiaq
Member

xiaq commented Sep 30, 2017

It would be nice if we have a pair of builtins for examining (lexical) scopes.

Scopes have two parts: a names part, storing variable names, and a uses part, storing other namespaces used in that scope:

elvish/eval/scope.go

Lines 3 to 7 in 2d837f1

// Scope represents a lexical scope. It holds variables and imports.
type Scope struct {
Names Namespace
Uses map[string]Namespace
}

Hence it makes sense to have two builtin functions for examining these two parts. One list-names and one list-uses.

list-names takes an optional namespace name and lists all names in that namespace. For instance, list-names builtin lists all names in the builtin namespace, and list-names local lists all names in the local namespace. When no namespace or "" is given, it should list all names in the local, up and builtin namespaces, akin to how variable name resolution works

elvish/eval/eval.go

Lines 368 to 404 in 2d837f1

// ResolveVar resolves a variable. When the variable cannot be found, nil is
// returned.
func (ec *EvalCtx) ResolveVar(ns, name string) Variable {
switch ns {
case "local":
return ec.local.Names[name]
case "up":
return ec.up.Names[name]
case "builtin":
return ec.Builtin.Names[name]
case "":
if v := ec.local.Names[name]; v != nil {
return v
}
if v, ok := ec.up.Names[name]; ok {
return v
}
return ec.Builtin.Names[name]
case "e":
if strings.HasPrefix(name, FnPrefix) {
return NewRoVariable(ExternalCmd{name[len(FnPrefix):]})
}
case "E":
return envVariable{name}
case "shared":
if ec.Daemon == nil {
throw(ErrStoreUnconnected)
}
return sharedVariable{ec.Daemon, name}
default:
ns := ec.ResolveMod(ns)
if ns != nil {
return ns[name]
}
}
return nil
}

In short, "list-names ns" should list all such names n that $ns:n is a valid variable.

list-uses does not take arguments, and always lists names of namespaces used in the local, up, or builtin scope. This is to mirror the behavior of namespace resolution, which always happens as described:

elvish/eval/eval.go

Lines 406 to 417 in 2d837f1

func (ec *EvalCtx) ResolveMod(name string) Namespace {
if ns, ok := ec.local.Uses[name]; ok {
return ns
}
if ns, ok := ec.up.Uses[name]; ok {
return ns
}
if ns, ok := ec.Builtin.Uses[name]; ok {
return ns
}
return nil
}

@krader1961

This comment has been minimized.

krader1961 commented Sep 30, 2017

Let's say you have namespaces a: and a:b: (from doing use a and use a:b). How would I use these commands to list just the namespaces nested within a:? Or is that a nonsensical question? It looks like command completions provide reasonable suggestions when I type a: including showing the nested a:b: namespace. Isn't that something we should be able to replicate using elvish script?

@xiaq

This comment has been minimized.

Member

xiaq commented Oct 1, 2017

@krader1961 There is no "nested namespace". The module a:b: happens to have a: as its prefix in name. (The situation is comparable to Perl's modules, if you are familiar with that). The fact that completion for a: includes a:b: is based on prefix matching, just like if you have variables $a-b and $a, typing $a will include $a-b as a candidate.

@krader1961

This comment has been minimized.

krader1961 commented Oct 1, 2017

Yeah, I see that now that I've read the docs more carefully after my previous question. The problem is that most people are going to look at namespaces a: and a:b: and conclude the latter is somehow nested inside the former. A careful reading of the rules for evaluating use statements makes the relationship clearer. But I suspect it will be a FAQ since nearly every new user will make the mistake I made. Which doesn't argue for changing the namespace model but does mean additional documentation, especially examples, is going to be needed.

@xiaq xiaq added this to the 0.11 milestone Jan 4, 2018

@xiaq xiaq modified the milestones: 0.11, 0.13 Jan 14, 2018

xiaq added a commit that referenced this issue Feb 2, 2018

Resolve nested modules recursively.
Names like `$a🅱️c:d` are resolved as if it is `$a:[b:][c:][d]`.

This also changes the resolution of variable names ending in :, so that
they can be used to refer to namespace variables.

Since namespace variables are now accessible and can be accessed like
maps, this addresses #492.

@xiaq xiaq modified the milestones: 0.13, 0.12 Mar 8, 2018

@xiaq

This comment has been minimized.

Member

xiaq commented Mar 8, 2018

A different API has been implemented: namespaces can now be indexed like maps, and has-key and keys work on namespaces.

@xiaq xiaq closed this Mar 8, 2018

@zzamboni

This comment has been minimized.

Contributor

zzamboni commented Mar 11, 2018

In my tests, keys works fine on namespaces, but has-key does not:

> kind-of $edit:
▶ ns
> keys $edit:
▶ move-dot-eol~
▶ rprompt-persistent
▶ match-prefix~
▶ complex-candidate~
▶ selected-file
...
> has-key $edit: after-readline
Exception: couldn't get key or index of type 'ns'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment