• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38002

06 Feb 2025 06:14AM UTC coverage: 20.322% (-2.4%) from 22.722%
#38002

push

local

web-flow
bpart: Fully switch to partitioned semantics (#57253)

This is the final PR in the binding partitions series (modulo bugs and
tweaks), i.e. it closes #54654 and thus closes #40399, which was the
original design sketch.

This thus activates the full designed semantics for binding partitions,
in particular allowing safe replacement of const bindings. It in
particular allows struct redefinitions. This thus closes
timholy/Revise.jl#18 and also closes #38584.

The biggest semantic change here is probably that this gets rid of the
notion of "resolvedness" of a binding. Previously, a lot of the behavior
of our implementation depended on when bindings were "resolved", which
could happen at basically an arbitrary point (in the compiler, in REPL
completion, in a different thread), making a lot of the semantics around
bindings ill- or at least implementation-defined. There are several
related issues in the bugtracker, so this closes #14055 closes #44604
closes #46354 closes #30277

It is also the last step to close #24569.
It also supports bindings for undef->defined transitions and thus closes
#53958 closes #54733 - however, this is not activated yet for
performance reasons and may need some further optimization.

Since resolvedness no longer exists, we need to replace it with some
hopefully more well-defined semantics. I will describe the semantics
below, but before I do I will make two notes:

1. There are a number of cases where these semantics will behave
slightly differently than the old semantics absent some other task going
around resolving random bindings.
2. The new behavior (except for the replacement stuff) was generally
permissible under the old semantics if the bindings happened to be
resolved at the right time.

With all that said, there are essentially three "strengths" of bindings:

1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no
binding", plus slightly more exotic corner cases around conflicts

2. Weakly declared bindin... (continued)

11 of 111 new or added lines in 7 files covered. (9.91%)

1273 existing lines in 68 files now uncovered.

9908 of 48755 relevant lines covered (20.32%)

105126.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

26.26
/base/strings/util.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
"""
4
    Base.Chars = Union{AbstractChar,Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},AbstractSet{<:AbstractChar}}
5

6
An alias type for a either single character or a tuple/vector/set of characters, used to describe arguments
7
of several string-matching functions such as [`startswith`](@ref) and [`strip`](@ref).
8

9
!!! compat "Julia 1.11"
10
    Julia versions prior to 1.11 only included `Set`, not `AbstractSet`, in `Base.Chars` types.
11
"""
12
const Chars = Union{AbstractChar,Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},AbstractSet{<:AbstractChar}}
13

14
# starts with and ends with predicates
15

16
"""
17
    startswith(s::AbstractString, prefix::Union{AbstractString,Base.Chars})
18

19
Return `true` if `s` starts with `prefix`, which can be a string, a character,
20
or a tuple/vector/set of characters. If `prefix` is a tuple/vector/set
21
of characters, test whether the first character of `s` belongs to that set.
22

23
See also [`endswith`](@ref), [`contains`](@ref).
24

25
# Examples
26
```jldoctest
27
julia> startswith("JuliaLang", "Julia")
28
true
29
```
30
"""
31
function startswith(a::AbstractString, b::AbstractString)
×
32
    i, j = iterate(a), iterate(b)
×
33
    while true
×
34
        j === nothing && return true # ran out of prefix: success!
×
35
        i === nothing && return false # ran out of source: failure
×
36
        i[1] == j[1] || return false # mismatch: failure
×
37
        i, j = iterate(a, i[2]), iterate(b, j[2])
×
38
    end
×
39
end
40
startswith(str::AbstractString, chars::Chars) = !isempty(str) && first(str)::AbstractChar in chars
26,776✔
41

42
"""
43
    endswith(s::AbstractString, suffix::Union{AbstractString,Base.Chars})
44

45
Return `true` if `s` ends with `suffix`, which can be a string, a character,
46
or a tuple/vector/set of characters. If `suffix` is a tuple/vector/set
47
of characters, test whether the last character of `s` belongs to that set.
48

49
See also [`startswith`](@ref), [`contains`](@ref).
50

51
# Examples
52
```jldoctest
53
julia> endswith("Sunday", "day")
54
true
55
```
56
"""
57
function endswith(a::AbstractString, b::AbstractString)
×
58
    a, b = Iterators.Reverse(a), Iterators.Reverse(b)
×
59
    i, j = iterate(a), iterate(b)
×
60
    while true
×
61
        j === nothing && return true # ran out of suffix: success!
×
62
        i === nothing && return false # ran out of source: failure
×
63
        i[1] == j[1] || return false # mismatch: failure
×
64
        i, j = iterate(a, i[2]), iterate(b, j[2])
×
65
    end
×
66
end
67
endswith(str::AbstractString, chars::Chars) = !isempty(str) && last(str) in chars
61✔
68

69
function startswith(a::Union{String, SubString{String}},
14✔
70
                    b::Union{String, SubString{String}})
71
    cub = ncodeunits(b)
14✔
72
    if ncodeunits(a) < cub
14✔
73
        false
74
    elseif _memcmp(a, b, sizeof(b)) == 0
9✔
UNCOV
75
        nextind(a, cub) == cub + 1 # check that end of `b` doesn't match a partial character in `a`
×
76
    else
77
        false
78
    end
79
end
80

81
"""
82
    startswith(io::IO, prefix::Union{AbstractString,Base.Chars})
83

84
Check if an `IO` object starts with a prefix, which can be either a string, a
85
character, or a tuple/vector/set of characters.  See also [`peek`](@ref).
86
"""
87
function Base.startswith(io::IO, prefix::Base.Chars)
×
88
    mark(io)
×
89
    c = read(io, Char)
×
90
    reset(io)
×
91
    return c in prefix
×
92
end
93
function Base.startswith(io::IO, prefix::Union{String,SubString{String}})
×
94
    mark(io)
×
95
    s = read(io, ncodeunits(prefix))
×
96
    reset(io)
×
97
    return s == codeunits(prefix)
×
98
end
99
Base.startswith(io::IO, prefix::AbstractString) = startswith(io, String(prefix))
×
100

UNCOV
101
function endswith(a::Union{String, SubString{String}},
×
102
                  b::Union{String, SubString{String}})
UNCOV
103
    astart = ncodeunits(a) - ncodeunits(b) + 1
×
UNCOV
104
    if astart < 1
×
105
        false
UNCOV
106
    elseif GC.@preserve(a, _memcmp(pointer(a, astart), b, sizeof(b))) == 0
×
UNCOV
107
        thisind(a, astart) == astart # check that end of `b` doesn't match a partial character in `a`
×
108
    else
109
        false
110
    end
111
end
112

113
"""
114
    contains(haystack::AbstractString, needle)
115

116
Return `true` if `haystack` contains `needle`.
117
This is the same as `occursin(needle, haystack)`, but is provided for consistency with
118
`startswith(haystack, needle)` and `endswith(haystack, needle)`.
119

120
See also [`occursin`](@ref), [`in`](@ref), [`issubset`](@ref).
121

122
# Examples
123
```jldoctest
124
julia> contains("JuliaLang is pretty cool!", "Julia")
125
true
126

127
julia> contains("JuliaLang is pretty cool!", 'a')
128
true
129

130
julia> contains("aba", r"a.a")
131
true
132

133
julia> contains("abba", r"a.a")
134
false
135
```
136

137
!!! compat "Julia 1.5"
138
    The `contains` function requires at least Julia 1.5.
139
"""
140
contains(haystack::AbstractString, needle) = occursin(needle, haystack)
2,821✔
141

