github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/var_ref.go (about)

     1  package eval
     2  
     3  import (
     4  	"strings"
     5  
     6  	"src.elv.sh/pkg/diag"
     7  	"src.elv.sh/pkg/eval/vars"
     8  )
     9  
    10  // This file implements variable resolution. Elvish has fully static lexical
    11  // scopes, so variable resolution involves some work in the compilation phase as
    12  // well.
    13  //
    14  // During compilation, a qualified variable name (whether in lvalue, like "x
    15  // = foo", or in variable use, like "$x") is searched in compiler's staticNs
    16  // tables to determine which scope they belong to, as well as their indicies in
    17  // that scope. This step is just called "resolve" in the code, and it stores
    18  // information in a varRef struct.
    19  //
    20  // During evaluation, the varRef is then used to look up the Var for the
    21  // variable. This step is called "deref" in the code.
    22  //
    23  // The resolve phase can take place during evaluation as well for introspection.
    24  
    25  // Keeps all the information statically about a variable referenced by a
    26  // qualified name.
    27  type varRef struct {
    28  	scope    varScope
    29  	index    int
    30  	subNames []string
    31  }
    32  
    33  type varScope int
    34  
    35  const (
    36  	localScope varScope = 1 + iota
    37  	captureScope
    38  	builtinScope
    39  	envScope
    40  	externalScope
    41  )
    42  
    43  // An interface satisfied by both *compiler and *Frame. Used to implement
    44  // resolveVarRef as a function that works for both types.
    45  type scopeSearcher interface {
    46  	searchLocal(k string) int
    47  	searchCapture(k string) int
    48  	searchBuiltin(k string, r diag.Ranger) int
    49  }
    50  
    51  // Resolves a qname into a varRef.
    52  func resolveVarRef(s scopeSearcher, qname string, r diag.Ranger) *varRef {
    53  	qname = strings.TrimPrefix(qname, ":")
    54  	if ref := resolveVarRefLocal(s, qname); ref != nil {
    55  		return ref
    56  	}
    57  	if ref := resolveVarRefCapture(s, qname); ref != nil {
    58  		return ref
    59  	}
    60  	if ref := resolveVarRefBuiltin(s, qname, r); ref != nil {
    61  		return ref
    62  	}
    63  	return nil
    64  }
    65  
    66  func resolveVarRefLocal(s scopeSearcher, qname string) *varRef {
    67  	first, rest := SplitQName(qname)
    68  	index := s.searchLocal(first)
    69  	if index != -1 {
    70  		return &varRef{scope: localScope, index: index, subNames: SplitQNameSegs(rest)}
    71  	}
    72  	return nil
    73  }
    74  
    75  func resolveVarRefCapture(s scopeSearcher, qname string) *varRef {
    76  	first, rest := SplitQName(qname)
    77  	if index := s.searchCapture(first); index != -1 {
    78  		return &varRef{scope: captureScope, index: index, subNames: SplitQNameSegs(rest)}
    79  	}
    80  	return nil
    81  }
    82  
    83  func resolveVarRefBuiltin(s scopeSearcher, qname string, r diag.Ranger) *varRef {
    84  	first, rest := SplitQName(qname)
    85  	if rest != "" {
    86  		// Try special namespace first.
    87  		switch first {
    88  		case "local:":
    89  			return resolveVarRefLocal(s, rest)
    90  		case "up:":
    91  			return resolveVarRefCapture(s, rest)
    92  		case "e:":
    93  			if strings.HasSuffix(rest, FnSuffix) {
    94  				return &varRef{scope: externalScope, subNames: []string{rest[:len(rest)-1]}}
    95  			}
    96  		case "E:":
    97  			return &varRef{scope: envScope, subNames: []string{rest}}
    98  		}
    99  	}
   100  	if index := s.searchBuiltin(first, r); index != -1 {
   101  		return &varRef{scope: builtinScope, index: index, subNames: SplitQNameSegs(rest)}
   102  	}
   103  	return nil
   104  }
   105  
   106  // Tries to resolve the command head as an internal command, i.e. a builtin
   107  // special command or a function.
   108  func resolveCmdHeadInternally(s scopeSearcher, head string, r diag.Ranger) (compileBuiltin, *varRef) {
   109  	special, ok := builtinSpecials[head]
   110  	if ok {
   111  		return special, nil
   112  	}
   113  	sigil, qname := SplitSigil(head)
   114  	if sigil == "" {
   115  		varName := qname + FnSuffix
   116  		ref := resolveVarRef(s, varName, r)
   117  		if ref != nil {
   118  			return nil, ref
   119  		}
   120  	}
   121  	return nil, nil
   122  }
   123  
   124  // Dereferences a varRef into a Var.
   125  func deref(fm *Frame, ref *varRef) vars.Var {
   126  	variable, subNames := derefBase(fm, ref)
   127  	for _, subName := range subNames {
   128  		ns, ok := variable.Get().(*Ns)
   129  		if !ok {
   130  			return nil
   131  		}
   132  		variable = ns.IndexName(subName)
   133  		if variable == nil {
   134  			return nil
   135  		}
   136  	}
   137  	return variable
   138  }
   139  
   140  func derefBase(fm *Frame, ref *varRef) (vars.Var, []string) {
   141  	switch ref.scope {
   142  	case localScope:
   143  		return fm.local.slots[ref.index], ref.subNames
   144  	case captureScope:
   145  		return fm.up.slots[ref.index], ref.subNames
   146  	case builtinScope:
   147  		return fm.Evaler.Builtin().slots[ref.index], ref.subNames
   148  	case envScope:
   149  		return vars.FromEnv(ref.subNames[0]), nil
   150  	case externalScope:
   151  		return vars.NewReadOnly(NewExternalCmd(ref.subNames[0])), nil
   152  	default:
   153  		return nil, nil
   154  	}
   155  }
   156  
   157  func (cp *compiler) searchLocal(k string) int {
   158  	return cp.thisScope().lookup(k)
   159  }
   160  
   161  func (cp *compiler) searchCapture(k string) int {
   162  	for i := len(cp.scopes) - 2; i >= 0; i-- {
   163  		index := cp.scopes[i].lookup(k)
   164  		if index != -1 {
   165  			// Record the capture from i+1 to len(cp.scopes)-1, and reuse the
   166  			// index to keep the index into the previous scope.
   167  			index = cp.captures[i+1].add(k, true, index)
   168  			for j := i + 2; j < len(cp.scopes); j++ {
   169  				index = cp.captures[j].add(k, false, index)
   170  			}
   171  			return index
   172  		}
   173  	}
   174  	return -1
   175  }
   176  
   177  func (cp *compiler) searchBuiltin(k string, r diag.Ranger) int {
   178  	index := cp.builtin.lookup(k)
   179  	if index != -1 {
   180  		cp.checkDeprecatedBuiltin(k, r)
   181  	}
   182  	return index
   183  }
   184  
   185  func (fm *Frame) searchLocal(k string) int {
   186  	return fm.local.lookup(k)
   187  }
   188  
   189  func (fm *Frame) searchCapture(k string) int {
   190  	return fm.up.lookup(k)
   191  }
   192  
   193  func (fm *Frame) searchBuiltin(k string, r diag.Ranger) int {
   194  	return fm.Evaler.Builtin().lookup(k)
   195  }