From cdc5e791888e61b8f78ee9ccb2fcd21b94c7493e Mon Sep 17 00:00:00 2001 From: Markus Kuhn Date: Sun, 15 Dec 2019 20:58:40 +0000 Subject: [PATCH 1/2] adding escape_raw_string() Implementation of the raw-string escaping convention. Can also be used to escape command-line arguments for Windows C/C++/Julia applications, which use the same \" escaping convention as Julia non-standard string literals. --- base/strings/io.jl | 49 ++++++++++++++++++++++++++++++++++++++++++++++ test/strings/io.jl | 1 + 2 files changed, 50 insertions(+) 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/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([],",") == "" From b6dd448e3afab4a4a0015bb2e6122b97f649e5da Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Mon, 30 Mar 2020 11:17:09 -0400 Subject: [PATCH 2/2] fix #35305, need escaping when printing string macro calls --- base/show.jl | 6 ++++-- test/show.jl | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) 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/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\""