summaryrefslogtreecommitdiff
path: root/src/pkg/bytes
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@debian.org>2013-03-04 21:27:36 +0100
committerMichael Stapelberg <michael@stapelberg.de>2013-03-04 21:27:36 +0100
commit04b08da9af0c450d645ab7389d1467308cfc2db8 (patch)
treedb247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/pkg/bytes
parent917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff)
downloadgolang-upstream/1.1_hg20130304.tar.gz
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'src/pkg/bytes')
-rw-r--r--src/pkg/bytes/asm_386.s20
-rw-r--r--src/pkg/bytes/asm_amd64.s30
-rw-r--r--src/pkg/bytes/asm_arm.s53
-rw-r--r--src/pkg/bytes/buffer.go38
-rw-r--r--src/pkg/bytes/buffer_test.go117
-rw-r--r--src/pkg/bytes/bytes.go96
-rw-r--r--src/pkg/bytes/bytes_decl.go8
-rw-r--r--src/pkg/bytes/bytes_test.go104
-rw-r--r--src/pkg/bytes/example_test.go65
-rw-r--r--src/pkg/bytes/reader.go21
-rw-r--r--src/pkg/bytes/reader_test.go49
11 files changed, 489 insertions, 112 deletions
diff --git a/src/pkg/bytes/asm_386.s b/src/pkg/bytes/asm_386.s
index e7833de0c..c444b55e1 100644
--- a/src/pkg/bytes/asm_386.s
+++ b/src/pkg/bytes/asm_386.s
@@ -3,31 +3,31 @@
// license that can be found in the LICENSE file.
TEXT ·IndexByte(SB),7,$0
- MOVL p+0(FP), SI
- MOVL len+4(FP), CX
- MOVB b+12(FP), AL
+ MOVL s+0(FP), SI
+ MOVL s+4(FP), CX
+ MOVB c+12(FP), AL
MOVL SI, DI
CLD; REPN; SCASB
JZ 3(PC)
- MOVL $-1, ret+16(FP)
+ MOVL $-1, r+16(FP)
RET
SUBL SI, DI
SUBL $1, DI
- MOVL DI, ret+16(FP)
+ MOVL DI, r+16(FP)
RET
TEXT ·Equal(SB),7,$0
- MOVL len+4(FP), BX
- MOVL len1+16(FP), CX
+ MOVL a+4(FP), BX
+ MOVL b+16(FP), CX
MOVL $0, AX
CMPL BX, CX
JNE eqret
- MOVL p+0(FP), SI
- MOVL q+12(FP), DI
+ MOVL a+0(FP), SI
+ MOVL b+12(FP), DI
CLD
REP; CMPSB
JNE eqret
MOVL $1, AX
eqret:
- MOVB AX, ret+24(FP)
+ MOVB AX, r+24(FP)
RET
diff --git a/src/pkg/bytes/asm_amd64.s b/src/pkg/bytes/asm_amd64.s
index 5caea5c52..482422642 100644
--- a/src/pkg/bytes/asm_amd64.s
+++ b/src/pkg/bytes/asm_amd64.s
@@ -3,12 +3,12 @@
// license that can be found in the LICENSE file.
TEXT ·IndexByte(SB),7,$0
- MOVQ p+0(FP), SI
- MOVL len+8(FP), BX
- MOVB b+16(FP), AL
+ MOVQ s+0(FP), SI
+ MOVQ s+8(FP), BX
+ MOVB c+24(FP), AL
MOVQ SI, DI
- CMPL BX, $16
+ CMPQ BX, $16
JLT small
// round up to first 16-byte boundary
@@ -63,15 +63,15 @@ condition:
JZ success
failure:
- MOVL $-1, ret+24(FP)
+ MOVQ $-1, r+32(FP)
RET
// handle for lengths < 16
small:
- MOVL BX, CX
+ MOVQ BX, CX
REPN; SCASB
JZ success
- MOVL $-1, ret+24(FP)
+ MOVQ $-1, r+32(FP)
RET
// we've found the chunk containing the byte
@@ -81,28 +81,28 @@ ssesuccess:
BSFW DX, DX
SUBQ SI, DI
ADDQ DI, DX
- MOVL DX, ret+24(FP)
+ MOVQ DX, r+32(FP)
RET
success:
SUBQ SI, DI
SUBL $1, DI
- MOVL DI, ret+24(FP)
+ MOVQ DI, r+32(FP)
RET
TEXT ·Equal(SB),7,$0
- MOVL len+8(FP), BX
- MOVL len1+24(FP), CX
+ MOVQ a+8(FP), BX
+ MOVQ b+32(FP), CX
MOVL $0, AX
- CMPL BX, CX
+ CMPQ BX, CX
JNE eqret
- MOVQ p+0(FP), SI
- MOVQ q+16(FP), DI
+ MOVQ a+0(FP), SI
+ MOVQ b+24(FP), DI
CLD
REP; CMPSB
MOVL $1, DX
CMOVLEQ DX, AX
eqret:
- MOVB AX, ret+32(FP)
+ MOVB AX, r+48(FP)
RET
diff --git a/src/pkg/bytes/asm_arm.s b/src/pkg/bytes/asm_arm.s
index 4ed0c1580..c7685f041 100644
--- a/src/pkg/bytes/asm_arm.s
+++ b/src/pkg/bytes/asm_arm.s
@@ -2,10 +2,55 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// no memchr implementation on arm yet
TEXT ·IndexByte(SB),7,$0
- B ·indexBytePortable(SB)
+ MOVW s+0(FP), R0
+ MOVW s+4(FP), R1
+ MOVBU c+12(FP), R2 // byte to find
+ MOVW R0, R4 // store base for later
+ ADD R0, R1 // end
+
+_loop:
+ CMP R0, R1
+ B.EQ _notfound
+ MOVBU.P 1(R0), R3
+ CMP R2, R3
+ B.NE _loop
+
+ SUB $1, R0 // R0 will be one beyond the position we want
+ SUB R4, R0 // remove base
+ MOVW R0, r+16(FP)
+ RET
+
+_notfound:
+ MOVW $-1, R0
+ MOVW R0, r+16(FP)
+ RET
-// no memcmp implementation on arm yet
TEXT ·Equal(SB),7,$0
- B ·equalPortable(SB)
+ MOVW a+4(FP), R1
+ MOVW b+16(FP), R3
+
+ CMP R1, R3 // unequal lengths are not equal
+ B.NE _notequal
+
+ MOVW a+0(FP), R0
+ MOVW b+12(FP), R2
+ ADD R0, R1 // end
+
+_next:
+ CMP R0, R1
+ B.EQ _equal // reached the end
+ MOVBU.P 1(R0), R4
+ MOVBU.P 1(R2), R5
+ CMP R4, R5
+ B.EQ _next
+
+_notequal:
+ MOVW $0, R0
+ MOVBU R0, r+24(FP)
+ RET
+
+_equal:
+ MOVW $1, R0
+ MOVBU R0, r+24(FP)
+ RET
diff --git a/src/pkg/bytes/buffer.go b/src/pkg/bytes/buffer.go
index afdf22055..85c157798 100644
--- a/src/pkg/bytes/buffer.go
+++ b/src/pkg/bytes/buffer.go
@@ -99,6 +99,19 @@ func (b *Buffer) grow(n int) int {
return b.off + m
}
+// Grow grows the buffer's capacity, if necessary, to guarantee space for
+// another n bytes. After Grow(n), at least n bytes can be written to the
+// buffer without another allocation.
+// If n is negative, Grow will panic.
+// If the buffer can't grow it will panic with ErrTooLarge.
+func (b *Buffer) Grow(n int) {
+ if n < 0 {
+ panic("bytes.Buffer.Grow: negative count")
+ }
+ m := b.grow(n)
+ b.buf = b.buf[0:m]
+}
+
// Write appends the contents of p to the buffer. The return
// value n is the length of p; err is always nil.
// If the buffer becomes too large, Write will panic with
@@ -347,16 +360,25 @@ func (b *Buffer) UnreadByte() error {
// ReadBytes returns err != nil if and only if the returned data does not end in
// delim.
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {
+ slice, err := b.readSlice(delim)
+ // return a copy of slice. The buffer's backing array may
+ // be overwritten by later calls.
+ line = append(line, slice...)
+ return
+}
+
+// readSlice is like ReadBytes but returns a reference to internal buffer data.
+func (b *Buffer) readSlice(delim byte) (line []byte, err error) {
i := IndexByte(b.buf[b.off:], delim)
- size := i + 1
+ end := b.off + i + 1
if i < 0 {
- size = len(b.buf) - b.off
+ end = len(b.buf)
err = io.EOF
}
- line = make([]byte, size)
- copy(line, b.buf[b.off:])
- b.off += size
- return
+ line = b.buf[b.off:end]
+ b.off = end
+ b.lastRead = opRead
+ return line, err
}
// ReadString reads until the first occurrence of delim in the input,
@@ -366,8 +388,8 @@ func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {
// ReadString returns err != nil if and only if the returned data does not end
// in delim.
func (b *Buffer) ReadString(delim byte) (line string, err error) {
- bytes, err := b.ReadBytes(delim)
- return string(bytes), err
+ slice, err := b.readSlice(delim)
+ return string(slice), err
}
// NewBuffer creates and initializes a new Buffer using buf as its initial
diff --git a/src/pkg/bytes/buffer_test.go b/src/pkg/bytes/buffer_test.go
index d0af11f10..f9fb2625a 100644
--- a/src/pkg/bytes/buffer_test.go
+++ b/src/pkg/bytes/buffer_test.go
@@ -8,20 +8,21 @@ import (
. "bytes"
"io"
"math/rand"
+ "runtime"
"testing"
"unicode/utf8"
)
-const N = 10000 // make this bigger for a larger (and slower) test
-var data string // test data for write tests
-var bytes []byte // test data; same as data but as a slice.
+const N = 10000 // make this bigger for a larger (and slower) test
+var data string // test data for write tests
+var testBytes []byte // test data; same as data but as a slice.
func init() {
- bytes = make([]byte, N)
+ testBytes = make([]byte, N)
for i := 0; i < N; i++ {
- bytes[i] = 'a' + byte(i%26)
+ testBytes[i] = 'a' + byte(i%26)
}
- data = string(bytes)
+ data = string(testBytes)
}
// Verify that contents of buf match the string s.
@@ -84,7 +85,7 @@ func fillBytes(t *testing.T, testname string, buf *Buffer, s string, n int, fub
}
func TestNewBuffer(t *testing.T) {
- buf := NewBuffer(bytes)
+ buf := NewBuffer(testBytes)
check(t, "NewBuffer", buf, data)
}
@@ -187,7 +188,7 @@ func TestLargeByteWrites(t *testing.T) {
limit = 9
}
for i := 3; i < limit; i += 3 {
- s := fillBytes(t, "TestLargeWrites (1)", &buf, "", 5, bytes)
+ s := fillBytes(t, "TestLargeWrites (1)", &buf, "", 5, testBytes)
empty(t, "TestLargeByteWrites (2)", &buf, s, make([]byte, len(data)/i))
}
check(t, "TestLargeByteWrites (3)", &buf, "")
@@ -205,7 +206,7 @@ func TestLargeStringReads(t *testing.T) {
func TestLargeByteReads(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
- s := fillBytes(t, "TestLargeReads (1)", &buf, "", 5, bytes[0:len(bytes)/i])
+ s := fillBytes(t, "TestLargeReads (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
empty(t, "TestLargeReads (2)", &buf, s, make([]byte, len(data)))
}
check(t, "TestLargeByteReads (3)", &buf, "")
@@ -219,7 +220,7 @@ func TestMixedReadsAndWrites(t *testing.T) {
if i%2 == 0 {
s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, data[0:wlen])
} else {
- s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, bytes[0:wlen])
+ s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen])
}
rlen := rand.Intn(len(data))
@@ -240,7 +241,7 @@ func TestNil(t *testing.T) {
func TestReadFrom(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
- s := fillBytes(t, "TestReadFrom (1)", &buf, "", 5, bytes[0:len(bytes)/i])
+ s := fillBytes(t, "TestReadFrom (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
var b Buffer
b.ReadFrom(&buf)
empty(t, "TestReadFrom (2)", &b, s, make([]byte, len(data)))
@@ -250,16 +251,16 @@ func TestReadFrom(t *testing.T) {
func TestWriteTo(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
- s := fillBytes(t, "TestReadFrom (1)", &buf, "", 5, bytes[0:len(bytes)/i])
+ s := fillBytes(t, "TestWriteTo (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
var b Buffer
buf.WriteTo(&b)
- empty(t, "TestReadFrom (2)", &b, s, make([]byte, len(data)))
+ empty(t, "TestWriteTo (2)", &b, s, make([]byte, len(data)))
}
}
func TestRuneIO(t *testing.T) {
const NRune = 1000
- // Built a test array while we write the data
+ // Built a test slice while we write the data
b := make([]byte, utf8.UTFMax*NRune)
var buf Buffer
n := 0
@@ -374,6 +375,72 @@ func TestReadBytes(t *testing.T) {
}
}
+func TestReadString(t *testing.T) {
+ for _, test := range readBytesTests {
+ buf := NewBufferString(test.buffer)
+ var err error
+ for _, expected := range test.expected {
+ var s string
+ s, err = buf.ReadString(test.delim)
+ if s != expected {
+ t.Errorf("expected %q, got %q", expected, s)
+ }
+ if err != nil {
+ break
+ }
+ }
+ if err != test.err {
+ t.Errorf("expected error %v, got %v", test.err, err)
+ }
+ }
+}
+
+func BenchmarkReadString(b *testing.B) {
+ const n = 32 << 10
+
+ data := make([]byte, n)
+ data[n-1] = 'x'
+ b.SetBytes(int64(n))
+ for i := 0; i < b.N; i++ {
+ buf := NewBuffer(data)
+ _, err := buf.ReadString('x')
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func TestGrow(t *testing.T) {
+ x := []byte{'x'}
+ y := []byte{'y'}
+ tmp := make([]byte, 72)
+ for _, startLen := range []int{0, 100, 1000, 10000, 100000} {
+ xBytes := Repeat(x, startLen)
+ for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
+ buf := NewBuffer(xBytes)
+ // If we read, this affects buf.off, which is good to test.
+ readBytes, _ := buf.Read(tmp)
+ buf.Grow(growLen)
+ yBytes := Repeat(y, growLen)
+ // Check no allocation occurs in write, as long as we're single-threaded.
+ var m1, m2 runtime.MemStats
+ runtime.ReadMemStats(&m1)
+ buf.Write(yBytes)
+ runtime.ReadMemStats(&m2)
+ if runtime.GOMAXPROCS(-1) == 1 && m1.Mallocs != m2.Mallocs {
+ t.Errorf("allocation occurred during write")
+ }
+ // Check that buffer has correct data.
+ if !Equal(buf.Bytes()[0:startLen-readBytes], xBytes[readBytes:]) {
+ t.Errorf("bad initial data at %d %d", startLen, growLen)
+ }
+ if !Equal(buf.Bytes()[startLen-readBytes:startLen-readBytes+growLen], yBytes) {
+ t.Errorf("bad written data at %d %d", startLen, growLen)
+ }
+ }
+ }
+}
+
// Was a bug: used to give EOF reading empty slice at EOF.
func TestReadEmptyAtEOF(t *testing.T) {
b := new(Buffer)
@@ -386,3 +453,25 @@ func TestReadEmptyAtEOF(t *testing.T) {
t.Errorf("wrong count; got %d want 0", n)
}
}
+
+func TestUnreadByte(t *testing.T) {
+ b := new(Buffer)
+ b.WriteString("abcdefghijklmnopqrstuvwxyz")
+
+ _, err := b.ReadBytes('m')
+ if err != nil {
+ t.Fatalf("ReadBytes: %v", err)
+ }
+
+ err = b.UnreadByte()
+ if err != nil {
+ t.Fatalf("UnreadByte: %v", err)
+ }
+ c, err := b.ReadByte()
+ if err != nil {
+ t.Fatalf("ReadByte: %v", err)
+ }
+ if c != 'm' {
+ t.Errorf("ReadByte = %q; want %q", c, 'm')
+ }
+}
diff --git a/src/pkg/bytes/bytes.go b/src/pkg/bytes/bytes.go
index 7d1426fb4..3bab65ef9 100644
--- a/src/pkg/bytes/bytes.go
+++ b/src/pkg/bytes/bytes.go
@@ -11,8 +11,8 @@ import (
"unicode/utf8"
)
-// Compare returns an integer comparing the two byte arrays lexicographically.
-// The result will be 0 if a==b, -1 if a < b, and +1 if a > b
+// Compare returns an integer comparing two byte slices lexicographically.
+// The result will be 0 if a==b, -1 if a < b, and +1 if a > b.
// A nil argument is equivalent to an empty slice.
func Compare(a, b []byte) int {
m := len(a)
@@ -37,10 +37,6 @@ func Compare(a, b []byte) int {
return 0
}
-// Equal returns a boolean reporting whether a == b.
-// A nil argument is equivalent to an empty slice.
-func Equal(a, b []byte) bool
-
func equalPortable(a, b []byte) bool {
if len(a) != len(b) {
return false
@@ -53,8 +49,8 @@ func equalPortable(a, b []byte) bool {
return true
}
-// explode splits s into an array of UTF-8 sequences, one per Unicode character (still arrays of bytes),
-// up to a maximum of n byte arrays. Invalid UTF-8 sequences are chopped into individual bytes.
+// explode splits s into a slice of UTF-8 sequences, one per Unicode character (still slices of bytes),
+// up to a maximum of n byte slices. Invalid UTF-8 sequences are chopped into individual bytes.
func explode(s []byte, n int) [][]byte {
if n <= 0 {
n = len(s)
@@ -226,7 +222,7 @@ func LastIndexAny(s []byte, chars string) int {
}
// Generic split: splits after each instance of sep,
-// including sepSave bytes of sep in the subarrays.
+// including sepSave bytes of sep in the subslices.
func genSplit(s, sep []byte, sepSave, n int) [][]byte {
if n == 0 {
return nil
@@ -287,15 +283,15 @@ func SplitAfter(s, sep []byte) [][]byte {
return genSplit(s, sep, len(sep), -1)
}
-// Fields splits the array s around each instance of one or more consecutive white space
-// characters, returning a slice of subarrays of s or an empty list if s contains only white space.
+// Fields splits the slice s around each instance of one or more consecutive white space
+// characters, returning a slice of subslices of s or an empty list if s contains only white space.
func Fields(s []byte) [][]byte {
return FieldsFunc(s, unicode.IsSpace)
}
// FieldsFunc interprets s as a sequence of UTF-8-encoded Unicode code points.
-// It splits the array s at each run of code points c satisfying f(c) and
-// returns a slice of subarrays of s. If no code points in s satisfy f(c), an
+// It splits the slice s at each run of code points c satisfying f(c) and
+// returns a slice of subslices of s. If no code points in s satisfy f(c), an
// empty slice is returned.
func FieldsFunc(s []byte, f func(rune) bool) [][]byte {
n := 0
@@ -333,45 +329,46 @@ func FieldsFunc(s []byte, f func(rune) bool) [][]byte {
return a[0:na]
}
-// Join concatenates the elements of a to create a single byte array. The separator
-// sep is placed between elements in the resulting array.
-func Join(a [][]byte, sep []byte) []byte {
- if len(a) == 0 {
+// Join concatenates the elements of s to create a new byte slice. The separator
+// sep is placed between elements in the resulting slice.
+func Join(s [][]byte, sep []byte) []byte {
+ if len(s) == 0 {
return []byte{}
}
- if len(a) == 1 {
- return a[0]
+ if len(s) == 1 {
+ // Just return a copy.
+ return append([]byte(nil), s[0]...)
}
- n := len(sep) * (len(a) - 1)
- for i := 0; i < len(a); i++ {
- n += len(a[i])
+ n := len(sep) * (len(s) - 1)
+ for _, v := range s {
+ n += len(v)
}
b := make([]byte, n)
- bp := copy(b, a[0])
- for _, s := range a[1:] {
+ bp := copy(b, s[0])
+ for _, v := range s[1:] {
bp += copy(b[bp:], sep)
- bp += copy(b[bp:], s)
+ bp += copy(b[bp:], v)
}
return b
}
-// HasPrefix tests whether the byte array s begins with prefix.
+// HasPrefix tests whether the byte slice s begins with prefix.
func HasPrefix(s, prefix []byte) bool {
return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix)
}
-// HasSuffix tests whether the byte array s ends with suffix.
+// HasSuffix tests whether the byte slice s ends with suffix.
func HasSuffix(s, suffix []byte) bool {
return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix)
}
-// Map returns a copy of the byte array s with all its characters modified
+// Map returns a copy of the byte slice s with all its characters modified
// according to the mapping function. If mapping returns a negative value, the character is
// dropped from the string with no replacement. The characters in s and the
// output are interpreted as UTF-8-encoded Unicode code points.
func Map(mapping func(r rune) rune, s []byte) []byte {
- // In the worst case, the array can grow when mapped, making
+ // In the worst case, the slice can grow when mapped, making
// things unpleasant. But it's so rare we barge in assuming it's
// fine. It could also shrink but that falls out naturally.
maxbytes := len(s) // length of b
@@ -412,28 +409,28 @@ func Repeat(b []byte, count int) []byte {
return nb
}
-// ToUpper returns a copy of the byte array s with all Unicode letters mapped to their upper case.
+// ToUpper returns a copy of the byte slice s with all Unicode letters mapped to their upper case.
func ToUpper(s []byte) []byte { return Map(unicode.ToUpper, s) }
-// ToUpper returns a copy of the byte array s with all Unicode letters mapped to their lower case.
+// ToLower returns a copy of the byte slice s with all Unicode letters mapped to their lower case.
func ToLower(s []byte) []byte { return Map(unicode.ToLower, s) }
-// ToTitle returns a copy of the byte array s with all Unicode letters mapped to their title case.
+// ToTitle returns a copy of the byte slice s with all Unicode letters mapped to their title case.
func ToTitle(s []byte) []byte { return Map(unicode.ToTitle, s) }
-// ToUpperSpecial returns a copy of the byte array s with all Unicode letters mapped to their
+// ToUpperSpecial returns a copy of the byte slice s with all Unicode letters mapped to their
// upper case, giving priority to the special casing rules.
func ToUpperSpecial(_case unicode.SpecialCase, s []byte) []byte {
return Map(func(r rune) rune { return _case.ToUpper(r) }, s)
}
-// ToLowerSpecial returns a copy of the byte array s with all Unicode letters mapped to their
+// ToLowerSpecial returns a copy of the byte slice s with all Unicode letters mapped to their
// lower case, giving priority to the special casing rules.
func ToLowerSpecial(_case unicode.SpecialCase, s []byte) []byte {
return Map(func(r rune) rune { return _case.ToLower(r) }, s)
}
-// ToTitleSpecial returns a copy of the byte array s with all Unicode letters mapped to their
+// ToTitleSpecial returns a copy of the byte slice s with all Unicode letters mapped to their
// title case, giving priority to the special casing rules.
func ToTitleSpecial(_case unicode.SpecialCase, s []byte) []byte {
return Map(func(r rune) rune { return _case.ToTitle(r) }, s)
@@ -514,6 +511,24 @@ func TrimFunc(s []byte, f func(r rune) bool) []byte {
return TrimRightFunc(TrimLeftFunc(s, f), f)
}
+// TrimPrefix returns s without the provided leading prefix string.
+// If s doesn't start with prefix, s is returned unchanged.
+func TrimPrefix(s, prefix []byte) []byte {
+ if HasPrefix(s, prefix) {
+ return s[len(prefix):]
+ }
+ return s
+}
+
+// TrimSuffix returns s without the provided trailing suffix string.
+// If s doesn't end with suffix, s is returned unchanged.
+func TrimSuffix(s, suffix []byte) []byte {
+ if HasSuffix(s, suffix) {
+ return s[:len(s)-len(suffix)]
+ }
+ return s
+}
+
// IndexFunc interprets s as a sequence of UTF-8-encoded Unicode code points.
// It returns the byte index in s of the first Unicode
// code point satisfying f(c), or -1 if none do.
@@ -552,7 +567,10 @@ func indexFunc(s []byte, f func(r rune) bool, truth bool) int {
// inverted.
func lastIndexFunc(s []byte, f func(r rune) bool, truth bool) int {
for i := len(s); i > 0; {
- r, size := utf8.DecodeLastRune(s[0:i])
+ r, size := rune(s[i-1]), 1
+ if r >= utf8.RuneSelf {
+ r, size = utf8.DecodeLastRune(s[0:i])
+ }
i -= size
if f(r) == truth {
return i
@@ -619,10 +637,8 @@ func Replace(s, old, new []byte, n int) []byte {
m = Count(s, old)
}
if m == 0 {
- // Nothing to do. Just copy.
- t := make([]byte, len(s))
- copy(t, s)
- return t
+ // Just return a copy.
+ return append([]byte(nil), s...)
}
if n < 0 || m < n {
n = m
diff --git a/src/pkg/bytes/bytes_decl.go b/src/pkg/bytes/bytes_decl.go
index 5d2b9e639..ce78be416 100644
--- a/src/pkg/bytes/bytes_decl.go
+++ b/src/pkg/bytes/bytes_decl.go
@@ -4,5 +4,13 @@
package bytes
+//go:noescape
+
// IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
func IndexByte(s []byte, c byte) int // asm_$GOARCH.s
+
+//go:noescape
+
+// Equal returns a boolean reporting whether a == b.
+// A nil argument is equivalent to an empty slice.
+func Equal(a, b []byte) bool // asm_$GOARCH.s
diff --git a/src/pkg/bytes/bytes_test.go b/src/pkg/bytes/bytes_test.go
index 000f23517..1d6274c33 100644
--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -6,6 +6,7 @@ package bytes_test
import (
. "bytes"
+ "math/rand"
"reflect"
"testing"
"unicode"
@@ -24,16 +25,16 @@ func eq(a, b []string) bool {
return true
}
-func arrayOfString(a [][]byte) []string {
- result := make([]string, len(a))
- for j := 0; j < len(a); j++ {
- result[j] = string(a[j])
+func sliceOfString(s [][]byte) []string {
+ result := make([]string, len(s))
+ for i, v := range s {
+ result[i] = string(v)
}
return result
}
// For ease of reading, the test cases use strings that are converted to byte
-// arrays before invoking the functions.
+// slices before invoking the functions.
var abcd = "abcd"
var faces = "☺☻☹"
@@ -434,7 +435,7 @@ var explodetests = []ExplodeTest{
func TestExplode(t *testing.T) {
for _, tt := range explodetests {
a := SplitN([]byte(tt.s), nil, tt.n)
- result := arrayOfString(a)
+ result := sliceOfString(a)
if !eq(result, tt.a) {
t.Errorf(`Explode("%s", %d) = %v; want %v`, tt.s, tt.n, result, tt.a)
continue
@@ -472,7 +473,7 @@ var splittests = []SplitTest{
func TestSplit(t *testing.T) {
for _, tt := range splittests {
a := SplitN([]byte(tt.s), []byte(tt.sep), tt.n)
- result := arrayOfString(a)
+ result := sliceOfString(a)
if !eq(result, tt.a) {
t.Errorf(`Split(%q, %q, %d) = %v; want %v`, tt.s, tt.sep, tt.n, result, tt.a)
continue
@@ -490,6 +491,12 @@ func TestSplit(t *testing.T) {
t.Errorf("Split disagrees withSplitN(%q, %q, %d) = %v; want %v", tt.s, tt.sep, tt.n, b, a)
}
}
+ if len(a) > 0 {
+ in, out := a[0], s
+ if cap(in) == cap(out) && &in[:1][0] == &out[:1][0] {
+ t.Errorf("Join(%#v, %q) didn't copy", a, tt.sep)
+ }
+ }
}
}
@@ -512,7 +519,7 @@ var splitaftertests = []SplitTest{
func TestSplitAfter(t *testing.T) {
for _, tt := range splitaftertests {
a := SplitAfterN([]byte(tt.s), []byte(tt.sep), tt.n)
- result := arrayOfString(a)
+ result := sliceOfString(a)
if !eq(result, tt.a) {
t.Errorf(`Split(%q, %q, %d) = %v; want %v`, tt.s, tt.sep, tt.n, result, tt.a)
continue
@@ -552,7 +559,7 @@ var fieldstests = []FieldsTest{
func TestFields(t *testing.T) {
for _, tt := range fieldstests {
a := Fields([]byte(tt.s))
- result := arrayOfString(a)
+ result := sliceOfString(a)
if !eq(result, tt.a) {
t.Errorf("Fields(%q) = %v; want %v", tt.s, a, tt.a)
continue
@@ -561,6 +568,14 @@ func TestFields(t *testing.T) {
}
func TestFieldsFunc(t *testing.T) {
+ for _, tt := range fieldstests {
+ a := FieldsFunc([]byte(tt.s), unicode.IsSpace)
+ result := sliceOfString(a)
+ if !eq(result, tt.a) {
+ t.Errorf("FieldsFunc(%q, unicode.IsSpace) = %v; want %v", tt.s, a, tt.a)
+ continue
+ }
+ }
pred := func(c rune) bool { return c == 'X' }
var fieldsFuncTests = []FieldsTest{
{"", []string{}},
@@ -570,15 +585,15 @@ func TestFieldsFunc(t *testing.T) {
}
for _, tt := range fieldsFuncTests {
a := FieldsFunc([]byte(tt.s), pred)
- result := arrayOfString(a)
+ result := sliceOfString(a)
if !eq(result, tt.a) {
t.Errorf("FieldsFunc(%q) = %v, want %v", tt.s, a, tt.a)
}
}
}
-// Test case for any function which accepts and returns a byte array.
-// For ease of creation, we write the byte arrays as strings.
+// Test case for any function which accepts and returns a byte slice.
+// For ease of creation, we write the byte slices as strings.
type StringTest struct {
in, out string
}
@@ -779,8 +794,8 @@ func TestRunes(t *testing.T) {
}
type TrimTest struct {
- f string
- in, cutset, out string
+ f string
+ in, arg, out string
}
var trimTests = []TrimTest{
@@ -805,12 +820,17 @@ var trimTests = []TrimTest{
{"TrimRight", "", "123", ""},
{"TrimRight", "", "", ""},
{"TrimRight", "☺\xc0", "☺", "☺\xc0"},
+ {"TrimPrefix", "aabb", "a", "abb"},
+ {"TrimPrefix", "aabb", "b", "aabb"},
+ {"TrimSuffix", "aabb", "a", "aabb"},
+ {"TrimSuffix", "aabb", "b", "aab"},
}
func TestTrim(t *testing.T) {
for _, tc := range trimTests {
name := tc.f
var f func([]byte, string) []byte
+ var fb func([]byte, []byte) []byte
switch name {
case "Trim":
f = Trim
@@ -818,12 +838,21 @@ func TestTrim(t *testing.T) {
f = TrimLeft
case "TrimRight":
f = TrimRight
+ case "TrimPrefix":
+ fb = TrimPrefix
+ case "TrimSuffix":
+ fb = TrimSuffix
default:
t.Errorf("Undefined trim function %s", name)
}
- actual := string(f([]byte(tc.in), tc.cutset))
+ var actual string
+ if f != nil {
+ actual = string(f([]byte(tc.in), tc.arg))
+ } else {
+ actual = string(fb([]byte(tc.in), []byte(tc.arg)))
+ }
if actual != tc.out {
- t.Errorf("%s(%q, %q) = %q; want %q", name, tc.in, tc.cutset, actual, tc.out)
+ t.Errorf("%s(%q, %q) = %q; want %q", name, tc.in, tc.arg, actual, tc.out)
}
}
}
@@ -1008,3 +1037,46 @@ func TestEqualFold(t *testing.T) {
}
}
}
+
+var makeFieldsInput = func() []byte {
+ x := make([]byte, 1<<20)
+ // Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space.
+ for i := range x {
+ switch rand.Intn(10) {
+ case 0:
+ x[i] = ' '
+ case 1:
+ if i > 0 && x[i-1] == 'x' {
+ copy(x[i-1:], "χ")
+ break
+ }
+ fallthrough
+ default:
+ x[i] = 'x'
+ }
+ }
+ return x
+}
+
+var fieldsInput = makeFieldsInput()
+
+func BenchmarkFields(b *testing.B) {
+ b.SetBytes(int64(len(fieldsInput)))
+ for i := 0; i < b.N; i++ {
+ Fields(fieldsInput)
+ }
+}
+
+func BenchmarkFieldsFunc(b *testing.B) {
+ b.SetBytes(int64(len(fieldsInput)))
+ for i := 0; i < b.N; i++ {
+ FieldsFunc(fieldsInput, unicode.IsSpace)
+ }
+}
+
+func BenchmarkTrimSpace(b *testing.B) {
+ s := []byte(" Some text. \n")
+ for i := 0; i < b.N; i++ {
+ TrimSpace(s)
+ }
+}
diff --git a/src/pkg/bytes/example_test.go b/src/pkg/bytes/example_test.go
index 6fe8cd5a9..ad2dbc69b 100644
--- a/src/pkg/bytes/example_test.go
+++ b/src/pkg/bytes/example_test.go
@@ -5,24 +5,81 @@
package bytes_test
import (
- . "bytes"
+ "bytes"
"encoding/base64"
+ "fmt"
"io"
"os"
+ "sort"
)
func ExampleBuffer() {
- var b Buffer // A Buffer needs no initialization.
+ var b bytes.Buffer // A Buffer needs no initialization.
b.Write([]byte("Hello "))
- b.Write([]byte("world!"))
+ fmt.Fprintf(&b, "world!")
b.WriteTo(os.Stdout)
// Output: Hello world!
}
func ExampleBuffer_reader() {
// A Buffer can turn a string or a []byte into an io.Reader.
- buf := NewBufferString("R29waGVycyBydWxlIQ==")
+ buf := bytes.NewBufferString("R29waGVycyBydWxlIQ==")
dec := base64.NewDecoder(base64.StdEncoding, buf)
io.Copy(os.Stdout, dec)
// Output: Gophers rule!
}
+
+func ExampleCompare() {
+ // Interpret Compare's result by comparing it to zero.
+ var a, b []byte
+ if bytes.Compare(a, b) < 0 {
+ // a less b
+ }
+ if bytes.Compare(a, b) <= 0 {
+ // a less or equal b
+ }
+ if bytes.Compare(a, b) > 0 {
+ // a greater b
+ }
+ if bytes.Compare(a, b) >= 0 {
+ // a greater or equal b
+ }
+
+ // Prefer Equal to Compare for equality comparisons.
+ if bytes.Equal(a, b) {
+ // a equal b
+ }
+ if !bytes.Equal(a, b) {
+ // a not equal b
+ }
+}
+
+func ExampleCompare_search() {
+ // Binary search to find a matching byte slice.
+ var needle []byte
+ var haystack [][]byte // Assume sorted
+ i := sort.Search(len(haystack), func(i int) bool {
+ // Return haystack[i] >= needle.
+ return bytes.Compare(haystack[i], needle) >= 0
+ })
+ if i < len(haystack) && bytes.Equal(haystack[i], needle) {
+ // Found it!
+ }
+}
+
+func ExampleTrimSuffix() {
+ var b = []byte("Hello, goodbye, etc!")
+ b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
+ b = bytes.TrimSuffix(b, []byte("gopher"))
+ b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
+ os.Stdout.Write(b)
+ // Output: Hello, world!
+}
+
+func ExampleTrimPrefix() {
+ var b = []byte("Goodbye,, world!")
+ b = bytes.TrimPrefix(b, []byte("Goodbye,"))
+ b = bytes.TrimPrefix(b, []byte("See ya,"))
+ fmt.Printf("Hello%s", b)
+ // Output: Hello, world!
+}
diff --git a/src/pkg/bytes/reader.go b/src/pkg/bytes/reader.go
index a062e54ba..77511b945 100644
--- a/src/pkg/bytes/reader.go
+++ b/src/pkg/bytes/reader.go
@@ -10,7 +10,7 @@ import (
"unicode/utf8"
)
-// A Reader implements the io.Reader, io.ReaderAt, io.Seeker,
+// A Reader implements the io.Reader, io.ReaderAt, io.WriterTo, io.Seeker,
// io.ByteScanner, and io.RuneScanner interfaces by reading from
// a byte slice.
// Unlike a Buffer, a Reader is read-only and supports seeking.
@@ -121,5 +121,24 @@ func (r *Reader) Seek(offset int64, whence int) (int64, error) {
return abs, nil
}
+// WriteTo implements the io.WriterTo interface.
+func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
+ r.prevRune = -1
+ if r.i >= len(r.s) {
+ return 0, nil
+ }
+ b := r.s[r.i:]
+ m, err := w.Write(b)
+ if m > len(b) {
+ panic("bytes.Reader.WriteTo: invalid Write count")
+ }
+ r.i += m
+ n = int64(m)
+ if m != len(b) && err == nil {
+ err = io.ErrShortWrite
+ }
+ return
+}
+
// NewReader returns a new Reader reading from b.
func NewReader(b []byte) *Reader { return &Reader{b, 0, -1} }
diff --git a/src/pkg/bytes/reader_test.go b/src/pkg/bytes/reader_test.go
index 2e4b1f26e..f0a3e26c4 100644
--- a/src/pkg/bytes/reader_test.go
+++ b/src/pkg/bytes/reader_test.go
@@ -8,6 +8,7 @@ import (
. "bytes"
"fmt"
"io"
+ "io/ioutil"
"os"
"testing"
)
@@ -86,3 +87,51 @@ func TestReaderAt(t *testing.T) {
}
}
}
+
+func TestReaderWriteTo(t *testing.T) {
+ for i := 0; i < 30; i += 3 {
+ var l int
+ if i > 0 {
+ l = len(data) / i
+ }
+ s := data[:l]
+ r := NewReader(testBytes[:l])
+ var b Buffer
+ n, err := r.WriteTo(&b)
+ if expect := int64(len(s)); n != expect {
+ t.Errorf("got %v; want %v", n, expect)
+ }
+ if err != nil {
+ t.Errorf("for length %d: got error = %v; want nil", l, err)
+ }
+ if b.String() != s {
+ t.Errorf("got string %q; want %q", b.String(), s)
+ }
+ if r.Len() != 0 {
+ t.Errorf("reader contains %v bytes; want 0", r.Len())
+ }
+ }
+}
+
+// verify that copying from an empty reader always has the same results,
+// regardless of the presence of a WriteTo method.
+func TestReaderCopyNothing(t *testing.T) {
+ type nErr struct {
+ n int64
+ err error
+ }
+ type justReader struct {
+ io.Reader
+ }
+ type justWriter struct {
+ io.Writer
+ }
+ discard := justWriter{ioutil.Discard} // hide ReadFrom
+
+ var with, withOut nErr
+ with.n, with.err = io.Copy(discard, NewReader(nil))
+ withOut.n, withOut.err = io.Copy(discard, justReader{NewReader(nil)})
+ if with != withOut {
+ t.Errorf("behavior differs: with = %#v; without: %#v", with, withOut)
+ }
+}