src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/ns.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "unsafe" 6 7 "src.elv.sh/pkg/eval/vars" 8 "src.elv.sh/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 := ns2.clone() 51 52 hasName := map[string]bool{} 53 for _, info := range ns.infos { 54 if !info.deleted { 55 hasName[info.name] = true 56 } 57 } 58 for i, info := range ns1.infos { 59 if !info.deleted && !hasName[info.name] { 60 ns.slots = append(ns.slots, ns1.slots[i]) 61 ns.infos = append(ns.infos, info) 62 } 63 } 64 return ns 65 } 66 67 func (ns *Ns) clone() *Ns { 68 return &Ns{ 69 append([]vars.Var(nil), ns.slots...), 70 append([]staticVarInfo(nil), ns.infos...)} 71 } 72 73 // Ns returns ns itself. 74 func (ns *Ns) Ns() *Ns { 75 return ns 76 } 77 78 // Kind returns "ns". 79 func (ns *Ns) Kind() string { 80 return "ns" 81 } 82 83 // Hash returns a hash of the address of ns. 84 func (ns *Ns) Hash() uint32 { 85 return hash.Pointer(unsafe.Pointer(ns)) 86 } 87 88 // Equal returns whether rhs has the same identity as ns. 89 func (ns *Ns) Equal(rhs any) bool { 90 if ns2, ok := rhs.(*Ns); ok { 91 return ns == ns2 92 } 93 return false 94 } 95 96 // Repr returns an opaque representation of the Ns showing its address. 97 func (ns *Ns) Repr(int) string { 98 return fmt.Sprintf("<ns %p>", ns) 99 } 100 101 // Index looks up a variable with the given name, and returns its value if it 102 // exists. This is only used for introspection from Elvish code; for 103 // introspection from Go code, use IndexString. 104 func (ns *Ns) Index(k any) (any, bool) { 105 if ks, ok := k.(string); ok { 106 variable := ns.IndexString(ks) 107 if variable == nil { 108 return nil, false 109 } 110 return variable.Get(), true 111 } 112 return nil, false 113 } 114 115 // IndexString looks up a variable with the given name, and returns its value if 116 // it exists, or nil if it does not. This is the type-safe version of Index and 117 // is useful for introspection from Go code. 118 func (ns *Ns) IndexString(k string) vars.Var { 119 _, i := ns.lookup(k) 120 if i != -1 { 121 return ns.slots[i] 122 } 123 return nil 124 } 125 126 func (ns *Ns) lookup(k string) (staticVarInfo, int) { 127 for i, info := range ns.infos { 128 if info.name == k && !info.deleted { 129 return info, i 130 } 131 } 132 return staticVarInfo{}, -1 133 } 134 135 // IterateKeys produces the names of all the variables in this Ns. 136 func (ns *Ns) IterateKeys(f func(any) bool) { 137 for _, info := range ns.infos { 138 if info.deleted { 139 continue 140 } 141 if !f(info.name) { 142 break 143 } 144 } 145 } 146 147 // IterateKeysString produces the names of all variables in the Ns. It is the 148 // type-safe version of IterateKeys and is useful for introspection from Go 149 // code. It doesn't support breaking early. 150 func (ns *Ns) IterateKeysString(f func(string)) { 151 for _, info := range ns.infos { 152 if !info.deleted { 153 f(info.name) 154 } 155 } 156 } 157 158 // HasKeyString reports whether the Ns has a variable with the given name. 159 func (ns *Ns) HasKeyString(k string) bool { 160 for _, info := range ns.infos { 161 if info.name == k && !info.deleted { 162 return true 163 } 164 } 165 return false 166 } 167 168 func (ns *Ns) static() *staticNs { 169 return &staticNs{ns.infos} 170 } 171 172 // NsBuilder is a helper type used for building an Ns. 173 type NsBuilder struct { 174 prefix string 175 m map[string]vars.Var 176 } 177 178 // BuildNs returns a helper for building an Ns. 179 func BuildNs() NsBuilder { 180 return BuildNsNamed("") 181 } 182 183 // BuildNsNamed returns a helper for building an Ns with the given name. The name is 184 // only used for the names of Go functions. 185 func BuildNsNamed(name string) NsBuilder { 186 prefix := "" 187 if name != "" { 188 prefix = "<" + name + ">:" 189 } 190 return NsBuilder{prefix, make(map[string]vars.Var)} 191 } 192 193 // AddVar adds a variable. 194 func (nb NsBuilder) AddVar(name string, v vars.Var) NsBuilder { 195 nb.m[name] = v 196 return nb 197 } 198 199 // AddVars adds all the variables given in the map. 200 func (nb NsBuilder) AddVars(m map[string]vars.Var) NsBuilder { 201 for name, v := range m { 202 nb.AddVar(name, v) 203 } 204 return nb 205 } 206 207 // AddFn adds a function. The resulting variable will be read-only. 208 func (nb NsBuilder) AddFn(name string, v Callable) NsBuilder { 209 return nb.AddVar(name+FnSuffix, vars.NewReadOnly(v)) 210 } 211 212 // AddNs adds a sub-namespace. The resulting variable will be read-only. 213 func (nb NsBuilder) AddNs(name string, v Nser) NsBuilder { 214 return nb.AddVar(name+NsSuffix, vars.NewReadOnly(v.Ns())) 215 } 216 217 // AddGoFn adds a Go function. The resulting variable will be read-only. 218 func (nb NsBuilder) AddGoFn(name string, impl any) NsBuilder { 219 return nb.AddFn(name, NewGoFn(nb.prefix+name, impl)) 220 } 221 222 // AddGoFns adds Go functions. The resulting variables will be read-only. 223 func (nb NsBuilder) AddGoFns(fns map[string]any) NsBuilder { 224 for name, impl := range fns { 225 nb.AddGoFn(name, impl) 226 } 227 return nb 228 } 229 230 // Ns builds a namespace. 231 func (nb NsBuilder) Ns() *Ns { 232 n := len(nb.m) 233 ns := &Ns{make([]vars.Var, n), make([]staticVarInfo, n)} 234 i := 0 235 for name, variable := range nb.m { 236 ns.slots[i] = variable 237 ns.infos[i] = staticVarInfo{name, vars.IsReadOnly(variable), false} 238 i++ 239 } 240 return ns 241 } 242 243 // The compile-time representation of a namespace. Called "static" namespace 244 // since it contains information that are known without executing the code. 245 // The data structure itself, however, is not static, and gets mutated as the 246 // compiler gains more information about the namespace. The zero value of 247 // staticNs is an empty namespace. 248 type staticNs struct { 249 infos []staticVarInfo 250 } 251 252 func (ns *staticNs) clone() *staticNs { 253 return &staticNs{append([]staticVarInfo(nil), ns.infos...)} 254 } 255 256 func (ns *staticNs) del(k string) { 257 if _, i := ns.lookup(k); i != -1 { 258 ns.infos[i].deleted = true 259 } 260 } 261 262 // Adds a name, shadowing any existing one, and returns the index for the new 263 // name. 264 func (ns *staticNs) add(k string) int { 265 ns.del(k) 266 ns.infos = append(ns.infos, staticVarInfo{k, false, false}) 267 return len(ns.infos) - 1 268 } 269 270 func (ns *staticNs) lookup(k string) (staticVarInfo, int) { 271 for i, info := range ns.infos { 272 if info.name == k && !info.deleted { 273 return info, i 274 } 275 } 276 return staticVarInfo{}, -1 277 } 278 279 type staticUpNs struct { 280 infos []upvalInfo 281 } 282 283 type upvalInfo struct { 284 name string 285 // Whether the upvalue comes from the immediate outer scope, i.e. the local 286 // scope a lambda is evaluated in. 287 local bool 288 // Index of the upvalue variable. If local is true, this is an index into 289 // the local scope. If local is false, this is an index into the up scope. 290 index int 291 } 292 293 func (up *staticUpNs) add(k string, local bool, index int) int { 294 for i, info := range up.infos { 295 if info.name == k { 296 return i 297 } 298 } 299 up.infos = append(up.infos, upvalInfo{k, local, index}) 300 return len(up.infos) - 1 301 }