Skip to content

Commit

Permalink
value: Add a Sliceable interface for operands of the [] operator (goo…
Browse files Browse the repository at this point in the history
…gle#105)

* value: Add a Sliceable interface for operands of the [] operator. Fixes google#104
* library: make the return value of range() a Sliceable. Add tests
  • Loading branch information
nicks authored and adonovan committed May 18, 2018
1 parent 06804d9 commit d3cd736
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 42 deletions.
46 changes: 5 additions & 41 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,11 +805,12 @@ func Call(thread *Thread, fn Value, args Tuple, kwargs []Tuple) (Value, error) {
}

func slice(x, lo, hi, step_ Value) (Value, error) {
n := Len(x)
if n < 0 {
n = 0 // n < 0 => invalid operand; will be rejected by type switch
sliceable, ok := x.(Sliceable)
if !ok {
return nil, fmt.Errorf("invalid slice operand %s", x.Type())
}

n := sliceable.Len()
step := 1
if step_ != None {
var err error
Expand Down Expand Up @@ -837,19 +838,6 @@ func slice(x, lo, hi, step_ Value) (Value, error) {
if end < start {
end = start // => empty result
}

if step == 1 {
// common case: simple subsequence
switch x := x.(type) {
case String:
return String(x[start:end]), nil
case *List:
elems := append([]Value{}, x.elems[start:end]...)
return NewList(elems), nil
case Tuple:
return x[start:end], nil
}
}
} else {
// negative stride
// default indices are effectively [n-1:-1], though to
Expand All @@ -876,31 +864,7 @@ func slice(x, lo, hi, step_ Value) (Value, error) {
}
}

// For positive strides, the loop condition is i < end.
// For negative strides, the loop condition is i > end.
sign := signum(step)
switch x := x.(type) {
case String:
var str []byte
for i := start; signum(end-i) == sign; i += step {
str = append(str, x[i])
}
return String(str), nil
case *List:
var list []Value
for i := start; signum(end-i) == sign; i += step {
list = append(list, x.elems[i])
}
return NewList(list), nil
case Tuple:
var tuple Tuple
for i := start; signum(end-i) == sign; i += step {
tuple = append(tuple, x[i])
}
return tuple, nil
}

return nil, fmt.Errorf("invalid slice operand %s", x.Type())
return sliceable.Slice(start, end, step), nil
}

// From Hacker's Delight, section 2.8.
Expand Down
22 changes: 21 additions & 1 deletion library.go
Original file line number Diff line number Diff line change
Expand Up @@ -878,12 +878,32 @@ var (
_ Indexable = rangeValue{}
_ Sequence = rangeValue{}
_ Comparable = rangeValue{}
_ Sliceable = rangeValue{}
)

func (r rangeValue) Len() int { return r.len }
func (r rangeValue) Index(i int) Value { return MakeInt(r.start + i*r.step) }
func (r rangeValue) Iterate() Iterator { return &rangeIterator{r, 0} }
func (r rangeValue) Freeze() {} // immutable

func (r rangeValue) Slice(start, end, step int) Value {
newStart := r.start + r.step*start
newStop := r.start + r.step*end
newStep := r.step * step
var newLen int
if step > 0 {
newLen = (newStop-1-newStart)/newStep + 1
} else {
newLen = (newStart-1-newStop)/-newStep + 1
}
return rangeValue{
start: newStart,
stop: newStop,
step: newStep,
len: newLen,
}
}

func (r rangeValue) Freeze() {} // immutable
func (r rangeValue) String() string {
if r.step != 1 {
return fmt.Sprintf("range(%d, %d, %d)", r.start, r.stop, r.step)
Expand Down
10 changes: 10 additions & 0 deletions testdata/builtins.sky
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ assert.eq(list(range(10, 2, -3)), [10, 7, 4])
assert.eq(list(range(-2, -10, -3)), [-2, -5, -8])
assert.eq(list(range(-10, -2, 3)), [-10, -7, -4])
assert.eq(list(range(10, 2, -1)), [10, 9, 8, 7, 6, 5, 4, 3])
assert.eq(list(range(5)[1:]), [1, 2, 3, 4])
assert.eq(len(range(5)[1:]), 4)
assert.eq(list(range(5)[:2]), [0, 1])
assert.eq(list(range(10)[1:]), [1, 2, 3, 4, 5, 6, 7, 8, 9])
assert.eq(list(range(10)[1:9:2]), [1, 3, 5, 7])
assert.eq(list(range(10)[1:10:2]), [1, 3, 5, 7, 9])
assert.eq(list(range(10)[1:11:2]), [1, 3, 5, 7, 9])
assert.eq(list(range(10)[::-2]), [9, 7, 5, 3, 1])
assert.eq(list(range(0, 10, 2)[::2]), [0, 4, 8])
assert.eq(list(range(0, 10, 2)[::-2]), [8, 4, 0])
assert.fails(lambda: range(3000000000), "3000000000 out of range") # signed 32-bit values only
assert.eq(len(range(0x7fffffff)), 0x7fffffff) # O(1)
# Two ranges compare equal if they denote the same sequence:
Expand Down
56 changes: 56 additions & 0 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ type Indexable interface {
Len() int
}

// A Sliceable is a sequence that can be cut into pieces with the slice operator (x[i:j:step]).
//
// All native indexable objects are sliceable.
// This is a separate interface for backwards-compatibility.
type Sliceable interface {
Indexable
// For positive strides (step > 0), 0 <= start <= end <= n.
// For negative strides (step < 0), -1 <= end <= start < n.
// The caller must ensure that the start and end indices are valid.
Slice(start, end, step int) Value
}

// A HasSetIndex is an Indexable value whose elements may be assigned (x[i] = y).
//
// The implementation should not add Len to a negative index as the
Expand All @@ -192,6 +204,9 @@ var (
_ HasSetIndex = (*List)(nil)
_ Indexable = Tuple(nil)
_ Indexable = String("")
_ Sliceable = Tuple(nil)
_ Sliceable = String("")
_ Sliceable = (*List)(nil)
)

// An Iterator provides a sequence of values to the caller.
Expand Down Expand Up @@ -386,6 +401,19 @@ func (s String) Hash() (uint32, error) { return hashString(string(s)), nil }
func (s String) Len() int { return len(s) } // bytes
func (s String) Index(i int) Value { return s[i : i+1] }

func (s String) Slice(start, end, step int) Value {
if step == 1 {
return String(s[start:end])
}

sign := signum(step)
var str []byte
for i := start; signum(end-i) == sign; i += step {
str = append(str, s[i])
}
return String(str)
}

func (s String) Attr(name string) (Value, error) { return builtinAttr(s, name, stringMethods) }
func (s String) AttrNames() []string { return builtinAttrNames(stringMethods) }

Expand Down Expand Up @@ -650,6 +678,20 @@ func (l *List) Truth() Bool { return l.Len() > 0 }
func (l *List) Len() int { return len(l.elems) }
func (l *List) Index(i int) Value { return l.elems[i] }

func (l *List) Slice(start, end, step int) Value {
if step == 1 {
elems := append([]Value{}, l.elems[start:end]...)
return NewList(elems)
}

sign := signum(step)
var list []Value
for i := start; signum(end-i) == sign; i += step {
list = append(list, l.elems[i])
}
return NewList(list)
}

func (l *List) Attr(name string) (Value, error) { return builtinAttr(l, name, listMethods) }
func (l *List) AttrNames() []string { return builtinAttrNames(listMethods) }

Expand Down Expand Up @@ -744,6 +786,20 @@ type Tuple []Value

func (t Tuple) Len() int { return len(t) }
func (t Tuple) Index(i int) Value { return t[i] }

func (t Tuple) Slice(start, end, step int) Value {
if step == 1 {
return t[start:end]
}

sign := signum(step)
var tuple Tuple
for i := start; signum(end-i) == sign; i += step {
tuple = append(tuple, t[i])
}
return tuple
}

func (t Tuple) Iterate() Iterator { return &tupleIterator{elems: t} }
func (t Tuple) Freeze() {
for _, elem := range t {
Expand Down

0 comments on commit d3cd736

Please sign in to comment.