mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
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:
parent
a1d43bf8d6
commit
1fd2ff3d92
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
79
eval/types/index_test.go
Normal 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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user