github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/var_ref.go (about) 1 package eval 2 3 import ( 4 "strings" 5 6 "github.com/markusbkk/elvish/pkg/diag" 7 "github.com/markusbkk/elvish/pkg/eval/vars" 8 ) 9 10 // This file implements variable resolution. Elvish has fully static lexical 11 // scopes, so variable resolution involves some work in the compilation phase as 12 // well. 13 // 14 // During compilation, a qualified variable name (whether in lvalue, like "x 15 // = foo", or in variable use, like "$x") is searched in compiler's staticNs 16 // tables to determine which scope they belong to, as well as their indices in 17 // that scope. This step is just called "resolve" in the code, and it stores 18 // information in a varRef struct. 19 // 20 // During evaluation, the varRef is then used to look up the Var for the 21 // variable. This step is called "deref" in the code. 22 // 23 // The resolve phase can take place during evaluation as well for introspection. 24 25 // Keeps all the information statically about a variable referenced by a 26 // qualified name. 27 type varRef struct { 28 scope varScope 29 info staticVarInfo 30 index int 31 subNames []string 32 } 33 34 type varScope int 35 36 const ( 37 localScope varScope = 1 + iota 38 captureScope 39 builtinScope 40 envScope 41 externalScope 42 ) 43 44 // An interface satisfied by both *compiler and *Frame. Used to implement 45 // resolveVarRef as a function that works for both types. 46 type scopeSearcher interface { 47 searchLocal(k string) (staticVarInfo, int) 48 searchCapture(k string) (staticVarInfo, int) 49 searchBuiltin(k string, r diag.Ranger) (staticVarInfo, int) 50 } 51 52 // Resolves a qname into a varRef. 53 func resolveVarRef(s scopeSearcher, qname string, r diag.Ranger) *varRef { 54 if strings.HasPrefix(qname, ":") { 55 // $:foo is reserved for fully-qualified names in future 56 return nil 57 } 58 if ref := resolveVarRefLocal(s, qname); ref != nil { 59 return ref 60 } 61 if ref := resolveVarRefCapture(s, qname); ref != nil { 62 return ref 63 } 64 if ref := resolveVarRefBuiltin(s, qname, r); ref != nil { 65 return ref 66 } 67 return nil 68 } 69 70 func resolveVarRefLocal(s scopeSearcher, qname string) *varRef { 71 first, rest := SplitQName(qname) 72 if info, index := s.searchLocal(first); index != -1 { 73 return &varRef{localScope, info, index, SplitQNameSegs(rest)} 74 } 75 return nil 76 } 77 78 func resolveVarRefCapture(s scopeSearcher, qname string) *varRef { 79 first, rest := SplitQName(qname) 80 if info, index := s.searchCapture(first); index != -1 { 81 return &varRef{captureScope, info, index, SplitQNameSegs(rest)} 82 } 83 return nil 84 } 85 86 func resolveVarRefBuiltin(s scopeSearcher, qname string, r diag.Ranger) *varRef { 87 first, rest := SplitQName(qname) 88 if rest != "" { 89 // Try special namespace first. 90 switch first { 91 case "e:": 92 if strings.HasSuffix(rest, FnSuffix) { 93 return &varRef{scope: externalScope, subNames: []string{rest[:len(rest)-1]}} 94 } 95 case "E:": 96 return &varRef{scope: envScope, subNames: []string{rest}} 97 } 98 } 99 if info, index := s.searchBuiltin(first, r); index != -1 { 100 return &varRef{builtinScope, info, index, SplitQNameSegs(rest)} 101 } 102 return nil 103 } 104 105 // Tries to resolve the command head as an internal command, i.e. a builtin 106 // special command or a function. 107 func resolveCmdHeadInternally(s scopeSearcher, head string, r diag.Ranger) (compileBuiltin, *varRef) { 108 special, ok := builtinSpecials[head] 109 if ok { 110 return special, nil 111 } 112 sigil, qname := SplitSigil(head) 113 if sigil == "" { 114 varName := qname + FnSuffix 115 ref := resolveVarRef(s, varName, r) 116 if ref != nil { 117 return nil, ref 118 } 119 } 120 return nil, nil 121 } 122 123 // Dereferences a varRef into a Var. 124 func deref(fm *Frame, ref *varRef) vars.Var { 125 variable, subNames := derefBase(fm, ref) 126 for _, subName := range subNames { 127 ns, ok := variable.Get().(*Ns) 128 if !ok { 129 return nil 130 } 131 variable = ns.IndexString(subName) 132 if variable == nil { 133 return nil 134 } 135 } 136 return variable 137 } 138 139 func derefBase(fm *Frame, ref *varRef) (vars.Var, []string) { 140 switch ref.scope { 141 case localScope: 142 return fm.local.slots[ref.index], ref.subNames 143 case captureScope: 144 return fm.up.slots[ref.index], ref.subNames 145 case builtinScope: 146 return fm.Evaler.Builtin().slots[ref.index], ref.subNames 147 case envScope: 148 return vars.FromEnv(ref.subNames[0]), nil 149 case externalScope: 150 return vars.NewReadOnly(NewExternalCmd(ref.subNames[0])), nil 151 default: 152 return nil, nil 153 } 154 } 155 156 func (cp *compiler) searchLocal(k string) (staticVarInfo, int) { 157 return cp.thisScope().lookup(k) 158 } 159 160 func (cp *compiler) searchCapture(k string) (staticVarInfo, int) { 161 for i := len(cp.scopes) - 2; i >= 0; i-- { 162 info, index := cp.scopes[i].lookup(k) 163 if index != -1 { 164 // Record the capture from i+1 to len(cp.scopes)-1, and reuse the 165 // index to keep the index into the previous scope. 166 index = cp.captures[i+1].add(k, true, index) 167 for j := i + 2; j < len(cp.scopes); j++ { 168 index = cp.captures[j].add(k, false, index) 169 } 170 return info, index 171 } 172 } 173 return staticVarInfo{}, -1 174 } 175 176 func (cp *compiler) searchBuiltin(k string, r diag.Ranger) (staticVarInfo, int) { 177 info, index := cp.builtin.lookup(k) 178 if index != -1 { 179 cp.checkDeprecatedBuiltin(k, r) 180 } 181 return info, index 182 } 183 184 func (fm *Frame) searchLocal(k string) (staticVarInfo, int) { 185 return fm.local.lookup(k) 186 } 187 188 func (fm *Frame) searchCapture(k string) (staticVarInfo, int) { 189 return fm.up.lookup(k) 190 } 191 192 func (fm *Frame) searchBuiltin(k string, r diag.Ranger) (staticVarInfo, int) { 193 return fm.Evaler.Builtin().lookup(k) 194 }