eval: ReflectBuiltinFn -> BuiltinFn.

This commit is contained in:
Qi Xiao 2018-02-06 00:02:27 -08:00
parent 234bcbef26
commit b7c853151f
20 changed files with 224 additions and 249 deletions

View File

@ -165,7 +165,7 @@ func makeNs(ed *Editor) eval.Ns {
"-narrow-read": NarrowRead,
}
for name, impl := range fns {
ns.SetFn(name, eval.NewReflectBuiltinFn("edit:"+name, impl))
ns.SetFn(name, eval.NewBuiltinFn("edit:"+name, impl))
}
submods := make(map[string]eval.Ns)

View File

@ -18,11 +18,11 @@ var (
)
var (
matchPrefix = eval.NewReflectBuiltinFn(
matchPrefix = eval.NewBuiltinFn(
"edit:match-prefix", wrapMatcher(strings.HasPrefix))
matchSubstr = eval.NewReflectBuiltinFn(
matchSubstr = eval.NewBuiltinFn(
"edit:match-substr", wrapMatcher(strings.Contains))
matchSubseq = eval.NewReflectBuiltinFn(
matchSubseq = eval.NewBuiltinFn(
"edit:match-subseq", wrapMatcher(util.HasSubseq))
_ = RegisterVariable("-matcher", func() vartypes.Variable {

View File

@ -43,7 +43,7 @@ func PromptVariable() vartypes.Variable {
out <- &ui.Styled{"> ", ui.Styles{}}
}
}
val := eval.Callable(eval.NewReflectBuiltinFn("default prompt", prompt))
val := eval.Callable(eval.NewBuiltinFn("default prompt", prompt))
return eval.NewVariableFromPtr(&val)
}
@ -69,7 +69,7 @@ func RpromptVariable() vartypes.Variable {
out <- &ui.Styled{rpromptStr, ui.Styles{"inverse"}}
}
val := eval.Callable(eval.NewReflectBuiltinFn("default rprompt", rprompt))
val := eval.Callable(eval.NewBuiltinFn("default rprompt", rprompt))
return eval.NewVariableFromPtr(&val)
}

View File

@ -10,15 +10,15 @@ import (
"github.com/xiaq/persistent/hash"
)
var reflectBuiltinFns = map[string]interface{}{}
var builtinFns = map[string]interface{}{}
func addToReflectBuiltinFns(moreFns map[string]interface{}) {
func addToBuiltinFns(moreFns map[string]interface{}) {
for name, impl := range moreFns {
reflectBuiltinFns[name] = impl
builtinFns[name] = impl
}
}
// ReflectBuiltinFn uses reflection to wrap arbitrary Go functions into Elvish
// BuiltinFn uses reflection to wrap arbitrary Go functions into Elvish
// functions.
//
// Parameters are passed following these rules:
@ -41,7 +41,7 @@ func addToReflectBuiltinFns(moreFns map[string]interface{}) {
// converted using goToElv. If the last return value has type error and is not
// nil, it is turned into an exception and no ouputting happens. If the last
// return value is a nil error, it is ignored.
type ReflectBuiltinFn struct {
type BuiltinFn struct {
name string
impl interface{}
@ -56,7 +56,7 @@ type ReflectBuiltinFn struct {
variadicArg reflect.Type
}
var _ Callable = &ReflectBuiltinFn{}
var _ Callable = &BuiltinFn{}
type (
Options map[string]interface{}
@ -77,10 +77,10 @@ var (
inputsType = reflect.TypeOf(Inputs(nil))
)
// NewReflectBuiltinFn creates a new ReflectBuiltinFn instance.
func NewReflectBuiltinFn(name string, impl interface{}) *ReflectBuiltinFn {
// NewBuiltinFn creates a new ReflectBuiltinFn instance.
func NewBuiltinFn(name string, impl interface{}) *BuiltinFn {
implType := reflect.TypeOf(impl)
b := &ReflectBuiltinFn{name: name, impl: impl}
b := &BuiltinFn{name: name, impl: impl}
i := 0
if i < implType.NumIn() && implType.In(i) == frameType {
@ -108,22 +108,22 @@ func NewReflectBuiltinFn(name string, impl interface{}) *ReflectBuiltinFn {
}
// Kind returns "fn".
func (*ReflectBuiltinFn) Kind() string {
func (*BuiltinFn) Kind() string {
return "fn"
}
// Equal compares identity.
func (b *ReflectBuiltinFn) Equal(rhs interface{}) bool {
func (b *BuiltinFn) Equal(rhs interface{}) bool {
return b == rhs
}
// Hash hashes the address.
func (b *ReflectBuiltinFn) Hash() uint32 {
func (b *BuiltinFn) Hash() uint32 {
return hash.Pointer(unsafe.Pointer(b))
}
// Repr returns an opaque representation "<builtin $name>".
func (b *ReflectBuiltinFn) Repr(int) string {
func (b *BuiltinFn) Repr(int) string {
return "<builtin " + b.name + ">"
}
@ -134,7 +134,7 @@ var errorType = reflect.TypeOf((*error)(nil)).Elem()
var errNoOptions = errors.New("function does not accept any options")
// Call calls the implementation using reflection.
func (b *ReflectBuiltinFn) Call(f *Frame, args []interface{}, opts map[string]interface{}) error {
func (b *BuiltinFn) Call(f *Frame, args []interface{}, opts map[string]interface{}) error {
if b.variadicArg != nil {
if len(args) < len(b.normalArgs) {
return fmt.Errorf("want %d or more arguments, got %d",

View File

@ -12,7 +12,7 @@ import (
var ErrNotInSameGroup = errors.New("not in the same process group")
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
// Command resolution
"external": external,
"has-external": hasExternal,

View File

@ -13,7 +13,7 @@ import (
// Sequence, list and maps.
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
"ns": nsFn,
"range": rangeFn,

View File

@ -7,7 +7,7 @@ import (
)
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
"src": src,
"-gc": _gc,
"-stack": _stack,

View File

@ -8,7 +8,7 @@ import (
// Flow control.
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
"run-parallel": runParallel,
// Exception and control
"fail": fail,

View File

@ -15,7 +15,7 @@ import (
var ErrStoreNotConnected = errors.New("store not connected")
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
// Directory
"cd": cd,
"dir-history": dirs,

View File

@ -13,7 +13,7 @@ import (
// Input and output.
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
// Value output
"put": put,

View File

@ -18,7 +18,7 @@ import (
var ErrArgs = errors.New("args error")
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
"nop": nop,
"kind-of": kindOf,
"constantly": constantly,
@ -49,7 +49,7 @@ func kindOf(fm *Frame, args ...interface{}) {
func constantly(args ...interface{}) Callable {
// XXX Repr of this fn is not right
return NewReflectBuiltinFn(
return NewBuiltinFn(
"created by constantly",
func(fm *Frame) {
out := fm.ports[1].Chan

View File

@ -8,7 +8,7 @@ import (
// Numerical operations.
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
// Comparison
"<": func(a, b float64) bool { return a < b },
"<=": func(a, b float64) bool { return a <= b },

View File

@ -5,7 +5,7 @@ import "github.com/elves/elvish/eval/types"
// Basic predicate commands.
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
"bool": types.Bool,
"not": not,
"is": is,

View File

@ -16,7 +16,7 @@ import (
var ErrInput = errors.New("input error")
func init() {
addToReflectBuiltinFns(map[string]interface{}{
addToBuiltinFns(map[string]interface{}{
"<s": func(a, b string) bool { return a < b },
"<=s": func(a, b string) bool { return a <= b },
"==s": func(a, b string) bool { return a == b },

View File

@ -1,25 +1,195 @@
package eval
import "testing"
import (
"errors"
"testing"
var builtinFnTests = []Test{
NewTest("kind-of $nop~").WantOutStrings("fn"),
NewTest("eq $nop~ { }").WantOutBools(false),
NewTest("put [&$nop~= foo][$nop~]").WantOutStrings("foo"),
NewTest("repr $nop~").WantBytesOutString("<builtin nop>\n"),
"github.com/elves/elvish/eval/types"
)
{"nop", wantNothing},
{"nop a b", wantNothing},
{"nop &k=v", wantNothing},
{"nop a b &k=v", wantNothing},
func TestReflectBuiltinFnCall(t *testing.T) {
theFrame := new(Frame)
theOptions := map[string]interface{}{}
{"kind-of bare 'str' [] [&] []{ }",
want{out: strs("string", "string", "list", "map", "fn")}},
var f Callable
callGood := func(fm *Frame, args []interface{}, opts map[string]interface{}) {
err := f.Call(fm, args, opts)
if err != nil {
t.Errorf("Failed to call f: %v", err)
}
}
callBad := func(fm *Frame, args []interface{}, opts map[string]interface{}) {
err := f.Call(fm, args, opts)
if err == nil {
t.Errorf("Calling f didn't return error")
}
}
{`f=(constantly foo); $f; $f`, want{out: strs("foo", "foo")}},
{`(constantly foo) bad`, want{err: errAny}},
}
func TestBuiltinFn(t *testing.T) {
runTests(t, builtinFnTests)
// *Frame parameter gets the Frame.
f = NewBuiltinFn("f", func(f *Frame) {
if f != theFrame {
t.Errorf("*Frame parameter doesn't get current frame")
}
})
callGood(theFrame, nil, theOptions)
// Options parameter gets options.
f = NewBuiltinFn("f", func(opts Options) {
if opts["foo"] != "bar" {
t.Errorf("Options parameter doesn't get options")
}
})
callGood(theFrame, nil, Options{"foo": "bar"})
// Combination of Frame and Options.
f = NewBuiltinFn("f", func(f *Frame, opts Options) {
if f != theFrame {
t.Errorf("*Frame parameter doesn't get current frame")
}
if opts["foo"] != "bar" {
t.Errorf("Options parameter doesn't get options")
}
})
callGood(theFrame, nil, Options{"foo": "bar"})
// Argument passing.
f = NewBuiltinFn("f", func(x, y string) {
if x != "lorem" {
t.Errorf("Argument x not passed")
}
if y != "ipsum" {
t.Errorf("Argument y not passed")
}
})
callGood(theFrame, []interface{}{"lorem", "ipsum"}, theOptions)
// Variadic arguments.
f = NewBuiltinFn("f", func(x ...string) {
if len(x) != 2 || x[0] != "lorem" || x[1] != "ipsum" {
t.Errorf("Variadic argument not passed")
}
})
callGood(theFrame, []interface{}{"lorem", "ipsum"}, theOptions)
// Conversion into int and float64.
f = NewBuiltinFn("f", func(i int, f float64) {
if i != 314 {
t.Errorf("Integer argument i not passed")
}
if f != 1.25 {
t.Errorf("Float argument f not passed")
}
})
callGood(theFrame, []interface{}{"314", "1.25"}, theOptions)
// Conversion of supplied inputs.
f = NewBuiltinFn("f", func(i Inputs) {
var values []interface{}
i(func(x interface{}) {
values = append(values, x)
})
if len(values) != 2 || values[0] != "foo" || values[1] != "bar" {
t.Errorf("Inputs parameter didn't get supplied inputs")
}
})
callGood(theFrame, []interface{}{types.MakeList("foo", "bar")}, theOptions)
// Conversion of implicit inputs.
inFrame := &Frame{ports: make([]*Port, 3)}
ch := make(chan interface{}, 10)
ch <- "foo"
ch <- "bar"
close(ch)
inFrame.ports[0] = &Port{Chan: ch}
f = NewBuiltinFn("f", func(i Inputs) {
var values []interface{}
i(func(x interface{}) {
values = append(values, x)
})
if len(values) != 2 || values[0] != "foo" || values[1] != "bar" {
t.Errorf("Inputs parameter didn't get implicit inputs")
}
})
callGood(inFrame, []interface{}{types.MakeList("foo", "bar")}, theOptions)
// Outputting of return values.
outFrame := &Frame{ports: make([]*Port, 3)}
ch = make(chan interface{}, 10)
outFrame.ports[1] = &Port{Chan: ch}
f = NewBuiltinFn("f", func() string { return "ret" })
callGood(outFrame, nil, theOptions)
select {
case ret := <-ch:
if ret != "ret" {
t.Errorf("Output is not the same as return value")
}
default:
t.Errorf("Return value is not outputted")
}
// Conversion of return values.
f = NewBuiltinFn("f", func() int { return 314 })
callGood(outFrame, nil, theOptions)
select {
case ret := <-ch:
if ret != "314" {
t.Errorf("Return value is not converted to string")
}
default:
t.Errorf("Return value is not outputted")
}
// Passing of error return value.
theError := errors.New("the error")
f = NewBuiltinFn("f", func() (string, error) {
return "x", theError
})
if f.Call(outFrame, nil, theOptions) != theError {
t.Errorf("Returned error is not passed")
}
select {
case <-ch:
t.Errorf("Return value is outputted when error is not nil")
default:
}
// Too many arguments.
f = NewBuiltinFn("f", func() {
t.Errorf("Function called when there are too many arguments")
})
callBad(theFrame, []interface{}{"x"}, theOptions)
// Too few arguments.
f = NewBuiltinFn("f", func(x string) {
t.Errorf("Function called when there are too few arguments")
})
callBad(theFrame, nil, theOptions)
f = NewBuiltinFn("f", func(x string, y ...string) {
t.Errorf("Function called when there are too few arguments")
})
callBad(theFrame, nil, theOptions)
// Options when the function does not accept options.
f = NewBuiltinFn("f", func() {
t.Errorf("Function called when there are extra options")
})
callBad(theFrame, nil, Options{"foo": "bar"})
// Wrong argument type.
f = NewBuiltinFn("f", func(x string) {
t.Errorf("Function called when arguments have wrong type")
})
callBad(theFrame, []interface{}{1}, theOptions)
// Wrong argument type: cannot convert to int.
f = NewBuiltinFn("f", func(x int) {
t.Errorf("Function called when arguments have wrong type")
})
callBad(theFrame, []interface{}{"x"}, theOptions)
// Wrong argument type: cannot convert to float64.
f = NewBuiltinFn("f", func(x float64) {
t.Errorf("Function called when arguments have wrong type")
})
callBad(theFrame, []interface{}{"x"}, theOptions)
}

View File

@ -17,7 +17,7 @@ func makeBuiltinNs() Ns {
"paths": &EnvList{envName: "PATH"},
"pwd": PwdVariable{},
}
AddReflectBuiltinFns(ns, "", reflectBuiltinFns)
AddReflectBuiltinFns(ns, "", builtinFns)
return ns
}
@ -28,6 +28,6 @@ func AddReflectBuiltinFns(ns Ns, nsName string, fns map[string]interface{}) {
if nsName != "" {
qname = nsName + ":" + name
}
ns.SetFn(name, NewReflectBuiltinFn(qname, impl))
ns.SetFn(name, NewBuiltinFn(qname, impl))
}
}

View File

@ -197,7 +197,7 @@ func (op fnOp) Invoke(fm *Frame) error {
// Initialize the function variable with the builtin nop function. This step
// allows the definition of recursive functions; the actual function will
// never be called.
fm.local[op.varName] = vartypes.NewAny(NewReflectBuiltinFn("<shouldn't be called>", nop))
fm.local[op.varName] = vartypes.NewAny(NewBuiltinFn("<shouldn't be called>", nop))
values, err := op.lambdaOp.Invoke(fm)
if err != nil {
return err

View File

@ -42,6 +42,6 @@ func Ns(daemon *daemon.Client, spawner *daemonp.Daemon) eval.Ns {
"sock": vartypes.NewRo(string(daemon.SockPath())),
"spawn" + eval.FnSuffix: vartypes.NewRo(
eval.NewReflectBuiltinFn("daemon:spawn", daemonSpawn)),
eval.NewBuiltinFn("daemon:spawn", daemonSpawn)),
}
}

View File

@ -1,195 +0,0 @@
package eval
import (
"errors"
"testing"
"github.com/elves/elvish/eval/types"
)
func TestReflectBuiltinFnCall(t *testing.T) {
theFrame := new(Frame)
theOptions := map[string]interface{}{}
var f Callable
callGood := func(fm *Frame, args []interface{}, opts map[string]interface{}) {
err := f.Call(fm, args, opts)
if err != nil {
t.Errorf("Failed to call f: %v", err)
}
}
callBad := func(fm *Frame, args []interface{}, opts map[string]interface{}) {
err := f.Call(fm, args, opts)
if err == nil {
t.Errorf("Calling f didn't return error")
}
}
// *Frame parameter gets the Frame.
f = NewReflectBuiltinFn("f", func(f *Frame) {
if f != theFrame {
t.Errorf("*Frame parameter doesn't get current frame")
}
})
callGood(theFrame, nil, theOptions)
// Options parameter gets options.
f = NewReflectBuiltinFn("f", func(opts Options) {
if opts["foo"] != "bar" {
t.Errorf("Options parameter doesn't get options")
}
})
callGood(theFrame, nil, Options{"foo": "bar"})
// Combination of Frame and Options.
f = NewReflectBuiltinFn("f", func(f *Frame, opts Options) {
if f != theFrame {
t.Errorf("*Frame parameter doesn't get current frame")
}
if opts["foo"] != "bar" {
t.Errorf("Options parameter doesn't get options")
}
})
callGood(theFrame, nil, Options{"foo": "bar"})
// Argument passing.
f = NewReflectBuiltinFn("f", func(x, y string) {
if x != "lorem" {
t.Errorf("Argument x not passed")
}
if y != "ipsum" {
t.Errorf("Argument y not passed")
}
})
callGood(theFrame, []interface{}{"lorem", "ipsum"}, theOptions)
// Variadic arguments.
f = NewReflectBuiltinFn("f", func(x ...string) {
if len(x) != 2 || x[0] != "lorem" || x[1] != "ipsum" {
t.Errorf("Variadic argument not passed")
}
})
callGood(theFrame, []interface{}{"lorem", "ipsum"}, theOptions)
// Conversion into int and float64.
f = NewReflectBuiltinFn("f", func(i int, f float64) {
if i != 314 {
t.Errorf("Integer argument i not passed")
}
if f != 1.25 {
t.Errorf("Float argument f not passed")
}
})
callGood(theFrame, []interface{}{"314", "1.25"}, theOptions)
// Conversion of supplied inputs.
f = NewReflectBuiltinFn("f", func(i Inputs) {
var values []interface{}
i(func(x interface{}) {
values = append(values, x)
})
if len(values) != 2 || values[0] != "foo" || values[1] != "bar" {
t.Errorf("Inputs parameter didn't get supplied inputs")
}
})
callGood(theFrame, []interface{}{types.MakeList("foo", "bar")}, theOptions)
// Conversion of implicit inputs.
inFrame := &Frame{ports: make([]*Port, 3)}
ch := make(chan interface{}, 10)
ch <- "foo"
ch <- "bar"
close(ch)
inFrame.ports[0] = &Port{Chan: ch}
f = NewReflectBuiltinFn("f", func(i Inputs) {
var values []interface{}
i(func(x interface{}) {
values = append(values, x)
})
if len(values) != 2 || values[0] != "foo" || values[1] != "bar" {
t.Errorf("Inputs parameter didn't get implicit inputs")
}
})
callGood(inFrame, []interface{}{types.MakeList("foo", "bar")}, theOptions)
// Outputting of return values.
outFrame := &Frame{ports: make([]*Port, 3)}
ch = make(chan interface{}, 10)
outFrame.ports[1] = &Port{Chan: ch}
f = NewReflectBuiltinFn("f", func() string { return "ret" })
callGood(outFrame, nil, theOptions)
select {
case ret := <-ch:
if ret != "ret" {
t.Errorf("Output is not the same as return value")
}
default:
t.Errorf("Return value is not outputted")
}
// Conversion of return values.
f = NewReflectBuiltinFn("f", func() int { return 314 })
callGood(outFrame, nil, theOptions)
select {
case ret := <-ch:
if ret != "314" {
t.Errorf("Return value is not converted to string")
}
default:
t.Errorf("Return value is not outputted")
}
// Passing of error return value.
theError := errors.New("the error")
f = NewReflectBuiltinFn("f", func() (string, error) {
return "x", theError
})
if f.Call(outFrame, nil, theOptions) != theError {
t.Errorf("Returned error is not passed")
}
select {
case <-ch:
t.Errorf("Return value is outputted when error is not nil")
default:
}
// Too many arguments.
f = NewReflectBuiltinFn("f", func() {
t.Errorf("Function called when there are too many arguments")
})
callBad(theFrame, []interface{}{"x"}, theOptions)
// Too few arguments.
f = NewReflectBuiltinFn("f", func(x string) {
t.Errorf("Function called when there are too few arguments")
})
callBad(theFrame, nil, theOptions)
f = NewReflectBuiltinFn("f", func(x string, y ...string) {
t.Errorf("Function called when there are too few arguments")
})
callBad(theFrame, nil, theOptions)
// Options when the function does not accept options.
f = NewReflectBuiltinFn("f", func() {
t.Errorf("Function called when there are extra options")
})
callBad(theFrame, nil, Options{"foo": "bar"})
// Wrong argument type.
f = NewReflectBuiltinFn("f", func(x string) {
t.Errorf("Function called when arguments have wrong type")
})
callBad(theFrame, []interface{}{1}, theOptions)
// Wrong argument type: cannot convert to int.
f = NewReflectBuiltinFn("f", func(x int) {
t.Errorf("Function called when arguments have wrong type")
})
callBad(theFrame, []interface{}{"x"}, theOptions)
// Wrong argument type: cannot convert to float64.
f = NewReflectBuiltinFn("f", func(x float64) {
t.Errorf("Function called when arguments have wrong type")
})
callBad(theFrame, []interface{}{"x"}, theOptions)
}

View File

@ -12,7 +12,7 @@ import (
func Ns() eval.Ns {
ns := eval.Ns{}
for name, impl := range fns {
ns[name+eval.FnSuffix] = vartypes.NewRo(eval.NewReflectBuiltinFn("str:"+name, impl))
ns[name+eval.FnSuffix] = vartypes.NewRo(eval.NewBuiltinFn("str:"+name, impl))
}
return ns
}