Make Value an empty interface.

This addresses #573. The implementations of Value are not yet changed.
This commit is contained in:
Qi Xiao 2018-01-24 23:57:58 +00:00
parent 40f9aa7401
commit 68bc4d925d
29 changed files with 116 additions and 102 deletions

View File

@ -54,7 +54,7 @@ func (bt BindingTable) Repr(indent int) string {
if err != nil {
panic(err)
}
builder.WritePair(parse.Quote(k.String()), indent+2, v.Repr(indent+2))
builder.WritePair(parse.Quote(k.String()), indent+2, types.Repr(v, indent+2))
}
return builder.String()

View File

@ -26,7 +26,7 @@ func complGetopt(ec *eval.Frame, a []types.Value, o map[string]types.Value) {
elemsv.Iterate(func(v types.Value) bool {
elem, ok := v.(types.String)
if !ok {
throwf("arg should be string, got %s", v.Kind())
throwf("arg should be string, got %s", types.Kind(v))
}
elems = append(elems, string(elem))
return true
@ -34,7 +34,7 @@ func complGetopt(ec *eval.Frame, a []types.Value, o map[string]types.Value) {
optsv.Iterate(func(v types.Value) bool {
m, ok := v.(types.MapLike)
if !ok {
throwf("opt should be map-like, got %s", v.Kind())
throwf("opt should be map-like, got %s", types.Kind(v))
}
get := func(ks string) (string, bool) {
kv := types.String(ks)
@ -82,7 +82,7 @@ func complGetopt(ec *eval.Frame, a []types.Value, o map[string]types.Value) {
}
arg, ok := v.(eval.Fn)
if !ok {
throwf("argument handler should be fn, got %s", v.Kind())
throwf("argument handler should be fn, got %s", types.Kind(v))
}
args = append(args, arg)
return true

View File

@ -131,7 +131,7 @@ func NewEditor(in *os.File, out *os.File, sigs chan os.Signal, ev *eval.Evaler)
// Forward reads from notifyChan to notification.
go func() {
for v := range notifyChan {
ed.Notify("[value out] %s", v.Repr(types.NoPretty))
ed.Notify("[value out] %s", types.Repr(v, types.NoPretty))
}
}()

View File

@ -48,7 +48,7 @@ type hookOp struct {
func (op *hookOp) Invoke(fm *eval.Frame) error {
fn, ok := op.hook.(eval.Fn)
if !ok {
fmt.Fprintf(os.Stderr, "not a function: %s\n", op.hook.Repr(types.NoPretty))
fmt.Fprintf(os.Stderr, "not a function: %s\n", types.Repr(op.hook, types.NoPretty))
return nil
}
err := fm.PCall(fn, op.args, eval.NoOpts)

View File

@ -100,7 +100,7 @@ func kindOf(ec *Frame, args []types.Value, opts map[string]types.Value) {
TakeNoOpt(opts)
out := ec.ports[1].Chan
for _, a := range args {
out <- types.String(a.Kind())
out <- types.String(types.Kind(a))
}
}
@ -136,7 +136,7 @@ func eq(ec *Frame, args []types.Value, opts map[string]types.Value) {
TakeNoOpt(opts)
result := true
for i := 0; i+1 < len(args); i++ {
if !args[i].Equal(args[i+1]) {
if !types.Equal(args[i], args[i+1]) {
result = false
break
}
@ -148,7 +148,7 @@ func notEq(ec *Frame, args []types.Value, opts map[string]types.Value) {
TakeNoOpt(opts)
result := true
for i := 0; i+1 < len(args); i++ {
if args[i].Equal(args[i+1]) {
if types.Equal(args[i], args[i+1]) {
result = false
break
}

View File

@ -185,7 +185,7 @@ func hasValue(ec *Frame, args []types.Value, opts map[string]types.Value) {
return !found
})
default:
throw(fmt.Errorf("argument of type '%s' is not iterable", container.Kind()))
throw(fmt.Errorf("argument of type '%s' is not iterable", types.Kind(container)))
}
ec.ports[1].Chan <- types.Bool(found)
@ -207,7 +207,7 @@ func hasKey(ec *Frame, args []types.Value, opts map[string]types.Value) {
_, _, _, err := types.ParseAndFixListIndex(types.ToString(key), container.Len())
found = (err == nil)
default:
throw(fmt.Errorf("couldn't get key or index of type '%s'", container.Kind()))
throw(fmt.Errorf("couldn't get key or index of type '%s'", types.Kind(container)))
}
ec.ports[1].Chan <- types.Bool(found)
@ -234,7 +234,7 @@ func count(ec *Frame, args []types.Value, opts map[string]types.Value) {
return true
})
} else {
throw(fmt.Errorf("cannot get length of a %s", v.Kind()))
throw(fmt.Errorf("cannot get length of a %s", types.Kind(v)))
}
default:
throw(errors.New("want 0 or 1 argument"))

View File

@ -72,7 +72,7 @@ func pprint(ec *Frame, args []types.Value, opts map[string]types.Value) {
TakeNoOpt(opts)
out := ec.ports[1].File
for _, arg := range args {
out.WriteString(arg.Repr(0))
out.WriteString(types.Repr(arg, 0))
out.WriteString("\n")
}
}
@ -84,7 +84,7 @@ func repr(ec *Frame, args []types.Value, opts map[string]types.Value) {
if i > 0 {
out.WriteString(" ")
}
out.WriteString(arg.Repr(types.NoPretty))
out.WriteString(types.Repr(arg, types.NoPretty))
}
out.WriteString("\n")
}

View File

@ -92,7 +92,7 @@ func joins(ec *Frame, args []types.Value, opts map[string]types.Value) {
}
buf.WriteString(string(s))
} else {
throwf("join wants string input, got %s", v.Kind())
throwf("join wants string input, got %s", types.Kind(v))
}
})
out := ec.ports[1].Chan

View File

@ -354,7 +354,7 @@ func (op *formOp) Invoke(ec *Frame) (errRet error) {
if ks, ok := k.(types.String); ok {
convertedOpts[string(ks)] = v
} else {
errOpt = fmt.Errorf("Option key must be string, got %s", k.Kind())
errOpt = fmt.Errorf("Option key must be string, got %s", types.Kind(k))
return false
}
return true
@ -559,7 +559,7 @@ func (op *redirOp) Invoke(ec *Frame) error {
CloseFile: false,
}
default:
srcUnwrap.error("string or file", "%s", src.Kind())
srcUnwrap.error("string or file", "%s", types.Kind(src))
}
}
return nil

View File

@ -139,7 +139,8 @@ func cat(lhs, rhs types.Value) (types.Value, error) {
return lhs, nil
}
}
return nil, fmt.Errorf("unsupported concat: %s and %s", lhs.Kind(), rhs.Kind())
return nil, fmt.Errorf("unsupported concat: %s and %s",
types.Kind(lhs), types.Kind(rhs))
}
func outerProduct(vs []types.Value, us []types.Value, f func(types.Value, types.Value) (types.Value, error)) ([]types.Value, error) {
@ -200,7 +201,7 @@ func doTilde(v types.Value) types.Value {
}
return v
default:
throw(fmt.Errorf("tilde doesn't work on value of type %s", v.Kind()))
throw(fmt.Errorf("tilde doesn't work on value of type %s", types.Kind(v)))
panic("unreachable")
}
}
@ -236,7 +237,7 @@ func (op *indexingOp) Invoke(ec *Frame) ([]types.Value, error) {
for _, v := range vs {
indexer, ok := v.(types.Indexer)
if !ok {
return nil, fmt.Errorf("a %s not indexable", v.Kind())
return nil, fmt.Errorf("a %s not indexable", types.Kind(v))
}
for _, index := range indicies {
result, err := indexer.Index(index)
@ -306,7 +307,7 @@ func (op variableOp) Invoke(ec *Frame) ([]types.Value, error) {
iterator, ok := value.(types.Iterator)
if !ok {
// Use qname[1:] to skip the leading "@"
return nil, fmt.Errorf("variable $%s:%s (kind %s) cannot be exploded", op.ns, op.name, value.Kind())
return nil, fmt.Errorf("variable $%s:%s (kind %s) cannot be exploded", op.ns, op.name, types.Kind(value))
}
return types.CollectFromIterator(iterator), nil
}

View File

@ -86,7 +86,7 @@ func scanValueToGo(src types.Value, dstPtr interface{}) {
if reflect.TypeOf(src).ConvertibleTo(dstReflect.Type()) {
dstReflect.Set(reflect.ValueOf(src).Convert(dstReflect.Type()))
} else {
throwf("need %s argument, got %s", dstReflect.Type().Name(), src.Kind())
throwf("need %s argument, got %s", dstReflect.Type().Name(), types.Kind(src))
}
}
}

View File

@ -39,7 +39,7 @@ func newEvalerPorts(stdin, stdout, stderr *os.File, prefix *string) evalerPorts
func relayChanToFile(ch <-chan types.Value, file *os.File, prefix *string, w *sync.WaitGroup) {
for v := range ch {
file.WriteString(*prefix)
file.WriteString(v.Repr(initIndent))
file.WriteString(types.Repr(v, initIndent))
file.WriteString("\n")
}
w.Done()

View File

@ -99,7 +99,7 @@ func replace(ec *eval.Frame, args []types.Value, opts map[string]types.Value) {
repl, ok := argRepl.(types.String)
if !ok {
throwf("replacement must be string when literal is set, got %s",
argRepl.Kind())
types.Kind(argRepl))
}
result = pattern.ReplaceAllLiteralString(string(argSource), string(repl))
} else {
@ -116,14 +116,15 @@ func replace(ec *eval.Frame, args []types.Value, opts map[string]types.Value) {
}
output, ok := values[0].(types.String)
if !ok {
throwf("replacement function must output one string, got %s", values[0].Kind())
throwf("replacement function must output one string, got %s",
types.Kind(values[0]))
}
return string(output)
}
result = pattern.ReplaceAllStringFunc(string(argSource), replFunc)
default:
throwf("replacement must be string or function, got %s",
argRepl.Kind())
types.Kind(argRepl))
}
}
out <- types.String(result)

View File

@ -56,7 +56,7 @@ func ScanArgsOptionalInput(ec *Frame, src []types.Value, dstArgs ...interface{})
value := src[len(dstArgs)]
iterable, ok := value.(types.Iterator)
if !ok {
throwf("need iterable argument, got %s", value.Kind())
throwf("need iterable argument, got %s", types.Kind(value))
}
return func(f func(types.Value)) {
iterable.Iterate(func(v types.Value) bool {

View File

@ -201,7 +201,7 @@ var scanArgTestCases = []struct {
func TestScanArg(t *testing.T) {
for _, tc := range scanArgTestCases {
scanValueToGo(tc.source, tc.destPtr)
if !equals(indirect(tc.destPtr), tc.want) {
if !types.Equal(indirect(tc.destPtr), tc.want) {
t.Errorf("scanArg(%s) got %q, want %v", tc.source,
indirect(tc.destPtr), tc.want)
}

View File

@ -213,7 +213,7 @@ func matchOut(want, got []types.Value) bool {
return false
}
for i := range got {
if !got[i].Equal(want[i]) {
if !types.Equal(got[i], want[i]) {
return false
}
}
@ -234,18 +234,9 @@ func compareSlice(wantValues, gotValues []interface{}) error {
len(wantValues), len(gotValues))
}
for i, want := range wantValues {
if !equals(want, gotValues[i]) {
if !types.Equal(want, gotValues[i]) {
return fmt.Errorf("want [%d] = %s, got %s", i, want, gotValues[i])
}
}
return nil
}
// equals compares two values. It uses Eq if want is a Value instance, or
// reflect.DeepEqual otherwise.
func equals(a, b interface{}) bool {
if aValue, ok := a.(types.Value); ok {
return aValue.Equal(b)
}
return reflect.DeepEqual(a, b)
}

View File

@ -1,5 +1,17 @@
package types
func Equal(x, y interface{}) bool {
return x.(Equaler).Equal(y)
import "reflect"
// Equaler wraps the Equal method.
type Equaler interface {
// Equal compares the receiver to another value. Two equal values must have
// the same hash code.
Equal(other interface{}) bool
}
func Equal(x, y interface{}) bool {
if equaler, ok := x.(Equaler); ok {
return equaler.Equal(y)
}
return reflect.DeepEqual(x, y)
}

View File

@ -1,5 +1,14 @@
package types
func Hash(v interface{}) uint32 {
return v.(Hasher).Hash()
// Hasher wraps the Hash method.
type Hasher interface {
// Hash computes the hash code of the receiver.
Hash() uint32
}
func Hash(v interface{}) uint32 {
if hasher, ok := v.(Hasher); ok {
return hasher.Hash()
}
return 0
}

13
eval/types/kind.go Normal file
View File

@ -0,0 +1,13 @@
package types
// Kinder wraps the Kind method.
type Kinder interface {
Kind() string
}
func Kind(v interface{}) string {
if kinder, ok := v.(Kinder); ok {
return kinder.Kind()
}
return "unknown"
}

View File

@ -62,7 +62,7 @@ func (l List) Repr(indent int) string {
b.Indent = indent
for it := l.inner.Iterator(); it.HasElem(); it.Next() {
v := it.Elem().(Value)
b.WriteElem(v.Repr(indent + 1))
b.WriteElem(Repr(v, indent+1))
}
return b.String()
}

View File

@ -27,7 +27,7 @@ func eqListLike(lhs ListLike, r interface{}) bool {
func hashListLike(l ListLike) uint32 {
h := hash.DJBInit
l.Iterate(func(v Value) bool {
h = hash.DJBCombine(h, v.Hash())
h = hash.DJBCombine(h, Hash(v))
return true
})
return h

View File

@ -24,7 +24,7 @@ func NoSuchKey(k Value) error {
}
func (err noSuchKeyError) Error() string {
return "no such key: " + err.key.Repr(NoPretty)
return "no such key: " + Repr(err.key, NoPretty)
}
var EmptyMapInner = hashmap.New(Equal, Hash)
@ -73,7 +73,7 @@ func (m Map) Repr(indent int) string {
builder.Indent = indent
for it := m.inner.Iterator(); it.HasElem(); it.Next() {
k, v := it.Elem()
builder.WritePair(k.(Value).Repr(indent+1), indent+2, v.(Value).Repr(indent+2))
builder.WritePair(Repr(k, indent+1), indent+2, Repr(v, indent+2))
}
return builder.String()
}

View File

@ -28,7 +28,7 @@ func EqMapLike(lhs MapLike, a interface{}) bool {
eq := true
lhs.IteratePair(func(k, v Value) bool {
v2, err := rhs.Index(k)
if err != nil || !v.Equal(v2) {
if err != nil || !Equal(v, v2) {
eq = false
return false
}
@ -40,8 +40,8 @@ func EqMapLike(lhs MapLike, a interface{}) bool {
func HashMapLike(m MapLike) uint32 {
h := hash.DJBInit
m.IteratePair(func(k, v Value) bool {
h = hash.DJBCombine(h, k.Hash())
h = hash.DJBCombine(h, v.Hash())
h = hash.DJBCombine(h, Hash(k))
h = hash.DJBCombine(h, Hash(v))
return true
})
return h

31
eval/types/repr.go Normal file
View File

@ -0,0 +1,31 @@
package types
import (
"fmt"
"github.com/elves/elvish/util"
)
// NoPretty can be passed to Repr to suppress pretty-printing.
const NoPretty = util.MinInt
// Reprer wraps the Repr method.
type Reprer interface {
// Repr returns a string that represents a Value. The string either be a
// literal of that Value that is preferably deep-equal to it (like `[a b c]`
// for a list), or a string enclosed in "<>" containing the kind and
// identity of the Value(like `<fn 0xdeadcafe>`).
//
// If indent is at least 0, it should be pretty-printed with the current
// indentation level of indent; the indent of the first line has already
// been written and shall not be written in Repr. The returned string
// should never contain a trailing newline.
Repr(indent int) string
}
func Repr(v interface{}, indent int) string {
if reprer, ok := v.(Reprer); ok {
return reprer.Repr(indent)
}
return fmt.Sprint(v)
}

View File

@ -46,7 +46,7 @@ func (s *Struct) Repr(indent int) string {
var builder MapReprBuilder
builder.Indent = indent
for i, name := range s.descriptor.fieldNames {
builder.WritePair(parse.Quote(name), indent+2, s.fields[i].Repr(indent+2))
builder.WritePair(parse.Quote(name), indent+2, Repr(s.fields[i], indent+2))
}
return builder.String()
}

View File

@ -22,7 +22,7 @@ func TestStructMethods(t *testing.T) {
if testStruct.Equal(testStruct2) {
t.Errorf(`testStruct.Equal(testStruct2) => true, want false`)
}
if s2, err := testStruct.Assoc(String("bar"), String("dolor")); !s2.Equal(testStruct2) {
if s2, err := testStruct.Assoc(String("bar"), String("dolor")); !Equal(s2, testStruct2) {
t.Errorf(`testStruct.Assoc(...) => %v, want %v`, s2, testStruct2)
} else if err != nil {
t.Errorf(`testStruct.Assoc(...) => error %s, want no error`, err)

View File

@ -1,55 +1,11 @@
// Package types contains basic types for the Elvish runtime.
package types
import (
"github.com/elves/elvish/util"
)
// Definitions for Value interfaces, some simple Value types and some common
// Value helpers.
// Value is an Elvish value.
type Value interface {
Kinder
Equaler
Hasher
Reprer
}
// Kinder wraps the Kind method.
type Kinder interface {
Kind() string
}
// Reprer wraps the Repr method.
type Reprer interface {
// Repr returns a string that represents a Value. The string either be a
// literal of that Value that is preferably deep-equal to it (like `[a b c]`
// for a list), or a string enclosed in "<>" containing the kind and
// identity of the Value(like `<fn 0xdeadcafe>`).
//
// If indent is at least 0, it should be pretty-printed with the current
// indentation level of indent; the indent of the first line has already
// been written and shall not be written in Repr. The returned string
// should never contain a trailing newline.
Repr(indent int) string
}
// NoPretty can be passed to Repr to suppress pretty-printing.
const NoPretty = util.MinInt
// Equaler wraps the Equal method.
type Equaler interface {
// Equal compares the receiver to another value. Two equal values must have
// the same hash code.
Equal(other interface{}) bool
}
// Hasher wraps the Hash method.
type Hasher interface {
// Hash computes the hash code of the receiver.
Hash() uint32
}
type Value interface{}
// Booler wraps the Bool method.
type Booler interface {
@ -69,7 +25,7 @@ func ToString(v Value) string {
if s, ok := v.(Stringer); ok {
return s.String()
}
return v.Repr(NoPretty)
return Repr(v, NoPretty)
}
// Lener wraps the Len method.

View File

@ -64,7 +64,7 @@ func (u ValueUnwrapper) Any() types.Value {
func (u ValueUnwrapper) String() types.String {
s, ok := u.values[0].(types.String)
if !ok {
u.error("string", "%s", u.values[0].Kind())
u.error("string", "%s", types.Kind(u.values[0]))
}
return s
}
@ -97,7 +97,7 @@ func (u ValueUnwrapper) FdOrClose() int {
func (u ValueUnwrapper) Callable() Callable {
c, ok := u.values[0].(Callable)
if !ok {
u.error("callable", "%s", u.values[0].Kind())
u.error("callable", "%s", types.Kind(u.values[0]))
}
return c
}
@ -105,7 +105,7 @@ func (u ValueUnwrapper) Callable() Callable {
func (u ValueUnwrapper) Iterable() types.Iterator {
it, ok := u.values[0].(types.Iterator)
if !ok {
u.error("iterable", "%s", u.values[0].Kind())
u.error("iterable", "%s", types.Kind(u.values[0]))
}
return it
}

View File

@ -34,7 +34,7 @@ var reprTests = []struct {
func TestRepr(t *testing.T) {
for _, test := range reprTests {
repr := test.v.Repr(types.NoPretty)
repr := types.Repr(test.v, types.NoPretty)
if repr != test.want {
t.Errorf("Repr = %s, want %s", repr, test.want)
}