From 06f2855ee2e2cc3b444c5dee3dd728d65fe5c824 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 19 Jan 2026 18:24:26 -0500 Subject: [PATCH 1/4] stub out package when StringView in Base --- src/StringViews.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/StringViews.jl b/src/StringViews.jl index 57d9e08..6fd5f4e 100644 --- a/src/StringViews.jl +++ b/src/StringViews.jl @@ -10,6 +10,10 @@ a `StringView` is intended to be usable in any context where you might have otherwise used `String`. """ module StringViews + +# no longer needed after https://github.com/JuliaLang/julia/pull/60526 +if !isdefined(Base, StringView) + export StringView, SVRegexMatch """ @@ -141,4 +145,6 @@ include("parse.jl") include("util.jl") include("search.jl") +end + end # module From 061cdbf0fa57056ebe13d39ac39b5a1a6ea76985 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 19 Jan 2026 18:27:04 -0500 Subject: [PATCH 2/4] typo --- src/StringViews.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StringViews.jl b/src/StringViews.jl index 6fd5f4e..e95fd7c 100644 --- a/src/StringViews.jl +++ b/src/StringViews.jl @@ -12,7 +12,7 @@ have otherwise used `String`. module StringViews # no longer needed after https://github.com/JuliaLang/julia/pull/60526 -if !isdefined(Base, StringView) +if !isdefined(Base, :StringView) export StringView, SVRegexMatch From 077aca01737f5d85cce3071751b072952765e5c3 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 13 Apr 2026 13:52:41 -0400 Subject: [PATCH 3/4] consistency with base --- src/StringViews.jl | 39 ++++++++++++++++++++++++------------ src/decoding.jl | 4 ++-- test/runtests.jl | 50 +++++++++++++++++++--------------------------- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/StringViews.jl b/src/StringViews.jl index e95fd7c..e68a58f 100644 --- a/src/StringViews.jl +++ b/src/StringViews.jl @@ -8,11 +8,19 @@ Unlike Julia's built-in `String` type (which also wraps UTF-8 data), the instance, and does not take "ownership" of or modify the array. Otherwise, a `StringView` is intended to be usable in any context where you might have otherwise used `String`. + +In Julia 1.14, the `StringView` type will be included in the Julia `Base` module, +at which point the `StringViews` module will become a trivial stub. """ module StringViews # no longer needed after https://github.com/JuliaLang/julia/pull/60526 -if !isdefined(Base, :StringView) +if isdefined(Base, :StringView) + +const SVRegexMatch = RegexMatch +export SVRegexMatch + +else export StringView, SVRegexMatch @@ -31,6 +39,20 @@ in the buffer. """ struct StringView{T<:AbstractVector{UInt8}} <: AbstractString data::T + + function StringView{T}(data::T) where {T <: AbstractVector{UInt8}} + # For now, StringViews code assumes one-based indexing + Base.require_one_based_indexing(data) + + # Prevent someone constructing e.g. a `StringView{AbstractVector{UInt8}}`, + # the existence of which will complicate the implementation and provide + # no usability benefit. + if !isconcretetype(T) + throw(ArgumentError("StringView must be parameterized with a concrete type")) + end + + new{T}(data) + end end const DenseStringView = StringView{<:Union{DenseVector{UInt8},<:Base.FastContiguousSubArray{UInt8,1,<:DenseVector{UInt8}}}} @@ -38,20 +60,10 @@ const StringAndSub = Union{String,SubString{String}} const StringViewAndSub = Union{StringView,SubString{<:StringView}} const DenseStringViewAndSub = Union{DenseStringView,SubString{<:DenseStringView}} -Base.Vector{UInt8}(s::StringView{Vector{UInt8}}) = s.data +StringView(v::AbstractVector{UInt8}) = StringView{typeof(v)}(v) Base.Vector{UInt8}(s::StringViewAndSub) = Vector{UInt8}(codeunits(s)) Base.Array{UInt8}(s::StringViewAndSub) = Vector{UInt8}(s) Base.String(s::StringViewAndSub) = String(copyto!(Base.StringVector(ncodeunits(s)), codeunits(s))) -StringView(s::StringView) = s -StringView{S}(s::StringView{S}) where {S<:AbstractVector{UInt8}} = s -StringView(s::String) = StringView(codeunits(s)) - -# iobuffer constructor (note that buf.data is always 1-based) -@inline function StringView(buf::IOBuffer, r::OrdinalRange{<:Integer,<:Integer}=Base.OneTo(buf.ptr-1)) - @boundscheck issubset(r, Base.OneTo(buf.size)) || throw(BoundsError(buf, r)) - StringView(@view buf.data[r]) -end - Base.copy(s::StringView) = StringView(copy(s.data)) Base.Symbol(s::DenseStringViewAndSub) = @@ -92,9 +104,10 @@ end Base.:(==)(s1::StringViewAndSub, s2::StringAndSub) = s2 == s1 Base.typemin(::Type{StringView{Vector{UInt8}}}) = StringView(Vector{UInt8}(undef,0)) -Base.typemin(::Type{StringView{Base.CodeUnits{UInt8, String}}}) = StringView("") +Base.typemin(::Type{StringView{Base.CodeUnits{UInt8, String}}}) = StringView(codeunits("")) Base.typemin(::T) where {T<:StringView} = typemin(T) Base.one(::Union{T,Type{T}}) where {T<:StringView} = typemin(T) +Base.oneunit(::Union{T, Type{T}}) where {T<:StringView} = typemin(T) if VERSION < v"1.10.0-DEV.1007" # JuliaLang/julia#47880 Base.isvalid(s::DenseStringViewAndSub) = ccall(:u8_isvalid, Int32, (Ptr{UInt8}, Int), s, sizeof(s)) ≠ 0 diff --git a/src/decoding.jl b/src/decoding.jl index fb897fe..1d3d0a2 100644 --- a/src/decoding.jl +++ b/src/decoding.jl @@ -68,7 +68,7 @@ end Base.getindex(s::StringView, r::UnitRange{<:Integer}) = s[Int(first(r)):Int(last(r))] @inline function Base.getindex(s::StringView, r::UnitRange{Int}) - isempty(r) && return "" + isempty(r) && return StringView(s.data[1:0]) i, j = first(r), last(r) @boundscheck begin checkbounds(s, r) @@ -76,7 +76,7 @@ Base.getindex(s::StringView, r::UnitRange{<:Integer}) = s[Int(first(r)):Int(last @inbounds isvalid(s, j) || Base.string_index_err(s, j) end j = nextind(s, j) - 1 - return StringView(@view s.data[i:j]) + return StringView(s.data[i:j]) end Base.length(s::StringView) = length_continued(s, 1, ncodeunits(s), ncodeunits(s)) diff --git a/test/runtests.jl b/test/runtests.jl index a7c89e3..8e1b1ed 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,11 +6,11 @@ s = StringView(b) ss = SubString(s, 2, 5) # "ooba" abc = StringView(0x61:0x63) invalid = StringView([0x8b, 0x52, 0x9b, 0x8d]) -su = StringView("föôẞαr") +stringview(s::String) = StringView(codeunits(s)) # convenience constructor +su = stringview("föôẞαr") @testset "construction/conversion" begin - @test StringView(s) === s - @test Vector{UInt8}(s) === Array{UInt8}(s) === codeunits(s) === b + @test Vector{UInt8}(s) == Array{UInt8}(s) == codeunits(s) === b @test Vector{UInt8}(StringView(@view b[1:3])) == b[1:3] @test codeunits(String(s)) == s.data @test Vector{UInt8}(abc) == collect(0x61:0x63) @@ -21,21 +21,11 @@ su = StringView("föôẞαr") @test c == "foobar" @test c.data !== s.data - buf = IOBuffer() - write(buf, s) - @test StringView(buf) == s == StringView(buf, 0x01:0x06) - @test StringView(buf, 3:5) == "oba" == StringView(buf, 0x03:0x01:0x05) - write(buf, "baz") - @test StringView(buf) == s * "baz" - @test String(take!(buf)) == s * "baz" - @test StringView(buf) == "" - @test_throws BoundsError StringView(buf, 3:4) - - @test StringView("foo") isa StringView{Base.CodeUnits{UInt8,String}} + @test stringview("foo") isa StringView{Base.CodeUnits{UInt8,String}} @test s isa StringViews.DenseStringView @test StringView(@view b[1:3]) isa StringViews.DenseStringView - @test StringView("foo") isa StringViews.DenseStringView + @test stringview("foo") isa StringViews.DenseStringView @test StringView(@view codeunits("foobar")[1:3]) isa StringViews.DenseStringView @test pointer(s) == pointer(b) == Base.unsafe_convert(Ptr{UInt8}, s) @@ -63,8 +53,8 @@ end @test Base.print_to_string(ss) == "ooba" - @test cmp("foobar","bar") == cmp(ss,"bar") == -cmp("bar",ss) == cmp(ss,StringView("bar")) - @test ss == StringView("ooba") == "ooba" == ss == "ooba" + @test cmp("foobar","bar") == cmp(ss,"bar") == -cmp("bar",ss) == cmp(ss,stringview("bar")) + @test ss == stringview("ooba") == "ooba" == ss == "ooba" @test isvalid(ss) end @@ -94,17 +84,17 @@ end @test findnext(r"[aeiou]+", s, 1) == 2:3 @test findnext(r"[aeiou]+", ss, 1) == 1:2 - sv = StringView(codeunits("foo 1234 bar")) + sv = stringview("foo 1234 bar") @test match(r"[0-9]+", sv).match.string === sv @test eltype(eachmatch(r"[0-9]+", sv)) == SVRegexMatch{typeof(sv)} # Regex match of substring of stringview - strv = only(match(r"^([a-z]+)$", SubString(StringView((b"abc"))))) + strv = only(match(r"^([a-z]+)$", SubString(StringView(b"abc")))) @test typeof(strv) == SubString{StringView{typeof(b"abc")}} end @testset "named subpatterns" begin - m = match(r"(?.)(.)(?.)", StringView(codeunits("xyz"))) + m = match(r"(?.)(.)(?.)", stringview("xyz")) @test haskey(m, :a) @test haskey(m, 2) @test haskey(m, "b") @@ -118,7 +108,7 @@ end @testset "parsing" begin for val in (true, 1234, 1234.5, 1234.5f0, 4.5+3.25im) sval = string(val) - for str in (StringView(sval), SubString("foo"*sval*"bar", 4, 3+length(sval))) + for str in (stringview(sval), SubString("foo"*sval*"bar", 4, 3+length(sval))) @test parse(typeof(val), str) === val end end @@ -139,7 +129,7 @@ end @test findnext(==("ba"), str, 1) === findnext(==("ba"), sS, 1) @test findprev(==("ba"), str, n) === findprev(==("ba"), sS, n) end - @test chomp(StringView("foo\n")) == "foo" + @test chomp(stringview("foo\n")) == "foo" # issue #5 let v = [0x32, 0x30, 0x32, 0x31, 0x2d, 0x31, 0x31, 0x2d, 0x31, 0x30, 0x20, 0x32, 0x31, 0x3a, 0x34, 0x32, 0x3a, 0x30, 0x35, 0x2e, 0x31, 0x31, 0x35, 0x38, 0x30, 0x37], @@ -147,10 +137,10 @@ end @test replace(String(copy(v)), pat) == replace(StringView(v), pat) end - @test findfirst(==('ø'), StringView("abc")) === nothing - @test findfirst(==('ø'), StringView("abæø")) == 5 - @test findlast(==('ø'), StringView("abc")) === nothing - @test findlast(==('ø'), StringView("abæø")) == 5 + @test findfirst(==('ø'), stringview("abc")) === nothing + @test findfirst(==('ø'), stringview("abæø")) == 5 + @test findlast(==('ø'), stringview("abc")) === nothing + @test findlast(==('ø'), stringview("abæø")) == 5 end @testset "replace" begin @@ -164,8 +154,8 @@ end end @testset "miscellaneous" begin - @test cmp("foobar","bar") == cmp(s,"bar") == -cmp("bar",s) == cmp(s,StringView("bar")) - @test s == StringView("foobar") == "foobar" == s == "foobar" != StringView("bar") + @test cmp("foobar","bar") == cmp(s,"bar") == -cmp("bar",s) == cmp(s,stringview("bar")) + @test s == stringview("foobar") == "foobar" == s == "foobar" != stringview("bar") @test cmp(abc, "bar") == cmp("abc","bar") @test Base.typemin(s) isa StringView{Vector{UInt8}} @@ -176,7 +166,7 @@ end @test oneunit(su) == oneunit(typeof(su)) == one(su) == "" @test isascii(s) - @test !isascii(StringView("fööbār")) + @test !isascii(stringview("fööbār")) @test isvalid(s) @test isvalid(abc) @@ -188,5 +178,5 @@ end end # issue #12 - @test_throws StringIndexError StringView(codeunits("fooα"))[1:5] + @test_throws StringIndexError stringview("fooα")[1:5] end From c6f1dd58ceadfceea238b13dd0e3eac4ffc9f897 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 13 Apr 2026 13:53:18 -0400 Subject: [PATCH 4/4] major version bump due to API changes --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3410cf8..818fef8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StringViews" uuid = "354b36f9-a18e-4713-926e-db85100087ba" authors = ["Steven G. Johnson "] -version = "1.3.7" +version = "2.0" [compat] julia = "1.6"