github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/ns.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "unsafe" 6 7 "github.com/markusbkk/elvish/pkg/eval/vars" 8 "github.com/markusbkk/elvish/pkg/persistent/hash" 9 ) 10 11 // Ns is the runtime representation of a namespace. The zero value of Ns is an 12 // empty namespace. To create a non-empty Ns, use either NsBuilder or CombineNs. 13 // 14 // An Ns is immutable after its associated code chunk has finished execution. 15 type Ns struct { 16 // All variables in the namespace. Static variable accesses are compiled 17 // into indexed accesses into this slice. 18 slots []vars.Var 19 // Static information for each variable, reflecting the state when the 20 // associated code chunk has finished execution. 21 // 22 // This is only used for introspection and seeding the compilation of a new 23 // code chunk. Normal static variable accesses are compiled into indexed 24 // accesses into the slots slice. 25 // 26 // This is a slice instead of a map with the names of variables as keys, 27 // because most namespaces are small enough for linear lookup to be faster 28 // than map access. 29 infos []staticVarInfo 30 } 31 32 // Nser is anything that can be converted to an *Ns. 33 type Nser interface { 34 Ns() *Ns 35 } 36 37 // Static information known about a variable. 38 type staticVarInfo struct { 39 name string 40 readOnly bool 41 // Deleted variables can still be kept in the Ns since there might be a 42 // reference to them in a closure. Shadowed variables are also considered 43 // deleted. 44 deleted bool 45 } 46 47 // CombineNs returns an *Ns that contains all the bindings from both ns1 and 48 // ns2. Names in ns2 takes precedence over those in ns1. 49 func CombineNs(ns1, ns2 *Ns) *Ns { 50 ns := &Ns{ 51 append([]vars.Var(nil), ns2.slots...), 52 append([]staticVarInfo(nil), ns2.infos...)} 53 hasName := map[string]bool{} 54 for _, info := range ns.infos { 55 if !info.deleted { 56 hasName[info.name] = true 57 } 58 } 59 for i, info := range ns1.infos { 60 if !info.deleted && !hasName[info.name] { 61 ns.slots = append(ns.slots, ns1.slots[i]) 62 ns.infos = append(ns.infos, info) 63 } 64 } 65 return ns 66 } 67 68 // Ns returns ns itself. 69 func (ns *Ns) Ns() *Ns { 70 return ns 71 } 72 73 // Kind returns "ns". 74 func (ns *Ns) Kind() string { 75 return "ns" 76 } 77 78 // Hash returns a hash of the address of ns. 79 func (ns *Ns) Hash() uint32 { 80 return hash.Pointer(unsafe.Pointer(ns)) 81 } 82 83 // Equal returns whether rhs has the same identity as ns. 84 func (ns *Ns) Equal(rhs interface{}) bool { 85 if ns2, ok := rhs.(*Ns); ok { 86 return ns == ns2 87 } 88 return false 89 } 90 91 // Repr returns an opaque representation of the Ns showing its address. 92 func (ns *Ns) Repr(int) string { 93 return fmt.Sprintf("<ns %p>", ns) 94 } 95 96 // Index looks up a variable with the given name, and returns its value if it 97 // exists. This is only used for introspection from Elvish code; for 98 // introspection from Go code, use IndexName. 99 func (ns *Ns) Index(k interface{}) (interface{}, bool) { 100 if ks, ok := k.(string); ok { 101 variable := ns.IndexString(ks) 102 if variable == nil { 103 return nil, false 104 } 105 return variable.Get(), true 106 } 107 return nil, false 108 } 109 110 // IndexName looks up a variable with the given name, and returns its value if 111 // it exists, or nil if it does not. This is the type-safe version of Index and 112 // is useful for introspection from Go code. 113 func (ns *Ns) IndexString(k string) vars.Var { 114 _, i := ns.lookup(k) 115 if i != -1 { 116 return ns.slots[i] 117 } 118 return nil 119 } 120 121 func (ns *Ns) lookup(k string) (staticVarInfo, int) { 122 for i, info := range ns.infos { 123 if info.name == k && !info.deleted { 124 return info, i 125 } 126 } 127 return staticVarInfo{}, -1 128 } 129 130 // IterateKeys produces the names of all the variables in this Ns. 131 func (ns *Ns) IterateKeys(f func(interface{}) bool) { 132 for _, info := range ns.infos { 133 if info.deleted { 134 continue 135 } 136 if !f(info.name) { 137 break 138 } 139 } 140 } 141 142 // IterateKeysString produces the names of all variables in the Ns. It is the 143 // type-safe version of IterateKeys and is useful for introspection from Go 144 // code. It doesn't support breaking early. 145 func (ns *Ns) IterateKeysString(f func(string)) { 146 for _, info := range ns.infos { 147 if !info.deleted { 148 f(info.name) 149 } 150 } 151 } 152 153 // HasKeyString reports whether the Ns has a variable with the given name. 154 func (ns *Ns) HasKeyString(k string) bool { 155 for _, info := range ns.infos { 156 if info.name == k && !info.deleted { 157 return true 158 } 159 } 160 return false 161 } 162 163 func (ns *Ns) static() *staticNs { 164 return &staticNs{ns.infos} 165 } 166 167 // NsBuilder is a helper type used for building an Ns. 168 type NsBuilder struct { 169 prefix string 170 m map[string]vars.Var 171 } 172 173 // BuildNs returns a helper for building an Ns. 174 func BuildNs() NsBuilder { 175 return BuildNsNamed("") 176 } 177 178 // BuildNs returns a helper for building an Ns with the given name. The name is 179 // only used for the names of Go functions. 180 func BuildNsNamed(name string) NsBuilder { 181 prefix := "" 182 if name != "" { 183 prefix = "<" + name + ">:" 184 } 185 return NsBuilder{prefix, make(map[string]vars.Var)} 186 } 187 188 // Add adds a variable. 189 func (nb NsBuilder) AddVar(name string, v vars.Var) NsBuilder { 190 nb.m[name] = v 191 return nb 192 } 193 194 // AddVars adds all the variables given in the map. 195 func (nb NsBuilder) AddVars(m map[string]vars.Var) NsBuilder { 196 for name, v := range m { 197 nb.AddVar(name, v) 198 } 199 return nb 200 } 201 202 // AddFn adds a function. The resulting variable will be read-only. 203 func (nb NsBuilder) AddFn(name string, v Callable) NsBuilder { 204 return nb.AddVar(name+FnSuffix, vars.NewReadOnly(v)) 205 } 206 207 // AddNs adds a sub-namespace. The resulting variable will be read-only. 208 func (nb NsBuilder) AddNs(name string, v Nser) NsBuilder { 209 return nb.AddVar(name+NsSuffix, vars.NewReadOnly(v.Ns())) 210 } 211 212 // AddGoFn adds a Go function. The resulting variable will be read-only. 213 func (nb NsBuilder) AddGoFn(name string, impl interface{}) NsBuilder { 214 return nb.AddFn(name, NewGoFn(nb.prefix+name, impl)) 215 } 216 217 // AddGoFns adds Go functions. The resulting variables will be read-only. 218 func (nb NsBuilder) AddGoFns(fns map[string]interface{}) NsBuilder { 219 for name, impl := range fns { 220 nb.AddGoFn(name, impl) 221 } 222 return nb 223 } 224 225 // Ns builds a namespace. 226 func (nb NsBuilder) Ns() *Ns { 227 n := len(nb.m) 228 ns := &Ns{make([]vars.Var, n), make([]staticVarInfo, n)} 229 i := 0 230 for name, variable := range nb.m { 231 ns.slots[i] = variable 232 ns.infos[i] = staticVarInfo{name, vars.IsReadOnly(variable), false} 233 i++ 234 } 235 return ns 236 } 237 238 // The compile-time representation of a namespace. Called "static" namespace 239 // since it contains information that are known without executing the code. 240 // The data structure itself, however, is not static, and gets mutated as the 241 // compiler gains more information about the namespace. The zero value of 242 // staticNs is an empty namespace. 243 type staticNs struct { 244 infos []staticVarInfo 245 } 246 247 func (ns *staticNs) clone() *staticNs { 248 return &staticNs{append([]staticVarInfo(nil), ns.infos...)} 249 } 250 251 func (ns *staticNs) del(k string) { 252 if _, i := ns.lookup(k); i != -1 { 253 ns.infos[i].deleted = true 254 } 255 } 256 257 // Adds a name, shadowing any existing one, and returns the index for the new 258 // name. 259 func (ns *staticNs) add(k string) int { 260 ns.del(k) 261 ns.infos = append(ns.infos, staticVarInfo{k, false, false}) 262 return len(ns.infos) - 1 263 } 264 265 func (ns *staticNs) lookup(k string) (staticVarInfo, int) { 266 for i, info := range ns.infos { 267 if info.name == k && !info.deleted { 268 return info, i 269 } 270 } 271 return staticVarInfo{}, -1 272 } 273 274 type staticUpNs struct { 275 infos []upvalInfo 276 } 277 278 type upvalInfo struct { 279 name string 280 // Whether the upvalue comes from the immediate outer scope, i.e. the local 281 // scope a lambda is evaluated in. 282 local bool 283 // Index of the upvalue variable. If local is true, this is an index into 284 // the local scope. If local is false, this is an index into the up scope. 285 index int 286 } 287 288 func (up *staticUpNs) add(k string, local bool, index int) int { 289 for i, info := range up.infos { 290 if info.name == k { 291 return i 292 } 293 } 294 up.infos = append(up.infos, upvalInfo{k, local, index}) 295 return len(up.infos) - 1 296 }