github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/cmds/elvish/eval/closure.go (about)

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