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  }