mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
47d9766f5c
Previously, to avoid showing deprecation warnings for the next release when the user is running on HEAD, a boolean CLI flag -show-deprecations is introduced, and is set to false in the master branch. The idea is that release branches will have this default to true, so people running released versions will see deprecations. However, this means that people running on HEAD will never see any deprecations unless they use this CLI flag, which is not ideal. This commit replaces the flag bool -show-deprecations with a numerical -deprecation-level flag, which requests deprecations that are active as of release 0.X to be shown. The default value of this flag will be the minor version number of the *last* release, so that people running HEAD will see as many deprecation warnings as people running the last release would. This number will be bumped just before releases.
247 lines
6.9 KiB
Go
247 lines
6.9 KiB
Go
package eval
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/elves/elvish/pkg/diag"
|
|
"github.com/elves/elvish/pkg/eval/vars"
|
|
"github.com/elves/elvish/pkg/prog"
|
|
)
|
|
|
|
// This file implements variable resolution. Elvish has fully static lexical
|
|
// scopes, so variable resolution involves some work in the compilation phase as
|
|
// well.
|
|
//
|
|
// During compilation, a qualified variable name (whether in lvalue, like "x
|
|
// = foo", or in variable use, like "$x") is searched in compiler's staticNs
|
|
// tables to determine which scope they belong to, as well as their indicies in
|
|
// that scope. This step is just called "resolve" in the code, and it stores
|
|
// information in a varRef struct.
|
|
//
|
|
// During evaluation, the varRef is then used to look up the Var for the
|
|
// variable. This step is called "deref" in the code.
|
|
//
|
|
// The resolve phase can take place during evaluation as well for introspection.
|
|
|
|
// Keeps all the information statically about a variable referenced by a
|
|
// qualified name.
|
|
type varRef struct {
|
|
scope varScope
|
|
index int
|
|
subNames []string
|
|
}
|
|
|
|
type varScope int
|
|
|
|
const (
|
|
localScope varScope = 1 + iota
|
|
captureScope
|
|
builtinScope
|
|
envScope
|
|
externalScope
|
|
)
|
|
|
|
// An interface satisfied by both *compiler and *Frame. Used to implement
|
|
// resolveVarRef as a function that works for both types.
|
|
type scopeSearcher interface {
|
|
searchLocal(k string) int
|
|
searchCapture(k string) int
|
|
searchBuiltin(k string, r diag.Ranger) int
|
|
}
|
|
|
|
// Resolves a qname into a varRef.
|
|
func resolveVarRef(s scopeSearcher, qname string, r diag.Ranger) *varRef {
|
|
qname = strings.TrimPrefix(qname, ":")
|
|
if ref := resolveVarRefLocal(s, qname); ref != nil {
|
|
return ref
|
|
}
|
|
if ref := resolveVarRefCapture(s, qname); ref != nil {
|
|
return ref
|
|
}
|
|
if ref := resolveVarRefBuiltin(s, qname, r); ref != nil {
|
|
return ref
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveVarRefLocal(s scopeSearcher, qname string) *varRef {
|
|
first, rest := SplitQName(qname)
|
|
index := s.searchLocal(first)
|
|
if index != -1 {
|
|
return &varRef{scope: localScope, index: index, subNames: SplitQNameSegs(rest)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveVarRefCapture(s scopeSearcher, qname string) *varRef {
|
|
first, rest := SplitQName(qname)
|
|
if index := s.searchCapture(first); index != -1 {
|
|
return &varRef{scope: captureScope, index: index, subNames: SplitQNameSegs(rest)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveVarRefBuiltin(s scopeSearcher, qname string, r diag.Ranger) *varRef {
|
|
first, rest := SplitQName(qname)
|
|
if rest != "" {
|
|
// Try the 5 special namespace that we pretend are subnamespaces of
|
|
// builtin:.
|
|
switch first {
|
|
case "local:":
|
|
return resolveVarRefLocal(s, rest)
|
|
case "up:":
|
|
return resolveVarRefCapture(s, rest)
|
|
case "builtin:":
|
|
return resolveVarRefBuiltin(s, rest, r)
|
|
case "e:":
|
|
if strings.HasSuffix(rest, FnSuffix) {
|
|
return &varRef{scope: externalScope, subNames: []string{rest[:len(rest)-1]}}
|
|
}
|
|
case "E:":
|
|
return &varRef{scope: envScope, subNames: []string{rest}}
|
|
}
|
|
}
|
|
if index := s.searchBuiltin(first, r); index != -1 {
|
|
return &varRef{scope: builtinScope, index: index, subNames: SplitQNameSegs(rest)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Tries to resolve the command head as an internal command, i.e. a builtin
|
|
// special command or a function.
|
|
func resolveCmdHeadInternally(s scopeSearcher, head string, r diag.Ranger) (compileBuiltin, *varRef) {
|
|
special, ok := builtinSpecials[head]
|
|
if ok {
|
|
return special, nil
|
|
}
|
|
sigil, qname := SplitSigil(head)
|
|
if sigil == "" {
|
|
varName := qname + FnSuffix
|
|
ref := resolveVarRef(s, varName, r)
|
|
if ref != nil {
|
|
return nil, ref
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// Dereferences a varRef into a Var.
|
|
func deref(fm *Frame, ref *varRef) vars.Var {
|
|
variable, subNames := derefBase(fm, ref)
|
|
for _, subName := range subNames {
|
|
ns, ok := variable.Get().(*Ns)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
variable = ns.IndexName(subName)
|
|
if variable == nil {
|
|
return nil
|
|
}
|
|
}
|
|
return variable
|
|
}
|
|
|
|
func derefBase(fm *Frame, ref *varRef) (vars.Var, []string) {
|
|
switch ref.scope {
|
|
case localScope:
|
|
return fm.local.slots[ref.index], ref.subNames
|
|
case captureScope:
|
|
return fm.up.slots[ref.index], ref.subNames
|
|
case builtinScope:
|
|
return fm.Evaler.Builtin().slots[ref.index], ref.subNames
|
|
case envScope:
|
|
return vars.FromEnv(ref.subNames[0]), nil
|
|
case externalScope:
|
|
return vars.NewReadOnly(NewExternalCmd(ref.subNames[0])), nil
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func (cp *compiler) searchLocal(k string) int {
|
|
return cp.thisScope().lookup(k)
|
|
}
|
|
|
|
func (cp *compiler) searchCapture(k string) int {
|
|
for i := len(cp.scopes) - 2; i >= 0; i-- {
|
|
index := cp.scopes[i].lookup(k)
|
|
if index != -1 {
|
|
// Record the capture from i+1 to len(cp.scopes)-1, and reuse the
|
|
// index to keep the index into the previous scope.
|
|
index = cp.captures[i+1].add(k, true, index)
|
|
for j := i + 2; j < len(cp.scopes); j++ {
|
|
index = cp.captures[j].add(k, false, index)
|
|
}
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (cp *compiler) searchBuiltin(k string, r diag.Ranger) int {
|
|
index := cp.builtin.lookup(k)
|
|
if index != -1 {
|
|
cp.checkDeprecatedBuiltin(k, r)
|
|
}
|
|
return index
|
|
}
|
|
|
|
func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) {
|
|
if cp.warn == nil || r == nil {
|
|
return
|
|
}
|
|
msg := ""
|
|
minLevel := 15
|
|
switch name {
|
|
case "-source~":
|
|
msg = `the "source" command is deprecated; use "eval" instead`
|
|
case "ord~":
|
|
msg = `the "ord" command is deprecated; use "str:to-codepoints" instead`
|
|
case "chr~":
|
|
msg = `the "chr" command is deprecated; use "str:from-codepoints" instead`
|
|
case "has-prefix~":
|
|
msg = `the "has-prefix" command is deprecated; use "str:has-prefix" instead`
|
|
case "has-suffix~":
|
|
msg = `the "has-suffix" command is deprecated; use "str:has-suffix" instead`
|
|
case "esleep~":
|
|
msg = `the "esleep" command is deprecated; use "sleep" instead`
|
|
case "eval-symlinks~":
|
|
msg = `the "eval-symlinks" command is deprecated; use "path:eval-symlinks" instead`
|
|
case "path-abs~":
|
|
msg = `the "path-abs" command is deprecated; use "path:abs" instead`
|
|
case "path-base~":
|
|
msg = `the "path-base" command is deprecated; use "path:base" instead`
|
|
case "path-clean~":
|
|
msg = `the "path-clean" command is deprecated; use "path:clean" instead`
|
|
case "path-dir~":
|
|
msg = `the "path-dir" command is deprecated; use "path:dir" instead`
|
|
case "path-ext~":
|
|
msg = `the "path-ext" command is deprecated; use "path:ext" instead`
|
|
case "-is-dir~":
|
|
msg = `the "-is-dir" command is deprecated; use "path:is-dir" instead`
|
|
default:
|
|
return
|
|
}
|
|
dep := deprecation{cp.srcMeta.Name, r.Range(), msg}
|
|
if prog.DeprecationLevel >= minLevel && cp.deprecations.register(dep) {
|
|
err := diag.Error{
|
|
Type: "deprecation", Message: msg,
|
|
Context: diag.Context{
|
|
Name: cp.srcMeta.Name, Source: cp.srcMeta.Code, Ranging: r.Range()}}
|
|
fmt.Fprintln(cp.warn, err.Show(""))
|
|
}
|
|
}
|
|
|
|
func (fm *Frame) searchLocal(k string) int {
|
|
return fm.local.lookup(k)
|
|
}
|
|
|
|
func (fm *Frame) searchCapture(k string) int {
|
|
return fm.up.lookup(k)
|
|
}
|
|
|
|
func (fm *Frame) searchBuiltin(k string, r diag.Ranger) int {
|
|
return fm.Evaler.Builtin().lookup(k)
|
|
}
|