142
"""
143
    endswith(suffix)
144

145
Create a function that checks whether its argument ends with `suffix`, i.e.
146
a function equivalent to `y -> endswith(y, suffix)`.
147

148
The returned function is of type `Base.Fix2{typeof(endswith)}`, which can be
149
used to implement specialized methods.
150

151
!!! compat "Julia 1.5"
152
    The single argument `endswith(suffix)` requires at least Julia 1.5.
153

154
# Examples
155
```jldoctest
156
julia> endswith("Julia")("Ends with Julia")
157
true
158

159
julia> endswith("Julia")("JuliaLang")
160
false
161
```
162
"""
163
endswith(s) = Base.Fix2(endswith, s)
9✔
164

165
"""
166
    startswith(prefix)
167

168
Create a function that checks whether its argument starts with `prefix`, i.e.
169
a function equivalent to `y -> startswith(y, prefix)`.
170

171
The returned function is of type `Base.Fix2{typeof(startswith)}`, which can be
172
used to implement specialized methods.
173

174
!!! compat "Julia 1.5"
175
    The single argument `startswith(prefix)` requires at least Julia 1.5.
176

177
# Examples
178
```jldoctest
179
julia> startswith("Julia")("JuliaLang")
180
true
181

182
julia> startswith("Julia")("Ends with Julia")
183
false
184
```
185
"""
186
startswith(s) = Base.Fix2(startswith, s)
×
187

188
"""
189
    contains(needle)
190

191
Create a function that checks whether its argument contains `needle`, i.e.
192
a function equivalent to `haystack -> contains(haystack, needle)`.
193

194
The returned function is of type `Base.Fix2{typeof(contains)}`, which can be
195
used to implement specialized methods.
196
"""
197
contains(needle) = Base.Fix2(contains, needle)
2✔
198

199
"""
200
    chop(s::AbstractString; head::Integer = 0, tail::Integer = 1)
201

202
Remove the first `head` and the last `tail` characters from `s`.
203
The call `chop(s)` removes the last character from `s`.
204
If it is requested to remove more characters than `length(s)`
205
then an empty string is returned.
206

207
See also [`chomp`](@ref), [`startswith`](@ref), [`first`](@ref).
208

209
# Examples
210
```jldoctest
211
julia> a = "March"
212
"March"
213

214
julia> chop(a)
215
"Marc"
216

217
julia> chop(a, head = 1, tail = 2)
218
"ar"
219

220
julia> chop(a, head = 5, tail = 5)
221
""
222
```
223
"""
224
function chop(s::AbstractString; head::Integer = 0, tail::Integer = 1)
×
225
    if isempty(s)
×
226
        return SubString(s)
×
227
    end
228
    SubString(s, nextind(s, firstindex(s), head), prevind(s, lastindex(s), tail))
×
229
end
230

231
# TODO: optimization for the default case based on
232
# chop(s::AbstractString) = SubString(s, firstindex(s), prevind(s, lastindex(s)))
233

234
"""
235
    chopprefix(s::AbstractString, prefix::Union{AbstractString,Regex}) -> SubString
236

237
Remove the prefix `prefix` from `s`. If `s` does not start with `prefix`, a string equal to `s` is returned.
238

239
See also [`chopsuffix`](@ref).
240

241
!!! compat "Julia 1.8"
242
    This function is available as of Julia 1.8.
243

244
# Examples
245
```jldoctest
246
julia> chopprefix("Hamburger", "Ham")
247
"burger"
248

249
julia> chopprefix("Hamburger", "hotdog")
250
"Hamburger"
251
```
252
"""
253
function chopprefix(s::AbstractString, prefix::AbstractString)
×
254
    k = firstindex(s)
×
255
    i, j = iterate(s), iterate(prefix)
×
256
    while true
×
257
        j === nothing && i === nothing && return SubString(s, 1, 0) # s == prefix: empty result
×
258
        j === nothing && return @inbounds SubString(s, k) # ran out of prefix: success!
×
259
        i === nothing && return SubString(s) # ran out of source: failure
×
260
        i[1] == j[1] || return SubString(s) # mismatch: failure
×
261
        k = i[2]
×
262
        i, j = iterate(s, k), iterate(prefix, j[2])
×
263
    end
×
264
end
265

266
function chopprefix(s::Union{String, SubString{String}},
×
267
                    prefix::Union{String, SubString{String}})
268
    if startswith(s, prefix)
×
269
        SubString(s, 1 + ncodeunits(prefix))
×
270
    else
271
        SubString(s)
×
272
    end
273
end
274

275
"""
276
    chopsuffix(s::AbstractString, suffix::Union{AbstractString,Regex}) -> SubString
277

278
Remove the suffix `suffix` from `s`. If `s` does not end with `suffix`, a string equal to `s` is returned.
279

280
See also [`chopprefix`](@ref).
281

282
!!! compat "Julia 1.8"
283
    This function is available as of Julia 1.8.
284

285
# Examples
286
```jldoctest
287
julia> chopsuffix("Hamburger", "er")
288
"Hamburg"
289

290
julia> chopsuffix("Hamburger", "hotdog")
291
"Hamburger"
292
```
293
"""
294
function chopsuffix(s::AbstractString, suffix::AbstractString)
×
295
    a, b = Iterators.Reverse(s), Iterators.Reverse(suffix)
×
296
    k = lastindex(s)
×
297
    i, j = iterate(a), iterate(b)
×
298
    while true
×
299
        j === nothing && i === nothing && return SubString(s, 1, 0) # s == suffix: empty result
×
300
        j === nothing && return @inbounds SubString(s, firstindex(s), k) # ran out of suffix: success!
×
301
        i === nothing && return SubString(s) # ran out of source: failure
×
302
        i[1] == j[1] || return SubString(s) # mismatch: failure
×
303
        k = i[2]
×
304
        i, j = iterate(a, k), iterate(b, j[2])
×
305
    end
×
306
end
307

UNCOV
308
function chopsuffix(s::Union{String, SubString{String}},
×
309
                    suffix::Union{String, SubString{String}})
UNCOV
310
    if !isempty(suffix) && endswith(s, suffix)
×
UNCOV
311
        astart = ncodeunits(s) - ncodeunits(suffix) + 1
×
UNCOV
312
        @inbounds SubString(s, firstindex(s), prevind(s, astart))
×
313
    else
314
        SubString(s)
×
315
    end
316
end
317

318

319
"""
320
    chomp(s::AbstractString) -> SubString
321

322
Remove a single trailing newline from a string.
323

324
See also [`chop`](@ref).
325

326
# Examples
327
```jldoctest
328
julia> chomp("Hello\\n")
329
"Hello"
330
```
331
"""
332
function chomp(s::AbstractString)
62✔
333
    i = lastindex(s)
