github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/ns.go (about)

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