eval/types: Remove the List type.

Vector from github.com/xiaq/persistent/vector can now be used as an
Elvish value. This addresses #573.
This commit is contained in:
Qi Xiao 2018-01-27 17:26:22 +00:00
parent a1d43bf8d6
commit 1fd2ff3d92
22 changed files with 415 additions and 441 deletions

View File

@ -15,11 +15,6 @@ type List struct {
Daemon *daemon.Client
}
var (
_ types.Value = List{}
_ types.ListLike = List{}
)
func (hv List) Kind() string {
return "list"
}
@ -64,26 +59,26 @@ func (hv List) Iterate(f func(types.Value) bool) {
}
}
func (hv List) Index(idx types.Value) (types.Value, error) {
func (hv List) Index(rawIndex types.Value) (types.Value, error) {
hv.RLock()
defer hv.RUnlock()
slice, i, j, err := types.ParseAndFixListIndex(types.ToString(idx), hv.Len())
index, err := types.ConvertListIndex(rawIndex, hv.Len())
if err != nil {
return nil, err
}
if slice {
cmds, err := hv.Daemon.Cmds(i+1, j+1)
if index.Slice {
cmds, err := hv.Daemon.Cmds(index.Lower+1, index.Upper+1)
if err != nil {
return nil, err
}
vs := make([]types.Value, len(cmds))
for i := range cmds {
vs[i] = string(cmds[i])
l := types.EmptyList
for _, cmd := range cmds {
l = l.Cons(cmd)
}
return types.MakeList(vs...), nil
return l, nil
}
s, err := hv.Daemon.Cmd(i + 1)
s, err := hv.Daemon.Cmd(index.Lower + 1)
return string(s), err
}

View File

@ -7,6 +7,7 @@ import (
"github.com/elves/elvish/eval"
"github.com/elves/elvish/eval/types"
"github.com/elves/elvish/eval/vartypes"
"github.com/xiaq/persistent/vector"
)
// The $le:{before,after}-readline lists that contain hooks. We might have more
@ -14,30 +15,29 @@ import (
var _ = RegisterVariable("before-readline", makeListVariable)
func (ed *Editor) beforeReadLine() types.List {
return ed.variables["before-readline"].Get().(types.List)
func (ed *Editor) beforeReadLine() vector.Vector {
return ed.variables["before-readline"].Get().(vector.Vector)
}
var _ = RegisterVariable("after-readline", makeListVariable)
func (ed *Editor) afterReadLine() types.List {
return ed.variables["after-readline"].Get().(types.List)
func (ed *Editor) afterReadLine() vector.Vector {
return ed.variables["after-readline"].Get().(vector.Vector)
}
func makeListVariable() vartypes.Variable {
return vartypes.NewValidatedPtr(types.EmptyList, vartypes.ShouldBeList)
}
func callHooks(ev *eval.Evaler, li types.List, args ...types.Value) {
func callHooks(ev *eval.Evaler, li vector.Vector, args ...types.Value) {
if li.Len() == 0 {
return
}
li.Iterate(func(v types.Value) bool {
op := eval.Op{&hookOp{v, args}, -1, -1}
for it := li.Iterator(); it.HasElem(); it.Next() {
op := eval.Op{&hookOp{it.Elem(), args}, -1, -1}
ev.Eval(op, eval.NewInternalSource("[hooks]"))
return true
})
}
}
type hookOp struct {

View File

@ -15,6 +15,7 @@ import (
"github.com/elves/elvish/parse"
"github.com/elves/elvish/store/storedefs"
"github.com/elves/elvish/util"
"github.com/xiaq/persistent/vector"
)
// Location mode.
@ -150,28 +151,26 @@ func locStart(ed *Editor) {
// convertListToDirs converts a list of strings to []storedefs.Dir. It uses the
// special score of PinnedScore to signify that the directory is pinned.
func convertListToDirs(li types.List) []storedefs.Dir {
func convertListToDirs(li vector.Vector) []storedefs.Dir {
pinned := make([]storedefs.Dir, 0, li.Len())
// XXX(xiaq): silently drops non-string items.
li.Iterate(func(v types.Value) bool {
if s, ok := v.(string); ok {
for it := li.Iterator(); it.HasElem(); it.Next() {
if s, ok := it.Elem().(string); ok {
pinned = append(pinned, storedefs.Dir{s, PinnedScore})
}
return true
})
}
return pinned
}
func convertListsToSet(lis ...types.List) map[string]struct{} {
func convertListsToSet(lis ...vector.Vector) map[string]struct{} {
set := make(map[string]struct{})
// XXX(xiaq): silently drops non-string items.
for _, li := range lis {
li.Iterate(func(v types.Value) bool {
if s, ok := v.(string); ok {
for it := li.Iterator(); it.HasElem(); it.Next() {
if s, ok := it.Elem().(string); ok {
set[s] = struct{}{}
}
return true
})
}
}
return set
}
@ -182,14 +181,14 @@ var _ = RegisterVariable("loc-hidden", func() vartypes.Variable {
return vartypes.NewValidatedPtr(types.EmptyList, vartypes.ShouldBeList)
})
func (ed *Editor) locHidden() types.List {
return ed.variables["loc-hidden"].Get().(types.List)
func (ed *Editor) locHidden() vector.Vector {
return ed.variables["loc-hidden"].Get().(vector.Vector)
}
var _ = RegisterVariable("loc-pinned", func() vartypes.Variable {
return vartypes.NewValidatedPtr(types.EmptyList, vartypes.ShouldBeList)
})
func (ed *Editor) locPinned() types.List {
return ed.variables["loc-pinned"].Get().(types.List)
func (ed *Editor) locPinned() vector.Vector {
return ed.variables["loc-pinned"].Get().(vector.Vector)
}

View File

@ -202,7 +202,7 @@ func hasKey(ec *Frame, args []types.Value, opts map[string]types.Value) {
default:
if len := types.Len(container); len >= 0 {
// XXX(xiaq): Not all types that implement Lener have numerical indices
_, _, _, err := types.ParseAndFixListIndex(types.ToString(key), len)
_, err := types.ConvertListIndex(key, len)
found = (err == nil)
break
}

View File

@ -14,7 +14,6 @@ import (
"github.com/elves/elvish/glob"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/util"
"github.com/xiaq/persistent/vector"
)
var outputCaptureBufferSize = 16
@ -312,17 +311,17 @@ func (cp *compiler) list(n *parse.Primary) ValuesOpBody {
type listOp struct{ subops []ValuesOp }
func (op listOp) Invoke(fm *Frame) ([]types.Value, error) {
vec := vector.Empty
list := types.EmptyList
for _, subop := range op.subops {
moreValues, err := subop.Exec(fm)
if err != nil {
return nil, err
}
for _, moreValue := range moreValues {
vec = vec.Cons(moreValue)
list = list.Cons(moreValue)
}
}
return []types.Value{types.NewList(vec)}, nil
return []types.Value{list}, nil
}
type exceptionCaptureOp struct{ subop Op }

View File

@ -54,7 +54,7 @@ func (envli *EnvList) Get() types.Value {
for _, path := range strings.Split(value, pathListSeparator) {
v = v.Cons(path)
}
envli.cacheValue = types.NewList(v)
envli.cacheValue = v
return envli.cacheValue
}

View File

@ -111,7 +111,7 @@ func (ev *Evaler) SetArgs(args []string) {
for _, arg := range args {
v = v.Cons(arg)
}
ev.Builtin["args"] = vartypes.NewRo(types.NewList(v))
ev.Builtin["args"] = vartypes.NewRo(v)
}
// SetLibDir sets the library directory, in which external modules are to be

View File

@ -14,10 +14,7 @@ var (
func newMatch(text string, start, end int, groups vector.Vector) *types.Struct {
return types.NewStruct(matchDescriptor, []types.Value{
string(text),
string(strconv.Itoa(start)),
string(strconv.Itoa(end)),
types.NewList(groups),
text, strconv.Itoa(start), strconv.Itoa(end), groups,
})
}

View File

@ -1,6 +1,10 @@
package types
import "errors"
import (
"errors"
"github.com/xiaq/persistent/vector"
)
// Assocer wraps the Assoc method.
type Assocer interface {
@ -9,27 +13,59 @@ type Assocer interface {
Assoc(k, v Value) (Value, error)
}
var errAssocUnsupported = errors.New("assoc is not supported")
var (
errAssocUnsupported = errors.New("assoc is not supported")
errAssocWithSlice = errors.New("assoc with slice not yet supported")
)
// Assoc takes a container, a key and value, and returns a modified version of
// the container, in which the key associated with the value. It is implemented
// for the builtin type string, and types satisfying the listAssocable or
// Assocer interface. For other types, it returns an error.
func Assoc(a, k, v Value) (Value, error) {
switch a := a.(type) {
case string:
return assocString(a, k, v)
case listAssocable:
return assocList(a, k, v)
case Assocer:
return a.Assoc(k, v)
}
return nil, errAssocUnsupported
}
var ErrReplacementMustBeString = errors.New("replacement must be string")
var errReplacementMustBeString = errors.New("replacement must be string")
func assocString(s string, k, v Value) (Value, error) {
i, j, err := indexString(s, k)
i, j, err := convertStringIndex(k, s)
if err != nil {
return nil, err
}
repl, ok := v.(string)
if !ok {
return nil, ErrReplacementMustBeString
return nil, errReplacementMustBeString
}
return s[:i] + repl + s[j:], nil
}
type listAssocable interface {
Lener
AssocN(int, interface{}) vector.Vector
}
var _ listAssocable = vector.Vector(nil)
func assocList(l listAssocable, k, v Value) (Value, error) {
kstring, ok := k.(string)
if kstring, ok = k.(string); !ok {
return nil, errIndexMustBeString
}
index, err := ConvertListIndex(kstring, l.Len())
if err != nil {
return nil, err
}
if index.Slice {
return nil, errAssocWithSlice
}
return l.AssocN(index.Lower, v), nil
}

View File

@ -1,6 +1,8 @@
package types
import "reflect"
import (
"reflect"
)
// Equaler wraps the Equal method.
type Equaler interface {
@ -10,16 +12,43 @@ type Equaler interface {
}
// Equal returns whether two values are equal. It is implemented for the builtin
// types bool and string, and types implementing the Equaler interface. For
// other types, it uses reflect.DeepEqual to compare the two values.
// types bool and string, and types satisfying the listEqualable or Equaler
// interface. For other types, it uses reflect.DeepEqual to compare the two
// values.
func Equal(x, y interface{}) bool {
switch x := x.(type) {
case bool:
return x == y
case string:
return x == y
case listEqualable:
var (
yy listEqualable
ok bool
)
if yy, ok = y.(listEqualable); !ok {
return false
}
if x.Len() != yy.Len() {
return false
}
ix := x.Iterator()
iy := yy.Iterator()
for ix.HasElem() && iy.HasElem() {
if !Equal(ix.Elem(), iy.Elem()) {
return false
}
ix.Next()
iy.Next()
}
return true
case Equaler:
return x.Equal(y)
}
return reflect.DeepEqual(x, y)
}
type listEqualable interface {
Lener
listIterable
}

View File

@ -1,6 +1,8 @@
package types
import "github.com/xiaq/persistent/hash"
import (
"github.com/xiaq/persistent/hash"
)
// Hasher wraps the Hash method.
type Hasher interface {
@ -9,8 +11,9 @@ type Hasher interface {
}
// Hash returns the 32-bit hash of a value. It is implemented for the builtin
// types bool and string, and types implementing the Hahser interface. For other
// values, it returns 0 (which is OK in terms of correctness).
// types bool and string, and types satisfying the listHashable or Hasher
// interface. For other values, it returns 0 (which is OK in terms of
// correctness).
func Hash(v interface{}) uint32 {
switch v := v.(type) {
case bool:
@ -18,6 +21,12 @@ func Hash(v interface{}) uint32 {
return 1
}
return 0
case listHashable:
h := hash.DJBInit
for it := v.Iterator(); it.HasElem(); it.Next() {
h = hash.DJBCombine(h, Hash(it.Elem()))
}
return h
case string:
return hash.String(v)
case Hasher:
@ -25,3 +34,5 @@ func Hash(v interface{}) uint32 {
}
return 0
}
type listHashable listIterable

View File

@ -2,7 +2,11 @@ package types
import (
"errors"
"strconv"
"strings"
"unicode/utf8"
"github.com/xiaq/persistent/vector"
)
// Indexer wraps the Index method.
@ -11,16 +15,22 @@ type Indexer interface {
Index(idx Value) (Value, error)
}
var errNotIndexable = errors.New("not indexable")
var (
errIndexMustBeString = errors.New("index must be string")
errNotIndexable = errors.New("not indexable")
errBadIndex = errors.New("bad index")
errIndexOutOfRange = errors.New("index out of range")
)
// Index indexes a value with the given key. It is implemented for the builtin
// type string, and types satisfying the listIndexable or Indexer interface. For
// other types, it returns a nil value and a non-nil error.
func Index(a, k Value) (Value, error) {
switch a := a.(type) {
case string:
i, j, err := indexString(a, k)
if err != nil {
return nil, err
}
return a[i:j], nil
return indexString(a, k)
case listIndexable:
return indexList(a, k)
case Indexer:
return a.Index(k)
default:
@ -39,20 +49,122 @@ func MustIndex(i Indexer, k Value) Value {
return v
}
func indexString(s string, idx Value) (int, int, error) {
slice, i, j, err := ParseAndFixListIndex(ToString(idx), len(s))
func indexString(s string, index Value) (string, error) {
i, j, err := convertStringIndex(index, s)
if err != nil {
return "", err
}
return s[i:j], nil
}
func convertStringIndex(rawIndex Value, s string) (int, int, error) {
index, err := ConvertListIndex(rawIndex, len(s))
if err != nil {
return 0, 0, err
}
r, size := utf8.DecodeRuneInString(s[i:])
r, size := utf8.DecodeRuneInString(s[index.Lower:])
if r == utf8.RuneError {
return 0, 0, ErrBadIndex
return 0, 0, errBadIndex
}
if slice {
if r, _ := utf8.DecodeLastRuneInString(s[:j]); r == utf8.RuneError {
return 0, 0, ErrBadIndex
if index.Slice {
if r, _ := utf8.DecodeLastRuneInString(s[:index.Upper]); r == utf8.RuneError {
return 0, 0, errBadIndex
}
return i, j, nil
return index.Lower, index.Upper, nil
}
return i, i + size, nil
return index.Lower, index.Lower + size, nil
}
type listIndexable interface {
Lener
Nth(int) interface{}
SubVector(int, int) vector.Vector
}
var _ listIndexable = vector.Vector(nil)
func indexList(l listIndexable, rawIndex Value) (Value, error) {
index, err := ConvertListIndex(rawIndex, l.Len())
if err != nil {
return nil, err
}
if index.Slice {
return l.SubVector(index.Lower, index.Upper), nil
}
return l.Nth(index.Lower), nil
}
// ListIndex represents a (converted) list index.
type ListIndex struct {
Slice bool
Lower int
Upper int
}
// ConvertListIndex parses a list index, check whether it is valid, and returns
// the converted structure.
func ConvertListIndex(rawIndex Value, n int) (*ListIndex, error) {
s, ok := rawIndex.(string)
if !ok {
return nil, errIndexMustBeString
}
slice, i, j, err := parseListIndex(s, n)
if err != nil {
return nil, err
}
if i < 0 {
i += n
}
if j < 0 {
j += n
}
if i < 0 || i >= n || (slice && (j < 0 || j > n || i > j)) {
return nil, errIndexOutOfRange
}
return &ListIndex{slice, i, j}, nil
}
// ListIndex = Number |
// Number ':' Number
func parseListIndex(s string, n int) (slice bool, i int, j int, err error) {
colon := strings.IndexRune(s, ':')
if colon == -1 {
// A single number
i, err := atoi(s)
if err != nil {
return false, 0, 0, err
}
return false, i, 0, nil
}
if s[:colon] == "" {
i = 0
} else {
i, err = atoi(s[:colon])
if err != nil {
return false, 0, 0, err
}
}
if s[colon+1:] == "" {
j = n
} else {
j, err = atoi(s[colon+1:])
if err != nil {
return false, 0, 0, err
}
}
// Two numbers
return true, i, j, nil
}
// atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to
// errIndexOutOfRange.
func atoi(a string) (int, error) {
i, err := strconv.Atoi(a)
if err != nil {
if err.(*strconv.NumError).Err == strconv.ErrRange {
return 0, errIndexOutOfRange
}
return 0, errBadIndex
}
return i, nil
}

79
eval/types/index_test.go Normal file
View File

@ -0,0 +1,79 @@
package types
import (
"testing"
)
var convertListIndexTests = []struct {
name string
// input
expr string
len int
// output
wantOut *ListIndex
wantErr bool
}{
{name: "stringIndex", expr: "a", len: 0,
wantErr: true},
{name: "floatIndex", expr: "1.0", len: 0,
wantErr: true},
{name: "emptyZeroIndex", expr: "0", len: 0,
wantErr: true},
{name: "emptyPosIndex", expr: "1", len: 0,
wantErr: true},
{name: "emptyNegIndex", expr: "-1", len: 0,
wantErr: true},
// BUG(xiaq): Should not be error
{name: "emptySliceAbbrevBoth", expr: ":", len: 0,
wantErr: true},
{name: "i<-n", expr: "-2", len: 1, wantErr: true},
{name: "i=-n", expr: "-1", len: 1,
wantOut: &ListIndex{Lower: 0}},
{name: "-n<i<0", expr: "-1", len: 2,
wantOut: &ListIndex{Lower: 1}},
{name: "i=0", expr: "0", len: 2,
wantOut: &ListIndex{Lower: 0}},
{name: "0<i<n", expr: "1", len: 2,
wantOut: &ListIndex{Lower: 1}},
{name: "i=n", expr: "1", len: 1,
wantErr: true},
{name: "i>n", expr: "2", len: 1,
wantErr: true},
{name: "sliceAbbrevBoth", expr: ":", len: 1,
wantOut: &ListIndex{Slice: true, Lower: 0, Upper: 1}},
{name: "sliceAbbrevBegin", expr: ":1", len: 1,
wantOut: &ListIndex{Slice: true, Lower: 0, Upper: 1}},
{name: "sliceAbbrevEnd", expr: "0:", len: 1,
wantOut: &ListIndex{Slice: true, Lower: 0, Upper: 1}},
{name: "sliceNegEnd", expr: "0:-1", len: 1,
wantOut: &ListIndex{Slice: true, Lower: 0, Upper: 0}},
{name: "sliceBeginEqualEnd", expr: "1:1", len: 2,
wantOut: &ListIndex{Slice: true, Lower: 1, Upper: 1}},
{name: "sliceBeginAboveEnd", expr: "1:0", len: 2,
wantErr: true},
}
func TestConvertListIndex(t *testing.T) {
for _, test := range convertListIndexTests {
index, err := ConvertListIndex(test.expr, test.len)
if !eqListIndex(index, test.wantOut) {
t.Errorf("ConvertListIndex(%q, %d) => %v, want %v",
test.expr, test.len, index, test.wantOut)
}
wantErr := "no error"
if test.wantErr {
wantErr = "non-nil error"
}
if test.wantErr != (err != nil) {
t.Errorf("ConvertListIndex(%q, %d) => err %v, want %s",
err, wantErr)
}
}
}
func eqListIndex(a, b *ListIndex) bool {
if a == nil || b == nil {
return a == b
}
return *a == *b
}

View File

@ -1,6 +1,10 @@
package types
import "errors"
import (
"errors"
"github.com/xiaq/persistent/vector"
)
// Iterator wraps the Iterate method.
type Iterator interface {
@ -9,6 +13,11 @@ type Iterator interface {
Iterate(func(v Value) bool)
}
// Iterate iterates the supplied value, and calls the supplied function in each
// of its elements. The function can return false to break the iteration. It is
// implemented for the builtin type string, and types satisfying the
// listIterable or Iterator interface. For these types, it always returns a nil
// error. For other types, it doesn't do anything and returns an error.
func Iterate(v Value, f func(Value) bool) error {
switch v := v.(type) {
case string:
@ -19,6 +28,13 @@ func Iterate(v Value, f func(Value) bool) error {
}
}
return nil
case listIterable:
for it := v.Iterator(); it.HasElem(); it.Next() {
if !f(it.Elem()) {
break
}
}
return nil
case Iterator:
v.Iterate(f)
return nil
@ -27,6 +43,13 @@ func Iterate(v Value, f func(Value) bool) error {
}
}
type listIterable interface {
Iterator() vector.Iterator
}
var _ listIterable = vector.Vector(nil)
// Collect collects all elements of an iterable value into a slice.
func Collect(it Value) ([]Value, error) {
var vs []Value
if len := Len(it); len >= 0 {

View File

@ -1,6 +1,10 @@
package types
import "fmt"
import (
"fmt"
"github.com/xiaq/persistent/vector"
)
// Kinder wraps the Kind method.
type Kinder interface {
@ -9,14 +13,16 @@ type Kinder interface {
// Kind returns the "kind" of the value, a concept similar to type but not yet
// very well defined. It is implemented for the builtin types bool and string,
// and types implementing the Kinder interface. For other types, it returns the
// Go type name of the argument preceeded by "!!".
// the Vector type, and types implementing the Kinder interface. For other
// types, it returns the Go type name of the argument preceeded by "!!".
func Kind(v interface{}) string {
switch v := v.(type) {
case bool:
return "bool"
case string:
return "string"
case vector.Vector:
return "list"
case Kinder:
return v.Kind()
default:

View File

@ -12,7 +12,7 @@ func TestKind(t *testing.T) {
tt.Test(t, tt.Fn("Kind", Kind), tt.Table{
Args(true).Rets("bool"),
Args("").Rets("string"),
Args(NewList(vector.Empty)).Rets("list"),
Args(vector.Empty).Rets("list"),
Args(NewMap(EmptyMapInner)).Rets("map"),
Args(NewStruct(NewStructDescriptor(), nil)).Rets("map"),
Args(NewFile(os.Stdin)).Rets("file"),

View File

@ -1,11 +1,18 @@
package types
import "github.com/xiaq/persistent/vector"
// Lener wraps the Len method.
type Lener interface {
// Len computes the length of the receiver.
Len() int
}
var _ Lener = vector.Vector(nil)
// Len returns the length of the value, or -1 if the value does not have a
// well-defined length. It is implemented for the builtin string type and types
// satisfying the Lener interface. For other types, it returns -1.
func Len(v interface{}) int {
switch v := v.(type) {
case string:

View File

@ -2,186 +2,52 @@ package types
import (
"bytes"
"encoding/json"
"errors"
"strconv"
"strings"
"github.com/xiaq/persistent/vector"
)
// Error definitions.
var (
ErrBadIndex = errors.New("bad index")
ErrIndexOutOfRange = errors.New("index out of range")
ErrAssocWithSlice = errors.New("assoc with slice not yet supported")
)
// List is a list of Value's.
type List struct {
inner vector.Vector
}
// EmptyList is an empty list.
var EmptyList = List{vector.Empty}
// Make sure that List implements ListLike and Assocer at compile time.
var (
_ ListLike = List{}
_ Assocer = List{}
)
var EmptyList = vector.Empty
// MakeList creates a new List from values.
func MakeList(vs ...Value) List {
func MakeList(vs ...Value) vector.Vector {
vec := vector.Empty
for _, v := range vs {
vec = vec.Cons(v)
}
return List{vec}
return vec
}
// NewList creates a new List from an existing Vector.
func NewList(vec vector.Vector) List {
return List{vec}
// ListReprBuilder helps to build Repr of list-like Values.
type ListReprBuilder struct {
Indent int
buf bytes.Buffer
}
func (List) Kind() string {
return "list"
func (b *ListReprBuilder) WriteElem(v string) {
if b.buf.Len() == 0 {
b.buf.WriteByte('[')
}
if b.Indent >= 0 {
// Pretty printing.
//
// Add a newline and indent+1 spaces, so that the
// starting & lines up with the first pair.
b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent+1))
} else if b.buf.Len() > 1 {
b.buf.WriteByte(' ')
}
b.buf.WriteString(v)
}
func (l List) Equal(rhs interface{}) bool {
return eqListLike(l, rhs)
}
func (l List) Hash() uint32 {
return hashListLike(l)
}
func (l List) Repr(indent int) string {
var b ListReprBuilder
b.Indent = indent
for it := l.inner.Iterator(); it.HasElem(); it.Next() {
v := it.Elem().(Value)
b.WriteElem(Repr(v, indent+1))
}
return b.String()
}
func (l List) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
buf.WriteByte('[')
first := true
for it := l.inner.Iterator(); it.HasElem(); it.Next() {
if first {
first = false
} else {
buf.WriteByte(',')
}
err := encoder.Encode(it.Elem())
if err != nil {
return nil, err
}
}
buf.WriteByte(']')
return buf.Bytes(), nil
}
func (l List) Len() int {
return l.inner.Len()
}
func (l List) Iterate(f func(Value) bool) {
for it := l.inner.Iterator(); it.HasElem(); it.Next() {
v := it.Elem().(Value)
if !f(v) {
break
}
}
}
func (l List) Index(idx Value) (Value, error) {
slice, i, j, err := ParseAndFixListIndex(ToString(idx), l.Len())
if err != nil {
return nil, err
}
if slice {
return List{l.inner.SubVector(i, j)}, nil
}
return l.inner.Nth(i).(Value), nil
}
func (l List) Assoc(idx, v Value) (Value, error) {
slice, i, _, err := ParseAndFixListIndex(ToString(idx), l.Len())
if err != nil {
return nil, err
}
if slice {
return nil, ErrAssocWithSlice
}
return List{l.inner.AssocN(i, v)}, nil
}
// ParseAndFixListIndex parses a list index and returns whether the index is a
// slice and "real" (-1 becomes n-1) indicies. It returns an error when an index
// is invalid or out of range.
func ParseAndFixListIndex(s string, n int) (bool, int, int, error) {
slice, i, j, err := parseListIndex(s, n)
if err != nil {
return false, 0, 0, err
}
if i < 0 {
i += n
}
if j < 0 {
j += n
}
if i < 0 || i >= n || (slice && (j < 0 || j > n || i > j)) {
return false, 0, 0, ErrIndexOutOfRange
}
return slice, i, j, nil
}
// ListIndex = Number |
// Number ':' Number
func parseListIndex(s string, n int) (slice bool, i int, j int, err error) {
colon := strings.IndexRune(s, ':')
if colon == -1 {
// A single number
i, err := atoi(s)
if err != nil {
return false, 0, 0, err
}
return false, i, 0, nil
}
if s[:colon] == "" {
i = 0
} else {
i, err = atoi(s[:colon])
if err != nil {
return false, 0, 0, err
}
}
if s[colon+1:] == "" {
j = n
} else {
j, err = atoi(s[colon+1:])
if err != nil {
return false, 0, 0, err
}
}
// Two numbers
return true, i, j, nil
}
func atoi(a string) (int, error) {
i, err := strconv.Atoi(a)
if err != nil {
if err.(*strconv.NumError).Err == strconv.ErrRange {
return 0, ErrIndexOutOfRange
} else {
return 0, ErrBadIndex
}
}
return i, nil
func (b *ListReprBuilder) String() string {
if b.buf.Len() == 0 {
return "[]"
}
if b.Indent >= 0 {
b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent))
}
b.buf.WriteByte(']')
return b.buf.String()
}

View File

@ -1,129 +0,0 @@
package types
import (
"testing"
)
var parseAndFixListIndexTests = []struct {
name string
// input
expr string
len int
// output
isError, isSlice bool
begin, end int
}{
{
name: "stringIndex",
expr: "a", len: 0,
isError: true,
},
{
name: "floatIndex",
expr: "1.0", len: 0,
isError: true,
},
{
name: "emptyZeroIndex",
expr: "0", len: 0,
isError: true,
},
{
name: "emptyPosIndex",
expr: "1", len: 0,
isError: true,
},
{
name: "emptyNegIndex",
expr: "-1", len: 0,
isError: true,
},
{
name: "emptySliceAbbrevBoth",
expr: ":", len: 0,
isError: true,
},
{
name: "i<-n",
expr: "-2", len: 1,
isError: true,
},
{
name: "i=-n",
expr: "-1", len: 1,
begin: 0, end: 0,
},
{
name: "-n<i<0",
expr: "-1", len: 2,
begin: 1, end: 0,
},
{
name: "i=0",
expr: "0", len: 2,
begin: 0, end: 0,
},
{
name: "0<i<n",
expr: "1", len: 2,
begin: 1, end: 0,
},
{
name: "i=n",
expr: "1", len: 1,
isError: true,
},
{
name: "i>n",
expr: "2", len: 1,
isError: true,
},
{
name: "sliceAbbrevBoth",
expr: ":", len: 1,
isSlice: true, begin: 0, end: 1,
},
{
name: "sliceAbbrevBegin",
expr: ":1", len: 1,
isSlice: true, begin: 0, end: 1,
},
{
name: "sliceAbbrevEnd",
expr: "0:", len: 1,
isSlice: true, begin: 0, end: 1,
},
{
name: "sliceNegEnd",
expr: "0:-1", len: 1,
isSlice: true, begin: 0, end: 0,
},
{
name: "sliceBeginEqualEnd",
expr: "1:1", len: 2,
isSlice: true, begin: 1, end: 1,
},
{
name: "sliceBeginAboveEnd",
expr: "1:0", len: 2,
isError: true,
},
}
func TestParseAndFixListIndex(t *testing.T) {
checkEqual := func(name, value string, want, got interface{}) {
if want != got {
t.Errorf("%s value: [%s] want: [%v] got: [%v]",
name, value, want, got)
}
}
for _, item := range parseAndFixListIndexTests {
isSlice, begin, end, err := ParseAndFixListIndex(item.expr, item.len)
checkEqual(item.name, "isSlice", item.isSlice, isSlice)
checkEqual(item.name, "begin", item.begin, begin)
checkEqual(item.name, "end", item.end, end)
checkEqual(item.name, "isError", item.isError, err != nil)
}
}

View File

@ -1,67 +1 @@
package types
import (
"bytes"
"strings"
"github.com/xiaq/persistent/hash"
)
type ListLike interface {
Lener
Iterator
Indexer
}
func eqListLike(lhs ListLike, r interface{}) bool {
rhs, ok := r.(ListLike)
if !ok {
return false
}
if lhs.Len() != rhs.Len() {
return false
}
return true
}
func hashListLike(l ListLike) uint32 {
h := hash.DJBInit
l.Iterate(func(v Value) bool {
h = hash.DJBCombine(h, Hash(v))
return true
})
return h
}
// ListReprBuilder helps to build Repr of list-like Values.
type ListReprBuilder struct {
Indent int
buf bytes.Buffer
}
func (b *ListReprBuilder) WriteElem(v string) {
if b.buf.Len() == 0 {
b.buf.WriteByte('[')
}
if b.Indent >= 0 {
// Pretty printing.
//
// Add a newline and indent+1 spaces, so that the
// starting & lines up with the first pair.
b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent+1))
} else if b.buf.Len() > 1 {
b.buf.WriteByte(' ')
}
b.buf.WriteString(v)
}
func (b *ListReprBuilder) String() string {
if b.buf.Len() == 0 {
return "[]"
}
if b.Indent >= 0 {
b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent))
}
b.buf.WriteByte(']')
return b.buf.String()
}

View File

@ -27,8 +27,9 @@ type Reprer interface {
// Repr returns the representation for a value, a string that is preferably (but
// not necessarily) an Elvish expression that evaluates to the argument. If
// indent >= 0, the representation is pretty-printed. It is implemented for the
// builtin types bool and string, and types satisfying the Reprer interface. For
// other types, it uses fmt.Sprint with the format "<unknown %v>".
// builtin types bool and string, and types satisfying the listReprable or
// Reprer interface. For other types, it uses fmt.Sprint with the format
// "<unknown %v>".
func Repr(v interface{}, indent int) string {
switch v := v.(type) {
case bool:
@ -38,9 +39,17 @@ func Repr(v interface{}, indent int) string {
return "$false"
case string:
return parse.Quote(v)
case listReprable:
b := ListReprBuilder{Indent: indent}
for it := v.Iterator(); it.HasElem(); it.Next() {
b.WriteElem(Repr(it.Elem(), indent+1))
}
return b.String()
case Reprer:
return v.Repr(indent)
default:
return fmt.Sprintf("<unknown %v>", v)
}
}
type listReprable listIterable

View File

@ -5,6 +5,7 @@ import (
"strconv"
"github.com/elves/elvish/eval/types"
"github.com/xiaq/persistent/vector"
)
var (
@ -15,7 +16,7 @@ var (
)
func ShouldBeList(v types.Value) error {
if _, ok := v.(types.List); !ok {
if _, ok := v.(vector.Vector); !ok {
return errShouldBeList
}
return nil