124✔
334
    (i < 1 || s[i] != '\n') && (return SubString(s, 1, i))
124✔
335
    j = prevind(s,i)
1✔
336
    (j < 1 || s[j] != '\r') && (return SubString(s, 1, j))
2✔
337
    return SubString(s, 1, prevind(s,j))
×
338
end
339
function chomp(s::String)
×
340
    i = lastindex(s)
×
341
    if i < 1 || codeunit(s,i) != 0x0a
×
342
        return @inbounds SubString(s, 1, i)
×
343
    elseif i < 2 || codeunit(s,i-1) != 0x0d
×
344
        return @inbounds SubString(s, 1, prevind(s, i))
×
345
    else
346
        return @inbounds SubString(s, 1, prevind(s, i-1))
×
347
    end
348
end
349

350
"""
351
    lstrip([pred=isspace,] str::AbstractString) -> SubString
352
    lstrip(str::AbstractString, chars) -> SubString
353

354
Remove leading characters from `str`, either those specified by `chars` or those for
355
which the function `pred` returns `true`.
356

357
The default behaviour is to remove leading whitespace and delimiters: see
358
[`isspace`](@ref) for precise details.
359

360
The optional `chars` argument specifies which characters to remove: it can be a single
361
character, or a vector or set of characters.
362

363
See also [`strip`](@ref) and [`rstrip`](@ref).
364

365
# Examples
366
```jldoctest
367
julia> a = lpad("March", 20)
368
"               March"
369

370
julia> lstrip(a)
371
"March"
372
```
373
"""
374
function lstrip(f, s::AbstractString)
×
375
    e = lastindex(s)
×
376
    for (i::Int, c::AbstractChar) in pairs(s)
×
377
        !f(c) && return @inbounds SubString(s, i, e)
×
378
    end
×
379
    SubString(s, e+1, e)
×
380
end
381
lstrip(s::AbstractString) = lstrip(isspace, s)
2,201✔
382
lstrip(s::AbstractString, chars::Chars) = lstrip(in(chars), s)
×
383
lstrip(::AbstractString, ::AbstractString) = throw(ArgumentError("Both arguments are strings. The second argument should be a `Char` or collection of `Char`s"))
×
384

385
"""
386
    rstrip([pred=isspace,] str::AbstractString) -> SubString
387
    rstrip(str::AbstractString, chars) -> SubString
388

389
Remove trailing characters from `str`, either those specified by `chars` or those for
390
which the function `pred` returns `true`.
391

392
The default behaviour is to remove trailing whitespace and delimiters: see
393
[`isspace`](@ref) for precise details.
394

395
The optional `chars` argument specifies which characters to remove: it can be a single
396
character, or a vector or set of characters.
397

398
See also [`strip`](@ref) and [`lstrip`](@ref).
399

400
# Examples
401
```jldoctest
402
julia> a = rpad("March", 20)
403
"March               "
404

405
julia> rstrip(a)
406
"March"
407
```
408
"""
409
function rstrip(f, s::AbstractString)
2,019✔
410
    for (i, c) in Iterators.reverse(pairs(s))
2,019✔
411
        f(c::AbstractChar) || return @inbounds SubString(s, 1, i::Int)
4,038✔
412
    end
×
413
    SubString(s, 1, 0)
×
414
end
415
rstrip(s::AbstractString) = rstrip(isspace, s)
2,201✔
416
rstrip(s::AbstractString, chars::Chars) = rstrip(in(chars), s)
144✔
417
rstrip(::AbstractString, ::AbstractString) = throw(ArgumentError("Both arguments are strings. The second argument should be a `Char` or collection of `Char`s"))
×
418

419

420
"""
421
    strip([pred=isspace,] str::AbstractString) -> SubString
422
    strip(str::AbstractString, chars) -> SubString
423

424
Remove leading and trailing characters from `str`, either those specified by `chars` or
425
those for which the function `pred` returns `true`.
426

427
The default behaviour is to remove leading and trailing whitespace and delimiters: see
428
[`isspace`](@ref) for precise details.
429

430
The optional `chars` argument specifies which characters to remove: it can be a single
431
character, vector or set of characters.
432

433
See also [`lstrip`](@ref) and [`rstrip`](@ref).
434

435
!!! compat "Julia 1.2"
436
    The method which accepts a predicate function requires Julia 1.2 or later.
437

438
# Examples
439
```jldoctest
440
julia> strip("{3, 5}\\n", ['{', '}', '\\n'])
441
"3, 5"
442
```
443
"""
444
strip(s::AbstractString) = lstrip(rstrip(s))
2,201✔
445
strip(s::AbstractString, chars::Chars) = lstrip(rstrip(s, chars), chars)
×
446
strip(::AbstractString, ::AbstractString) = throw(ArgumentError("Both arguments are strings. The second argument should be a `Char` or collection of `Char`s"))
×
447
strip(f, s::AbstractString) = lstrip(f, rstrip(f, s))
×
448

449
## string padding functions ##
450

451
"""
452
    lpad(s, n::Integer, p::Union{AbstractChar,AbstractString}=' ') -> String
453

454
Stringify `s` and pad the resulting string on the left with `p` to make it `n`
455
characters (in [`textwidth`](@ref)) long. If `s` is already `n` characters long, an equal
456
string is returned. Pad with spaces by default.
457

458
# Examples
459
```jldoctest
460
julia> lpad("March", 10)
461
"     March"
462
```
463
!!! compat "Julia 1.7"
464
    In Julia 1.7, this function was changed to use `textwidth` rather than a raw character (codepoint) count.
465
"""
466
lpad(s, n::Integer, p::Union{AbstractChar,AbstractString}=' ') = lpad(string(s)::AbstractString, n, string(p))
×
467

468
function lpad(
8✔
469
    s::Union{AbstractChar,AbstractString},
470
    n::Integer,
471
    p::Union{AbstractChar,AbstractString}=' ',
472
)
473
    stringfn = if _isannotated(s) || _isannotated(p)
140✔
474
        annotatedstring else string end
×
475
    n = Int(n)::Int
7✔
476
    m = signed(n) - Int(textwidth(s))::Int
7✔
477
    m ≤ 0 && return stringfn(s)
7✔
478
    l = Int(textwidth(p))::Int
×
479
    if l == 0
×
480
        throw(ArgumentError("$(repr(p)) has zero textwidth" * (ncodeunits(p) != 1 ? "" :
×
481
            "; maybe you want pad^max(0, npad - ncodeunits(str)) * str to pad by codeunits" *
482
            (s isa AbstractString && codeunit(s) != UInt8 ? "?" : " (bytes)?"))))
483
    end
484
    q, r = divrem(m, l)
×
485
    r == 0 ? stringfn(p^q, s) : stringfn(p^q, first(p, r), s)
×
486
end
487

