github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/var_ref.go (about) 1 package eval 2 3 import ( 4 "strings" 5 6 "src.elv.sh/pkg/diag" 7 "src.elv.sh/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 indicies 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 index int 30 subNames []string 31 } 32 33 type varScope int 34 35 const ( 36 localScope varScope = 1 + iota 37 captureScope 38 builtinScope 39 envScope 40 externalScope 41 ) 42 43 // An interface satisfied by both *compiler and *Frame. Used to implement 44 // resolveVarRef as a function that works for both types. 45 type scopeSearcher interface { 46 searchLocal(k string) int 47 searchCapture(k string) int 48 searchBuiltin(k string, r diag.Ranger) int 49 } 50 51 // Resolves a qname into a varRef. 52 func resolveVarRef(s scopeSearcher, qname string, r diag.Ranger) *varRef { 53 qname = strings.TrimPrefix(qname, ":") 54 if ref := resolveVarRefLocal(s, qname); ref != nil { 55 return ref 56 } 57 if ref := resolveVarRefCapture(s, qname); ref != nil { 58 return ref 59 } 60 if ref := resolveVarRefBuiltin(s, qname, r); ref != nil { 61 return ref 62 } 63 return nil 64 } 65 66 func resolveVarRefLocal(s scopeSearcher, qname string) *varRef { 67 first, rest := SplitQName(qname) 68 index := s.searchLocal(first) 69 if index != -1 { 70 return &varRef{scope: localScope, index: index, subNames: SplitQNameSegs(rest)} 71 } 72 return nil 73 } 74 75 func resolveVarRefCapture(s scopeSearcher, qname string) *varRef { 76 first, rest := SplitQName(qname) 77 if index := s.searchCapture(first); index != -1 { 78 return &varRef{scope: captureScope, index: index, subNames: SplitQNameSegs(rest)} 79 } 80 return nil 81 } 82 83 func resolveVarRefBuiltin(s scopeSearcher, qname string, r diag.Ranger) *varRef { 84 first, rest := SplitQName(qname) 85 if rest != "" { 86 // Try special namespace first. 87 switch first { 88 case "local:": 89 return resolveVarRefLocal(s, rest) 90 case "up:": 91 return resolveVarRefCapture(s, rest) 92 case "e:": 93 if strings.HasSuffix(rest, FnSuffix) { 94 return &varRef{scope: externalScope, subNames: []string{rest[:len(rest)-1]}} 95 } 96 case "E:": 97 return &varRef{scope: envScope, subNames: []string{rest}} 98 } 99 } 100 if index := s.searchBuiltin(first, r); index != -1 { 101 return &varRef{scope: builtinScope, index: index, subNames: SplitQNameSegs(rest)} 102 } 103 return nil 104 } 105 106 // Tries to resolve the command head as an internal command, i.e. a builtin 107 // special command or a function. 108 func resolveCmdHeadInternally(s scopeSearcher, head string, r diag.Ranger) (compileBuiltin, *varRef) { 109 special, ok := builtinSpecials[head] 110 if ok { 111 return special, nil 112 } 113 sigil, qname := SplitSigil(head) 114 if sigil == "" { 115 varName := qname + FnSuffix 116 ref := resolveVarRef(s, varName, r) 117 if ref != nil { 118 return nil, ref 119 } 120 } 121 return nil, nil 122 } 123 124 // Dereferences a varRef into a Var. 125 func deref(fm *Frame, ref *varRef) vars.Var { 126 variable, subNames := derefBase(fm, ref) 127 for _, subName := range subNames { 128 ns, ok := variable.Get().(*Ns) 129 if !ok { 130 return nil 131 } 132 variable = ns.IndexName(subName) 133 if variable == nil { 134 return nil 135 } 136 } 137 return variable 138 } 139 140 func derefBase(fm *Frame, ref *varRef) (vars.Var, []string) { 141 switch ref.scope { 142 case localScope: 143 return fm.local.slots[ref.index], ref.subNames 144 case captureScope: 145 return fm.up.slots[ref.index], ref.subNames 146 case builtinScope: 147 return fm.Evaler.Builtin().slots[ref.index], ref.subNames 148 case envScope: 149 return vars.FromEnv(ref.subNames[0]), nil 150 case externalScope: 151 return vars.NewReadOnly(NewExternalCmd(ref.subNames[0])), nil 152 default: 153 return nil, nil 154 } 155 } 156 157 func (cp *compiler) searchLocal(k string) int { 158 return cp.thisScope().lookup(k) 159 } 160 161 func (cp *compiler) searchCapture(k string) int { 162 for i := len(cp.scopes) - 2; i >= 0; i-- { 163 index := cp.scopes[i].lookup(k) 164 if index != -1 { 165 // Record the capture from i+1 to len(cp.scopes)-1, and reuse the 166 // index to keep the index into the previous scope. 167 index = cp.captures[i+1].add(k, true, index) 168 for j := i + 2; j < len(cp.scopes); j++ { 169 index = cp.captures[j].add(k, false, index) 170 } 171 return index 172 } 173 } 174 return -1 175 } 176 177 func (cp *compiler) searchBuiltin(k string, r diag.Ranger) int { 178 index := cp.builtin.lookup(k) 179 if index != -1 { 180 cp.checkDeprecatedBuiltin(k, r) 181 } 182 return index 183 } 184 185 func (fm *Frame) searchLocal(k string) int { 186 return fm.local.lookup(k) 187 } 188 189 func (fm *Frame) searchCapture(k string) int { 190 return fm.up.lookup(k) 191 } 192 193 func (fm *Frame) searchBuiltin(k string, r diag.Ranger) int { 194 return fm.Evaler.Builtin().lookup(k) 195 }