@@ -136,24 +136,32 @@ extension Parser {
136
136
/// not part of the represented string. If the last line has its newline
137
137
/// escaped by a trailing `\`, mark that string segment as unexpected and
138
138
/// generate a missing segment that doesn't have a trailing `\`.
139
- private func reclassifyNewlineOfLastSegmentAsTrivia( firstSegment: RawStringSegmentSyntax ? , middleSegments: inout [ RawStringLiteralSegmentsSyntax . Element ] ) -> Bool {
139
+ private func reclassifyNewlineOfLastSegmentAsTrivia( rawStringDelimitersToken : RawTokenSyntax ? , firstSegment: RawStringSegmentSyntax ? , middleSegments: inout [ RawStringLiteralSegmentsSyntax . Element ] ) -> Bool {
140
140
switch middleSegments. last {
141
141
case . stringSegment( let lastMiddleSegment) :
142
142
if let newlineSuffix = lastMiddleSegment. content. tokenText. newlineSuffix {
143
143
// The newline at the end of the last line in the string literal is not part of the represented string.
144
144
// Mark it as trivia.
145
145
var content = lastMiddleSegment. content. reclassifyAsTrailingTrivia ( [ newlineSuffix] , arena: self . arena)
146
146
var unexpectedBeforeContent : RawTokenSyntax ?
147
- if content. tokenText. hasSuffix ( " \\ " ) {
148
- // The newline on the last line must not be escaped
149
- unexpectedBeforeContent = content
150
- content = RawTokenSyntax (
151
- missing: . stringSegment,
152
- text: SyntaxText ( rebasing: content. tokenText [ 0 ..< content. tokenText. count - 1 ] ) ,
153
- leadingTriviaPieces: content. leadingTriviaPieces,
154
- trailingTriviaPieces: content. trailingTriviaPieces,
155
- arena: self . arena
156
- )
147
+ if content. tokenText. hasNonEscapedBackslashSuffix ( rawStringDelimiters: rawStringDelimitersToken? . tokenText ?? " " , newline: " " ) {
148
+ // The newline on the last line must not be escaped in non-raw string literals.
149
+
150
+ if let rawStringDelimitersToken = rawStringDelimitersToken {
151
+ // ... except in raw string literals where the C++ parser accepts the
152
+ // last line to be escaped. To match the C++ parser's behavior, we also
153
+ // need to allow escaped newline in raw string literals.
154
+ content = content. reclassifyAsTrailingTrivia ( [ . backslashes( 1 ) , . pounds( rawStringDelimitersToken. tokenText. count) ] , arena: self . arena)
155
+ } else {
156
+ unexpectedBeforeContent = content
157
+ content = RawTokenSyntax (
158
+ missing: . stringSegment,
159
+ text: SyntaxText ( rebasing: content. tokenText [ 0 ..< content. tokenText. count - 1 ] ) ,
160
+ leadingTriviaPieces: content. leadingTriviaPieces,
161
+ trailingTriviaPieces: content. trailingTriviaPieces,
162
+ arena: self . arena
163
+ )
164
+ }
157
165
}
158
166
159
167
middleSegments [ middleSegments. count - 1 ] = . stringSegment(
@@ -185,6 +193,7 @@ extension Parser {
185
193
/// `.insufficientIndentationInMultilineStringLiteral` lexer error will be
186
194
/// attached to the string segment token.
187
195
private func postProcessIndentationAndEscapedNewlineOfMiddleSegments(
196
+ rawStringDelimitersToken: RawTokenSyntax ? ,
188
197
middleSegments: inout [ RawStringLiteralSegmentsSyntax . Element ] ,
189
198
isFirstSegmentOnNewLine: Bool ,
190
199
indentation: SyntaxText ,
@@ -231,16 +240,19 @@ extension Parser {
231
240
232
241
isSegmentOnNewLine = segment. content. tokenText. newlineSuffix != nil
233
242
243
+ let rawDelimiters = rawStringDelimitersToken? . tokenText ?? " "
244
+ assert ( rawDelimiters. allSatisfy ( { $0 == UInt8 ( ascii: " # " ) } ) )
245
+
234
246
// If the segment has a `\` in front of its trailing newline, that newline
235
247
// is not part of the represented string and should be trivia.
236
248
237
249
let backslashNewlineSuffix : [ RawTriviaPiece ] ?
238
- if segment. content. tokenText. hasSuffix ( " \\ \r \n " ) {
239
- backslashNewlineSuffix = [ . backslashes( 1 ) , . carriageReturnLineFeeds( 1 ) ]
240
- } else if segment. content. tokenText. hasSuffix ( " \\ \n " ) {
241
- backslashNewlineSuffix = [ . backslashes( 1 ) , . newlines( 1 ) ]
242
- } else if segment. content. tokenText. hasSuffix ( " \\ \r " ) {
243
- backslashNewlineSuffix = [ . backslashes( 1 ) , . carriageReturns( 1 ) ]
250
+ if segment. content. tokenText. hasNonEscapedBackslashSuffix ( rawStringDelimiters : rawDelimiters , newline : " \r \n " ) {
251
+ backslashNewlineSuffix = [ . backslashes( 1 ) , . pounds ( rawDelimiters . count ) , . carriageReturnLineFeeds( 1 ) ] . filter { $0 . byteLength > 0 }
252
+ } else if segment. content. tokenText. hasNonEscapedBackslashSuffix ( rawStringDelimiters : rawDelimiters , newline : " \n " ) {
253
+ backslashNewlineSuffix = [ . backslashes( 1 ) , . pounds ( rawDelimiters . count ) , . newlines( 1 ) ] . filter { $0 . byteLength > 0 }
254
+ } else if segment. content. tokenText. hasNonEscapedBackslashSuffix ( rawStringDelimiters : rawDelimiters , newline : " \r " ) {
255
+ backslashNewlineSuffix = [ . backslashes( 1 ) , . pounds ( rawDelimiters . count ) , . carriageReturns( 1 ) ] . filter { $0 . byteLength > 0 }
244
256
} else {
245
257
backslashNewlineSuffix = nil
246
258
}
@@ -281,6 +293,7 @@ extension Parser {
281
293
/// escaped by a trailing `\`, mark that string segment as unexpected and
282
294
/// generate a missing segment that doesn't have a trailing `\`.
283
295
private func postProcessMultilineStringLiteral(
296
+ rawStringDelimitersToken: RawTokenSyntax ? ,
284
297
openQuote: RawTokenSyntax ,
285
298
segments allSegments: [ RawStringLiteralSegmentsSyntax . Element ] ,
286
299
closeQuote: RawTokenSyntax
@@ -331,6 +344,7 @@ extension Parser {
331
344
// Check that the close quote is on new line
332
345
333
346
let closeDelimiterOnNewLine = reclassifyNewlineOfLastSegmentAsTrivia (
347
+ rawStringDelimitersToken: rawStringDelimitersToken,
334
348
firstSegment: firstSegment,
335
349
middleSegments: & middleSegments
336
350
)
@@ -391,6 +405,7 @@ extension Parser {
391
405
// Check indentation of segments and escaped newlines at end of segment
392
406
393
407
postProcessIndentationAndEscapedNewlineOfMiddleSegments (
408
+ rawStringDelimitersToken: rawStringDelimitersToken,
394
409
middleSegments: & middleSegments,
395
410
isFirstSegmentOnNewLine: isFirstSegmentOnNewLine,
396
411
indentation: indentation,
@@ -525,7 +540,7 @@ extension Parser {
525
540
let ( unexpectedBeforeCloseDelimiter, closeDelimiter) = self . parseStringDelimiter ( openDelimiter: openDelimiter)
526
541
527
542
if openQuote. tokenKind == . multilineStringQuote, !openQuote. isMissing, !closeQuote. isMissing {
528
- let postProcessed = postProcessMultilineStringLiteral ( openQuote: openQuote, segments: segments, closeQuote: closeQuote)
543
+ let postProcessed = postProcessMultilineStringLiteral ( rawStringDelimitersToken : openDelimiter , openQuote: openQuote, segments: segments, closeQuote: closeQuote)
529
544
return RawStringLiteralExprSyntax (
530
545
openDelimiter: openDelimiter,
531
546
RawUnexpectedNodesSyntax ( combining: unexpectedBeforeOpenQuote, postProcessed. unexpectedBeforeOpenQuote, arena: self . arena) ,
@@ -552,3 +567,20 @@ extension Parser {
552
567
}
553
568
}
554
569
}
570
+
571
+ // MARK: - Utilities
572
+
573
+ fileprivate extension SyntaxText {
574
+ private func hasSuffix( _ other: String ) -> Bool {
575
+ var other = other
576
+ return other. withSyntaxText { self . hasSuffix ( $0) }
577
+ }
578
+
579
+ /// Returns `true` if this string end with `\<rawStringDelimiters><newline>`
580
+ /// but the backslash is not escaped, i.e. doesn't end with
581
+ /// `\\<rawStringDelimiters><newline>`
582
+ func hasNonEscapedBackslashSuffix( rawStringDelimiters: SyntaxText , newline: SyntaxText ) -> Bool {
583
+ return self . hasSuffix ( " \\ \( rawStringDelimiters) \( newline) " )
584
+ && !self . hasSuffix ( " \\ \\ \( rawStringDelimiters) \( newline) " )
585
+ }
586
+ }
0 commit comments