diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-01-17 12:40:45 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-01-17 12:40:45 +0100 |
commit | 3e45412327a2654a77944249962b3652e6142299 (patch) | |
tree | bc3bf69452afa055423cbe0c5cfa8ca357df6ccf /src/pkg/bytes | |
parent | c533680039762cacbc37db8dc7eed074c3e497be (diff) | |
download | golang-upstream/2011.01.12.tar.gz |
Imported Upstream version 2011.01.12upstream/2011.01.12
Diffstat (limited to 'src/pkg/bytes')
-rw-r--r-- | src/pkg/bytes/Makefile | 2 | ||||
-rw-r--r-- | src/pkg/bytes/asm_amd64.s | 93 | ||||
-rw-r--r-- | src/pkg/bytes/buffer.go | 78 | ||||
-rw-r--r-- | src/pkg/bytes/buffer_test.go | 61 | ||||
-rw-r--r-- | src/pkg/bytes/bytes.go | 287 | ||||
-rw-r--r-- | src/pkg/bytes/bytes_test.go | 625 |
6 files changed, 770 insertions, 376 deletions
diff --git a/src/pkg/bytes/Makefile b/src/pkg/bytes/Makefile index d50e624d6..03395c7a4 100644 --- a/src/pkg/bytes/Makefile +++ b/src/pkg/bytes/Makefile @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -include ../../Make.$(GOARCH) +include ../../Make.inc TARG=bytes GOFILES=\ diff --git a/src/pkg/bytes/asm_amd64.s b/src/pkg/bytes/asm_amd64.s index 7e78700ec..c6793cbdc 100644 --- a/src/pkg/bytes/asm_amd64.s +++ b/src/pkg/bytes/asm_amd64.s @@ -3,15 +3,90 @@ // license that can be found in the LICENSE file. TEXT ·IndexByte(SB),7,$0 - MOVQ p+0(FP), SI - MOVL len+8(FP), CX - MOVB b+16(FP), AL - MOVQ SI, DI + MOVQ p+0(FP), SI + MOVL len+8(FP), BX + MOVB b+16(FP), AL + MOVQ SI, DI + + CMPL BX, $16 + JLT small + + // round up to first 16-byte boundary + TESTQ $15, SI + JZ aligned + MOVQ SI, CX + ANDQ $~15, CX + ADDQ $16, CX + + // search the beginning + SUBQ SI, CX + REPN; SCASB + JZ success + +// DI is 16-byte aligned; get ready to search using SSE instructions +aligned: + // round down to last 16-byte boundary + MOVQ BX, R11 + ADDQ SI, R11 + ANDQ $~15, R11 + + // shuffle X0 around so that each byte contains c + MOVD AX, X0 + PUNPCKLBW X0, X0 + PUNPCKLBW X0, X0 + PSHUFL $0, X0, X0 + JMP condition + +sse: + // move the next 16-byte chunk of the buffer into X1 + MOVO (DI), X1 + // compare bytes in X0 to X1 + PCMPEQB X0, X1 + // take the top bit of each byte in X1 and put the result in DX + PMOVMSKB X1, DX + TESTL DX, DX + JNZ ssesuccess + ADDQ $16, DI + +condition: + CMPQ DI, R11 + JLT sse + + // search the end + MOVQ SI, CX + ADDQ BX, CX + SUBQ R11, CX + // if CX == 0, the zero flag will be set and we'll end up + // returning a false success + JZ failure REPN; SCASB - JZ 3(PC) - MOVL $-1, ret+24(FP) + JZ success + +failure: + MOVL $-1, ret+24(FP) + RET + +// handle for lengths < 16 +small: + MOVL BX, CX + REPN; SCASB + JZ success + MOVL $-1, ret+24(FP) RET - SUBQ SI, DI - SUBL $1, DI - MOVL DI, ret+24(FP) + +// we've found the chunk containing the byte +// now just figure out which specific byte it is +ssesuccess: + // get the index of the least significant set bit + BSFW DX, DX + SUBQ SI, DI + ADDQ DI, DX + MOVL DX, ret+24(FP) + RET + +success: + SUBQ SI, DI + SUBL $1, DI + MOVL DI, ret+24(FP) RET + diff --git a/src/pkg/bytes/buffer.go b/src/pkg/bytes/buffer.go index 01e6aef67..2574b4f43 100644 --- a/src/pkg/bytes/buffer.go +++ b/src/pkg/bytes/buffer.go @@ -12,14 +12,6 @@ import ( "utf8" ) -// Copy from string to byte array at offset doff. Assume there's room. -func copyString(dst []byte, doff int, str string) { - for soff := 0; soff < len(str); soff++ { - dst[doff] = str[soff] - doff++ - } -} - // A Buffer is a variable-sized buffer of bytes with Read and Write methods. // The zero value for Buffer is an empty buffer ready to use. type Buffer struct { @@ -27,8 +19,20 @@ type Buffer struct { off int // read at &buf[off], write at &buf[len(buf)] runeBytes [utf8.UTFMax]byte // avoid allocation of slice on each WriteByte or Rune bootstrap [64]byte // memory to hold first slice; helps small buffers (Printf) avoid allocation. + lastRead readOp // last read operation, so that Unread* can work correctly. } +// The readOp constants describe the last action performed on +// the buffer, so that UnreadRune and UnreadByte can +// check for invalid usage. +type readOp int + +const ( + opInvalid readOp = iota // Non-read operation. + opReadRune // Read rune. + opRead // Any other read operation. +) + // Bytes returns a slice of the contents of the unread portion of the buffer; // len(b.Bytes()) == b.Len(). If the caller changes the contents of the // returned slice, the contents of the buffer will change provided there @@ -52,6 +56,7 @@ func (b *Buffer) Len() int { return len(b.buf) - b.off } // Truncate discards all but the first n unread bytes from the buffer. // It is an error to call b.Truncate(n) with n > b.Len(). func (b *Buffer) Truncate(n int) { + b.lastRead = opInvalid if n == 0 { // Reuse buffer space. b.off = 0 @@ -90,6 +95,7 @@ func (b *Buffer) grow(n int) int { // Write appends the contents of p to the buffer. The return // value n is the length of p; err is always nil. func (b *Buffer) Write(p []byte) (n int, err os.Error) { + b.lastRead = opInvalid m := b.grow(len(p)) copy(b.buf[m:], p) return len(p), nil @@ -98,9 +104,9 @@ func (b *Buffer) Write(p []byte) (n int, err os.Error) { // WriteString appends the contents of s to the buffer. The return // value n is the length of s; err is always nil. func (b *Buffer) WriteString(s string) (n int, err os.Error) { + b.lastRead = opInvalid m := b.grow(len(s)) - copyString(b.buf, m, s) - return len(s), nil + return copy(b.buf[m:], s), nil } // MinRead is the minimum slice size passed to a Read call by @@ -114,6 +120,7 @@ const MinRead = 512 // Any error except os.EOF encountered during the read // is also returned. func (b *Buffer) ReadFrom(r io.Reader) (n int64, err os.Error) { + b.lastRead = opInvalid // If buffer is empty, reset to recover space. if b.off >= len(b.buf) { b.Truncate(0) @@ -150,6 +157,7 @@ func (b *Buffer) ReadFrom(r io.Reader) (n int64, err os.Error) { // occurs. The return value n is the number of bytes written. // Any error encountered during the write is also returned. func (b *Buffer) WriteTo(w io.Writer) (n int64, err os.Error) { + b.lastRead = opInvalid for b.off < len(b.buf) { m, e := w.Write(b.buf[b.off:]) n += int64(m) @@ -167,6 +175,7 @@ func (b *Buffer) WriteTo(w io.Writer) (n int64, err os.Error) { // The returned error is always nil, but is included // to match bufio.Writer's WriteByte. func (b *Buffer) WriteByte(c byte) os.Error { + b.lastRead = opInvalid m := b.grow(1) b.buf[m] = c return nil @@ -181,7 +190,7 @@ func (b *Buffer) WriteRune(r int) (n int, err os.Error) { b.WriteByte(byte(r)) return 1, nil } - n = utf8.EncodeRune(r, b.runeBytes[0:]) + n = utf8.EncodeRune(b.runeBytes[0:], r) b.Write(b.runeBytes[0:n]) return n, nil } @@ -191,6 +200,7 @@ func (b *Buffer) WriteRune(r int) (n int, err os.Error) { // buffer has no data to return, err is os.EOF even if len(p) is zero; // otherwise it is nil. func (b *Buffer) Read(p []byte) (n int, err os.Error) { + b.lastRead = opInvalid if b.off >= len(b.buf) { // Buffer is empty, reset to recover space. b.Truncate(0) @@ -198,6 +208,9 @@ func (b *Buffer) Read(p []byte) (n int, err os.Error) { } n = copy(p, b.buf[b.off:]) b.off += n + if n > 0 { + b.lastRead = opRead + } return } @@ -206,18 +219,23 @@ func (b *Buffer) Read(p []byte) (n int, err os.Error) { // If there are fewer than n bytes in the buffer, Next returns the entire buffer. // The slice is only valid until the next call to a read or write method. func (b *Buffer) Next(n int) []byte { + b.lastRead = opInvalid m := b.Len() if n > m { n = m } data := b.buf[b.off : b.off+n] b.off += n + if n > 0 { + b.lastRead = opRead + } return data } // ReadByte reads and returns the next byte from the buffer. // If no byte is available, it returns error os.EOF. func (b *Buffer) ReadByte() (c byte, err os.Error) { + b.lastRead = opInvalid if b.off >= len(b.buf) { // Buffer is empty, reset to recover space. b.Truncate(0) @@ -225,6 +243,7 @@ func (b *Buffer) ReadByte() (c byte, err os.Error) { } c = b.buf[b.off] b.off++ + b.lastRead = opRead return c, nil } @@ -234,11 +253,13 @@ func (b *Buffer) ReadByte() (c byte, err os.Error) { // If the bytes are an erroneous UTF-8 encoding, it // consumes one byte and returns U+FFFD, 1. func (b *Buffer) ReadRune() (r int, size int, err os.Error) { + b.lastRead = opInvalid if b.off >= len(b.buf) { // Buffer is empty, reset to recover space. b.Truncate(0) return 0, 0, os.EOF } + b.lastRead = opReadRune c := b.buf[b.off] if c < utf8.RuneSelf { b.off++ @@ -249,6 +270,37 @@ func (b *Buffer) ReadRune() (r int, size int, err os.Error) { return r, n, nil } +// UnreadRune unreads the last rune returned by ReadRune. +// If the most recent read or write operation on the buffer was +// not a ReadRune, UnreadRune returns an error. (In this regard +// it is stricter than UnreadByte, which will unread the last byte +// from any read operation.) +func (b *Buffer) UnreadRune() os.Error { + if b.lastRead != opReadRune { + return os.ErrorString("bytes.Buffer: UnreadRune: previous operation was not ReadRune") + } + b.lastRead = opInvalid + if b.off > 0 { + _, n := utf8.DecodeLastRune(b.buf[0:b.off]) + b.off -= n + } + return nil +} + +// UnreadByte unreads the last byte returned by the most recent +// read operation. If write has happened since the last read, UnreadByte +// returns an error. +func (b *Buffer) UnreadByte() os.Error { + if b.lastRead != opReadRune && b.lastRead != opRead { + return os.ErrorString("bytes.Buffer: UnreadByte: previous operation was not a read") + } + b.lastRead = opInvalid + if b.off > 0 { + b.off-- + } + return nil +} + // NewBuffer creates and initializes a new Buffer using buf as its initial // contents. It is intended to prepare a Buffer to read existing data. It // can also be used to to size the internal buffer for writing. To do that, @@ -259,7 +311,5 @@ func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} } // initial contents. It is intended to prepare a buffer to read an existing // string. func NewBufferString(s string) *Buffer { - buf := make([]byte, len(s)) - copyString(buf, 0, s) - return &Buffer{buf: buf} + return &Buffer{buf: []byte(s)} } diff --git a/src/pkg/bytes/buffer_test.go b/src/pkg/bytes/buffer_test.go index bc696f4b5..509793d24 100644 --- a/src/pkg/bytes/buffer_test.go +++ b/src/pkg/bytes/buffer_test.go @@ -30,19 +30,19 @@ func check(t *testing.T, testname string, buf *Buffer, s string) { bytes := buf.Bytes() str := buf.String() if buf.Len() != len(bytes) { - t.Errorf("%s: buf.Len() == %d, len(buf.Bytes()) == %d\n", testname, buf.Len(), len(bytes)) + t.Errorf("%s: buf.Len() == %d, len(buf.Bytes()) == %d", testname, buf.Len(), len(bytes)) } if buf.Len() != len(str) { - t.Errorf("%s: buf.Len() == %d, len(buf.String()) == %d\n", testname, buf.Len(), len(str)) + t.Errorf("%s: buf.Len() == %d, len(buf.String()) == %d", testname, buf.Len(), len(str)) } if buf.Len() != len(s) { - t.Errorf("%s: buf.Len() == %d, len(s) == %d\n", testname, buf.Len(), len(s)) + t.Errorf("%s: buf.Len() == %d, len(s) == %d", testname, buf.Len(), len(s)) } if string(bytes) != s { - t.Errorf("%s: string(buf.Bytes()) == %q, s == %q\n", testname, string(bytes), s) + t.Errorf("%s: string(buf.Bytes()) == %q, s == %q", testname, string(bytes), s) } } @@ -55,10 +55,10 @@ func fillString(t *testing.T, testname string, buf *Buffer, s string, n int, fus for ; n > 0; n-- { m, err := buf.WriteString(fus) if m != len(fus) { - t.Errorf(testname+" (fill 2): m == %d, expected %d\n", m, len(fus)) + t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fus)) } if err != nil { - t.Errorf(testname+" (fill 3): err should always be nil, found err == %s\n", err) + t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err) } s += fus check(t, testname+" (fill 4)", buf, s) @@ -75,10 +75,10 @@ func fillBytes(t *testing.T, testname string, buf *Buffer, s string, n int, fub for ; n > 0; n-- { m, err := buf.Write(fub) if m != len(fub) { - t.Errorf(testname+" (fill 2): m == %d, expected %d\n", m, len(fub)) + t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fub)) } if err != nil { - t.Errorf(testname+" (fill 3): err should always be nil, found err == %s\n", err) + t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err) } s += string(fub) check(t, testname+" (fill 4)", buf, s) @@ -110,7 +110,7 @@ func empty(t *testing.T, testname string, buf *Buffer, s string, fub []byte) { break } if err != nil { - t.Errorf(testname+" (empty 2): err should always be nil, found err == %s\n", err) + t.Errorf(testname+" (empty 2): err should always be nil, found err == %s", err) } s = s[n:] check(t, testname+" (empty 3)", buf, s) @@ -132,21 +132,21 @@ func TestBasicOperations(t *testing.T) { buf.Truncate(0) check(t, "TestBasicOperations (3)", &buf, "") - n, err := buf.Write(Bytes(data[0:1])) + n, err := buf.Write([]byte(data[0:1])) if n != 1 { - t.Errorf("wrote 1 byte, but n == %d\n", n) + t.Errorf("wrote 1 byte, but n == %d", n) } if err != nil { - t.Errorf("err should always be nil, but err == %s\n", err) + t.Errorf("err should always be nil, but err == %s", err) } check(t, "TestBasicOperations (4)", &buf, "a") buf.WriteByte(data[1]) check(t, "TestBasicOperations (5)", &buf, "ab") - n, err = buf.Write(Bytes(data[2:26])) + n, err = buf.Write([]byte(data[2:26])) if n != 24 { - t.Errorf("wrote 25 bytes, but n == %d\n", n) + t.Errorf("wrote 25 bytes, but n == %d", n) } check(t, "TestBasicOperations (6)", &buf, string(data[0:26])) @@ -162,14 +162,14 @@ func TestBasicOperations(t *testing.T) { buf.WriteByte(data[1]) c, err := buf.ReadByte() if err != nil { - t.Errorf("ReadByte unexpected eof\n") + t.Error("ReadByte unexpected eof") } if c != data[1] { - t.Errorf("ReadByte wrong value c=%v\n", c) + t.Errorf("ReadByte wrong value c=%v", c) } c, err = buf.ReadByte() if err == nil { - t.Errorf("ReadByte unexpected not eof\n") + t.Error("ReadByte unexpected not eof") } } } @@ -238,7 +238,7 @@ func TestMixedReadsAndWrites(t *testing.T) { func TestNil(t *testing.T) { var b *Buffer if b.String() != "<nil>" { - t.Error("expcted <nil>; got %q", b.String()) + t.Errorf("expcted <nil>; got %q", b.String()) } } @@ -272,13 +272,13 @@ func TestRuneIO(t *testing.T) { var buf Buffer n := 0 for r := 0; r < NRune; r++ { - size := utf8.EncodeRune(r, b[n:]) + size := utf8.EncodeRune(b[n:], r) nbytes, err := buf.WriteRune(r) if err != nil { - t.Fatalf("WriteRune(0x%x) error: %s", r, err) + t.Fatalf("WriteRune(%U) error: %s", r, err) } if nbytes != size { - t.Fatalf("WriteRune(0x%x) expected %d, got %d", r, size, nbytes) + t.Fatalf("WriteRune(%U) expected %d, got %d", r, size, nbytes) } n += size } @@ -289,12 +289,27 @@ func TestRuneIO(t *testing.T) { t.Fatalf("incorrect result from WriteRune: %q not %q", buf.Bytes(), b) } + p := make([]byte, utf8.UTFMax) // Read it back with ReadRune for r := 0; r < NRune; r++ { - size := utf8.EncodeRune(r, b) + size := utf8.EncodeRune(p, r) nr, nbytes, err := buf.ReadRune() if nr != r || nbytes != size || err != nil { - t.Fatalf("ReadRune(0x%x) got 0x%x,%d not 0x%x,%d (err=%s)", r, nr, nbytes, r, size, err) + t.Fatalf("ReadRune(%U) got %U,%d not %U,%d (err=%s)", r, nr, nbytes, r, size, err) + } + } + + // Check that UnreadRune works + buf.Reset() + buf.Write(b) + for r := 0; r < NRune; r++ { + r1, size, _ := buf.ReadRune() + if err := buf.UnreadRune(); err != nil { + t.Fatalf("UnreadRune(%U) got error %q", r, err) + } + r2, nbytes, err := buf.ReadRune() + if r1 != r2 || r1 != r || nbytes != size || err != nil { + t.Fatalf("ReadRune(%U) after UnreadRune got %U,%d not %U,%d (err=%s)", r, r2, nbytes, r, size, err) } } } diff --git a/src/pkg/bytes/bytes.go b/src/pkg/bytes/bytes.go index bcf7b8609..bfe2ef39d 100644 --- a/src/pkg/bytes/bytes.go +++ b/src/pkg/bytes/bytes.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // The bytes package implements functions for the manipulation of byte slices. -// Analagous to the facilities of the strings package. +// Analogous to the facilities of the strings package. package bytes import ( @@ -127,7 +127,21 @@ func LastIndex(s, sep []byte) int { return -1 } -// IndexAny interprets s as a sequence of UTF-8 encoded Unicode code points. +// IndexRune interprets s as a sequence of UTF-8-encoded Unicode code points. +// It returns the byte index of the first occurrence in s of the given rune. +// It returns -1 if rune is not present in s. +func IndexRune(s []byte, rune int) int { + for i := 0; i < len(s); { + r, size := utf8.DecodeRune(s[i:]) + if r == rune { + return i + } + i += size + } + return -1 +} + +// IndexAny interprets s as a sequence of UTF-8-encoded Unicode code points. // It returns the byte index of the first occurrence in s of any of the Unicode // code points in chars. It returns -1 if chars is empty or if there is no code // point in common. @@ -151,6 +165,25 @@ func IndexAny(s []byte, chars string) int { return -1 } +// LastIndexAny interprets s as a sequence of UTF-8-encoded Unicode code +// points. It returns the byte index of the last occurrence in s of any of +// the Unicode code points in chars. It returns -1 if chars is empty or if +// there is no code point in common. +func LastIndexAny(s []byte, chars string) int { + if len(chars) > 0 { + for i := len(s); i > 0; { + rune, size := utf8.DecodeLastRune(s[0:i]) + i -= size + for _, m := range chars { + if rune == m { + return i + } + } + } + } + return -1 +} + // Generic split: splits after each instance of sep, // including sepSave bytes of sep in the subarrays. func genSplit(s, sep []byte, sepSave, n int) [][]byte { @@ -179,17 +212,22 @@ func genSplit(s, sep []byte, sepSave, n int) [][]byte { return a[0 : na+1] } -// Split splits the array s around each instance of sep, returning an array of subarrays of s. -// If sep is empty, Split splits s after each UTF-8 sequence. -// If n >= 0, Split splits s into at most n subarrays; the last subarray will contain an unsplit remainder. -// Thus if n == 0, the result will ne nil. +// Split slices s into subslices separated by sep and returns a slice of +// the subslices between those separators. +// If sep is empty, Split splits after each UTF-8 sequence. +// The count determines the number of subslices to return: +// n > 0: at most n subslices; the last subslice will be the unsplit remainder. +// n == 0: the result is nil (zero subslices) +// n < 0: all subslices func Split(s, sep []byte, n int) [][]byte { return genSplit(s, sep, 0, n) } -// SplitAfter splits the array s after each instance of sep, returning an array of subarrays of s. -// If sep is empty, SplitAfter splits s after each UTF-8 sequence. -// If n >= 0, SplitAfter splits s into at most n subarrays; the last subarray will contain an -// unsplit remainder. -// Thus if n == 0, the result will ne nil. +// SplitAfter slices s into subslices after each instance of sep and +// returns a slice of those subslices. +// If sep is empty, Split splits after each UTF-8 sequence. +// The count determines the number of subslices to return: +// n > 0: at most n subslices; the last subslice will be the unsplit remainder. +// n == 0: the result is nil (zero subslices) +// n < 0: all subslices func SplitAfter(s, sep []byte, n int) [][]byte { return genSplit(s, sep, len(sep), n) } @@ -197,12 +235,20 @@ func SplitAfter(s, sep []byte, n int) [][]byte { // 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. 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 +// empty slice is returned. +func FieldsFunc(s []byte, f func(int) bool) [][]byte { n := 0 inField := false for i := 0; i < len(s); { rune, size := utf8.DecodeRune(s[i:]) wasInField := inField - inField = !unicode.IsSpace(rune) + inField = !f(rune) if inField && !wasInField { n++ } @@ -214,12 +260,12 @@ func Fields(s []byte) [][]byte { fieldStart := -1 for i := 0; i <= len(s) && na < n; { rune, size := utf8.DecodeRune(s[i:]) - if fieldStart < 0 && size > 0 && !unicode.IsSpace(rune) { + if fieldStart < 0 && size > 0 && !f(rune) { fieldStart = i i += size continue } - if fieldStart >= 0 && (size == 0 || unicode.IsSpace(rune)) { + if fieldStart >= 0 && (size == 0 || f(rune)) { a[na] = s[fieldStart:i] na++ fieldStart = -1 @@ -278,7 +324,7 @@ func HasSuffix(s, suffix []byte) bool { // Map returns a copy of the byte array 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. +// output are interpreted as UTF-8-encoded Unicode code points. func Map(mapping func(rune int) int, s []byte) []byte { // In the worst case, the array can grow when mapped, making // things unpleasant. But it's so rare we barge in assuming it's @@ -298,12 +344,10 @@ func Map(mapping func(rune int) int, s []byte) []byte { // Grow the buffer. maxbytes = maxbytes*2 + utf8.UTFMax nb := make([]byte, maxbytes) - for i, c := range b[0:nbytes] { - nb[i] = c - } + copy(nb, b[0:nbytes]) b = nb } - nbytes += utf8.EncodeRune(rune, b[nbytes:maxbytes]) + nbytes += utf8.EncodeRune(b[nbytes:maxbytes], rune) } i += wid } @@ -332,52 +376,147 @@ 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. func ToTitle(s []byte) []byte { return Map(unicode.ToTitle, s) } -// TrimLeftFunc returns a subslice of s by slicing off all leading UTF-8 encoded +// ToUpperSpecial returns a copy of the byte array 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 int) int { return _case.ToUpper(r) }, s) +} + +// ToLowerSpecial returns a copy of the byte array 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 int) int { return _case.ToLower(r) }, s) +} + +// ToTitleSpecial returns a copy of the byte array 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 int) int { return _case.ToTitle(r) }, s) +} + + +// isSeparator reports whether the rune could mark a word boundary. +// TODO: update when package unicode captures more of the properties. +func isSeparator(rune int) bool { + // ASCII alphanumerics and underscore are not separators + if rune <= 0x7F { + switch { + case '0' <= rune && rune <= '9': + return false + case 'a' <= rune && rune <= 'z': + return false + case 'A' <= rune && rune <= 'Z': + return false + case rune == '_': + return false + } + return true + } + // Letters and digits are not separators + if unicode.IsLetter(rune) || unicode.IsDigit(rune) { + return false + } + // Otherwise, all we can do for now is treat spaces as separators. + return unicode.IsSpace(rune) +} + +// BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. + +// Title returns a copy of s with all Unicode letters that begin words +// mapped to their title case. +func Title(s []byte) []byte { + // Use a closure here to remember state. + // Hackish but effective. Depends on Map scanning in order and calling + // the closure once per rune. + prev := ' ' + return Map( + func(r int) int { + if isSeparator(prev) { + prev = r + return unicode.ToTitle(r) + } + prev = r + return r + }, + s) +} + +// TrimLeftFunc returns a subslice of s by slicing off all leading UTF-8-encoded // Unicode code points c that satisfy f(c). func TrimLeftFunc(s []byte, f func(r int) bool) []byte { - var start, wid int - for start = 0; start < len(s); start += wid { - wid = 1 - rune := int(s[start]) - if rune >= utf8.RuneSelf { - rune, wid = utf8.DecodeRune(s[start:]) - } - if !f(rune) { - break - } + i := indexFunc(s, f, false) + if i == -1 { + return nil } - return s[start:] + return s[i:] } // TrimRightFunc returns a subslice of s by slicing off all trailing UTF-8 // encoded Unicode code points c that satisfy f(c). func TrimRightFunc(s []byte, f func(r int) bool) []byte { - var end, wid int - for end = len(s); end > 0; end -= wid { - wid = 1 - rune := int(s[end-wid]) - if rune >= utf8.RuneSelf { - // Back up & look for beginning of rune. Mustn't pass start. - for wid = 2; end-wid >= 0 && !utf8.RuneStart(s[end-wid]); wid++ { - } - if end-wid < 0 { // invalid UTF-8 sequence; stop processing - break - } - rune, wid = utf8.DecodeRune(s[end-wid : end]) - } - if !f(rune) { - break - } + i := lastIndexFunc(s, f, false) + if i >= 0 && s[i] >= utf8.RuneSelf { + _, wid := utf8.DecodeRune(s[i:]) + i += wid + } else { + i++ } - return s[0:end] + return s[0:i] } // TrimFunc returns a subslice of s by slicing off all leading and trailing -// UTF-8 encoded Unicode code points c that satisfy f(c). +// UTF-8-encoded Unicode code points c that satisfy f(c). func TrimFunc(s []byte, f func(r int) bool) []byte { return TrimRightFunc(TrimLeftFunc(s, f), f) } +// 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. +func IndexFunc(s []byte, f func(r int) bool) int { + return indexFunc(s, f, true) +} + +// LastIndexFunc interprets s as a sequence of UTF-8-encoded Unicode code points. +// It returns the byte index in s of the last Unicode +// code point satisfying f(c), or -1 if none do. +func LastIndexFunc(s []byte, f func(r int) bool) int { + return lastIndexFunc(s, f, true) +} + +// indexFunc is the same as IndexFunc except that if +// truth==false, the sense of the predicate function is +// inverted. +func indexFunc(s []byte, f func(r int) bool, truth bool) int { + start := 0 + for start < len(s) { + wid := 1 + rune := int(s[start]) + if rune >= utf8.RuneSelf { + rune, wid = utf8.DecodeRune(s[start:]) + } + if f(rune) == truth { + return start + } + start += wid + } + return -1 +} + +// lastIndexFunc is the same as LastIndexFunc except that if +// truth==false, the sense of the predicate function is +// inverted. +func lastIndexFunc(s []byte, f func(r int) bool, truth bool) int { + for i := len(s); i > 0; { + rune, size := utf8.DecodeLastRune(s[0:i]) + i -= size + if f(rune) == truth { + return i + } + } + return -1 +} + func makeCutsetFunc(cutset string) func(rune int) bool { return func(rune int) bool { for _, c := range cutset { @@ -390,71 +529,29 @@ func makeCutsetFunc(cutset string) func(rune int) bool { } // Trim returns a subslice of s by slicing off all leading and -// trailing UTF-8 encoded Unicode code points contained in cutset. +// trailing UTF-8-encoded Unicode code points contained in cutset. func Trim(s []byte, cutset string) []byte { return TrimFunc(s, makeCutsetFunc(cutset)) } // TrimLeft returns a subslice of s by slicing off all leading -// UTF-8 encoded Unicode code points contained in cutset. +// UTF-8-encoded Unicode code points contained in cutset. func TrimLeft(s []byte, cutset string) []byte { return TrimLeftFunc(s, makeCutsetFunc(cutset)) } // TrimRight returns a subslice of s by slicing off all trailing -// UTF-8 encoded Unicode code points that are contained in cutset. +// UTF-8-encoded Unicode code points that are contained in cutset. func TrimRight(s []byte, cutset string) []byte { return TrimRightFunc(s, makeCutsetFunc(cutset)) } // TrimSpace returns a subslice of s by slicing off all leading and -// trailing white space, as as defined by Unicode. +// trailing white space, as defined by Unicode. func TrimSpace(s []byte) []byte { return TrimFunc(s, unicode.IsSpace) } -// How big to make a byte array when growing. -// Heuristic: Scale by 50% to give n log n time. -func resize(n int) int { - if n < 16 { - n = 16 - } - return n + n/2 -} - -// Add appends the contents of t to the end of s and returns the result. -// If s has enough capacity, it is extended in place; otherwise a -// new array is allocated and returned. -func Add(s, t []byte) []byte { - lens := len(s) - lent := len(t) - if lens+lent <= cap(s) { - s = s[0 : lens+lent] - } else { - news := make([]byte, lens+lent, resize(lens+lent)) - copy(news, s) - s = news - } - copy(s[lens:lens+lent], t) - return s -} - -// AddByte appends byte b to the end of s and returns the result. -// If s has enough capacity, it is extended in place; otherwise a -// new array is allocated and returned. -func AddByte(s []byte, t byte) []byte { - lens := len(s) - if lens+1 <= cap(s) { - s = s[0 : lens+1] - } else { - news := make([]byte, lens+1, resize(lens+1)) - copy(news, s) - s = news - } - s[lens] = t - return s -} - // Runes returns a slice of runes (Unicode code points) equivalent to s. func Runes(s []byte) []int { t := make([]int, utf8.RuneCount(s)) diff --git a/src/pkg/bytes/bytes_test.go b/src/pkg/bytes/bytes_test.go index 8197543dc..063686ec5 100644 --- a/src/pkg/bytes/bytes_test.go +++ b/src/pkg/bytes/bytes_test.go @@ -8,6 +8,7 @@ import ( . "bytes" "testing" "unicode" + "utf8" ) func eq(a, b []string) bool { @@ -45,16 +46,16 @@ type BinOpTest struct { } var comparetests = []BinOpTest{ - BinOpTest{"", "", 0}, - BinOpTest{"a", "", 1}, - BinOpTest{"", "a", -1}, - BinOpTest{"abc", "abc", 0}, - BinOpTest{"ab", "abc", -1}, - BinOpTest{"abc", "ab", 1}, - BinOpTest{"x", "ab", 1}, - BinOpTest{"ab", "x", -1}, - BinOpTest{"x", "a", 1}, - BinOpTest{"b", "x", -1}, + {"", "", 0}, + {"a", "", 1}, + {"", "a", -1}, + {"abc", "abc", 0}, + {"ab", "abc", -1}, + {"abc", "ab", 1}, + {"x", "ab", 1}, + {"ab", "x", -1}, + {"x", "a", 1}, + {"b", "x", -1}, } func TestCompare(t *testing.T) { @@ -73,55 +74,81 @@ func TestCompare(t *testing.T) { } var indexTests = []BinOpTest{ - BinOpTest{"", "", 0}, - BinOpTest{"", "a", -1}, - BinOpTest{"", "foo", -1}, - BinOpTest{"fo", "foo", -1}, - BinOpTest{"foo", "foo", 0}, - BinOpTest{"oofofoofooo", "f", 2}, - BinOpTest{"oofofoofooo", "foo", 4}, - BinOpTest{"barfoobarfoo", "foo", 3}, - BinOpTest{"foo", "", 0}, - BinOpTest{"foo", "o", 1}, - BinOpTest{"abcABCabc", "A", 3}, + {"", "", 0}, + {"", "a", -1}, + {"", "foo", -1}, + {"fo", "foo", -1}, + {"foo", "foo", 0}, + {"oofofoofooo", "f", 2}, + {"oofofoofooo", "foo", 4}, + {"barfoobarfoo", "foo", 3}, + {"foo", "", 0}, + {"foo", "o", 1}, + {"abcABCabc", "A", 3}, // cases with one byte strings - test IndexByte and special case in Index() - BinOpTest{"", "a", -1}, - BinOpTest{"x", "a", -1}, - BinOpTest{"x", "x", 0}, - BinOpTest{"abc", "a", 0}, - BinOpTest{"abc", "b", 1}, - BinOpTest{"abc", "c", 2}, - BinOpTest{"abc", "x", -1}, + {"", "a", -1}, + {"x", "a", -1}, + {"x", "x", 0}, + {"abc", "a", 0}, + {"abc", "b", 1}, + {"abc", "c", 2}, + {"abc", "x", -1}, + {"barfoobarfooyyyzzzyyyzzzyyyzzzyyyxxxzzzyyy", "x", 33}, + {"foofyfoobarfoobar", "y", 4}, + {"oooooooooooooooooooooo", "r", -1}, } var lastIndexTests = []BinOpTest{ - BinOpTest{"", "", 0}, - BinOpTest{"", "a", -1}, - BinOpTest{"", "foo", -1}, - BinOpTest{"fo", "foo", -1}, - BinOpTest{"foo", "foo", 0}, - BinOpTest{"foo", "f", 0}, - BinOpTest{"oofofoofooo", "f", 7}, - BinOpTest{"oofofoofooo", "foo", 7}, - BinOpTest{"barfoobarfoo", "foo", 9}, - BinOpTest{"foo", "", 3}, - BinOpTest{"foo", "o", 2}, - BinOpTest{"abcABCabc", "A", 3}, - BinOpTest{"abcABCabc", "a", 6}, + {"", "", 0}, + {"", "a", -1}, + {"", "foo", -1}, + {"fo", "foo", -1}, + {"foo", "foo", 0}, + {"foo", "f", 0}, + {"oofofoofooo", "f", 7}, + {"oofofoofooo", "foo", 7}, + {"barfoobarfoo", "foo", 9}, + {"foo", "", 3}, + {"foo", "o", 2}, + {"abcABCabc", "A", 3}, + {"abcABCabc", "a", 6}, } var indexAnyTests = []BinOpTest{ - BinOpTest{"", "", -1}, - BinOpTest{"", "a", -1}, - BinOpTest{"", "abc", -1}, - BinOpTest{"a", "", -1}, - BinOpTest{"a", "a", 0}, - BinOpTest{"aaa", "a", 0}, - BinOpTest{"abc", "xyz", -1}, - BinOpTest{"abc", "xcz", 2}, - BinOpTest{"ab☺c", "x☺yz", 2}, - BinOpTest{"aRegExp*", ".(|)*+?^$[]", 7}, - BinOpTest{dots + dots + dots, " ", -1}, + {"", "", -1}, + {"", "a", -1}, + {"", "abc", -1}, + {"a", "", -1}, + {"a", "a", 0}, + {"aaa", "a", 0}, + {"abc", "xyz", -1}, + {"abc", "xcz", 2}, + {"ab☺c", "x☺yz", 2}, + {"aRegExp*", ".(|)*+?^$[]", 7}, + {dots + dots + dots, " ", -1}, +} + +var lastIndexAnyTests = []BinOpTest{ + {"", "", -1}, + {"", "a", -1}, + {"", "abc", -1}, + {"a", "", -1}, + {"a", "a", 0}, + {"aaa", "a", 2}, + {"abc", "xyz", -1}, + {"abc", "ab", 1}, + {"a☺b☻c☹d", "uvw☻xyz", 2 + len("☺")}, + {"a.RegExp*", ".(|)*+?^$[]", 8}, + {dots + dots + dots, " ", -1}, +} + +var indexRuneTests = []BinOpTest{ + {"", "a", -1}, + {"", "☺", -1}, + {"foo", "☹", -1}, + {"foo", "o", 1}, + {"foo☺bar", "☺", 3}, + {"foo☺☻☹bar", "☹", 9}, } // Execute f on each test case. funcName should be the name of f; it's used @@ -137,18 +164,23 @@ func runIndexTests(t *testing.T, f func(s, sep []byte) int, funcName string, tes } } -func TestIndex(t *testing.T) { runIndexTests(t, Index, "Index", indexTests) } -func TestLastIndex(t *testing.T) { runIndexTests(t, LastIndex, "LastIndex", lastIndexTests) } -func TestIndexAny(t *testing.T) { - for _, test := range indexAnyTests { +func runIndexAnyTests(t *testing.T, f func(s []byte, chars string) int, funcName string, testCases []BinOpTest) { + for _, test := range testCases { a := []byte(test.a) - actual := IndexAny(a, test.b) + actual := f(a, test.b) if actual != test.i { - t.Errorf("IndexAny(%q,%q) = %v; want %v", a, test.b, actual, test.i) + t.Errorf("%s(%q,%q) = %v; want %v", funcName, a, test.b, actual, test.i) } } } +func TestIndex(t *testing.T) { runIndexTests(t, Index, "Index", indexTests) } +func TestLastIndex(t *testing.T) { runIndexTests(t, LastIndex, "LastIndex", lastIndexTests) } +func TestIndexAny(t *testing.T) { runIndexAnyTests(t, IndexAny, "IndexAny", indexAnyTests) } +func TestLastIndexAny(t *testing.T) { + runIndexAnyTests(t, LastIndexAny, "LastIndexAny", lastIndexAnyTests) +} + func TestIndexByte(t *testing.T) { for _, tt := range indexTests { if len(tt.b) != 1 { @@ -167,6 +199,67 @@ func TestIndexByte(t *testing.T) { } } +// test a larger buffer with different sizes and alignments +func TestIndexByteBig(t *testing.T) { + const n = 1024 + b := make([]byte, n) + for i := 0; i < n; i++ { + // different start alignments + b1 := b[i:] + for j := 0; j < len(b1); j++ { + b1[j] = 'x' + pos := IndexByte(b1, 'x') + if pos != j { + t.Errorf("IndexByte(%q, 'x') = %v", b1, pos) + } + b1[j] = 0 + pos = IndexByte(b1, 'x') + if pos != -1 { + t.Errorf("IndexByte(%q, 'x') = %v", b1, pos) + } + } + // different end alignments + b1 = b[:i] + for j := 0; j < len(b1); j++ { + b1[j] = 'x' + pos := IndexByte(b1, 'x') + if pos != j { + t.Errorf("IndexByte(%q, 'x') = %v", b1, pos) + } + b1[j] = 0 + pos = IndexByte(b1, 'x') + if pos != -1 { + t.Errorf("IndexByte(%q, 'x') = %v", b1, pos) + } + } + // different start and end alignments + b1 = b[i/2 : n-(i+1)/2] + for j := 0; j < len(b1); j++ { + b1[j] = 'x' + pos := IndexByte(b1, 'x') + if pos != j { + t.Errorf("IndexByte(%q, 'x') = %v", b1, pos) + } + b1[j] = 0 + pos = IndexByte(b1, 'x') + if pos != -1 { + t.Errorf("IndexByte(%q, 'x') = %v", b1, pos) + } + } + } +} + +func TestIndexRune(t *testing.T) { + for _, tt := range indexRuneTests { + a := []byte(tt.a) + r, _ := utf8.DecodeRuneInString(tt.b) + pos := IndexRune(a, r) + if pos != tt.i { + t.Errorf(`IndexRune(%q, '%c') = %v`, tt.a, r, pos) + } + } +} + func BenchmarkIndexByte4K(b *testing.B) { bmIndex(b, IndexByte, 4<<10) } func BenchmarkIndexByte4M(b *testing.B) { bmIndex(b, IndexByte, 4<<20) } @@ -211,9 +304,10 @@ type ExplodeTest struct { } var explodetests = []ExplodeTest{ - ExplodeTest{abcd, -1, []string{"a", "b", "c", "d"}}, - ExplodeTest{faces, -1, []string{"☺", "☻", "☹"}}, - ExplodeTest{abcd, 2, []string{"a", "bcd"}}, + {"", -1, []string{}}, + {abcd, -1, []string{"a", "b", "c", "d"}}, + {faces, -1, []string{"☺", "☻", "☹"}}, + {abcd, 2, []string{"a", "bcd"}}, } func TestExplode(t *testing.T) { @@ -240,19 +334,19 @@ type SplitTest struct { } var splittests = []SplitTest{ - SplitTest{abcd, "a", 0, nil}, - SplitTest{abcd, "a", -1, []string{"", "bcd"}}, - SplitTest{abcd, "z", -1, []string{"abcd"}}, - SplitTest{abcd, "", -1, []string{"a", "b", "c", "d"}}, - SplitTest{commas, ",", -1, []string{"1", "2", "3", "4"}}, - SplitTest{dots, "...", -1, []string{"1", ".2", ".3", ".4"}}, - SplitTest{faces, "☹", -1, []string{"☺☻", ""}}, - SplitTest{faces, "~", -1, []string{faces}}, - SplitTest{faces, "", -1, []string{"☺", "☻", "☹"}}, - SplitTest{"1 2 3 4", " ", 3, []string{"1", "2", "3 4"}}, - SplitTest{"1 2", " ", 3, []string{"1", "2"}}, - SplitTest{"123", "", 2, []string{"1", "23"}}, - SplitTest{"123", "", 17, []string{"1", "2", "3"}}, + {abcd, "a", 0, nil}, + {abcd, "a", -1, []string{"", "bcd"}}, + {abcd, "z", -1, []string{"abcd"}}, + {abcd, "", -1, []string{"a", "b", "c", "d"}}, + {commas, ",", -1, []string{"1", "2", "3", "4"}}, + {dots, "...", -1, []string{"1", ".2", ".3", ".4"}}, + {faces, "☹", -1, []string{"☺☻", ""}}, + {faces, "~", -1, []string{faces}}, + {faces, "", -1, []string{"☺", "☻", "☹"}}, + {"1 2 3 4", " ", 3, []string{"1", "2", "3 4"}}, + {"1 2", " ", 3, []string{"1", "2"}}, + {"123", "", 2, []string{"1", "23"}}, + {"123", "", 17, []string{"1", "2", "3"}}, } func TestSplit(t *testing.T) { @@ -274,19 +368,19 @@ func TestSplit(t *testing.T) { } var splitaftertests = []SplitTest{ - SplitTest{abcd, "a", -1, []string{"a", "bcd"}}, - SplitTest{abcd, "z", -1, []string{"abcd"}}, - SplitTest{abcd, "", -1, []string{"a", "b", "c", "d"}}, - SplitTest{commas, ",", -1, []string{"1,", "2,", "3,", "4"}}, - SplitTest{dots, "...", -1, []string{"1...", ".2...", ".3...", ".4"}}, - SplitTest{faces, "☹", -1, []string{"☺☻☹", ""}}, - SplitTest{faces, "~", -1, []string{faces}}, - SplitTest{faces, "", -1, []string{"☺", "☻", "☹"}}, - SplitTest{"1 2 3 4", " ", 3, []string{"1 ", "2 ", "3 4"}}, - SplitTest{"1 2 3", " ", 3, []string{"1 ", "2 ", "3"}}, - SplitTest{"1 2", " ", 3, []string{"1 ", "2"}}, - SplitTest{"123", "", 2, []string{"1", "23"}}, - SplitTest{"123", "", 17, []string{"1", "2", "3"}}, + {abcd, "a", -1, []string{"a", "bcd"}}, + {abcd, "z", -1, []string{"abcd"}}, + {abcd, "", -1, []string{"a", "b", "c", "d"}}, + {commas, ",", -1, []string{"1,", "2,", "3,", "4"}}, + {dots, "...", -1, []string{"1...", ".2...", ".3...", ".4"}}, + {faces, "☹", -1, []string{"☺☻☹", ""}}, + {faces, "~", -1, []string{faces}}, + {faces, "", -1, []string{"☺", "☻", "☹"}}, + {"1 2 3 4", " ", 3, []string{"1 ", "2 ", "3 4"}}, + {"1 2 3", " ", 3, []string{"1 ", "2 ", "3"}}, + {"1 2", " ", 3, []string{"1 ", "2"}}, + {"123", "", 2, []string{"1", "23"}}, + {"123", "", 17, []string{"1", "2", "3"}}, } func TestSplitAfter(t *testing.T) { @@ -310,17 +404,17 @@ type FieldsTest struct { } var fieldstests = []FieldsTest{ - FieldsTest{"", []string{}}, - FieldsTest{" ", []string{}}, - FieldsTest{" \t ", []string{}}, - FieldsTest{" abc ", []string{"abc"}}, - FieldsTest{"1 2 3 4", []string{"1", "2", "3", "4"}}, - FieldsTest{"1 2 3 4", []string{"1", "2", "3", "4"}}, - FieldsTest{"1\t\t2\t\t3\t4", []string{"1", "2", "3", "4"}}, - FieldsTest{"1\u20002\u20013\u20024", []string{"1", "2", "3", "4"}}, - FieldsTest{"\u2000\u2001\u2002", []string{}}, - FieldsTest{"\n™\t™\n", []string{"™", "™"}}, - FieldsTest{faces, []string{faces}}, + {"", []string{}}, + {" ", []string{}}, + {" \t ", []string{}}, + {" abc ", []string{"abc"}}, + {"1 2 3 4", []string{"1", "2", "3", "4"}}, + {"1 2 3 4", []string{"1", "2", "3", "4"}}, + {"1\t\t2\t\t3\t4", []string{"1", "2", "3", "4"}}, + {"1\u20002\u20013\u20024", []string{"1", "2", "3", "4"}}, + {"\u2000\u2001\u2002", []string{}}, + {"\n™\t™\n", []string{"™", "™"}}, + {faces, []string{faces}}, } func TestFields(t *testing.T) { @@ -334,6 +428,23 @@ func TestFields(t *testing.T) { } } +func TestFieldsFunc(t *testing.T) { + pred := func(c int) bool { return c == 'X' } + var fieldsFuncTests = []FieldsTest{ + {"", []string{}}, + {"XX", []string{}}, + {"XXhiXXX", []string{"hi"}}, + {"aXXbXXXcX", []string{"a", "b", "c"}}, + } + for _, tt := range fieldsFuncTests { + a := FieldsFunc([]byte(tt.s), pred) + result := arrayOfString(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. type StringTest struct { @@ -341,51 +452,47 @@ type StringTest struct { } var upperTests = []StringTest{ - StringTest{"", ""}, - StringTest{"abc", "ABC"}, - StringTest{"AbC123", "ABC123"}, - StringTest{"azAZ09_", "AZAZ09_"}, - StringTest{"\u0250\u0250\u0250\u0250\u0250", "\u2C6F\u2C6F\u2C6F\u2C6F\u2C6F"}, // grows one byte per char + {"", ""}, + {"abc", "ABC"}, + {"AbC123", "ABC123"}, + {"azAZ09_", "AZAZ09_"}, + {"\u0250\u0250\u0250\u0250\u0250", "\u2C6F\u2C6F\u2C6F\u2C6F\u2C6F"}, // grows one byte per char } var lowerTests = []StringTest{ - StringTest{"", ""}, - StringTest{"abc", "abc"}, - StringTest{"AbC123", "abc123"}, - StringTest{"azAZ09_", "azaz09_"}, - StringTest{"\u2C6D\u2C6D\u2C6D\u2C6D\u2C6D", "\u0251\u0251\u0251\u0251\u0251"}, // shrinks one byte per char + {"", ""}, + {"abc", "abc"}, + {"AbC123", "abc123"}, + {"azAZ09_", "azaz09_"}, + {"\u2C6D\u2C6D\u2C6D\u2C6D\u2C6D", "\u0251\u0251\u0251\u0251\u0251"}, // shrinks one byte per char } const space = "\t\v\r\f\n\u0085\u00a0\u2000\u3000" var trimSpaceTests = []StringTest{ - StringTest{"", ""}, - StringTest{"abc", "abc"}, - StringTest{space + "abc" + space, "abc"}, - StringTest{" ", ""}, - StringTest{" \t\r\n \t\t\r\r\n\n ", ""}, - StringTest{" \t\r\n x\t\t\r\r\n\n ", "x"}, - StringTest{" \u2000\t\r\n x\t\t\r\r\ny\n \u3000", "x\t\t\r\r\ny"}, - StringTest{"1 \t\r\n2", "1 \t\r\n2"}, - StringTest{" x\x80", "x\x80"}, // invalid UTF-8 on end - StringTest{" x\xc0", "x\xc0"}, // invalid UTF-8 on end -} - -// Bytes returns a new slice containing the bytes in s. -// Borrowed from strings to avoid dependency. -func Bytes(s string) []byte { - b := make([]byte, len(s)) - for i := 0; i < len(s); i++ { - b[i] = s[i] - } - return b + {"", ""}, + {"abc", "abc"}, + {space + "abc" + space, "abc"}, + {" ", ""}, + {" \t\r\n \t\t\r\r\n\n ", ""}, + {" \t\r\n x\t\t\r\r\n\n ", "x"}, + {" \u2000\t\r\n x\t\t\r\r\ny\n \u3000", "x\t\t\r\r\ny"}, + {"1 \t\r\n2", "1 \t\r\n2"}, + {" x\x80", "x\x80"}, + {" x\xc0", "x\xc0"}, + {"x \xc0\xc0 ", "x \xc0\xc0"}, + {"x \xc0", "x \xc0"}, + {"x \xc0 ", "x \xc0"}, + {"x \xc0\xc0 ", "x \xc0\xc0"}, + {"x ☺\xc0\xc0 ", "x ☺\xc0\xc0"}, + {"x ☺ ", "x ☺"}, } // Execute f on each test case. funcName should be the name of f; it's used // in failure reports. func runStringTests(t *testing.T, f func([]byte) []byte, funcName string, testCases []StringTest) { for _, tc := range testCases { - actual := string(f(Bytes(tc.in))) + actual := string(f([]byte(tc.in))) if actual != tc.out { t.Errorf("%s(%q) = %q; want %q", funcName, tc.in, actual, tc.out) } @@ -418,7 +525,7 @@ func TestMap(t *testing.T) { // 1. Grow. This triggers two reallocations in Map. maxRune := func(rune int) int { return unicode.MaxRune } - m := Map(maxRune, Bytes(a)) + m := Map(maxRune, []byte(a)) expect := tenRunes(unicode.MaxRune) if string(m) != expect { t.Errorf("growing: expected %q got %q", expect, m) @@ -426,21 +533,21 @@ func TestMap(t *testing.T) { // 2. Shrink minRune := func(rune int) int { return 'a' } - m = Map(minRune, Bytes(tenRunes(unicode.MaxRune))) + m = Map(minRune, []byte(tenRunes(unicode.MaxRune))) expect = a if string(m) != expect { t.Errorf("shrinking: expected %q got %q", expect, m) } // 3. Rot13 - m = Map(rot13, Bytes("a to zed")) + m = Map(rot13, []byte("a to zed")) expect = "n gb mrq" if string(m) != expect { t.Errorf("rot13: expected %q got %q", expect, m) } // 4. Rot13^2 - m = Map(rot13, Map(rot13, Bytes("a to zed"))) + m = Map(rot13, Map(rot13, []byte("a to zed"))) expect = "a to zed" if string(m) != expect { t.Errorf("rot13: expected %q got %q", expect, m) @@ -453,7 +560,7 @@ func TestMap(t *testing.T) { } return -1 } - m = Map(dropNotLatin, Bytes("Hello, 세계")) + m = Map(dropNotLatin, []byte("Hello, 세계")) expect = "Hello" if string(m) != expect { t.Errorf("drop: expected %q got %q", expect, m) @@ -466,60 +573,19 @@ func TestToLower(t *testing.T) { runStringTests(t, ToLower, "ToLower", lowerTest func TestTrimSpace(t *testing.T) { runStringTests(t, TrimSpace, "TrimSpace", trimSpaceTests) } -type AddTest struct { - s, t string - cap int -} - -var addtests = []AddTest{ - AddTest{"", "", 0}, - AddTest{"a", "", 1}, - AddTest{"a", "b", 1}, - AddTest{"abc", "def", 100}, -} - -func TestAdd(t *testing.T) { - for _, test := range addtests { - b := make([]byte, len(test.s), test.cap) - for i := 0; i < len(test.s); i++ { - b[i] = test.s[i] - } - b = Add(b, []byte(test.t)) - if string(b) != test.s+test.t { - t.Errorf("Add(%q,%q) = %q", test.s, test.t, string(b)) - } - } -} - -func TestAddByte(t *testing.T) { - const N = 2e5 - b := make([]byte, 0) - for i := 0; i < N; i++ { - b = AddByte(b, byte(i)) - } - if len(b) != N { - t.Errorf("AddByte: too small; expected %d got %d", N, len(b)) - } - for i, c := range b { - if c != byte(i) { - t.Fatalf("AddByte: b[%d] should be %d is %d", i, c, byte(i)) - } - } -} - type RepeatTest struct { in, out string count int } var RepeatTests = []RepeatTest{ - RepeatTest{"", "", 0}, - RepeatTest{"", "", 1}, - RepeatTest{"", "", 2}, - RepeatTest{"-", "", 0}, - RepeatTest{"-", "-", 1}, - RepeatTest{"-", "----------", 10}, - RepeatTest{"abc ", "abc abc abc ", 3}, + {"", "", 0}, + {"", "", 1}, + {"", "", 2}, + {"-", "", 0}, + {"-", "-", 1}, + {"-", "----------", 10}, + {"abc ", "abc abc abc ", 3}, } func TestRepeat(t *testing.T) { @@ -553,13 +619,13 @@ type RunesTest struct { } var RunesTests = []RunesTest{ - RunesTest{"", []int{}, false}, - RunesTest{" ", []int{32}, false}, - RunesTest{"ABC", []int{65, 66, 67}, false}, - RunesTest{"abc", []int{97, 98, 99}, false}, - RunesTest{"\u65e5\u672c\u8a9e", []int{26085, 26412, 35486}, false}, - RunesTest{"ab\x80c", []int{97, 98, 0xFFFD, 99}, true}, - RunesTest{"ab\xc0c", []int{97, 98, 0xFFFD, 99}, true}, + {"", []int{}, false}, + {" ", []int{32}, false}, + {"ABC", []int{65, 66, 67}, false}, + {"abc", []int{97, 98, 99}, false}, + {"\u65e5\u672c\u8a9e", []int{26085, 26412, 35486}, false}, + {"ab\x80c", []int{97, 98, 0xFFFD, 99}, true}, + {"ab\xc0c", []int{97, 98, 0xFFFD, 99}, true}, } func TestRunes(t *testing.T) { @@ -587,26 +653,27 @@ type TrimTest struct { } var trimTests = []TrimTest{ - TrimTest{Trim, "abba", "a", "bb"}, - TrimTest{Trim, "abba", "ab", ""}, - TrimTest{TrimLeft, "abba", "ab", ""}, - TrimTest{TrimRight, "abba", "ab", ""}, - TrimTest{TrimLeft, "abba", "a", "bba"}, - TrimTest{TrimRight, "abba", "a", "abb"}, - TrimTest{Trim, "<tag>", "<>", "tag"}, - TrimTest{Trim, "* listitem", " *", "listitem"}, - TrimTest{Trim, `"quote"`, `"`, "quote"}, - TrimTest{Trim, "\u2C6F\u2C6F\u0250\u0250\u2C6F\u2C6F", "\u2C6F", "\u0250\u0250"}, + {Trim, "abba", "a", "bb"}, + {Trim, "abba", "ab", ""}, + {TrimLeft, "abba", "ab", ""}, + {TrimRight, "abba", "ab", ""}, + {TrimLeft, "abba", "a", "bba"}, + {TrimRight, "abba", "a", "abb"}, + {Trim, "<tag>", "<>", "tag"}, + {Trim, "* listitem", " *", "listitem"}, + {Trim, `"quote"`, `"`, "quote"}, + {Trim, "\u2C6F\u2C6F\u0250\u0250\u2C6F\u2C6F", "\u2C6F", "\u0250\u0250"}, //empty string tests - TrimTest{Trim, "abba", "", "abba"}, - TrimTest{Trim, "", "123", ""}, - TrimTest{Trim, "", "", ""}, - TrimTest{TrimLeft, "abba", "", "abba"}, - TrimTest{TrimLeft, "", "123", ""}, - TrimTest{TrimLeft, "", "", ""}, - TrimTest{TrimRight, "abba", "", "abba"}, - TrimTest{TrimRight, "", "123", ""}, - TrimTest{TrimRight, "", "", ""}, + {Trim, "abba", "", "abba"}, + {Trim, "", "123", ""}, + {Trim, "", "", ""}, + {TrimLeft, "abba", "", "abba"}, + {TrimLeft, "", "123", ""}, + {TrimLeft, "", "", ""}, + {TrimRight, "abba", "", "abba"}, + {TrimRight, "", "123", ""}, + {TrimRight, "", "", ""}, + {TrimRight, "☺\xc0", "☺", "☺\xc0"}, } func TestTrim(t *testing.T) { @@ -629,22 +696,90 @@ func TestTrim(t *testing.T) { } } +type predicate struct { + f func(r int) bool + name string +} + +var isSpace = predicate{unicode.IsSpace, "IsSpace"} +var isDigit = predicate{unicode.IsDigit, "IsDigit"} +var isUpper = predicate{unicode.IsUpper, "IsUpper"} +var isValidRune = predicate{ + func(r int) bool { + return r != utf8.RuneError + }, + "IsValidRune", +} + type TrimFuncTest struct { - f func(r int) bool - name, in, out string + f predicate + in, out string +} + +func not(p predicate) predicate { + return predicate{ + func(r int) bool { + return !p.f(r) + }, + "not " + p.name, + } } var trimFuncTests = []TrimFuncTest{ - TrimFuncTest{unicode.IsSpace, "IsSpace", space + " hello " + space, "hello"}, - TrimFuncTest{unicode.IsDigit, "IsDigit", "\u0e50\u0e5212hello34\u0e50\u0e51", "hello"}, - TrimFuncTest{unicode.IsUpper, "IsUpper", "\u2C6F\u2C6F\u2C6F\u2C6FABCDhelloEF\u2C6F\u2C6FGH\u2C6F\u2C6F", "hello"}, + {isSpace, space + " hello " + space, "hello"}, + {isDigit, "\u0e50\u0e5212hello34\u0e50\u0e51", "hello"}, + {isUpper, "\u2C6F\u2C6F\u2C6F\u2C6FABCDhelloEF\u2C6F\u2C6FGH\u2C6F\u2C6F", "hello"}, + {not(isSpace), "hello" + space + "hello", space}, + {not(isDigit), "hello\u0e50\u0e521234\u0e50\u0e51helo", "\u0e50\u0e521234\u0e50\u0e51"}, + {isValidRune, "ab\xc0a\xc0cd", "\xc0a\xc0"}, + {not(isValidRune), "\xc0a\xc0", "a"}, } func TestTrimFunc(t *testing.T) { for _, tc := range trimFuncTests { - actual := string(TrimFunc([]byte(tc.in), tc.f)) + actual := string(TrimFunc([]byte(tc.in), tc.f.f)) if actual != tc.out { - t.Errorf("TrimFunc(%q, %q) = %q; want %q", tc.in, tc.name, actual, tc.out) + t.Errorf("TrimFunc(%q, %q) = %q; want %q", tc.in, tc.f.name, actual, tc.out) + } + } +} + +type IndexFuncTest struct { + in string + f predicate + first, last int +} + +var indexFuncTests = []IndexFuncTest{ + {"", isValidRune, -1, -1}, + {"abc", isDigit, -1, -1}, + {"0123", isDigit, 0, 3}, + {"a1b", isDigit, 1, 1}, + {space, isSpace, 0, len(space) - 3}, // last rune in space is 3 bytes + {"\u0e50\u0e5212hello34\u0e50\u0e51", isDigit, 0, 18}, + {"\u2C6F\u2C6F\u2C6F\u2C6FABCDhelloEF\u2C6F\u2C6FGH\u2C6F\u2C6F", isUpper, 0, 34}, + {"12\u0e50\u0e52hello34\u0e50\u0e51", not(isDigit), 8, 12}, + + // tests of invalid UTF-8 + {"\x801", isDigit, 1, 1}, + {"\x80abc", isDigit, -1, -1}, + {"\xc0a\xc0", isValidRune, 1, 1}, + {"\xc0a\xc0", not(isValidRune), 0, 2}, + {"\xc0☺\xc0", not(isValidRune), 0, 4}, + {"\xc0☺\xc0\xc0", not(isValidRune), 0, 5}, + {"ab\xc0a\xc0cd", not(isValidRune), 2, 4}, + {"a\xe0\x80cd", not(isValidRune), 1, 2}, +} + +func TestIndexFunc(t *testing.T) { + for _, tc := range indexFuncTests { + first := IndexFunc([]byte(tc.in), tc.f.f) + if first != tc.first { + t.Errorf("IndexFunc(%q, %s) = %d; want %d", tc.in, tc.f.name, first, tc.first) + } + last := LastIndexFunc([]byte(tc.in), tc.f.f) + if last != tc.last { + t.Errorf("LastIndexFunc(%q, %s) = %d; want %d", tc.in, tc.f.name, last, tc.last) } } } @@ -657,25 +792,25 @@ type ReplaceTest struct { } var ReplaceTests = []ReplaceTest{ - ReplaceTest{"hello", "l", "L", 0, "hello"}, - ReplaceTest{"hello", "l", "L", -1, "heLLo"}, - ReplaceTest{"hello", "x", "X", -1, "hello"}, - ReplaceTest{"", "x", "X", -1, ""}, - ReplaceTest{"radar", "r", "<r>", -1, "<r>ada<r>"}, - ReplaceTest{"", "", "<>", -1, "<>"}, - ReplaceTest{"banana", "a", "<>", -1, "b<>n<>n<>"}, - ReplaceTest{"banana", "a", "<>", 1, "b<>nana"}, - ReplaceTest{"banana", "a", "<>", 1000, "b<>n<>n<>"}, - ReplaceTest{"banana", "an", "<>", -1, "b<><>a"}, - ReplaceTest{"banana", "ana", "<>", -1, "b<>na"}, - ReplaceTest{"banana", "", "<>", -1, "<>b<>a<>n<>a<>n<>a<>"}, - ReplaceTest{"banana", "", "<>", 10, "<>b<>a<>n<>a<>n<>a<>"}, - ReplaceTest{"banana", "", "<>", 6, "<>b<>a<>n<>a<>n<>a"}, - ReplaceTest{"banana", "", "<>", 5, "<>b<>a<>n<>a<>na"}, - ReplaceTest{"banana", "", "<>", 1, "<>banana"}, - ReplaceTest{"banana", "a", "a", -1, "banana"}, - ReplaceTest{"banana", "a", "a", 1, "banana"}, - ReplaceTest{"☺☻☹", "", "<>", -1, "<>☺<>☻<>☹<>"}, + {"hello", "l", "L", 0, "hello"}, + {"hello", "l", "L", -1, "heLLo"}, + {"hello", "x", "X", -1, "hello"}, + {"", "x", "X", -1, ""}, + {"radar", "r", "<r>", -1, "<r>ada<r>"}, + {"", "", "<>", -1, "<>"}, + {"banana", "a", "<>", -1, "b<>n<>n<>"}, + {"banana", "a", "<>", 1, "b<>nana"}, + {"banana", "a", "<>", 1000, "b<>n<>n<>"}, + {"banana", "an", "<>", -1, "b<><>a"}, + {"banana", "ana", "<>", -1, "b<>na"}, + {"banana", "", "<>", -1, "<>b<>a<>n<>a<>n<>a<>"}, + {"banana", "", "<>", 10, "<>b<>a<>n<>a<>n<>a<>"}, + {"banana", "", "<>", 6, "<>b<>a<>n<>a<>n<>a"}, + {"banana", "", "<>", 5, "<>b<>a<>n<>a<>na"}, + {"banana", "", "<>", 1, "<>banana"}, + {"banana", "a", "a", -1, "banana"}, + {"banana", "a", "a", 1, "banana"}, + {"☺☻☹", "", "<>", -1, "<>☺<>☻<>☹<>"}, } func TestReplace(t *testing.T) { @@ -685,3 +820,25 @@ func TestReplace(t *testing.T) { } } } + +type TitleTest struct { + in, out string +} + +var TitleTests = []TitleTest{ + {"", ""}, + {"a", "A"}, + {" aaa aaa aaa ", " Aaa Aaa Aaa "}, + {" Aaa Aaa Aaa ", " Aaa Aaa Aaa "}, + {"123a456", "123a456"}, + {"double-blind", "Double-Blind"}, + {"ÿøû", "Ÿøû"}, +} + +func TestTitle(t *testing.T) { + for _, tt := range TitleTests { + if s := string(Title([]byte(tt.in))); s != tt.out { + t.Errorf("Title(%q) = %q, want %q", tt.in, s, tt.out) + } + } +} |