Skip to content

Commit

Permalink
Add lazy string type (#33711)
Browse files Browse the repository at this point in the history
  • Loading branch information
Keno authored Jan 20, 2022
1 parent cc96240 commit f290339
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 71 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Standard library changes
* `extrema` now supports `init` keyword argument ([#36265], [#43604]).
* Intersect returns a result with the eltype of the type-promoted eltypes of the two inputs ([#41769]).
* `Iterators.countfrom` now accepts any type that defines `+`. ([#37747])
* The `LazyString` and the `lazy"str"` macro were added to support delayed construction of error messages in error paths. ([#33711])

#### InteractiveUtils
* A new macro `@time_imports` for reporting any time spent importing packages and their dependencies ([#41612])
Expand Down
4 changes: 4 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ include("refpointer.jl")
include("checked.jl")
using .Checked

# Lazy strings
include("strings/lazy.jl")

# array structures
include("indices.jl")
include("array.jl")
Expand Down Expand Up @@ -200,6 +203,7 @@ include("dict.jl")
include("abstractset.jl")
include("set.jl")

# Strings
include("char.jl")
include("strings/basic.jl")
include("strings/string.jl")
Expand Down
34 changes: 20 additions & 14 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -914,19 +914,21 @@ end
# copy from an some iterable object into an AbstractArray
function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer)
if (sstart < 1)
throw(ArgumentError(string("source start offset (",sstart,") is < 1")))
throw(ArgumentError(LazyString("source start offset (",sstart,") is < 1")))
end
y = iterate(src)
for j = 1:(sstart-1)
if y === nothing
throw(ArgumentError(string("source has fewer elements than required, ",
"expected at least ",sstart,", got ",j-1)))
throw(ArgumentError(LazyString(
"source has fewer elements than required, ",
"expected at least ", sstart,", got ", j-1)))
end
y = iterate(src, y[2])
end
if y === nothing
throw(ArgumentError(string("source has fewer elements than required, ",
"expected at least ",sstart,", got ",sstart-1)))
throw(ArgumentError(LazyString(
"source has fewer elements than required, ",
"expected at least ",sstart," got ", sstart-1)))
end
i = Int(dstart)
while y !== nothing
Expand All @@ -940,19 +942,22 @@ end

# this method must be separate from the above since src might not have a length
function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer, n::Integer)
n < 0 && throw(ArgumentError(string("tried to copy n=", n, " elements, but n should be nonnegative")))
n < 0 && throw(ArgumentError(LazyString("tried to copy n=",n,
", elements, but n should be nonnegative")))
n == 0 && return dest
dmax = dstart + n - 1
inds = LinearIndices(dest)
if (dstart inds || dmax inds) | (sstart < 1)
sstart < 1 && throw(ArgumentError(string("source start offset (",sstart,") is < 1")))
sstart < 1 && throw(ArgumentError(LazyString("source start offset (",
sstart,") is < 1")))
throw(BoundsError(dest, dstart:dmax))
end
y = iterate(src)
for j = 1:(sstart-1)
if y === nothing
throw(ArgumentError(string("source has fewer elements than required, ",
"expected at least ",sstart,", got ",j-1)))
throw(ArgumentError(LazyString(
"source has fewer elements than required, ",
"expected at least ",sstart,", got ",j-1)))
end
y = iterate(src, y[2])
end
Expand Down Expand Up @@ -1064,7 +1069,8 @@ function copyto!(dest::AbstractArray, dstart::Integer,
src::AbstractArray, sstart::Integer,
n::Integer)
n == 0 && return dest
n < 0 && throw(ArgumentError(string("tried to copy n=", n, " elements, but n should be nonnegative")))
n < 0 && throw(ArgumentError(LazyString("tried to copy n=",
n," elements, but n should be nonnegative")))
destinds, srcinds = LinearIndices(dest), LinearIndices(src)
(checkbounds(Bool, destinds, dstart) && checkbounds(Bool, destinds, dstart+n-1)) || throw(BoundsError(dest, dstart:dstart+n-1))
(checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw(BoundsError(src, sstart:sstart+n-1))
Expand All @@ -1082,12 +1088,12 @@ end
function copyto!(B::AbstractVecOrMat{R}, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int},
A::AbstractVecOrMat{S}, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) where {R,S}
if length(ir_dest) != length(ir_src)
throw(ArgumentError(string("source and destination must have same size (got ",
length(ir_src)," and ",length(ir_dest),")")))
throw(ArgumentError(LazyString("source and destination must have same size (got ",
length(ir_src)," and ",length(ir_dest),")")))
end
if length(jr_dest) != length(jr_src)
throw(ArgumentError(string("source and destination must have same size (got ",
length(jr_src)," and ",length(jr_dest),")")))
throw(ArgumentError(LazyString("source and destination must have same size (got ",
length(jr_src)," and ",length(jr_dest),")")))
end
@boundscheck checkbounds(B, ir_dest, jr_dest)
@boundscheck checkbounds(A, ir_src, jr_src)
Expand Down
2 changes: 2 additions & 0 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ add_with_overflow(x::T, y::T) where {T<:SignedInt} = checked_sadd_int(x, y)
add_with_overflow(x::T, y::T) where {T<:UnsignedInt} = checked_uadd_int(x, y)
add_with_overflow(x::Bool, y::Bool) = (x+y, false)

include("strings/lazy.jl")

# core array operations
include("indices.jl")
include("array.jl")
Expand Down
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export
IOStream,
LinRange,
Irrational,
LazyString,
Matrix,
MergeSort,
Missing,
Expand Down Expand Up @@ -986,6 +987,7 @@ export
@v_str, # version number
@raw_str, # raw string with no interpolation/unescaping
@NamedTuple,
@lazy_str, # lazy string

# documentation
@text_str,
Expand Down
11 changes: 6 additions & 5 deletions base/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ using Core.Intrinsics: sqrt_llvm
using .Base: IEEEFloat

@noinline function throw_complex_domainerror(f::Symbol, x)
throw(DomainError(x, string("$f will only return a complex result if called with a ",
"complex argument. Try $f(Complex(x)).")))
throw(DomainError(x,
LazyString(f," will only return a complex result if called with a complex argument. Try ", f,"(Complex(x)).")))
end
@noinline function throw_exp_domainerror(x)
throw(DomainError(x, string("Exponentiation yielding a complex result requires a ",
"complex argument.\nReplace x^y with (x+0im)^y, ",
"Complex(x)^y, or similar.")))
throw(DomainError(x, LazyString(
"Exponentiation yielding a complex result requires a ",
"complex argument.\nReplace x^y with (x+0im)^y, ",
"Complex(x)^y, or similar.")))
end

# non-type specific math functions
Expand Down
63 changes: 63 additions & 0 deletions base/strings/lazy.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
LazyString <: AbstractString
A lazy representation of string interpolation. This is useful when a string
needs to be constructed in a context where performing the actual interpolation
and string construction is unnecessary or undesirable (e.g. in error paths
of functions).
This type is designed to be cheap to construct at runtime, trying to offload
as much work as possible to either the macro or later printing operations.
!!! compat "Julia 1.8"
`LazyString` requires Julia 1.8 or later.
"""
mutable struct LazyString <: AbstractString
parts::Tuple
# Created on first access
str::String
LazyString(args...) = new(args)
end

"""
lazy"str"
Create a [`LazyString`](@ref) using regular string interpolation syntax.
Note that interpolations are *evaluated* at LazyString construction time,
but *printing* is delayed until the first access to the string.
!!! compat "Julia 1.8"
`lazy"str"` requires Julia 1.8 or later.
"""
macro lazy_str(text)
parts = Any[]
lastidx = idx = 1
while (idx = findnext('$', text, idx)) !== nothing
lastidx < idx && push!(parts, text[lastidx:idx-1])
idx += 1
expr, idx = Meta.parseatom(text, idx; filename=string(__source__.file))
push!(parts, esc(expr))
lastidx = idx
end
lastidx <= lastindex(text) && push!(parts, text[lastidx:end])
:(LazyString($(parts...)))
end

function String(l::LazyString)
if !isdefined(l, :str)
l.str = sprint() do io
for p in l.parts
print(io, p)
end
end
end
return l.str
end

hash(s::LazyString, h::UInt64) = hash(String(s), h)
lastindex(s::LazyString) = lastindex(String(s))
iterate(s::LazyString) = iterate(String(s))
iterate(s::LazyString, i::Integer) = iterate(String(s), i)
isequal(a::LazyString, b::LazyString) = isequal(String(a), String(b))
==(a::LazyString, b::LazyString) = (String(a) == String(b))
ncodeunits(s::LazyString) = ncodeunits(String(s))
Loading

0 comments on commit f290339

Please sign in to comment.