488
"""
489
    rpad(s, n::Integer, p::Union{AbstractChar,AbstractString}=' ') -> String
490

491
Stringify `s` and pad the resulting string on the right with `p` to make it `n`
492
characters (in [`textwidth`](@ref)) long. If `s` is already `n` characters long, an equal
493
string is returned. Pad with spaces by default.
494

495
# Examples
496
```jldoctest
497
julia> rpad("March", 20)
498
"March               "
499
```
500
!!! compat "Julia 1.7"
501
    In Julia 1.7, this function was changed to use `textwidth` rather than a raw character (codepoint) count.
502
"""
503
rpad(s, n::Integer, p::Union{AbstractChar,AbstractString}=' ') = rpad(string(s)::AbstractString, n, string(p))
×
504

505
function rpad(
506
    s::Union{AbstractChar,AbstractString},
507
    n::Integer,
508
    p::Union{AbstractChar,AbstractString}=' ',
509
)
510
    stringfn = if _isannotated(s) || _isannotated(p)
39✔
511
        annotatedstring else string end
512
    n = Int(n)::Int
513
    m = signed(n) - Int(textwidth(s))::Int
514
    m ≤ 0 && return stringfn(s)
515
    l = Int(textwidth(p))::Int
516
    if l == 0
517
        throw(ArgumentError("$(repr(p)) has zero textwidth" * (ncodeunits(p) != 1 ? "" :
518
            "; maybe you want str * pad^max(0, npad - ncodeunits(str)) to pad by codeunits" *
519
            (s isa AbstractString && codeunit(s) != UInt8 ? "?" : " (bytes)?"))))
520
    end
521
    q, r = divrem(m, l)
522
    r == 0 ? stringfn(s, p^q) : stringfn(s, p^q, first(p, r))
523
end
524

525
"""
526
    rtruncate(str::AbstractString, maxwidth::Integer, replacement::Union{AbstractString,AbstractChar} = '…')
527

528
Truncate `str` to at most `maxwidth` columns (as estimated by [`textwidth`](@ref)), replacing the last characters
529
with `replacement` if necessary. The default replacement string is "…".
530

531
# Examples
532
```jldoctest
533
julia> s = rtruncate("🍕🍕 I love 🍕", 10)
534
"🍕🍕 I lo…"
535

536
julia> textwidth(s)
537
10
538

539
julia> rtruncate("foo", 3)
540
"foo"
541
```
542

543
!!! compat "Julia 1.12"
544
    This function was added in Julia 1.12.
545

546
See also [`ltruncate`](@ref) and [`ctruncate`](@ref).
547
"""
548
function rtruncate(str::AbstractString, maxwidth::Integer, replacement::Union{AbstractString,AbstractChar} = '…')
×
549
    ret = string_truncate_boundaries(str, Int(maxwidth), replacement, Val(:right))
×
550
    if isnothing(ret)
×
551
        return string(str)
×
552
    else
553
        left, _ = ret::Tuple{Int,Int}
×
554
        @views return str[begin:left] * replacement
×
555
    end
556
end
557

558
"""
559
    ltruncate(str::AbstractString, maxwidth::Integer, replacement::Union{AbstractString,AbstractChar} = '…')
560

561
Truncate `str` to at most `maxwidth` columns (as estimated by [`textwidth`](@ref)), replacing the first characters
562
with `replacement` if necessary. The default replacement string is "…".
563

564
# Examples
565
```jldoctest
566
julia> s = ltruncate("🍕🍕 I love 🍕", 10)
567
"…I love 🍕"
568

569
julia> textwidth(s)
570
10
571

572
julia> ltruncate("foo", 3)
573
"foo"
574
```
575

576
!!! compat "Julia 1.12"
577
    This function was added in Julia 1.12.
578

579
See also [`rtruncate`](@ref) and [`ctruncate`](@ref).
580
"""
581
function ltruncate(str::AbstractString, maxwidth::Integer, replacement::Union{AbstractString,AbstractChar} = '…')
×
582
    ret = string_truncate_boundaries(str, Int(maxwidth), replacement, Val(:left))
×
583
    if isnothing(ret)
×
584
        return string(str)
×
585
    else
586
        _, right = ret::Tuple{Int,Int}
×
587
        @views return replacement * str[right:end]
×
588
    end
589
end
590

591
"""
592
    ctruncate(str::AbstractString, maxwidth::Integer, replacement::Union{AbstractString,AbstractChar} = '…'; prefer_left::Bool = true)
593

594
Truncate `str` to at most `maxwidth` columns (as estimated by [`textwidth`](@ref)), replacing the middle characters
595
with `replacement` if necessary. The default replacement string is "…". By default, the truncation
596
prefers keeping chars on the left, but this can be changed by setting `prefer_left` to `false`.
597

598
# Examples
599
```jldoctest
600
julia> s = ctruncate("🍕🍕 I love 🍕", 10)
601
"🍕🍕 …e 🍕"
602

603
julia> textwidth(s)
604
10
605

606
julia> ctruncate("foo", 3)
607
"foo"
608
```
609

610
!!! compat "Julia 1.12"
611
    This function was added in Julia 1.12.
612

613
See also [`ltruncate`](@ref) and [`rtruncate`](@ref).
614
"""
615
function ctruncate(str::AbstractString, maxwidth::Integer, replacement::Union{AbstractString,AbstractChar} = '…'; prefer_left::Bool = true)
×
616
    ret = string_truncate_boundaries(str, Int(maxwidth), replacement, Val(:center), prefer_left)
×
617
    if isnothing(ret)
×
618
        return string(str)
×
619
    else
620
        left, right = ret::Tuple{Int,Int}
×
621
        @views return str[begin:left] * replacement * str[right:end]
×
622
    end
623
end
624

625
# return whether textwidth(str) <= maxwidth
626
function check_textwidth(str::AbstractString, maxwidth::Integer)
×
627
    # check efficiently for early return if str is wider than maxwidth
628
    total_width = 0
×
629
    for c in str
×
630
        total_width += textwidth(c)
×
631
        total_width > maxwidth && return false
×
632
    end
×
633
    return true
×
634
end
635

636
function string_truncate_boundaries(
×
637
            str::AbstractString,
638
            maxwidth::Integer,
639
            replacement::Union{AbstractString,AbstractChar},
640
            ::Val{mode},
641
            prefer_left::Bool = true) where {mode}
642
    maxwidth >= 0 || throw(ArgumentError("maxwidth $maxwidth should be non-negative"))
×
643
    check_textwidth(str, maxwidth) && return nothing
×
644

645
    l0, _ = left, right = firstindex(str), lastindex(str)
×
646
    width = textwidth(replacement)
×
647
    # used to balance the truncated width on either side
648
    rm_width_left, rm_width_right, force_other = 0, 0, false
×
649
    @inbounds while true
×
650
        if mode === :left || (mode === :center && (!prefer_left || left > l0))
×
651
            rm_width = textwidth(str[right])
×
652
            if mode === :left || (rm_width_right <= rm_width_left || force_other)
×
653
                force_other = false
×
654
                (width += rm_width) <= maxwidth || break
×
655
                rm_width_right += rm_width
×
656
                right = prevind(str, right)
×
657
            else
