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