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

     1  package eval
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  	"unsafe"
     9  
    10  	"src.elv.sh/pkg/diag"
    11  	"src.elv.sh/pkg/eval/errs"
    12  	"src.elv.sh/pkg/eval/vals"
    13  	"src.elv.sh/pkg/eval/vars"
    14  	"src.elv.sh/pkg/parse"
    15  	"src.elv.sh/pkg/persistent/hash"
    16  )
    17  
    18  // A user-defined function in Elvish code. Each closure has its unique identity.
    19  type closure struct {
    20  	ArgNames []string
    21  	// The index of the rest argument. -1 if there is no rest argument.
    22  	RestArg     int
    23  	OptNames    []string
    24  	OptDefaults []interface{}
    25  	Op          effectOp
    26  	NewLocal    []string
    27  	Captured    *Ns
    28  	SrcMeta     parse.Source
    29  	DefRange    diag.Ranging
    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 address.
    40  func (c *closure) Equal(rhs interface{}) bool {
    41  	return c == rhs
    42  }
    43  
    44  // Hash returns the hash of the address of the closure.
    45  func (c *closure) Hash() uint32 {
    46  	return hash.Pointer(unsafe.Pointer(c))
    47  }
    48  
    49  // Repr returns an opaque representation "<closure 0x23333333>".
    50  func (c *closure) Repr(int) string {
    51  	return fmt.Sprintf("<closure %p>", c)
    52  }
    53  
    54  // Call calls a closure.
    55  func (c *closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error {
    56  	// Check number of arguments.
    57  	if c.RestArg != -1 {
    58  		if len(args) < len(c.ArgNames)-1 {
    59  			return errs.ArityMismatch{
    60  				What:     "arguments here",
    61  				ValidLow: len(c.ArgNames) - 1, ValidHigh: -1, Actual: len(args)}
    62  		}
    63  	} else {
    64  		if len(args) != len(c.ArgNames) {
    65  			return errs.ArityMismatch{
    66  				What:     "arguments here",
    67  				ValidLow: len(c.ArgNames), ValidHigh: len(c.ArgNames), Actual: len(args)}
    68  		}
    69  	}
    70  	// Check whether all supplied options are supported. This map contains the
    71  	// subset of keys from opts that can be found in c.OptNames.
    72  	optSupported := make(map[string]struct{})
    73  	for _, name := range c.OptNames {
    74  		_, ok := opts[name]
    75  		if ok {
    76  			optSupported[name] = struct{}{}
    77  		}
    78  	}
    79  	if len(optSupported) < len(opts) {
    80  		// Report all the options that are not supported.
    81  		unsupported := make([]string, 0, len(opts)-len(optSupported))
    82  		for name := range opts {
    83  			_, supported := optSupported[name]
    84  			if !supported {
    85  				unsupported = append(unsupported, parse.Quote(name))
    86  			}
    87  		}
    88  		sort.Strings(unsupported)
    89  		return UnsupportedOptionsError{unsupported}
    90  	}
    91  
    92  	// This Frame is dedicated to the current form, so we can modify it in place.
    93  
    94  	// BUG(xiaq): When evaluating closures, async access to global variables
    95  	// and ports can be problematic.
    96  
    97  	// Make upvalue namespace and capture variables.
    98  	fm.up = c.Captured
    99  
   100  	// Populate local scope with arguments, options, and newly created locals.
   101  	localSize := len(c.ArgNames) + len(c.OptNames) + len(c.NewLocal)
   102  	local := &Ns{make([]vars.Var, localSize), make([]string, localSize), make([]bool, localSize)}
   103  
   104  	for i, name := range c.ArgNames {
   105  		local.names[i] = name
   106  	}
   107  	if c.RestArg == -1 {
   108  		for i := range c.ArgNames {
   109  			local.slots[i] = vars.FromInit(args[i])
   110  		}
   111  	} else {
   112  		for i := 0; i < c.RestArg; i++ {
   113  			local.slots[i] = vars.FromInit(args[i])
   114  		}
   115  		restOff := len(args) - len(c.ArgNames)
   116  		local.slots[c.RestArg] = vars.FromInit(
   117  			vals.MakeList(args[c.RestArg : c.RestArg+restOff+1]...))
   118  		for i := c.RestArg + 1; i < len(c.ArgNames); i++ {
   119  			local.slots[i] = vars.FromInit(args[i+restOff])
   120  		}
   121  	}
   122  
   123  	offset := len(c.ArgNames)
   124  	for i, name := range c.OptNames {
   125  		v, ok := opts[name]
   126  		if !ok {
   127  			v = c.OptDefaults[i]
   128  		}
   129  		local.names[offset+i] = name
   130  		local.slots[offset+i] = vars.FromInit(v)
   131  	}
   132  
   133  	offset += len(c.OptNames)
   134  	for i, name := range c.NewLocal {
   135  		local.names[offset+i] = name
   136  		local.slots[offset+i] = MakeVarFromName(name)
   137  	}
   138  
   139  	fm.local = local
   140  	fm.srcMeta = c.SrcMeta
   141  	return c.Op.exec(fm)
   142  }
   143  
   144  // MakeVarFromName creates a Var with a suitable type constraint inferred from
   145  // the name.
   146  func MakeVarFromName(name string) vars.Var {
   147  	switch {
   148  	case strings.HasSuffix(name, FnSuffix):
   149  		val := NewGoFn("nop~", nop)
   150  		return vars.FromPtr(&val)
   151  	case strings.HasSuffix(name, NsSuffix):
   152  		val := (*Ns)(nil)
   153  		return vars.FromPtr(&val)
   154  	default:
   155  		return vars.FromInit(nil)
   156  	}
   157  }
   158  
   159  // UnsupportedOptionsError is an error returned by a closure call when there are
   160  // unsupported options.
   161  type UnsupportedOptionsError struct {
   162  	Options []string
   163  }
   164  
   165  func (er UnsupportedOptionsError) Error() string {
   166  	if len(er.Options) == 1 {
   167  		return fmt.Sprintf("unsupported option: %s", er.Options[0])
   168  	}
   169  	return fmt.Sprintf("unsupported options: %s", strings.Join(er.Options, ", "))
   170  }
   171  
   172  func (c *closure) Fields() vals.StructMap { return closureFields{c} }
   173  
   174  type closureFields struct{ c *closure }
   175  
   176  func (closureFields) IsStructMap() {}
   177  
   178  func (cf closureFields) ArgNames() vals.List { return listOfStrings(cf.c.ArgNames) }
   179  func (cf closureFields) RestArg() string     { return strconv.Itoa(cf.c.RestArg) }
   180  func (cf closureFields) OptNames() vals.List { return listOfStrings(cf.c.OptNames) }
   181  func (cf closureFields) Src() parse.Source   { return cf.c.SrcMeta }
   182  
   183  func (cf closureFields) OptDefaults() vals.List {
   184  	return vals.MakeList(cf.c.OptDefaults...)
   185  }
   186  
   187  func (cf closureFields) Body() string {
   188  	r := cf.c.Op.(diag.Ranger).Range()
   189  	return cf.c.SrcMeta.Code[r.From:r.To]
   190  }
   191  
   192  func (cf closureFields) Def() string {
   193  	return cf.c.SrcMeta.Code[cf.c.DefRange.From:cf.c.DefRange.To]
   194  }
   195  
   196  func listOfStrings(ss []string) vals.List {
   197  	list := vals.EmptyList
   198  	for _, s := range ss {
   199  		list = list.Cons(s)
   200  	}
   201  	return list
   202  }