658
                force_other = true
×
659
            end
660
        end
661
        if mode ∈ (:right, :center)
×
662
            rm_width = textwidth(str[left])
×
663
            if mode === :left || (rm_width_left <= rm_width_right || force_other)
×
664
                force_other = false
×
665
                (width += textwidth(str[left])) <= maxwidth || break
×
666
                rm_width_left += rm_width
×
667
                left = nextind(str, left)
×
668
            else
669
                force_other = true
×
670
            end
671
        end
672
    end
×
673
    return prevind(str, left), nextind(str, right)
×
674
end
675

676
"""
677
    eachsplit(str::AbstractString, dlm; limit::Integer=0, keepempty::Bool=true)
678
    eachsplit(str::AbstractString; limit::Integer=0, keepempty::Bool=false)
679

680
Split `str` on occurrences of the delimiter(s) `dlm` and return an iterator over the
681
substrings.  `dlm` can be any of the formats allowed by [`findnext`](@ref)'s first argument
682
(i.e. as a string, regular expression or a function), or as a single character or collection
683
of characters.
684

685
If `dlm` is omitted, it defaults to [`isspace`](@ref).
686

687
The optional keyword arguments are:
688
 - `limit`: the maximum size of the result. `limit=0` implies no maximum (default)
689
 - `keepempty`: whether empty fields should be kept in the result. Default is `false` without
690
   a `dlm` argument, `true` with a `dlm` argument.
691

692
See also [`split`](@ref).
693

694
!!! compat "Julia 1.8"
695
    The `eachsplit` function requires at least Julia 1.8.
696

697
# Examples
698
```jldoctest
699
julia> a = "Ma.rch"
700
"Ma.rch"
701

702
julia> b = eachsplit(a, ".")
703
Base.SplitIterator{String, String}("Ma.rch", ".", 0, true)
704

705
julia> collect(b)
706
2-element Vector{SubString{String}}:
707
 "Ma"
708
 "rch"
709
```
710
"""
711
function eachsplit end
712

713
# Forcing specialization on `splitter` improves performance (roughly 30% decrease in runtime)
714
# and prevents a major invalidation risk (1550 MethodInstances)
715
struct SplitIterator{S<:AbstractString,F}
716
    str::S
253,615✔
717
    splitter::F
718
    limit::Int
719
    keepempty::Bool
720
end
721

722
eltype(::Type{<:SplitIterator{T}}) where T = SubString{T}
×
723
eltype(::Type{<:SplitIterator{<:SubString{T}}}) where T = SubString{T}
×
724

725
IteratorSize(::Type{<:SplitIterator}) = SizeUnknown()
×
726

727
# i: the starting index of the substring to be extracted
728
# k: the starting index of the next substring to be extracted
729
# n: the number of splits returned so far; always less than iter.limit - 1 (1 for the rest)
730
function iterate(iter::SplitIterator, (i, k, n)=(firstindex(iter.str), firstindex(iter.str), 0))
×
731
    i - 1 > ncodeunits(iter.str)::Int && return nothing
802✔
732
    r = findnext(iter.splitter, iter.str, k)::Union{Nothing,Int,UnitRange{Int}}
376✔
733
    while r !== nothing && n != iter.limit - 1 && first(r) <= ncodeunits(iter.str)
398✔
734
        j, k = first(r), nextind(iter.str, last(r))::Int
512✔
735
        k_ = k <= j ? nextind(iter.str, j)::Int : k
256✔
736
        if i < k
256✔
737
            substr = @inbounds SubString(iter.str, i, prevind(iter.str, j)::Int)
256✔
738
            (iter.keepempty || i < j) && return (substr, (k, k_, n + 1))
256✔
739
            i = k
×
740
        end
741
        k = k_
22✔
742
        r = findnext(iter.splitter, iter.str, k)::Union{Nothing,Int,UnitRange{Int}}
22✔
743
    end
22✔
744
    iter.keepempty || i <= ncodeunits(iter.str) || return nothing
169✔
745
    @inbounds SubString(iter.str, i), (ncodeunits(iter.str) + 2, k, n + 1)
142✔
746
end
747

748
# Specialization for partition(s,n) to return a SubString
749
eltype(::Type{PartitionIterator{T}}) where {T<:AbstractString} = SubString{T}
×
750
# SubStrings do not nest
751
eltype(::Type{PartitionIterator{T}}) where {T<:SubString} = T
×
752

753
function iterate(itr::PartitionIterator{<:AbstractString}, state = firstindex(itr.c))
×
754
    state > ncodeunits(itr.c) && return nothing
×
755
    r = min(nextind(itr.c, state, itr.n - 1), lastindex(itr.c))
×
756
    return SubString(itr.c, state, r), nextind(itr.c, r)
×
757
end
758

759
eachsplit(str::T, splitter; limit::Integer=0, keepempty::Bool=true) where {T<:AbstractString} =
507,230✔
760
    SplitIterator(str, splitter, limit, keepempty)
761

762
eachsplit(str::T, splitter::Union{Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},Set{<:AbstractChar}};
×
763
          limit::Integer=0, keepempty=true) where {T<:AbstractString} =
764
    eachsplit(str, in(splitter); limit, keepempty)
765

766
eachsplit(str::T, splitter::AbstractChar; limit::Integer=0, keepempty=true) where {T<:AbstractString} =
505,126✔
767
    eachsplit(str, isequal(splitter); limit, keepempty)
768

769
# a bit oddball, but standard behavior in Perl, Ruby & Python:
770
eachsplit(str::AbstractString; limit::Integer=0, keepempty=false) =
×
771
    eachsplit(str, isspace; limit, keepempty)
772

773
"""
774
    eachrsplit(str::AbstractString, dlm; limit::Integer=0, keepempty::Bool=true)
775
    eachrsplit(str::AbstractString; limit::Integer=0, keepempty::Bool=false)
776

777
Return an iterator over `SubString`s of `str`, produced when splitting on
778
the delimiter(s) `dlm`, and yielded in reverse order (from right to left).
779
`dlm` can be any of the formats allowed by [`findprev`](@ref)'s first argument
780
(i.e. a string, a single character or a function), or a collection of characters.
781

782
If `dlm` is omitted, it defaults to [`isspace`](@ref), and `keepempty` default to `false`.
783

784
The optional keyword arguments are:
785
 - If `limit > 0`, the iterator will split at most `limit - 1` times before returning
786
   the rest of the string unsplit. `limit < 1` implies no cap to splits (default).
787
 - `keepempty`: whether empty fields should be returned when iterating
788
   Default is `false` without a `dlm` argument, `true` with a `dlm` argument.
789

790
Note that unlike [`split`](@ref), [`rsplit`](@ref) and [`eachsplit`](@ref), this
791
function iterates the substrings right to left as they occur in the input.
792

793
See also [`eachsplit`](@ref), [`rsplit`](@ref).
794

795
!!! compat "Julia 1.11"
796
    This function requires Julia 1.11 or later.
797

798
# Examples
799
```jldoctest
800
julia> a = "Ma.r.ch";
801

802
julia> collect(eachrsplit(a, ".")) == ["ch", "r", "Ma"]
803
true
804

805
julia> collect(eachrsplit(a, "."; limit=2)) == ["ch", "Ma.r"]
806
true
807
```
808
"""
809
function eachrsplit end
810

