Skip to content

Commit 6f454e8

Browse files
committed
allow escaping newlines with \ inside strings
This allows the use of `\` in front of newlines inside non-raw/non-custom string or command literals as a line continuation character, so the following newline is ignored. This way, long strings without any newlines in them don't have to be written in a single line or be broken up. I think we might also want to use this to improve the printing of long strings in the REPL by printing them as multiline strings, making use of `\` for long lines if necessary, but that can be discussed separately. The command literal part is technically breaking, but the current behavior is probably unintuitive enough that this can be considered a minor change. For string literals, this should be entirely non-breaking since a single `\` before a newline currently throws a parsing error. closes #37728
1 parent ccf7824 commit 6f454e8

File tree

3 files changed

+96
-9
lines changed

3 files changed

+96
-9
lines changed

base/shell.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ function shell_parse(str::AbstractString, interpolate::Bool=true;
8888
in_double_quotes = !in_double_quotes
8989
i = consume_upto!(arg, s, i, j)
9090
elseif c == '\\'
91-
if in_double_quotes
91+
if !isempty(st) && peek(st)[2] == '\n'
92+
i = consume_upto!(arg, s, i, j) + 1
93+
_ = popfirst!(st)
94+
elseif in_double_quotes
9295
isempty(st) && error("unterminated double quote")
9396
k, c′ = peek(st)
9497
if c′ == '"' || c′ == '$' || c′ == '\\'

src/julia-parser.scm

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@
311311
(define (numchk n s)
312312
(or n (error (string "invalid numeric constant \"" s "\""))))
313313

314+
(define (string-lastchar s)
315+
(string.char s (string.dec s (length s))))
316+
314317
(define (read-number port leadingdot neg)
315318
(let ((str (open-output-string))
316319
(pred char-numeric?)
@@ -408,7 +411,7 @@
408411
(string.sub s 1)
409412
s)
410413
r is-float32-literal)))
411-
(if (and (eqv? #\. (string.char s (string.dec s (length s))))
414+
(if (and (eqv? #\. (string-lastchar s))
412415
(let ((nxt (peek-char port)))
413416
(and (not (eof-object? nxt))
414417
(or (identifier-start-char? nxt)
@@ -2114,13 +2117,22 @@
21142117
(define (parse-string-literal s delim raw)
21152118
(let ((p (ts:port s)))
21162119
((if raw identity unescape-parsed-string-literal)
2117-
(if (eqv? (peek-char p) delim)
2118-
(if (eqv? (peek-char (take-char p)) delim)
2119-
(map-first strip-leading-newline
2120-
(dedent-triplequoted-string
2121-
(parse-string-literal- 2 (take-char p) s delim raw)))
2122-
(list ""))
2123-
(parse-string-literal- 0 p s delim raw)))))
2120+
(map (lambda (s) (if (and (not raw) (string? s))
2121+
(let ((spl (string-split s "\\\n")))
2122+
(foldl (lambda (line s)
2123+
(if (and (> (length s) 0) (eqv? (string-lastchar s) #\\))
2124+
(string s "\\\n" line)
2125+
(string s line)))
2126+
""
2127+
spl))
2128+
s))
2129+
(if (eqv? (peek-char p) delim)
2130+
(if (eqv? (peek-char (take-char p)) delim)
2131+
(map-first strip-leading-newline
2132+
(dedent-triplequoted-string
2133+
(parse-string-literal- 2 (take-char p) s delim raw)))
2134+
(list ""))
2135+
(parse-string-literal- 0 p s delim raw))))))
21242136

21252137
(define (strip-leading-newline s)
21262138
(let ((n (sizeof s)))

test/syntax.jl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,3 +2787,75 @@ macro m_nospecialize_unnamed_hygiene()
27872787
end
27882788

27892789
@test @m_nospecialize_unnamed_hygiene()(1) === Any
2790+
2791+
@testset "escaping newlines inside strings" begin
2792+
c = "c"
2793+
2794+
@test "a\
2795+
b" == "ab"
2796+
@test "a\
2797+
b" == "a b"
2798+
@test raw"a\
2799+
b" == "a\\\nb"
2800+
@test "a$c\
2801+
b" == "acb"
2802+
@test "\\
2803+
" == "\\\n"
2804+
2805+
2806+
@test """
2807+
a\
2808+
b""" == "ab"
2809+
@test """
2810+
a\
2811+
b""" == "a b"
2812+
@test """
2813+
a\
2814+
b""" == " ab"
2815+
@test raw"""
2816+
a\
2817+
b""" == "a\\\nb"
2818+
@test """
2819+
a$c\
2820+
b""" == "acb"
2821+
@test """
2822+
\\
2823+
""" == "\\\n"
2824+
2825+
2826+
@test `a\
2827+
b` == `ab`
2828+
@test `a\
2829+
b` == `a b`
2830+
@test `a$c\
2831+
b` == `acb`
2832+
@test `"a\
2833+
b"` == `ab`
2834+
@test `'a\
2835+
b'` == `ab`
2836+
@test `\\
2837+
` == `'\'`
2838+
2839+
2840+
@test ```
2841+
a\
2842+
b``` == `ab`
2843+
@test ```
2844+
a\
2845+
b``` == `a b`
2846+
@test ```
2847+
a\
2848+
b``` == ` ab`
2849+
@test ```
2850+
a$c\
2851+
b``` == `acb`
2852+
@test ```
2853+
"a\
2854+
b"``` == `ab`
2855+
@test ```
2856+
'a\
2857+
b'``` == `ab`
2858+
@test ```
2859+
\\
2860+
``` == `'\'`
2861+
end

0 commit comments

Comments
 (0)