Skip to content

Commit

Permalink
improved prevind and nextind
Browse files Browse the repository at this point in the history
  • Loading branch information
bkamins committed Sep 23, 2017
1 parent 19921aa commit ea7ecb2
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 2 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ This section lists changes that do not have deprecation warnings.
Library improvements
--------------------

* The functions `nextind` and `prevind` now accept `nchar` argument that indicates
number of characters to move ([#23805]).

* The functions `strip`, `lstrip` and `rstrip` now return `SubString` ([#22496]).

* The functions `strwidth` and `charwidth` have been merged into `textwidth`([#20816]).
Expand Down
63 changes: 61 additions & 2 deletions base/strings/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,23 @@ end
prevind(s::DirectIndexString, i::Integer) = Int(i)-1
nextind(s::DirectIndexString, i::Integer) = Int(i)+1

function prevind(s::DirectIndexString, i::Integer, nchar::Integer)
nchar > 0 || throw(ArgumentError("nchar must be greater than 0"))
Int(i)-nchar
end

function nextind(s::DirectIndexString, i::Integer, nchar::Integer)
nchar > 0 || throw(ArgumentError("nchar must be greater than 0"))
Int(i)+nchar
end


"""
prevind(str::AbstractString, i::Integer)
prevind(str::AbstractString, i::Integer, nchar::Integer=1)
Get the previous valid string index before `i`.
Returns a value less than `1` at the beginning of the string.
If the `nchar` argument is given the function goes back `nchar` characters.
# Examples
```jldoctest
Expand All @@ -252,6 +264,10 @@ julia> prevind("αβγdef", 3)
julia> prevind("αβγdef", 1)
0
julia> prevind("αβγdef", 3, 2)
0
```
"""
function prevind(s::AbstractString, i::Integer)
Expand All @@ -269,11 +285,32 @@ function prevind(s::AbstractString, i::Integer)
return 0 # out of range
end

function prevind(s::AbstractString, i::Integer, nchar::Integer)
nchar > 0 || throw(ArgumentError("nchar must be greater than 0"))
e = endof(s)
j = Int(i)
j < 1 && return 0
while nchar > 0
if j > e
j = e
else
j -= 1
while j >= 1 && !isvalid(s,j)
j -= 1
end
end
j < 1 && return 0
nchar -= 1
end
j
end

"""
nextind(str::AbstractString, i::Integer)
nextind(str::AbstractString, i::Integer, nchar::Integer=1)
Get the next valid string index after `i`.
Returns a value greater than `endof(str)` at or after the end of the string.
If the `nchar` argument is given the function goes forward `nchar` characters.
# Examples
```jldoctest
Expand All @@ -282,6 +319,9 @@ julia> str = "αβγdef";
julia> nextind(str, 1)
3
julia> nextind(str, 1, 2)
5
julia> endof(str)
9
Expand All @@ -305,6 +345,25 @@ function nextind(s::AbstractString, i::Integer)
next(s,e)[2] # out of range
end

function nextind(s::AbstractString, i::Integer, nchar::Integer)
nchar > 0 || throw(ArgumentError("nchar must be greater than 0"))
e = endof(s)
j = Int(i)
while nchar > 0
if j < 1
j = 1
else
j > e && return j + nchar
j == e && return next(s,e)[2] + nchar - 1
for j = j+1:e
isvalid(s,j) && break
end
end
nchar -= 1
end
j
end

checkbounds(s::AbstractString, i::Integer) = start(s) <= i <= endof(s) || throw(BoundsError(s, i))
checkbounds(s::AbstractString, r::AbstractRange{<:Integer}) = isempty(r) || (minimum(r) >= start(s) && maximum(r) <= endof(s)) || throw(BoundsError(s, r))
# The following will end up using a deprecated checkbounds, when the covariant parameter is not Integer
Expand Down
38 changes: 38 additions & 0 deletions base/strings/string.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,25 @@ function prevind(s::String, i::Integer)
j
end

function prevind(s::String, i::Integer, nchar::Integer)
nchar > 0 || throw(ArgumentError("nchar must be greater than 0"))
j = Int(i)
e = sizeof(s)
while nchar > 0
if j > e
j = endof(s)
else
j -= 1
@inbounds while j > 0 && is_valid_continuation(codeunit(s,j))
j -= 1
end
end
nchar -= 1
j <= 0 && return j - nchar
end
j
end

function nextind(s::String, i::Integer)
j = Int(i)
if j < 1
Expand All @@ -120,6 +139,25 @@ function nextind(s::String, i::Integer)
j
end

function nextind(s::String, i::Integer, nchar::Integer)
nchar > 0 || throw(ArgumentError("nchar must be greater than 0"))
j = Int(i)
e = sizeof(s)
while nchar > 0
if j < 1
j = 1
else
j += 1
@inbounds while j <= e && is_valid_continuation(codeunit(s,j))
j += 1
end
end
nchar -= 1
j > e && return j + nchar
end
j
end

## checking UTF-8 & ACSII validity ##

byte_string_classify(data::Vector{UInt8}) =
Expand Down
120 changes: 120 additions & 0 deletions test/strings/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,127 @@ end
@test "a" * 'b' * 'c' == "abc"
end

<<<<<<< 19921aa00468edf11f3d61f8c9b8d639ec71cd23
@testset "unrecognized escapes in string/char literals" begin
@test_throws ParseError parse("\"\\.\"")
@test_throws ParseError parse("\'\\.\'")
=======
# Simple case, with just ANSI Latin 1 characters
@test "áB" != CharStr("áá") # returns false with bug
@test cmp("áB", CharStr("áá")) == -1 # returns 0 with bug

# Case with Unicode characters
@test cmp("\U1f596\U1f596", CharStr("\U1f596")) == 1 # Gives BoundsError with bug
@test cmp(CharStr("\U1f596"), "\U1f596\U1f596") == -1

# repeat function
@inferred repeat(GenericString("x"), 1)
@test repeat("xx",3) == repeat("x",6) == repeat('x',6) == repeat(GenericString("x"), 6) == "xxxxxx"
@test repeat("αα",3) == repeat("α",6) == repeat('α',6) == repeat(GenericString("α"), 6) == "αααααα"
@test repeat("x",1) == repeat('x',1) == "x"^1 == 'x'^1 == GenericString("x")^1 == "x"
@test repeat("x",0) == repeat('x',0) == "x"^0 == 'x'^0 == GenericString("x")^0 == ""

for S in ["xxx", "ååå", "∀∀∀", "🍕🍕🍕"]
c = S[1]
s = string(c)
@test_throws ArgumentError repeat(c, -1)
@test_throws ArgumentError repeat(s, -1)
@test_throws ArgumentError repeat(S, -1)
@test repeat(c, 0) == ""
@test repeat(s, 0) == ""
@test repeat(S, 0) == ""
@test repeat(c, 1) == s
@test repeat(s, 1) == s
@test repeat(S, 1) == S
@test repeat(c, 3) == S
@test repeat(s, 3) == S
@test repeat(S, 3) == S*S*S
end

# issue #12495: check that logical indexing attempt raises ArgumentError
@test_throws ArgumentError "abc"[[true, false, true]]
@test_throws ArgumentError "abc"[BitArray([true, false, true])]

@test "ab" * "cd" == "abcd"
@test 'a' * "bc" == "abc"
@test "ab" * 'c' == "abc"
@test 'a' * 'b' == "ab"
@test 'a' * "b" * 'c' == "abc"
@test "a" * 'b' * 'c' == "abc"

# unrecognized escapes in string/char literals
@test_throws ParseError parse("\"\\.\"")
@test_throws ParseError parse("\'\\.\'")

# prevind and nextind tests

let
strs = Any["∀α>β:α+1>β", GenericString("∀α>β:α+1>β")]
for i in 1:2
@test prevind(strs[i], 1) == 0
@test prevind(strs[i], 1, 1) == 0
@test prevind(strs[i], 2) == 1
@test prevind(strs[i], 2, 1) == 1
@test prevind(strs[i], 4) == 1
@test prevind(strs[i], 4, 1) == 1
@test prevind(strs[i], 5) == 4
@test prevind(strs[i], 5, 1) == 4
@test prevind(strs[i], 5, 2) == 1
@test prevind(strs[i], 5, 3) == 0
@test prevind(strs[i], 15) == 14
@test prevind(strs[i], 15, 1) == 14
@test prevind(strs[i], 15, 2) == 13
@test prevind(strs[i], 15, 3) == 12
@test prevind(strs[i], 15, 4) == 10
@test prevind(strs[i], 15, 10) == 0
@test prevind(strs[i], 15, 9) == 1
@test prevind(strs[i], 15, 10) == 0
@test prevind(strs[i], 16) == 15
@test prevind(strs[i], 16, 1) == 15
@test prevind(strs[i], 16, 2) == 14
@test prevind(strs[i], 20) == 15
@test prevind(strs[i], 20, 1) == 15
@test prevind(strs[i], 20, 10) == 1
@test_throws ArgumentError prevind(strs[i], 20, 0)

@test nextind(strs[i], -1) == 1
@test nextind(strs[i], -1, 1) == 1
@test nextind(strs[i], 0, 2) == 4
@test nextind(strs[i], 0, 20) == 26
@test nextind(strs[i], 0, 10) == 15
@test nextind(strs[i], 1) == 4
@test nextind(strs[i], 1, 1) == 4
@test nextind(strs[i], 1, 2) == 6
@test nextind(strs[i], 1, 9) == 15
@test nextind(strs[i], 1, 10) == 17
@test nextind(strs[i], 2) == 4
@test nextind(strs[i], 2, 1) == 4
@test nextind(strs[i], 3) == 4
@test nextind(strs[i], 3, 1) == 4
@test nextind(strs[i], 4) == 6
@test nextind(strs[i], 4, 1) == 6
@test nextind(strs[i], 14) == 15
@test nextind(strs[i], 14, 1) == 15
@test nextind(strs[i], 15) == 17
@test nextind(strs[i], 15, 1) == 17
@test nextind(strs[i], 20) == 21
@test nextind(strs[i], 20, 1) == 21
@test_throws ArgumentError nextind(strs[i], 20, 0)

for x in -10:20
n = p = x
for i in 1:40
p = prevind(strs[i], p)
@test prevind(strs[i], x, i) == p
n = nextind(strs[i], n)
@test nextind(strs[i], x, i) == n
end
end
end
@test prevind(strs[1], -1) == -2
@test prevind(strs[1], -1, 1) == -2

@test prevind(strs[2], -1) == 0
@test prevind(strs[2], -1, 1) == 0
>>>>>>> improved prevind and nextind
end

0 comments on commit ea7ecb2

Please sign in to comment.