File: ns.go

package info (click to toggle)
elvish 0.21.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,372 kB
  • sloc: javascript: 236; sh: 130; python: 104; makefile: 88; xml: 9
file content (301 lines) | stat: -rw-r--r-- 7,829 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
package eval

import (
	"fmt"
	"unsafe"

	"src.elv.sh/pkg/eval/vars"
	"src.elv.sh/pkg/persistent/hash"
)

// Ns is the runtime representation of a namespace. The zero value of Ns is an
// empty namespace. To create a non-empty Ns, use either NsBuilder or CombineNs.
//
// An Ns is immutable after its associated code chunk has finished execution.
type Ns struct {
	// All variables in the namespace. Static variable accesses are compiled
	// into indexed accesses into this slice.
	slots []vars.Var
	// Static information for each variable, reflecting the state when the
	// associated code chunk has finished execution.
	//
	// This is only used for introspection and seeding the compilation of a new
	// code chunk. Normal static variable accesses are compiled into indexed
	// accesses into the slots slice.
	//
	// This is a slice instead of a map with the names of variables as keys,
	// because most namespaces are small enough for linear lookup to be faster
	// than map access.
	infos []staticVarInfo
}

// Nser is anything that can be converted to an *Ns.
type Nser interface {
	Ns() *Ns
}

// Static information known about a variable.
type staticVarInfo struct {
	name     string
	readOnly bool
	// Deleted variables can still be kept in the Ns since there might be a
	// reference to them in a closure. Shadowed variables are also considered
	// deleted.
	deleted bool
}

// CombineNs returns an *Ns that contains all the bindings from both ns1 and
// ns2. Names in ns2 takes precedence over those in ns1.
func CombineNs(ns1, ns2 *Ns) *Ns {
	ns := ns2.clone()

	hasName := map[string]bool{}
	for _, info := range ns.infos {
		if !info.deleted {
			hasName[info.name] = true
		}
	}
	for i, info := range ns1.infos {
		if !info.deleted && !hasName[info.name] {
			ns.slots = append(ns.slots, ns1.slots[i])
			ns.infos = append(ns.infos, info)
		}
	}
	return ns
}

func (ns *Ns) clone() *Ns {
	return &Ns{
		append([]vars.Var(nil), ns.slots...),
		append([]staticVarInfo(nil), ns.infos...)}
}

// Ns returns ns itself.
func (ns *Ns) Ns() *Ns {
	return ns
}

// Kind returns "ns".
func (ns *Ns) Kind() string {
	return "ns"
}

// Hash returns a hash of the address of ns.
func (ns *Ns) Hash() uint32 {
	return hash.Pointer(unsafe.Pointer(ns))
}

// Equal returns whether rhs has the same identity as ns.
func (ns *Ns) Equal(rhs any) bool {
	if ns2, ok := rhs.(*Ns); ok {
		return ns == ns2
	}
	return false
}

// Repr returns an opaque representation of the Ns showing its address.
func (ns *Ns) Repr(int) string {
	return fmt.Sprintf("<ns %p>", ns)
}

// Index looks up a variable with the given name, and returns its value if it
// exists. This is only used for introspection from Elvish code; for
// introspection from Go code, use IndexString.
func (ns *Ns) Index(k any) (any, bool) {
	if ks, ok := k.(string); ok {
		variable := ns.IndexString(ks)
		if variable == nil {
			return nil, false
		}
		return variable.Get(), true
	}
	return nil, false
}

// IndexString looks up a variable with the given name, and returns its value if
// it exists, or nil if it does not. This is the type-safe version of Index and
// is useful for introspection from Go code.
func (ns *Ns) IndexString(k string) vars.Var {
	_, i := ns.lookup(k)
	if i != -1 {
		return ns.slots[i]
	}
	return nil
}

func (ns *Ns) lookup(k string) (staticVarInfo, int) {
	for i, info := range ns.infos {
		if info.name == k && !info.deleted {
			return info, i
		}
	}
	return staticVarInfo{}, -1
}

// IterateKeys produces the names of all the variables in this Ns.
func (ns *Ns) IterateKeys(f func(any) bool) {
	for _, info := range ns.infos {
		if info.deleted {
			continue
		}
		if !f(info.name) {
			break
		}
	}
}

// IterateKeysString produces the names of all variables in the Ns. It is the
// type-safe version of IterateKeys and is useful for introspection from Go
// code. It doesn't support breaking early.
func (ns *Ns) IterateKeysString(f func(string)) {
	for _, info := range ns.infos {
		if !info.deleted {
			f(info.name)
		}
	}
}