811
struct RSplitIterator{S <: AbstractString, F}
812
    str::S
813
    splitter::F
814
    limit::Int
815
    keepempty::Bool
816
end
817

818
eltype(::Type{<:RSplitIterator{T}}) where T = SubString{T}
×
819
eltype(::Type{<:RSplitIterator{<:SubString{T}}}) where T = SubString{T}
×
820

821
IteratorSize(::Type{<:RSplitIterator}) = SizeUnknown()
×
822

823
eachrsplit(str::T, splitter; limit::Integer=0, keepempty::Bool=true) where {T<:AbstractString} =
×
824
    RSplitIterator(str, splitter, limit, keepempty)
825

826
eachrsplit(str::T, splitter::Union{Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},Set{<:AbstractChar}};
×
827
          limit::Integer=0, keepempty=true) where {T<:AbstractString} =
828
    eachrsplit(str, in(splitter); limit, keepempty)
829

830
eachrsplit(str::T, splitter::AbstractChar; limit::Integer=0, keepempty=true) where {T<:AbstractString} =
×
831
    eachrsplit(str, isequal(splitter); limit, keepempty)
832

833
# a bit oddball, but standard behavior in Perl, Ruby & Python:
834
eachrsplit(str::AbstractString; limit::Integer=0, keepempty=false) =
×
835
    eachrsplit(str, isspace; limit, keepempty)
836

837
function Base.iterate(it::RSplitIterator, (to, remaining_splits)=(lastindex(it.str), it.limit-1))
×
838
    to < 0 && return nothing
×
839
    from = 1
×
840
    next_to = -1
×
841
    while !iszero(remaining_splits)
×
842
        pos = findprev(it.splitter, it.str, to)
×
843
        # If no matches: It returns the rest of the string, then the iterator stops.
844
        if pos === nothing
×
845
            from = 1
×
846
            next_to = -1
×
847
            break
×
848
        else
849
            from = nextind(it.str, last(pos))
×
850
            # pos can be empty if we search for a zero-width delimiter, in which
851
            # case pos is to:to-1.
852
            # In this case, next_to must be to - 1, except if to is 0 or 1, in
853
            # which case, we must stop iteration for some reason.
854
            next_to = (isempty(pos) & (to < 2)) ? -1 : prevind(it.str, first(pos))
×
855

856
            # If the element we emit is empty, discard it based on keepempty
857
            if from > to && !(it.keepempty)
×
858
                to = next_to
×
859
                continue
×
860
            end
861
            break
×
862
        end
863
    end
×
864
    from > to && !(it.keepempty) && return nothing
×
865
    return (SubString(it.str, from, to), (next_to, remaining_splits-1))
×
866
end
867

868
"""
869
    split(str::AbstractString, dlm; limit::Integer=0, keepempty::Bool=true)
870
    split(str::AbstractString; limit::Integer=0, keepempty::Bool=false)
871

872
Split `str` into an array of substrings on occurrences of the delimiter(s) `dlm`.  `dlm`
873
can be any of the formats allowed by [`findnext`](@ref)'s first argument (i.e. as a
874
string, regular expression or a function), or as a single character or collection of
875
characters.
876

877
If `dlm` is omitted, it defaults to [`isspace`](@ref).
878

879
The optional keyword arguments are:
880
 - `limit`: the maximum size of the result. `limit=0` implies no maximum (default)
881
 - `keepempty`: whether empty fields should be kept in the result. Default is `false` without
882
   a `dlm` argument, `true` with a `dlm` argument.
883

884
See also [`rsplit`](@ref), [`eachsplit`](@ref).
885

886
# Examples
887
```jldoctest
888
julia> a = "Ma.rch"
889
"Ma.rch"
890

891
julia> split(a, ".")
892
2-element Vector{SubString{String}}:
893
 "Ma"
894
 "rch"
895
```
896
"""
897
function split(str::T, splitter;
253,615✔
898
               limit::Integer=0, keepempty::Bool=true) where {T<:AbstractString}
899
    collect(eachsplit(str, splitter; limit, keepempty))
253,615✔
900
end
901

902
# a bit oddball, but standard behavior in Perl, Ruby & Python:
903
split(str::AbstractString;
×
904
      limit::Integer=0, keepempty::Bool=false) =
×
905
    split(str, isspace; limit, keepempty)
906

907
"""
908
    rsplit(s::AbstractString; limit::Integer=0, keepempty::Bool=false)
909
    rsplit(s::AbstractString, chars; limit::Integer=0, keepempty::Bool=true)
910

911
Similar to [`split`](@ref), but starting from the end of the string.
912

913
# Examples
914
```jldoctest
915
julia> a = "M.a.r.c.h"
916
"M.a.r.c.h"
917

918
julia> rsplit(a, ".")
919
5-element Vector{SubString{String}}:
920
 "M"
921
 "a"
922
 "r"
923
 "c"
924
 "h"
925

926
julia> rsplit(a, "."; limit=1)
927
1-element Vector{SubString{String}}:
928
 "M.a.r.c.h"
929

930
julia> rsplit(a, "."; limit=2)
931
2-element Vector{SubString{String}}:
932
 "M.a.r.c"
933
 "h"
934
```
935
"""
936
function rsplit(str::T, splitter;
×
937
               limit::Integer=0, keepempty::Bool=true) where {T<:AbstractString}
938
    reverse!(collect(eachrsplit(str, splitter; limit, keepempty)))
×
939
end
940

941
# a bit oddball, but standard behavior in Perl, Ruby & Python:
942
rsplit(str::AbstractString;
×
943
      limit::Integer=0, keepempty::Bool=false) =
944
    rsplit(str, isspace; limit, keepempty)
945

UNCOV
946
_replace(io, repl, str, r, pattern) = print(io, repl)
×
947
_replace(io, repl::Function, str, r, pattern) =
×
948
    print(io, repl(SubString(str, first(r), last(r))))
949
_replace(io, repl::Function, str, r, pattern::Function) =
×
950
    print(io, repl(str[first(r)]))
951

952
_pat_replacer(x) = x
×
953
_free_pat_replacer(x) = nothing
×
954

955
_pat_replacer(x::AbstractChar) = isequal(x)
2✔
956
_pat_replacer(x::Union{Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},Set{<:AbstractChar}}) = in(x)
×
957

958
# note: leave str untyped here to make it easier for packages like StringViews to hook in
959
function _replace_init(str, pat_repl::NTuple{N, Pair}, count::Int) where N
9✔
960
    count < 0 && throw(DomainError(count, "`count` must be non-negative."))
9✔
961
    e1 = nextind(str, lastindex(str)) # sizeof(str)+1
18✔
962
    a = firstindex(str)
×
963
    patterns = map(p -> _pat_replacer(first(p)), pat_repl)
18✔
964
    replaces = map(last, pat_repl)
