github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/elvish/eval/closure.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/u-root/u-root/cmds/core/elvish/eval/vals"
     8  	"github.com/u-root/u-root/cmds/core/elvish/eval/vars"
     9  	"github.com/u-root/u-root/cmds/core/elvish/hash"
    10  	"github.com/u-root/u-root/cmds/core/elvish/parse"
    11  )
    12  
    13  // ErrArityMismatch is thrown by a closure when the number of arguments the user
    14  // supplies does not match with what is required.
    15  var ErrArityMismatch = errors.New("arity mismatch")
    16  
    17  // Closure is a closure defined in elvish script.
    18  type Closure struct {
    19  	ArgNames []string
    20  	// The name for the rest argument. If empty, the function has fixed arity.
    21  	RestArg     string
    22  	OptNames    []string
    23  	OptDefaults []interface{}
    24  	Op          Op
    25  	Captured    Ns
    26  	SrcMeta     *Source
    27  	DefBegint   int
    28  	DefEnd      int
    29  }
    30  
    31  var _ Callable = &Closure{}
    32  
    33  // Kind returns "fn".
    34  func (*Closure) Kind() string {
    35  	return "fn"
    36  }
    37  
    38  // Equal compares by identity.
    39  func (c *Closure) Equal(rhs interface{}) bool {
    40  	return c == rhs
    41  }
    42  
    43  func (c *Closure) Hash() uint32 {
    44  	return hash.Hash(c)
    45  }
    46  
    47  // Repr returns an opaque representation "<closure 0x23333333>".
    48  func (c *Closure) Repr(int) string {
    49  	return fmt.Sprintf("<closure %p>", c)
    50  }
    51  
    52  func (c *Closure) Index(k interface{}) (interface{}, bool) {
    53  	switch k {
    54  	case "arg-names":
    55  		return vals.MakeStringList(c.ArgNames...), true
    56  	case "rest-arg":
    57  		return c.RestArg, true
    58  	case "opt-names":
    59  		return vals.MakeStringList(c.OptNames...), true
    60  	case "opt-defaults":
    61  		return vals.MakeList(c.OptDefaults...), true
    62  	case "body":
    63  		return c.SrcMeta.code[c.Op.Begin:c.Op.End], true
    64  	case "def":
    65  		return c.SrcMeta.code[c.DefBegint:c.DefEnd], true
    66  	case "src":
    67  		return c.SrcMeta, true
    68  	}
    69  	return nil, false
    70  }
    71  
    72  func (c *Closure) IterateKeys(f func(interface{}) bool) {
    73  	vals.Feed(f, "arg-names", "rest-arg",
    74  		"opt-names", "opt-defaults", "body", "def", "src")
    75  }
    76  
    77  // Call calls a closure.
    78  func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error {
    79  	if c.RestArg != "" {
    80  		if len(c.ArgNames) > len(args) {
    81  			return fmt.Errorf("need %d or more arguments, got %d", len(c.ArgNames), len(args))
    82  		}
    83  	} else {
    84  		if len(c.ArgNames) != len(args) {
    85  			return fmt.Errorf("need %d arguments, got %d", len(c.ArgNames), len(args))
    86  		}
    87  	}
    88  
    89  	// This evalCtx is dedicated to the current form, so we modify it in place.
    90  	// BUG(xiaq): When evaluating closures, async access to global variables
    91  	// and ports can be problematic.
    92  
    93  	// Make upvalue namespace and capture variables.
    94  	// TODO(xiaq): Is it safe to simply assign ec.up = c.Captured?
    95  	fm.up = make(Ns)
    96  	for name, variable := range c.Captured {
    97  		fm.up[name] = variable
    98  	}
    99  
   100  	// Populate local scope with arguments, possibly a rest argument, and
   101  	// options.
   102  	fm.local = make(Ns)
   103  	for i, name := range c.ArgNames {
   104  		fm.local[name] = vars.NewAnyWithInit(args[i])
   105  	}
   106  	if c.RestArg != "" {
   107  		fm.local[c.RestArg] = vars.NewAnyWithInit(vals.MakeList(args[len(c.ArgNames):]...))
   108  	}
   109  	optUsed := make(map[string]struct{})
   110  	for i, name := range c.OptNames {
   111  		v, ok := opts[name]
   112  		if ok {
   113  			optUsed[name] = struct{}{}
   114  		} else {
   115  			v = c.OptDefaults[i]
   116  		}
   117  		fm.local[name] = vars.NewAnyWithInit(v)
   118  	}
   119  	for name := range opts {
   120  		_, used := optUsed[name]
   121  		if !used {
   122  			return fmt.Errorf("unknown option %s", parse.Quote(name))
   123  		}
   124  	}
   125  
   126  	fm.traceback = fm.addTraceback()
   127  
   128  	fm.srcMeta = c.SrcMeta
   129  	return c.Op.Exec(fm)
   130  }