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 <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 0ea34b1..a3ba809 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -66,6 +66,7 @@
 	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 0000000..ce8a913
--- /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 f53ae1f..11c558c 100644
--- a/src/strings/strings.go
+++ b/src/strings/strings.go
@@ -9,6 +9,7 @@
 
 import (
 	"internal/bytealg"
+	"internal/stringslite"
 	"unicode"
 	"unicode/utf8"
 )
@@ -115,7 +116,7 @@
 
 // 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 @@
 
 // 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 @@
 
 // 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 @@
 // 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 @@
 // 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 @@
 // 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)
 }