elvish/pkg/eval/closure.go

148 lines
3.8 KiB
Go
Raw Normal View History

2016-02-19 05:52:05 +08:00
package eval
import (
"fmt"
"strconv"
2017-08-31 02:52:27 +08:00
"unsafe"
"github.com/elves/elvish/pkg/diag"
"github.com/elves/elvish/pkg/eval/errs"
2019-12-24 04:00:59 +08:00
"github.com/elves/elvish/pkg/eval/vals"
"github.com/elves/elvish/pkg/eval/vars"
"github.com/elves/elvish/pkg/parse"
2017-08-31 02:52:27 +08:00
"github.com/xiaq/persistent/hash"
2016-02-19 05:52:05 +08:00
)
2019-04-19 05:15:34 +08:00
// Closure is a closure defined in Elvish script. Each closure has its unique
// identity.
2016-02-19 05:52:05 +08:00
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
Captured Ns
SrcMeta parse.Source
DefRange diag.Ranging
2016-02-19 05:52:05 +08:00
}
var _ Callable = &Closure{}
2017-06-11 07:04:53 +08:00
// Kind returns "fn".
2016-02-19 05:52:05 +08:00
func (*Closure) Kind() string {
return "fn"
}
2019-04-19 05:15:34 +08:00
// Equal compares by address.
2017-08-31 01:47:50 +08:00
func (c *Closure) Equal(rhs interface{}) bool {
return c == rhs
}
2019-04-19 05:15:34 +08:00
// Hash returns the hash of the address of the closure.
2017-08-31 02:52:27 +08:00
func (c *Closure) Hash() uint32 {
return hash.Pointer(unsafe.Pointer(c))
}
2017-06-11 07:04:53 +08:00
// Repr returns an opaque representation "<closure 0x23333333>".
func (c *Closure) Repr(int) string {
return fmt.Sprintf("<closure %p>", c)
2016-02-19 05:52:05 +08:00
}
func listOfStrings(ss []string) vals.List {
list := vals.EmptyList
for _, s := range ss {
list = list.Cons(s)
}
return list
}
2016-02-19 05:52:05 +08:00
// Call calls a closure.
func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error {
if c.RestArg != -1 {
if len(args) < len(c.ArgNames)-1 {
return errs.ArityMismatch{
What: "arguments here",
ValidLow: len(c.ArgNames) - 1, ValidHigh: -1, Actual: len(args)}
}
} else {
if len(args) != len(c.ArgNames) {
return errs.ArityMismatch{
What: "arguments here",
ValidLow: len(c.ArgNames), ValidHigh: len(c.ArgNames), Actual: len(args)}
}
2016-02-19 05:52:05 +08:00
}
// This evalCtx is dedicated to the current form, so we 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.
// TODO(xiaq): Is it safe to simply assign ec.up = c.Captured?
fm.up = make(Ns)
for name, variable := range c.Captured {
fm.up[name] = variable
}
// Populate local scope with arguments and options.
fm.local = make(Ns)
if c.RestArg == -1 {
for i, name := range c.ArgNames {
fm.local[name] = vars.FromInit(args[i])
}
} else {
for i := 0; i < c.RestArg; i++ {
fm.local[c.ArgNames[i]] = vars.FromInit(args[i])
}
restOff := len(args) - len(c.ArgNames)
fm.local[c.ArgNames[c.RestArg]] = vars.FromInit(
vals.MakeList(args[c.RestArg : c.RestArg+restOff+1]...))
for i := c.RestArg + 1; i < len(c.ArgNames); i++ {
fm.local[c.ArgNames[i]] = vars.FromInit(args[i+restOff])
}
2016-02-19 05:52:05 +08:00
}
optUsed := make(map[string]struct{})
for i, name := range c.OptNames {
v, ok := opts[name]
if ok {
optUsed[name] = struct{}{}
} else {
v = c.OptDefaults[i]
}
fm.local[name] = vars.FromInit(v)
}
for name := range opts {
_, used := optUsed[name]
if !used {
return fmt.Errorf("unknown option %s", parse.Quote(name))
}
2016-09-15 06:00:52 +08:00
}
2016-02-19 05:52:05 +08:00
fm.srcMeta = c.SrcMeta
return c.Op.exec(fm)
2016-02-19 05:52:05 +08:00
}
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]
}