// HasKeyString reports whether the Ns has a variable with the given name.
func (ns *Ns) HasKeyString(k string) bool {
	for _, info := range ns.infos {
		if info.name == k && !info.deleted {
			return true
		}
	}
	return false
}

func (ns *Ns) static() *staticNs {
	return &staticNs{ns.infos}
}

// NsBuilder is a helper type used for building an Ns.
type NsBuilder struct {
	prefix string
	m      map[string]vars.Var
}

// BuildNs returns a helper for building an Ns.
func BuildNs() NsBuilder {
	return BuildNsNamed("")
}

// BuildNsNamed returns a helper for building an Ns with the given name. The name is
// only used for the names of Go functions.
func BuildNsNamed(name string) NsBuilder {
	prefix := ""
	if name != "" {
		prefix = "<" + name + ">:"
	}
	return NsBuilder{prefix, make(map[string]vars.Var)}
}

// AddVar adds a variable.
func (nb NsBuilder) AddVar(name string, v vars.Var) NsBuilder {
	nb.m[name] = v
	return nb
}

// AddVars adds all the variables given in the map.
func (nb NsBuilder) AddVars(m map[string]vars.Var) NsBuilder {
	for name, v := range m {
		nb.AddVar(name, v)
	}
	return nb
}

// AddFn adds a function. The resulting variable will be read-only.
func (nb NsBuilder) AddFn(name string, v Callable) NsBuilder {
	return nb.AddVar(name+FnSuffix, vars.NewReadOnly(v))
}

// AddNs adds a sub-namespace. The resulting variable will be read-only.
func (nb NsBuilder) AddNs(name string, v Nser) NsBuilder {
	return nb.AddVar(name+NsSuffix, vars.NewReadOnly(v.Ns()))
}

// AddGoFn adds a Go function. The resulting variable will be read-only.
func (nb NsBuilder) AddGoFn(name string, impl any) NsBuilder {
	return nb.AddFn(name, NewGoFn(nb.prefix+name, impl))
}

// AddGoFns adds Go functions. The resulting variables will be read-only.
func (nb NsBuilder) AddGoFns(fns map[string]any) NsBuilder {
	for name, impl := range fns {
		nb.AddGoFn(name, impl)
	}
	return nb
}

// Ns builds a namespace.
func (nb NsBuilder) Ns() *Ns {
	n := len(nb.m)
	ns := &Ns{make([]vars.Var, n), make([]staticVarInfo, n)}
	i := 0
	for name, variable := range nb.m {
		ns.slots[i] = variable
		ns.infos[i] = staticVarInfo{name, vars.IsReadOnly(variable), false}
		i++
	}
	return ns
}

// The compile-time representation of a namespace. Called "static" namespace
// since it contains information that are known without executing the code.
// The data structure itself, however, is not static, and gets mutated as the
// compiler gains more information about the namespace. The zero value of
// staticNs is an empty namespace.
type staticNs struct {
	infos []staticVarInfo
}

func (ns *staticNs) clone() *staticNs {
	return &staticNs{append([]staticVarInfo(nil), ns.infos...)}
}

func (ns *staticNs) del(k string) {
	if _, i := ns.lookup(k); i != -1 {
		ns.infos[i].deleted = true
	}
}

// Adds a name, shadowing any existing one, and returns the index for the new
// name.
func (ns *staticNs) add(k string) int {
	ns.del(k)
	ns.infos = append(ns.infos, staticVarInfo{k, false, false})
	return len(ns.infos) - 1
}

func (ns *staticNs) lookup(k string) (staticVarInfo, int) {
	for i, info := range ns.infos {
		if info.name == k && !info.deleted {
			return info, i
		}
	}
	return staticVarInfo{}, -1
}

type staticUpNs struct {
	infos []upvalInfo
}

type upvalInfo struct {
	name string
	// Whether the upvalue comes from the immediate outer scope, i.e. the local
	// scope a lambda is evaluated in.
	local bool
	// Index of the upvalue variable. If local is true, this is an index into
	// the local scope. If local is false, this is an index into the up scope.
	index int
}

func (up *staticUpNs) add(k string, local bool, index int) int {
	for i, info := range up.infos {
		if info.name == k {
			return i
		}
	}
	up.infos = append(up.infos, upvalInfo{k, local, index})
	return len(up.infos) - 1
}