123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- package eval
- import (
- "fmt"
- "sort"
- "strconv"
- "strings"
- "unsafe"
- "src.elv.sh/pkg/diag"
- "src.elv.sh/pkg/eval/errs"
- "src.elv.sh/pkg/eval/vals"
- "src.elv.sh/pkg/eval/vars"
- "src.elv.sh/pkg/parse"
- "src.elv.sh/pkg/persistent/hash"
- )
- // Closure is a function defined with Elvish code. Each Closure has its unique
- // identity.
- type Closure struct {
- ArgNames []string
- // The index of the rest argument. -1 if there is no rest argument.
- RestArg int
- OptNames []string
- OptDefaults []any
- SrcMeta parse.Source
- DefRange diag.Ranging
- op effectOp
- newLocal []staticVarInfo
- captured *Ns
- }
- var _ Callable = &Closure{}
- // Kind returns "fn".
- func (*Closure) Kind() string {
- return "fn"
- }
- // Equal compares by address.
- func (c *Closure) Equal(rhs any) bool {
- return c == rhs
- }
- // Hash returns the hash of the address of the closure.
- func (c *Closure) Hash() uint32 {
- return hash.Pointer(unsafe.Pointer(c))
- }
- // Repr returns an opaque representation "<closure 0x23333333>".
- func (c *Closure) Repr(int) string {
- return fmt.Sprintf("<closure %p>", c)
- }
- // Call calls a closure.
- func (c *Closure) Call(fm *Frame, args []any, opts map[string]any) error {
- // Check number of arguments.
- if c.RestArg != -1 {
- if len(args) < len(c.ArgNames)-1 {
- return errs.ArityMismatch{What: "arguments",
- ValidLow: len(c.ArgNames) - 1, ValidHigh: -1, Actual: len(args)}
- }
- } else {
- if len(args) != len(c.ArgNames) {
- return errs.ArityMismatch{What: "arguments",
- ValidLow: len(c.ArgNames), ValidHigh: len(c.ArgNames), Actual: len(args)}
- }
- }
- // Check whether all supplied options are supported. This map contains the
- // subset of keys from opts that can be found in c.OptNames.
- optSupported := make(map[string]struct{})
- for _, name := range c.OptNames {
- _, ok := opts[name]
- if ok {
- optSupported[name] = struct{}{}
- }
- }
- if len(optSupported) < len(opts) {
- // Report all the options that are not supported.
- unsupported := make([]string, 0, len(opts)-len(optSupported))
- for name := range opts {
- _, supported := optSupported[name]
- if !supported {
- unsupported = append(unsupported, parse.Quote(name))
- }
- }
- sort.Strings(unsupported)
- return UnsupportedOptionsError{unsupported}
- }
- // This Frame is dedicated to the current form, so we can modify it in place.
- // BUG(xiaq): When evaluating closures, async access to global variables
- // and ports can be problematic.
- // Make upvalue namespace and capture variables.
- fm.up = c.captured
- // Populate local scope with arguments, options, and newly created locals.
- localSize := len(c.ArgNames) + len(c.OptNames) + len(c.newLocal)
- local := &Ns{make([]vars.Var, localSize), make([]staticVarInfo, localSize)}
- for i, name := range c.ArgNames {
- local.infos[i] = staticVarInfo{name, false, false}
- }
- if c.RestArg == -1 {
- for i := range c.ArgNames {
- local.slots[i] = vars.FromInit(args[i])
- }
- } else {
- for i := 0; i < c.RestArg; i++ {
- local.slots[i] = vars.FromInit(args[i])
- }
- restOff := len(args) - len(c.ArgNames)
- local.slots[c.RestArg] = vars.FromInit(
- vals.MakeList(args[c.RestArg : c.RestArg+restOff+1]...))
- for i := c.RestArg + 1; i < len(c.ArgNames); i++ {
- local.slots[i] = vars.FromInit(args[i+restOff])
- }
- }
- offset := len(c.ArgNames)
- for i, name := range c.OptNames {
- v, ok := opts[name]
- if !ok {
- v = c.OptDefaults[i]
- }
- local.infos[offset+i] = staticVarInfo{name, false, false}
- local.slots[offset+i] = vars.FromInit(v)
- }
- offset += len(c.OptNames)
- for i, info := range c.newLocal {
- local.infos[offset+i] = info
- // TODO: Take info.readOnly into account too when creating variable
- local.slots[offset+i] = MakeVarFromName(info.name)
- }
- fm.local = local
- fm.srcMeta = c.SrcMeta
- fm.defers = new([]func(*Frame) Exception)
- exc := c.op.exec(fm)
- excDefer := fm.runDefers()
- // TODO: Combine exc and excDefer if both are not nil
- if excDefer != nil && exc == nil {
- exc = excDefer
- }
- return exc
- }
- var (
- fnDefault = NewGoFn("nop~", nop)
- nsDefault = &Ns{}
- )
- // MakeVarFromName creates a Var with a suitable type constraint inferred from
- // the name.
- func MakeVarFromName(name string) vars.Var {
- switch {
- case strings.HasSuffix(name, FnSuffix):
- val := fnDefault
- return vars.FromPtr(&val)
- case strings.HasSuffix(name, NsSuffix):
- val := nsDefault
- return vars.FromPtr(&val)
- default:
- return vars.FromInit(nil)
- }
- }
- // UnsupportedOptionsError is an error returned by a closure call when there are
- // unsupported options.
- type UnsupportedOptionsError struct {
- Options []string
- }
- func (er UnsupportedOptionsError) Error() string {
- if len(er.Options) == 1 {
- return fmt.Sprintf("unsupported option: %s", er.Options[0])
- }
- return fmt.Sprintf("unsupported options: %s", strings.Join(er.Options, ", "))
- }
- func (c *Closure) Fields() vals.StructMap { return closureFields{c} }
- type closureFields struct{ c *Closure }
- func (closureFields) IsStructMap() {}
- func (cf closureFields) ArgNames() vals.List { return vals.MakeListFromStrings(cf.c.ArgNames...) }
- func (cf closureFields) RestArg() string { return strconv.Itoa(cf.c.RestArg) }
- func (cf closureFields) OptNames() vals.List { return vals.MakeListFromStrings(cf.c.OptNames...) }
- func (cf closureFields) Src() parse.Source { return cf.c.SrcMeta }
- func (cf closureFields) OptDefaults() vals.List {
- return vals.MakeList(cf.c.OptDefaults...)
- }
- func (cf closureFields) Body() string {
- r := cf.c.op.(diag.Ranger).Range()
- return cf.c.SrcMeta.Code[r.From:r.To]
- }
- func (cf closureFields) Def() string {
- return cf.c.SrcMeta.Code[cf.c.DefRange.From:cf.c.DefRange.To]
- }
|