Skip to content

Commit 787d610

Browse files
committed
Support interpolation in @blk_str
1 parent 9a59ec5 commit 787d610

File tree

2 files changed

+68
-8
lines changed

2 files changed

+68
-8
lines changed

src/BlockScalars.jl

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,49 @@ function block(str::AbstractString, style::Symbol, chomp::Symbol)
146146
end
147147

148148
macro blk_str(str::AbstractString, indicators::AbstractString="")
149-
return block(str, indicators)
149+
parsed = interpolate(str)
150+
151+
# When no string interpolation needs to take place we can just process the multiline
152+
# string during parse time. If string interpolation needs to take place we'll evaluate
153+
# the multiline string at runtime so that we can process after interpolation has taken
154+
# place.
155+
result = if parsed isa String
156+
block(unescape_string(parsed), indicators)
157+
else
158+
Expr(:call, :(BlockScalars.block), parsed, indicators)
159+
end
160+
161+
return esc(result)
162+
end
163+
164+
function interpolate(str::AbstractString)
165+
components = []
166+
start = 1
167+
lastind = lastindex(str)
168+
169+
state = iterate(str)
170+
while state !== nothing
171+
c, i = state
172+
173+
if c == '$'
174+
ending = prevind(str, i, 2)
175+
start <= ending && push!(components, SubString(str, start, ending))
176+
177+
expr, i = Meta.parse(str, i; greedy=false)
178+
push!(components, expr)
179+
start = i
180+
end
181+
182+
state = iterate(str, i)
183+
end
184+
185+
# When interpolation isn't used we can just return the original string
186+
start == 1 && return str
187+
188+
ending = lastind
189+
start <= ending && push!(components, SubString(str, start, ending))
190+
191+
return Expr(:string, components...)
150192
end
151193

152194
end

test/runtests.jl

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BlockScalars: @blk_str, block
1+
using BlockScalars: BlockScalars, @blk_str, block, interpolate
22
using Test
33
using YAML: YAML
44

@@ -114,14 +114,32 @@ end
114114
end
115115
end
116116

117+
@testset "interpolate" begin
118+
@test interpolate("x") == "x"
119+
@test interpolate("\$x") == Expr(:string, :x)
120+
@test interpolate("\$(x)") == Expr(:string, :x)
121+
@test interpolate("\$(\"x\")") == Expr(:string, "x")
122+
123+
@test interpolate("<\$x>") == Expr(:string, "<", :x, ">")
124+
@test interpolate("<\$(x)>") == Expr(:string, "<", :x, ">")
125+
126+
# The quoting in these examples can result in exceptions being raised during parsing
127+
# if handled incorrectly.
128+
@test interpolate("\"\\n\"") == "\"\\n\""
129+
@test interpolate("\$(join([\"a\", \"b\"], \", \"))") == Expr(:string, :(join(["a", "b"], ", ")))
130+
end
117131

118132
@testset "@blk_str" begin
119-
@testset "invalid indicators" begin
120-
@test_throws LoadError macroexpand(@__MODULE__, :(@blk_str "" "fs_")) # Too many indicators
121-
@test_throws LoadError macroexpand(@__MODULE__, :(@blk_str "" "sf")) # Order matters
122-
@test_throws LoadError macroexpand(@__MODULE__, :(@blk_str "" "_s")) # Invalid style
123-
@test_throws LoadError macroexpand(@__MODULE__, :(@blk_str "" "f_")) # Invalid chomp
124-
@test_throws LoadError macroexpand(@__MODULE__, :(@blk_str "" "_")) # Invalid style/chomp
133+
@testset "quoting" begin
134+
# Use of double-quotes could cause failure if not handled properly:
135+
# `syntax: incomplete: invalid string syntax`
136+
@test blk"\"\n\"" == "\" \""
137+
end
138+
139+
@testset "string-interpolation" begin
140+
# If processing would accidentally take place in the interpolated code then
141+
# we could see "a b " as the result.
142+
@test blk"""$(join(("a", "b") .* "\n", ""))"""fc == "a b\n"
125143
end
126144
end
127145
end

0 commit comments

Comments
 (0)