src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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  // Closure is a function defined with Elvish code. Each Closure has its unique
    19  // identity.
    20  type Closure struct {
    21  	ArgNames []string
    22  	// The index of the rest argument. -1 if there is no rest argument.
    23  	RestArg     int
    24  	OptNames    []string
    25  	OptDefaults []any
    26  	SrcMeta     parse.Source
    27  	DefRange    diag.Ranging
    28  	op          effectOp
    29  	newLocal    []staticVarInfo
    30  	captured    *Ns
    31  }
    32  
    33  var (
    34  	_ Callable       = &Closure{}
    35  	_ vals.PseudoMap = &Closure{}
    36  )
    37  
    38  // Kind returns "fn".
    39  func (*Closure) Kind() string {
    40  	return "fn"
    41  }
    42  
    43  // Equal compares by address.
    44  func (c *Closure) Equal(rhs any) bool {
    45  	return c == rhs
    46  }
    47  
    48  // Hash returns the hash of the address of the closure.
    49  func (c *Closure) Hash() uint32 {
    50  	return hash.Pointer(unsafe.Pointer(c))
    51  }
    52  
    53  // Call calls a closure.
    54  func (c *Closure) Call(fm *Frame, args []any, opts map[string]any) error {
    55  	// Check number of arguments.
    56  	if c.RestArg != -1 {
    57  		if len(args) < len(c.ArgNames)-1 {
    58  			return errs.ArityMismatch{What: "arguments",
    59  				ValidLow: len(c.ArgNames) - 1, ValidHigh: -1, Actual: len(args)}
    60  		}
    61  	} else {
    62  		if len(args) != len(c.ArgNames) {
    63  			return errs.ArityMismatch{What: "arguments",
    64  				ValidLow: len(c.ArgNames), ValidHigh: len(c.ArgNames), Actual: len(args)}
    65  		}
    66  	}
    67  	// Check whether all supplied options are supported. This map contains the
    68  	// subset of keys from opts that can be found in c.OptNames.
    69  	optSupported := make(map[string]struct{})
    70  	for _, name := range c.OptNames {
    71  		_, ok := opts[name]
    72  		if ok {
    73  			optSupported[name] = struct{}{}
    74  		}
    75  	}
    76  	if len(optSupported) < len(opts) {
    77  		// Report all the options that are not supported.
    78  		unsupported := make([]string, 0, len(opts)-len(optSupported))
    79  		for name := range opts {
    80  			_, supported := optSupported[name]
    81  			if !supported {
    82  				unsupported = append(unsupported, parse.Quote(name))
    83  			}
    84  		}
    85  		sort.Strings(unsupported)
    86  		return UnsupportedOptionsError{unsupported}
    87  	}
    88  
    89  	// This Frame is dedicated to the current form, so we can modify it in place.
    90  
    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  	fm.up = c.captured
    96  
    97  	// Populate local scope with arguments, options, and newly created locals.
    98  	localSize := len(c.ArgNames) + len(c.OptNames) + len(c.newLocal)
    99  	local := &Ns{make([]vars.Var, localSize), make([]staticVarInfo, localSize)}
   100  
   101  	for i, name := range c.ArgNames {
   102  		local.infos[i] = staticVarInfo{name, false, false}
   103  	}
   104  	if c.RestArg == -1 {
   105  		for i := range c.ArgNames {
   106  			local.slots[i] = vars.FromInit(args[i])
   107  		}
   108  	} else {
   109  		for i := 0; i < c.RestArg; i++ {
   110  			local.slots[i] = vars.FromInit(args[i])
   111  		}
   112  		restOff := len(args) - len(c.ArgNames)
   113  		local.slots[c.RestArg] = vars.FromInit(
   114  			vals.MakeList(args[c.RestArg : c.RestArg+restOff+1]...))
   115  		for i := c.RestArg + 1; i < len(c.ArgNames); i++ {
   116  			local.slots[i] = vars.FromInit(args[i+restOff])
   117  		}
   118  	}
   119  
   120  	offset := len(c.ArgNames)
   121  	for i, name := range c.OptNames {
   122  		v, ok := opts[name]
   123  		if !ok {
   124  			v = c.OptDefaults[i]
   125  		}
   126  		local.infos[offset+i] = staticVarInfo{name, false, false}
   127  		local.slots[offset+i] = vars.FromInit(v)
   128  	}
   129  
   130  	offset += len(c.OptNames)
   131  	for i, info := range c.newLocal {
   132  		local.infos[offset+i] = info
   133  		// TODO: Take info.readOnly into account too when creating variable
   134  		local.slots[offset+i] = MakeVarFromName(info.name)
   135  	}
   136  
   137  	fm.local = local
   138  	fm.srcMeta = c.SrcMeta
   139  	fm.defers = new([]func(*Frame) Exception)
   140  	exc := c.op.exec(fm)
   141  	excDefer := fm.runDefers()
   142  	// TODO: Combine exc and excDefer if both are not nil
   143  	if excDefer != nil && exc == nil {
   144  		exc = excDefer
   145  	}
   146  	return exc
   147  }
   148  
   149  // MakeVarFromName creates a Var with a suitable type constraint inferred from
   150  // the name.
   151  func MakeVarFromName(name string) vars.Var {
   152  	switch {
   153  	case strings.HasSuffix(name, FnSuffix):
   154  		val := nopGoFn
   155  		return vars.FromPtr(&val)
   156  	case strings.HasSuffix(name, NsSuffix):
   157  		val := &Ns{}
   158  		return vars.FromPtr(&val)
   159  	default:
   160  		return vars.FromInit(nil)
   161  	}
   162  }
   163  
   164  // UnsupportedOptionsError is an error returned by a closure call when there are
   165  // unsupported options.
   166  type UnsupportedOptionsError struct {
   167  	Options []string
   168  }
   169  
   170  func (er UnsupportedOptionsError) Error() string {
   171  	if len(er.Options) == 1 {
   172  		return fmt.Sprintf("unsupported option: %s", er.Options[0])
   173  	}
   174  	return fmt.Sprintf("unsupported options: %s", strings.Join(er.Options, ", "))
   175  }
   176  
   177  func (c *Closure) Fields() vals.StructMap { return closureFields{c} }
   178  
   179  type closureFields struct{ c *Closure }
   180  
   181  func (closureFields) IsStructMap() {}
   182  
   183  func (cf closureFields) ArgNames() vals.List { return vals.MakeListSlice(cf.c.ArgNames) }
   184  func (cf closureFields) RestArg() string     { return strconv.Itoa(cf.c.RestArg) }
   185  func (cf closureFields) OptNames() vals.List { return vals.MakeListSlice(cf.c.OptNames) }
   186  func (cf closureFields) Src() parse.Source   { return cf.c.SrcMeta }
   187  
   188  func (cf closureFields) OptDefaults() vals.List {
   189  	return vals.MakeList(cf.c.OptDefaults...)
   190  }
   191  
   192  func (cf closureFields) Body() string {
   193  	r := cf.c.op.(diag.Ranger).Range()
   194  	return cf.c.SrcMeta.Code[r.From:r.To]
   195  }
   196  
   197  func (cf closureFields) Def() string {
   198  	return cf.c.SrcMeta.Code[cf.c.DefRange.From:cf.c.DefRange.To]
   199  }