From 8afe250b2042ae0661cb108a10d6d0f9debc9020 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= <bkamins@sgh.waw.pl>
Date: Tue, 31 Oct 2017 13:08:23 +0100
Subject: [PATCH] implements thisind function that returns the largest valid
 index less or equal than the given index

---
 NEWS.md                   |  3 +++
 base/exports.jl           |  1 +
 base/strings/basic.jl     | 34 +++++++++++++++++++++++++++++++++-
 base/strings/string.jl    | 13 ++++++++++++-
 base/strings/types.jl     | 14 ++++++++++++++
 doc/src/stdlib/strings.md |  1 +
 test/strings/basic.jl     | 26 ++++++++++++++++++++++++++
 7 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index cdf940755907f..d7e1ceeab61d7 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -261,6 +261,9 @@ This section lists changes that do not have deprecation warnings.
 Library improvements
 --------------------
 
+  * The function `thisind(s::AbstractString, i::Integer)` returns the largest valid index
+    less or equal than `i` in the string `s` or `0` if no such index exists ([#24414]).
+
   * `Irrational` is now a subtype of `AbstractIrrational` ([#24245]).
 
   * The function `chop` now accepts two arguments `head` and `tail` allowing to specify
diff --git a/base/exports.jl b/base/exports.jl
index 0e30396010261..c7fd3af088f92 100644
--- a/base/exports.jl
+++ b/base/exports.jl
@@ -788,6 +788,7 @@ export
     strip,
     summary,
     textwidth,
+    thisind,
     titlecase,
     transcode,
     ucfirst,
diff --git a/base/strings/basic.jl b/base/strings/basic.jl
index c04e56eb9a6ac..cc09f8dce7c2c 100644
--- a/base/strings/basic.jl
+++ b/base/strings/basic.jl
@@ -232,6 +232,39 @@ end
 
 ## Generic indexing functions ##
 
+"""
+    thisind(str::AbstractString, i::Integer)
+
+Get the largest valid string index at or before `i`.
+Returns `0` if there is no valid string index at or before `i`.
+Returns `endof(str)` if `i≥endof(str)`.
+
+# Examples
+```jldoctest
+julia> thisind("αβγdef", -5)
+0
+
+julia> thisind("αβγdef", 1)
+1
+
+julia> thisind("αβγdef", 3)
+3
+
+julia> thisind("αβγdef", 4)
+3
+
+julia> thisind("αβγdef", 20)
+9
+"""
+function thisind(s::AbstractString, i::Integer)
+    j = Int(i)
+    isvalid(s, j) && return j
+    j < start(s) && return 0
+    e = endof(s)
+    j >= endof(s) && return e
+    prevind(s, j)
+end
+
 """
     prevind(str::AbstractString, i::Integer, nchar::Integer=1)
 
@@ -249,7 +282,6 @@ julia> prevind("αβγdef", 1)
 
 julia> prevind("αβγdef", 3, 2)
 0
-
 ```
 """
 function prevind(s::AbstractString, i::Integer)
diff --git a/base/strings/string.jl b/base/strings/string.jl
index 45d1a05b352ca..c5c998f7f4419 100644
--- a/base/strings/string.jl
+++ b/base/strings/string.jl
@@ -104,7 +104,18 @@ function ==(a::String, b::String)
     al == sizeof(b) && 0 == ccall(:memcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}, UInt), a, b, al)
 end
 
-## prevind and nextind ##
+## thisind, prevind and nextind ##
+
+function thisind(s::String, i::Integer)
+    j = Int(i)
+    j < 1 && return 0
+    e = endof(s)
+    j >= e && return e
+    @inbounds while j > 0 && is_valid_continuation(codeunit(s,j))
+        j -= 1
+    end
+    j
+end
 
 function prevind(s::String, i::Integer)
     j = Int(i)
diff --git a/base/strings/types.jl b/base/strings/types.jl
index 382b33d5d1698..e3eb6d98312b8 100644
--- a/base/strings/types.jl
+++ b/base/strings/types.jl
@@ -86,6 +86,20 @@ function isvalid(s::SubString, i::Integer)
     return (start(s) <= i <= endof(s)) && isvalid(s.string, s.offset+i)
 end
 
+function thisind(s::SubString{String}, i::Integer)
+    j = Int(i)
+    j < 1 && return 0
+    e = endof(s)
+    j >= e && return e
+    offset = s.offset
+    str = s.string
+    j += offset
+    @inbounds while j > offset && is_valid_continuation(codeunit(str, j))
+        j -= 1
+    end
+    j-offset
+end
+
 nextind(s::SubString, i::Integer) = nextind(s.string, i+s.offset)-s.offset
 prevind(s::SubString, i::Integer) = prevind(s.string, i+s.offset)-s.offset
 
diff --git a/doc/src/stdlib/strings.md b/doc/src/stdlib/strings.md
index dca748eb40a2f..36ed901262f92 100644
--- a/doc/src/stdlib/strings.md
+++ b/doc/src/stdlib/strings.md
@@ -60,6 +60,7 @@ Base.chop
 Base.chomp
 Base.ind2chr
 Base.chr2ind
+Base.thisind
 Base.nextind
 Base.prevind
 Base.Random.randstring
diff --git a/test/strings/basic.jl b/test/strings/basic.jl
index 0c7cdb54824ae..a99d635bdcaae 100644
--- a/test/strings/basic.jl
+++ b/test/strings/basic.jl
@@ -565,6 +565,32 @@ end
     @test_throws ParseError parse("\'\\.\'")
 end
 
+@testset "thisind" begin
+    let strs = Any["∀α>β:α+1>β", s"∀α>β:α+1>β",
+                   SubString("123∀α>β:α+1>β123", 4, 18),
+                   SubString(s"123∀α>β:α+1>β123", 4, 18)]
+        for s in strs
+            @test thisind(s, -2) == 0
+            @test thisind(s, 0) == 0
+            @test thisind(s, 1) == 1
+            @test thisind(s, 2) == 1
+            @test thisind(s, 3) == 1
+            @test thisind(s, 4) == 4
+            @test thisind(s, 5) == 4
+            @test thisind(s, 6) == 6
+            @test thisind(s, 15) == 15
+            @test thisind(s, 16) == 15
+            @test thisind(s, 30) == 15
+        end
+    end
+
+    let strs = Any["", s"", SubString("123", 2, 1), SubString(s"123", 2, 1)]
+        for s in strs, i in -2:2
+            @test thisind(s, i) == 0
+        end
+    end
+end
+
 @testset "prevind and nextind" begin
     let strs = Any["∀α>β:α+1>β", GenericString("∀α>β:α+1>β")]
         for i in 1:2