9✔
965
    rs = map(patterns) do p
18✔
966
        r = findnext(p, str, a)
9✔
967
        if r === nothing || first(r) == 0
9✔
968
            return e1+1:0
18✔
969
        end
UNCOV
970
        r isa Int && (r = r:r) # findnext / performance fix
×
UNCOV
971
        return r
×
972
    end
973
    return e1, patterns, replaces, rs, all(>(e1), map(first, rs))
9✔
974
end
975

976
# note: leave str untyped here to make it easier for packages like StringViews to hook in
UNCOV
977
function _replace_finish(io::IO, str, count::Int,
×
978
                         e1::Int, patterns::Tuple, replaces::Tuple, rs::Tuple)
979
    n = 1
×
980
    i = a = firstindex(str)
×
UNCOV
981
    while true
×
982
        p = argmin(map(first, rs)) # TODO: or argmin(rs), to pick the shortest first match ?
×
UNCOV
983
        r = rs[p]
×
UNCOV
984
        j, k = first(r), last(r)
×
UNCOV
985
        j > e1 && break
×
UNCOV
986
        if i == a || i <= k
×
987
            # copy out preserved portion
UNCOV
988
            GC.@preserve str unsafe_write(io, pointer(str, i), UInt(j-i))
×
989
            # copy out replacement string
UNCOV
990
            _replace(io, replaces[p], str, r, patterns[p])
×
991
        end
UNCOV
992
        if k < j
×
993
            i = j
×
994
            j == e1 && break
×
995
            k = nextind(str, j)
×
996
        else
UNCOV
997
            i = k = nextind(str, k)
×
998
        end
UNCOV
999
        n == count && break
×
1000
        let k = k
×
1001
            rs = map(patterns, rs) do p, r
×
1002
                if first(r) < k
×
1003
                    r = findnext(p, str, k)
×
1004
                    if r === nothing || first(r) == 0
×
1005
                        return e1+1:0
×
1006
                    end
1007
                    r isa Int && (r = r:r) # findnext / performance fix
×
1008
                end
1009
                return r
×
1010
            end
1011
        end
1012
        n += 1
×
1013
    end
×
UNCOV
1014
    foreach(_free_pat_replacer, patterns)
×
UNCOV
1015
    write(io, SubString(str, i))
×
UNCOV
1016
    return io
×
1017
end
1018

1019
# note: leave str untyped here to make it easier for packages like StringViews to hook in
1020
function _replace_(io::IO, str, pat_repl::NTuple{N, Pair}, count::Int) where N
×
1021
    if count == 0
×
1022
        write(io, str)
×
1023
        return io
×
1024
    end
1025
    e1, patterns, replaces, rs, notfound = _replace_init(str, pat_repl, count)
×
1026
    if notfound
×
1027
        foreach(_free_pat_replacer, patterns)
×
1028
        write(io, str)
×
1029
        return io
×
1030
    end
1031
    return _replace_finish(io, str, count, e1, patterns, replaces, rs)
×
1032
end
1033

1034
# note: leave str untyped here to make it easier for packages like StringViews to hook in
1035
function _replace_(str, pat_repl::NTuple{N, Pair}, count::Int) where N
9✔
1036
    count == 0 && return String(str)
9✔
1037
    e1, patterns, replaces, rs, notfound = _replace_init(str, pat_repl, count)
9✔
1038
    if notfound
9✔
1039
        foreach(_free_pat_replacer, patterns)
×
1040
        return String(str)
9✔
1041
    end
UNCOV
1042
    out = IOBuffer(sizehint=floor(Int, 1.2sizeof(str)))
×
UNCOV
1043
    return String(take!(_replace_finish(out, str, count, e1, patterns, replaces, rs)))
×
1044
end
1045

1046
"""
1047
    replace([io::IO], s::AbstractString, pat=>r, [pat2=>r2, ...]; [count::Integer])
1048

1049
Search for the given pattern `pat` in `s`, and replace each occurrence with `r`.
1050
If `count` is provided, replace at most `count` occurrences.
1051
`pat` may be a single character, a vector or a set of characters, a string,
1052
or a regular expression.
1053
If `r` is a function, each occurrence is replaced with `r(s)`
1054
where `s` is the matched substring (when `pat` is a `AbstractPattern` or `AbstractString`) or
1055
character (when `pat` is an `AbstractChar` or a collection of `AbstractChar`).
1056
If `pat` is a regular expression and `r` is a [`SubstitutionString`](@ref), then capture group
1057
references in `r` are replaced with the corresponding matched text.
1058
To remove instances of `pat` from `string`, set `r` to the empty `String` (`""`).
1059

1060
The return value is a new string after the replacements.  If the `io::IO` argument
1061
is supplied, the transformed string is instead written to `io` (returning `io`).
1062
(For example, this can be used in conjunction with an [`IOBuffer`](@ref) to re-use
1063
a pre-allocated buffer array in-place.)
1064

1065
Multiple patterns can be specified, and they will be applied left-to-right
1066
simultaneously, so only one pattern will be applied to any character, and the
1067
patterns will only be applied to the input text, not the replacements.
1068

1069
!!! compat "Julia 1.7"
1070
    Support for multiple patterns requires version 1.7.
1071

1072
!!! compat "Julia 1.10"
1073
    The `io::IO` argument requires version 1.10.
1074

1075
# Examples
1076
```jldoctest
1077
julia> replace("Python is a programming language.", "Python" => "Julia")
1078
"Julia is a programming language."
1079

1080
julia> replace("The quick foxes run quickly.", "quick" => "slow", count=1)
1081
"The slow foxes run quickly."
1082

1083
julia> replace("The quick foxes run quickly.", "quick" => "", count=1)
1084
"The  foxes run quickly."
1085

1086
julia> replace("The quick foxes run quickly.", r"fox(es)?" => s"bus\\1")
1087
"The quick buses run quickly."
1088

1089
julia> replace("abcabc", "a" => "b", "b" => "c", r".+" => "a")
1090
"bca"
1091
```
1092
"""
1093
replace(io::IO, s::AbstractString, pat_f::Pair...; count=typemax(Int)) =
×
1094
    _replace_(io, String(s), pat_f, Int(count))
1095

1096
replace(s::AbstractString, pat_f::Pair...; count=typemax(Int)) =
250✔
1097
    _replace_(String(s), pat_f, Int(count))
1098

1099

1100
# TODO: allow transform as the first argument to replace?
1101

1102
# hex <-> bytes conversion
1103

