mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 01:47:51 +08:00
973fe39798
This addresses #1114.
214 lines
5.6 KiB
Go
214 lines
5.6 KiB
Go
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"
|
|
)
|
|
|
|
// A user-defined function in 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 []interface{}
|
|
Op effectOp
|
|
NewLocal []staticVarInfo
|
|
Captured *Ns
|
|
SrcMeta parse.Source
|
|
DefRange diag.Ranging
|
|
}
|
|
|
|
var _ Callable = &closure{}
|
|
|
|
// Kind returns "fn".
|
|
func (*closure) Kind() string {
|
|
return "fn"
|
|
}
|
|
|
|
// Equal compares by address.
|
|
func (c *closure) Equal(rhs interface{}) 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 []interface{}, opts map[string]interface{}) 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 listOfStrings(cf.c.ArgNames) }
|
|
func (cf closureFields) RestArg() string { return strconv.Itoa(cf.c.RestArg) }
|
|
func (cf closureFields) OptNames() vals.List { return listOfStrings(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]
|
|
}
|
|
|
|
func listOfStrings(ss []string) vals.List {
|
|
list := vals.EmptyList
|
|
for _, s := range ss {
|
|
list = list.Cons(s)
|
|
}
|
|
return list
|
|
}
|