diff --git a/util/strings.go b/util/strings.go index d7e54e85..ebb19f2b 100644 --- a/util/strings.go +++ b/util/strings.go @@ -1,6 +1,7 @@ package util import ( + "errors" "strings" ) @@ -36,3 +37,35 @@ func FindFirstEOL(s string) int { func FindLastSOL(s string) int { return strings.LastIndex(s, "\n") + 1 } + +var ( + IndexOutOfRange = errors.New("substring out of range") +) + +// SubStringByRune returns the range of the i-th rune (inclusive) through the +// j-th rune (exclusive) in s. +func SubstringByRune(s string, low, high int) (string, error) { + if low > high || low < 0 || high < 0 { + return "", IndexOutOfRange + } + var bLow, bHigh, j int + for i := range s { + if j == low { + bLow = i + } + if j == high { + bHigh = i + } + j++ + } + if j < high { + return "", IndexOutOfRange + } + if low == high { + return "", nil + } + if j == high { + bHigh = len(s) + } + return s[bLow:bHigh], nil +} diff --git a/util/strings_test.go b/util/strings_test.go index 58c3de73..6c5cab4b 100644 --- a/util/strings_test.go +++ b/util/strings_test.go @@ -20,3 +20,29 @@ func TestFindContext(t *testing.T) { } } } + +var SubstringByRuneTests = []struct { + s string + low, high int + wantedStr string + wantedErr error +}{ + {"Hello world", 1, 4, "ell", nil}, + {"你好世界", 0, 0, "", nil}, + {"你好世界", 1, 1, "", nil}, + {"你好世界", 1, 2, "好", nil}, + {"你好世界", 1, 4, "好世界", nil}, + {"你好世界", -1, -1, "", IndexOutOfRange}, + {"你好世界", 0, 5, "", IndexOutOfRange}, + {"你好世界", 5, 5, "", IndexOutOfRange}, +} + +func TestSubstringByRune(t *testing.T) { + for _, tt := range SubstringByRuneTests { + s, e := SubstringByRune(tt.s, tt.low, tt.high) + if s != tt.wantedStr || e != tt.wantedErr { + t.Errorf("SubstringByRune(%q, %v, %d) => (%q, %v), want (%q, %v)", + tt.s, tt.low, tt.high, s, e, tt.wantedStr, tt.wantedErr) + } + } +}