diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2013-03-04 21:27:36 +0100 |
---|---|---|
committer | Michael Stapelberg <michael@stapelberg.de> | 2013-03-04 21:27:36 +0100 |
commit | 04b08da9af0c450d645ab7389d1467308cfc2db8 (patch) | |
tree | db247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/pkg/bytes | |
parent | 917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff) | |
download | golang-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.s | 20 | ||||
-rw-r--r-- | src/pkg/bytes/asm_amd64.s | 30 | ||||
-rw-r--r-- | src/pkg/bytes/asm_arm.s | 53 | ||||
-rw-r--r-- | src/pkg/bytes/buffer.go | 38 | ||||
-rw-r--r-- | src/pkg/bytes/buffer_test.go | 117 | ||||
-rw-r--r-- | src/pkg/bytes/bytes.go | 96 | ||||
-rw-r--r-- | src/pkg/bytes/bytes_decl.go | 8 | ||||
-rw-r--r-- | src/pkg/bytes/bytes_test.go | 104 | ||||
-rw-r--r-- | src/pkg/bytes/example_test.go | 65 | ||||
-rw-r--r-- | src/pkg/bytes/reader.go | 21 | ||||
-rw-r--r-- | src/pkg/bytes/reader_test.go | 49 |
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) + } +} |