diff --git a/base/show.jl b/base/show.jl index 74f9440b2e534..5cee604cbaed4 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1500,11 +1500,13 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In is_core_macro(args[1], "@big_str") print(io, args[3]) # x"y" and x"y"z - elseif isa(args[1], Symbol) && + elseif isa(args[1], Symbol) && nargs >= 3 && isa(args[3], String) && startswith(string(args[1]::Symbol), "@") && endswith(string(args[1]::Symbol), "_str") s = string(args[1]::Symbol) - print(io, s[2:prevind(s,end,4)], "\"", args[3], "\"") + print(io, s[2:prevind(s,end,4)], "\"") + escape_raw_string(io, args[3]) + print(io, "\"") if nargs == 4 print(io, args[4]) end diff --git a/base/strings/io.jl b/base/strings/io.jl index 0afddcbdefb88..22a8d109cf0e7 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -523,6 +523,55 @@ julia> println(raw"\\\\x \\\\\\"") """ macro raw_str(s); s; end +""" + escape_raw_string(s::AbstractString) + escape_raw_string(io, s::AbstractString) + +Escape a string in the manner used for parsing raw string literals. +For each double-quote (`"`) character in input string `s`, this +function counts the number _n_ of preceeding backslash (`\\`) characters, +and then increases there the number of backslashes from _n_ to 2_n_+1 +(even for _n_ = 0). It also doubles a sequence of backslashes at the end +of the string. + +This escaping convention is used in raw strings and other non-standard +string literals. (It also happens to be the escaping convention +expected by the Microsoft C/C++ compiler runtime when it parses a +command-line string into the argv[] array.) + +See also: [`escape_string`](@ref) +""" +function escape_raw_string(io, str::AbstractString) + escapes = 0 + for c in str + if c == '\\' + escapes += 1 + else + if c == '"' + # if one or more backslashes are followed by + # a double quote then escape all backslashes + # and the double quote + escapes = escapes * 2 + 1 + end + while escapes > 0 + write(io, '\\') + escapes -= 1 + end + escapes = 0 + write(io, c) + end + end + # also escape any trailing backslashes, + # so they do not affect the closing quote + while escapes > 0 + write(io, '\\') + write(io, '\\') + escapes -= 1 + end +end +escape_raw_string(str::AbstractString) = sprint(escape_raw_string, str; + sizehint = lastindex(str) + 2) + ## multiline strings ## """ diff --git a/test/show.jl b/test/show.jl index 430808387a533..8df1b87af3fde 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1133,6 +1133,9 @@ z856739 = [:a, :b] @test sprint(show, Meta.parse("a\"b\"c")) == ":(a\"b\"c)" @test sprint(show, Meta.parse("aa\"b\"")) == ":(aa\"b\")" @test sprint(show, Meta.parse("a\"b\"cc")) == ":(a\"b\"cc)" +@test sprint(show, Meta.parse("a\"\"\"issue \"35305\" \"\"\"")) == ":(a\"issue \\\"35305\\\" \")" +@test sprint(show, Meta.parse("a\"\$\"")) == ":(a\"\$\")" +@test sprint(show, Meta.parse("a\"\\b\"")) == ":(a\"\\b\")" # 11111111111111111111, 0xfffffffffffffffff, 1111...many digits... @test sprint(show, Meta.parse("11111111111111111111")) == ":(11111111111111111111)" # @test_repr "Base.@int128_str \"11111111111111111111\"" diff --git a/test/strings/io.jl b/test/strings/io.jl index 2454323551f17..9fd36d565408e 100644 --- a/test/strings/io.jl +++ b/test/strings/io.jl @@ -148,6 +148,7 @@ @test "aaa \\g \n" == unescape_string(str, ['g']) @test "aaa \\g \\n" == unescape_string(str, ['g', 'n']) end + @test Base.escape_raw_string(raw"\"\\\"\\-\\") == "\\\"\\\\\\\"\\\\-\\\\" end @testset "join()" begin @test join([]) == join([],",") == ""