1package eval 2 3import ( 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. 15type 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. 33type Nser interface { 34 Ns() *Ns 35} 36 37// Static information known about a variable. 38type 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. 49func 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. 69func (ns *Ns) Ns() *Ns { 70 return ns 71} 72 73// Kind returns "ns". 74func (ns *Ns) Kind() string { 75 return "ns" 76} 77 78// Hash returns a hash of the address of ns. 79func (ns *Ns) Hash() uint32 { 80 return hash.Pointer(unsafe.Pointer(ns)) 81} 82 83// Equal returns whether rhs has the same identity as ns. 84func (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. 92func (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. 99func (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. 113func (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 121func (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. 131func (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. 145func (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. 154func (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 163func (ns *Ns) static() *staticNs { 164 return &staticNs{ns.infos} 165} 166 167// NsBuilder is a helper type used for building an Ns. 168type NsBuilder struct { 169 prefix string 170 m map[string]vars.Var 171} 172 173// BuildNs returns a helper for building an Ns. 174func 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. 180func 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. 189func (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. 195func (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. 203func (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. 208func (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. 213func (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. 218func (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. 226func (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. 243type staticNs struct { 244 infos []staticVarInfo 245} 246 247func (ns *staticNs) clone() *staticNs { 248 return &staticNs{append([]staticVarInfo(nil), ns.infos...)} 249} 250 251func (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. 259func (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 265func (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 274type staticUpNs struct { 275 infos []upvalInfo 276} 277 278type 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 288func (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} 297