1104
"""
1105
    hex2bytes(itr)
1106

1107
Given an iterable `itr` of ASCII codes for a sequence of hexadecimal digits, returns a
1108
`Vector{UInt8}` of bytes  corresponding to the binary representation: each successive pair
1109
of hexadecimal digits in `itr` gives the value of one byte in the return vector.
1110

1111
The length of `itr` must be even, and the returned array has half of the length of `itr`.
1112
See also [`hex2bytes!`](@ref) for an in-place version, and [`bytes2hex`](@ref) for the inverse.
1113

1114
!!! compat "Julia 1.7"
1115
    Calling `hex2bytes` with iterators producing `UInt8` values requires
1116
    Julia 1.7 or later. In earlier versions, you can `collect` the iterator
1117
    before calling `hex2bytes`.
1118

1119
# Examples
1120
```jldoctest
1121
julia> s = string(12345, base = 16)
1122
"3039"
1123

1124
julia> hex2bytes(s)
1125
2-element Vector{UInt8}:
1126
 0x30
1127
 0x39
1128

1129
julia> a = b"01abEF"
1130
6-element Base.CodeUnits{UInt8, String}:
1131
 0x30
1132
 0x31
1133
 0x61
1134
 0x62
1135
 0x45
1136
 0x46
1137

1138
julia> hex2bytes(a)
1139
3-element Vector{UInt8}:
1140
 0x01
1141
 0xab
1142
 0xef
1143
```
1144
"""
1145
function hex2bytes end
1146

1147
hex2bytes(s) = hex2bytes!(Vector{UInt8}(undef, length(s)::Int >> 1), s)
61,091✔
1148

1149
# special case - valid bytes are checked in the generic implementation
1150
function hex2bytes!(dest::AbstractArray{UInt8}, s::String)
1151
    sizeof(s) != length(s) && throw(ArgumentError("input string must consist of hexadecimal characters only"))
61,085✔
1152

1153
    hex2bytes!(dest, transcode(UInt8, s))
61,085✔
1154
end
1155

1156
"""
1157
    hex2bytes!(dest::AbstractVector{UInt8}, itr)
1158

1159
Convert an iterable `itr` of bytes representing a hexadecimal string to its binary
1160
representation, similar to [`hex2bytes`](@ref) except that the output is written in-place
1161
to `dest`. The length of `dest` must be half the length of `itr`.
1162

1163
!!! compat "Julia 1.7"
1164
    Calling hex2bytes! with iterators producing UInt8 requires
1165
    version 1.7. In earlier versions, you can collect the iterable
1166
    before calling instead.
1167
"""
1168
function hex2bytes!(dest::AbstractArray{UInt8}, itr)
6✔
1169
    isodd(length(itr)) && throw(ArgumentError("length of iterable must be even"))
6✔
1170
    @boundscheck 2*length(dest) != length(itr) && throw(ArgumentError("length of output array must be half of the length of input iterable"))
6✔
1171
    iszero(length(itr)) && return dest
6✔
1172

1173
    next = iterate(itr)
12✔
1174
    @inbounds for i in eachindex(dest)
6✔
1175
        x,state = next::NTuple{2,Any}
120✔
1176
        y,state = iterate(itr, state)::NTuple{2,Any}
240✔
1177
        next = iterate(itr, state)
234✔
1178
        dest[i] = number_from_hex(x) << 4 + number_from_hex(y)
120✔
1179
    end
234✔
1180

1181
    return dest
6✔
1182
end
1183

1184
@inline number_from_hex(c::AbstractChar) = number_from_hex(Char(c))
×
1185
@inline number_from_hex(c::Char) = number_from_hex(UInt8(c))
294✔
1186
@inline function number_from_hex(c::UInt8)
1187
    UInt8('0') <= c <= UInt8('9') && return c - UInt8('0')
240✔
1188
    c |= 0b0100000
72✔
1189
    UInt8('a') <= c <= UInt8('f') && return c - UInt8('a') + 0x0a
72✔
1190
    throw(ArgumentError("byte is not an ASCII hexadecimal digit"))
×
1191
end
1192

1193
"""
1194
    bytes2hex(itr) -> String
1195
    bytes2hex(io::IO, itr)
1196

1197
Convert an iterator `itr` of bytes to its hexadecimal string representation, either
1198
returning a `String` via `bytes2hex(itr)` or writing the string to an `io` stream
1199
via `bytes2hex(io, itr)`.  The hexadecimal characters are all lowercase.
1200

1201
!!! compat "Julia 1.7"
1202
    Calling `bytes2hex` with arbitrary iterators producing `UInt8` values requires
1203
    Julia 1.7 or later. In earlier versions, you can `collect` the iterator
1204
    before calling `bytes2hex`.
1205

1206
# Examples
1207
```jldoctest
1208
julia> a = string(12345, base = 16)
1209
"3039"
1210

1211
julia> b = hex2bytes(a)
1212
2-element Vector{UInt8}:
1213
 0x30
1214
 0x39
1215

1216
julia> bytes2hex(b)
1217
"3039"
1218
```
1219
"""
1220
function bytes2hex end
1221

1222
function bytes2hex(itr)
61,090✔
1223
    eltype(itr) === UInt8 || throw(ArgumentError("eltype of iterator not UInt8"))
61,090✔
1224
    b = Base.StringMemory(2*length(itr))
61,090✔
1225
    @inbounds for (i, x) in enumerate(itr)
61,090✔
1226
        b[2i - 1] = hex_chars[1 + x >> 4]
1,221,800✔
1227
        b[2i    ] = hex_chars[1 + x & 0xf]
1,221,800✔
1228
    end
2,382,510✔
1229
    return unsafe_takestring(b)
61,090✔
1230
end
1231

1232
function bytes2hex(io::IO, itr)
97✔
1233
    eltype(itr) === UInt8 || throw(ArgumentError("eltype of iterator not UInt8"))
97✔
1234
    for x in itr
97✔
1235
        print(io, Char(hex_chars[1 + x >> 4]), Char(hex_chars[1 + x & 0xf]))
1,940✔
1236
    end
1,940✔
1237
end
1238

1239
# check for pure ASCII-ness
1240
function ascii(s::String)
×
1241
    for i in 1:sizeof(s)
×
1242
        @inbounds codeunit(s, i) < 0x80 || __throw_invalid_ascii(s, i)
×
1243
    end
×
1244
    return s
×
1245
end
1246
@noinline __throw_invalid_ascii(s::String, i::Int) = throw(ArgumentError("invalid ASCII at index $i in $(repr(s))"))
×
1247

1248
"""
1249
    ascii(s::AbstractString)
1250

1251
Convert a string to `String` type and check that it contains only ASCII data, otherwise
1252
throwing an `ArgumentError` indicating the position of the first non-ASCII byte.
1253

1254
See also the [`isascii`](@ref) predicate to filter or replace non-ASCII characters.
1255

1256
# Examples
1257
```jldoctest
1258
julia> ascii("abcdeγfgh")
1259
ERROR: ArgumentError: invalid ASCII at index 6 in "abcdeγfgh"
1260
Stacktrace:
1261
[...]
1262

1263
julia> ascii("abcdefgh")
1264
"abcdefgh"
1265
```
1266
"""
1267
ascii(x::AbstractString) = ascii(String(x))
×
1268

1269
Base.rest(s::Union{String,SubString{String}}, i=1) = SubString(s, i)
×
1270
function Base.rest(s::AbstractString, st...)
×
1271
    io = IOBuffer()
×
1272
    for c in Iterators.rest(s, st...)
×
1273
        print(io, c)
×
1274
    end
×
1275
    return String(take!(io))
×
1276
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc