From d69f87445cb28d68b4d62d8b80dff8a8d5a9203e Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Wed, 24 Apr 2024 09:52:52 -0700 Subject: [PATCH] strings, internal/stringslite: lite version of strings package To be used by internal/filepathlite, which is to be used by os. There are probably other places where it would be convenient to have strings functions accessible to RUNTIME level packages. Change-Id: Icda59e7a9e26d9e8f3692db0ea4fb7b3dbf570d7 Reviewed-on: https://go-review.googlesource.com/c/go/+/581516 Reviewed-by: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI --- src/go/build/deps_test.go | 1 + src/internal/stringslite/strings.go | 124 ++++++++++++++++++++++++++++ src/strings/strings.go | 100 ++-------------------- 3 files changed, 133 insertions(+), 92 deletions(-) create mode 100644 src/internal/stringslite/strings.go diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 0ea34b1bd7..a3ba8092be 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -66,6 +66,7 @@ var depsRules = ` internal/goexperiment, internal/goos < internal/bytealg + < internal/stringslite < internal/itoa < internal/unsafeheader < runtime/internal/sys diff --git a/src/internal/stringslite/strings.go b/src/internal/stringslite/strings.go new file mode 100644 index 0000000000..ce8a913297 --- /dev/null +++ b/src/internal/stringslite/strings.go @@ -0,0 +1,124 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package stringslite implements a subset of strings, +// only using packages that may be imported by "os". +// +// Tests for these functions are in the strings package. +package stringslite + +import "internal/bytealg" + +func HasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[0:len(prefix)] == prefix +} + +func HasSuffix(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +func IndexByte(s string, c byte) int { + return bytealg.IndexByteString(s, c) +} + +func Index(s, substr string) int { + n := len(substr) + switch { + case n == 0: + return 0 + case n == 1: + return IndexByte(s, substr[0]) + case n == len(s): + if substr == s { + return 0 + } + return -1 + case n > len(s): + return -1 + case n <= bytealg.MaxLen: + // Use brute force when s and substr both are small + if len(s) <= bytealg.MaxBruteForce { + return bytealg.IndexString(s, substr) + } + c0 := substr[0] + c1 := substr[1] + i := 0 + t := len(s) - n + 1 + fails := 0 + for i < t { + if s[i] != c0 { + // IndexByte is faster than bytealg.IndexString, so use it as long as + // we're not getting lots of false positives. + o := IndexByte(s[i+1:t], c0) + if o < 0 { + return -1 + } + i += o + 1 + } + if s[i+1] == c1 && s[i:i+n] == substr { + return i + } + fails++ + i++ + // Switch to bytealg.IndexString when IndexByte produces too many false positives. + if fails > bytealg.Cutover(i) { + r := bytealg.IndexString(s[i:], substr) + if r >= 0 { + return r + i + } + return -1 + } + } + return -1 + } + c0 := substr[0] + c1 := substr[1] + i := 0 + t := len(s) - n + 1 + fails := 0 + for i < t { + if s[i] != c0 { + o := IndexByte(s[i+1:t], c0) + if o < 0 { + return -1 + } + i += o + 1 + } + if s[i+1] == c1 && s[i:i+n] == substr { + return i + } + i++ + fails++ + if fails >= 4+i>>4 && i < t { + // See comment in ../bytes/bytes.go. + j := bytealg.IndexRabinKarp(s[i:], substr) + if j < 0 { + return -1 + } + return i + j + } + } + return -1 +} + +func Cut(s, sep string) (before, after string, found bool) { + if i := Index(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +} + +func CutPrefix(s, prefix string) (after string, found bool) { + if !HasPrefix(s, prefix) { + return s, false + } + return s[len(prefix):], true +} + +func CutSuffix(s, suffix string) (before string, found bool) { + if !HasSuffix(s, suffix) { + return s, false + } + return s[:len(s)-len(suffix)], true +} diff --git a/src/strings/strings.go b/src/strings/strings.go index f53ae1f9a7..11c558c4c3 100644 --- a/src/strings/strings.go +++ b/src/strings/strings.go @@ -9,6 +9,7 @@ package strings import ( "internal/bytealg" + "internal/stringslite" "unicode" "unicode/utf8" ) @@ -115,7 +116,7 @@ func LastIndex(s, substr string) int { // IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s. func IndexByte(s string, c byte) int { - return bytealg.IndexByteString(s, c) + return stringslite.IndexByte(s, c) } // IndexRune returns the index of the first instance of the Unicode code point @@ -460,12 +461,12 @@ func Join(elems []string, sep string) string { // HasPrefix reports whether the string s begins with prefix. func HasPrefix(s, prefix string) bool { - return len(s) >= len(prefix) && s[0:len(prefix)] == prefix + return stringslite.HasPrefix(s, prefix) } // HasSuffix reports whether the string s ends with suffix. func HasSuffix(s, suffix string) bool { - return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix + return stringslite.HasSuffix(s, suffix) } // Map returns a copy of the string s with all its characters modified @@ -1225,83 +1226,7 @@ hasUnicode: // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s. func Index(s, substr string) int { - n := len(substr) - switch { - case n == 0: - return 0 - case n == 1: - return IndexByte(s, substr[0]) - case n == len(s): - if substr == s { - return 0 - } - return -1 - case n > len(s): - return -1 - case n <= bytealg.MaxLen: - // Use brute force when s and substr both are small - if len(s) <= bytealg.MaxBruteForce { - return bytealg.IndexString(s, substr) - } - c0 := substr[0] - c1 := substr[1] - i := 0 - t := len(s) - n + 1 - fails := 0 - for i < t { - if s[i] != c0 { - // IndexByte is faster than bytealg.IndexString, so use it as long as - // we're not getting lots of false positives. - o := IndexByte(s[i+1:t], c0) - if o < 0 { - return -1 - } - i += o + 1 - } - if s[i+1] == c1 && s[i:i+n] == substr { - return i - } - fails++ - i++ - // Switch to bytealg.IndexString when IndexByte produces too many false positives. - if fails > bytealg.Cutover(i) { - r := bytealg.IndexString(s[i:], substr) - if r >= 0 { - return r + i - } - return -1 - } - } - return -1 - } - c0 := substr[0] - c1 := substr[1] - i := 0 - t := len(s) - n + 1 - fails := 0 - for i < t { - if s[i] != c0 { - o := IndexByte(s[i+1:t], c0) - if o < 0 { - return -1 - } - i += o + 1 - } - if s[i+1] == c1 && s[i:i+n] == substr { - return i - } - i++ - fails++ - if fails >= 4+i>>4 && i < t { - // See comment in ../bytes/bytes.go. - j := bytealg.IndexRabinKarp(s[i:], substr) - if j < 0 { - return -1 - } - return i + j - } - } - return -1 + return stringslite.Index(s, substr) } // Cut slices s around the first instance of sep, @@ -1309,10 +1234,7 @@ func Index(s, substr string) int { // The found result reports whether sep appears in s. // If sep does not appear in s, cut returns s, "", false. func Cut(s, sep string) (before, after string, found bool) { - if i := Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, "", false + return stringslite.Cut(s, sep) } // CutPrefix returns s without the provided leading prefix string @@ -1320,10 +1242,7 @@ func Cut(s, sep string) (before, after string, found bool) { // If s doesn't start with prefix, CutPrefix returns s, false. // If prefix is the empty string, CutPrefix returns s, true. func CutPrefix(s, prefix string) (after string, found bool) { - if !HasPrefix(s, prefix) { - return s, false - } - return s[len(prefix):], true + return stringslite.CutPrefix(s, prefix) } // CutSuffix returns s without the provided ending suffix string @@ -1331,8 +1250,5 @@ func CutPrefix(s, prefix string) (after string, found bool) { // If s doesn't end with suffix, CutSuffix returns s, false. // If suffix is the empty string, CutSuffix returns s, true. func CutSuffix(s, suffix string) (before string, found bool) { - if !HasSuffix(s, suffix) { - return s, false - } - return s[:len(s)-len(suffix)], true + return stringslite.CutSuffix(s, suffix) } -- 2.48.1