@@ -257,17 +257,22 @@ extension Lexer {
257
257
let flags : Lexer . Lexeme . Flags
258
258
let error : LexerError ?
259
259
let stateTransition : StateTransition ?
260
+ /// If set, overritdes the trailing trivia lexing mode of the current state
261
+ /// for this lexeme.
262
+ let trailingTriviaLexingMode : Lexer . Cursor . TriviaLexingMode ?
260
263
261
264
init (
262
265
_ tokenKind: RawTokenKind ,
263
266
flags: Lexer . Lexeme . Flags = [ ] ,
264
267
error: LexerError ? = nil ,
265
- stateTransition: StateTransition ? = nil
268
+ stateTransition: StateTransition ? = nil ,
269
+ trailingTriviaLexingMode: Lexer . Cursor . TriviaLexingMode ? = nil
266
270
) {
267
271
self . tokenKind = tokenKind
268
272
self . flags = flags
269
273
self . error = error
270
274
self . stateTransition = stateTransition
275
+ self . trailingTriviaLexingMode = trailingTriviaLexingMode
271
276
}
272
277
}
273
278
}
@@ -315,16 +320,9 @@ extension Lexer.Cursor {
315
320
316
321
// Trailing trivia.
317
322
let trailingTriviaStart = self
318
- let newlineInTrailingTrivia : NewlinePresence
319
- if let trailingTriviaMode = currentState. trailingTriviaLexingMode ( cursor: self ) {
320
- newlineInTrailingTrivia = self . lexTrivia ( mode: trailingTriviaMode)
321
- } else {
322
- newlineInTrailingTrivia = . absent
323
+ if let trailingTriviaMode = result. trailingTriviaLexingMode ?? currentState. trailingTriviaLexingMode ( cursor: self ) {
324
+ _ = self . lexTrivia ( mode: trailingTriviaMode)
323
325
}
324
- assert (
325
- newlineInTrailingTrivia == . absent,
326
- " trailingTrivia should not have a newline "
327
- )
328
326
329
327
if self . currentState. shouldPopStateWhenReachingNewlineInTrailingTrivia && self . is ( at: " \r " , " \n " ) {
330
328
self . stateStack. perform ( stateTransition: . pop, stateAllocator: stateAllocator)
@@ -902,7 +900,7 @@ extension Lexer.Cursor {
902
900
UInt8 ( ascii: " 9 " ) :
903
901
return self . lexNumber ( )
904
902
case UInt8 ( ascii: #"'"# ) , UInt8 ( ascii: #"""# ) :
905
- return self . lexStringQuote ( leadingDelimiterLength: 0 )
903
+ return self . lexStringQuote ( isOpening : true , leadingDelimiterLength: 0 )
906
904
907
905
case UInt8 ( ascii: " ` " ) :
908
906
return self . lexEscapedIdentifier ( )
@@ -927,7 +925,7 @@ extension Lexer.Cursor {
927
925
private mutating func lexAfterRawStringDelimiter( delimiterLength: Int ) -> Lexer . Result {
928
926
switch self . peek ( ) {
929
927
case UInt8 ( ascii: #"'"# ) , UInt8 ( ascii: #"""# ) :
930
- return self . lexStringQuote ( leadingDelimiterLength: delimiterLength)
928
+ return self . lexStringQuote ( isOpening : true , leadingDelimiterLength: delimiterLength)
931
929
case nil :
932
930
return Lexer . Result ( . eof)
933
931
default :
@@ -938,7 +936,7 @@ extension Lexer.Cursor {
938
936
private mutating func lexAfterStringLiteral( ) -> Lexer . Result {
939
937
switch self . peek ( ) {
940
938
case UInt8 ( ascii: #"'"# ) , UInt8 ( ascii: #"""# ) :
941
- return self . lexStringQuote ( leadingDelimiterLength: 0 )
939
+ return self . lexStringQuote ( isOpening : false , leadingDelimiterLength: 0 )
942
940
case nil :
943
941
return Lexer . Result ( . eof)
944
942
default :
@@ -1025,9 +1023,28 @@ extension Lexer.Cursor {
1025
1023
case normal
1026
1024
/// Don't lex newlines (`\r` and `\r`) as trivia
1027
1025
case noNewlines
1026
+ /// Lex the characters that escape a newline in a multi-line string literal
1027
+ /// as trivia.
1028
+ ///
1029
+ /// Matches the following regex: `\\?#*[ \t]*(\r\n|\r|\n)
1030
+ case escapedNewlineInMultiLineStringLiteral
1028
1031
}
1029
1032
1030
1033
fileprivate mutating func lexTrivia( mode: TriviaLexingMode ) -> NewlinePresence {
1034
+ if mode == . escapedNewlineInMultiLineStringLiteral {
1035
+ _ = self . advance ( matching: " \\ " )
1036
+ self . advance ( while: { $0 == " # " } )
1037
+ self . advance ( while: { $0 == " " || $0 == " \t " } )
1038
+ if self . advance ( matching: " \r " ) {
1039
+ _ = self . advance ( matching: " \n " )
1040
+ return . present
1041
+ } else if self . advance ( matching: " \n " ) {
1042
+ return . present
1043
+ } else {
1044
+ return . absent
1045
+ }
1046
+ }
1047
+
1031
1048
var hasNewline = false
1032
1049
while true {
1033
1050
let start = self
@@ -1701,7 +1718,9 @@ extension Lexer.Cursor {
1701
1718
}
1702
1719
}
1703
1720
1704
- mutating func lexStringQuote( leadingDelimiterLength: Int ) -> Lexer . Result {
1721
+ /// `isOpening` is `true` if this string quote is the opening quote of a string
1722
+ /// literal and `false` if we are lexing the closing quote of a string literal.
1723
+ mutating func lexStringQuote( isOpening: Bool , leadingDelimiterLength: Int ) -> Lexer . Result {
1705
1724
if self . advance ( matching: " ' " ) {
1706
1725
return Lexer . Result ( . singleQuote, stateTransition: stateTransitionAfterLexingStringQuote ( kind: . singleQuote) )
1707
1726
}
@@ -1735,7 +1754,20 @@ extension Lexer.Cursor {
1735
1754
}
1736
1755
1737
1756
self = lookingForMultilineString
1738
- return Lexer . Result ( . multilineStringQuote, stateTransition: stateTransitionAfterLexingStringQuote ( kind: . multiLine) )
1757
+ let trailingTriviaLexingMode : TriviaLexingMode ?
1758
+ if isOpening && self . is ( at: " \n " , " \r " ) {
1759
+ // The opening quote of a multi-line string literal must be followed by
1760
+ // a newline that's not part of the represented string.
1761
+ trailingTriviaLexingMode = . escapedNewlineInMultiLineStringLiteral
1762
+ } else {
1763
+ trailingTriviaLexingMode = nil
1764
+ }
1765
+
1766
+ return Lexer . Result (
1767
+ . multilineStringQuote,
1768
+ stateTransition: stateTransitionAfterLexingStringQuote ( kind: . multiLine) ,
1769
+ trailingTriviaLexingMode: trailingTriviaLexingMode
1770
+ )
1739
1771
} else {
1740
1772
return Lexer . Result ( . stringQuote, stateTransition: stateTransitionAfterLexingStringQuote ( kind: . singleLine) )
1741
1773
}
@@ -1753,6 +1785,23 @@ extension Lexer.Cursor {
1753
1785
return tmp. advanceIfStringDelimiter ( delimiterLength: delimiterLength) && tmp. is ( at: " ( " )
1754
1786
}
1755
1787
1788
+ /// Returns `true` if we are positioned at a backslash that escapes the newline
1789
+ /// character in a multi-line string literal.
1790
+ private func isAtEscapedNewline( delimiterLength: Int ) -> Bool {
1791
+ guard self . is ( at: " \\ " ) else {
1792
+ return false
1793
+ }
1794
+
1795
+ var tmp = self
1796
+ let backslashConsumed = tmp. advance ( matching: " \\ " ) // Skip over the '\' to look for '#' and '('
1797
+ assert ( backslashConsumed)
1798
+ guard tmp. advanceIfStringDelimiter ( delimiterLength: delimiterLength) else {
1799
+ return false
1800
+ }
1801
+ tmp. advance ( while: { $0 == " " || $0 == " \t " } )
1802
+ return tmp. is ( at: " \r " , " \n " )
1803
+ }
1804
+
1756
1805
mutating func lexInStringLiteral( stringLiteralKind: StringLiteralKind , delimiterLength: Int ) -> Lexer . Result {
1757
1806
/*
1758
1807
if IsMultilineString && *CurPtr != '\n' && *CurPtr != '\r' {
@@ -1768,6 +1817,11 @@ extension Lexer.Cursor {
1768
1817
. stringSegment,
1769
1818
stateTransition: . push( newState: . inStringInterpolationStart( stringLiteralKind: stringLiteralKind) )
1770
1819
)
1820
+ } else if self . isAtEscapedNewline ( delimiterLength: delimiterLength) {
1821
+ return Lexer . Result (
1822
+ . stringSegment,
1823
+ trailingTriviaLexingMode: . escapedNewlineInMultiLineStringLiteral
1824
+ )
1771
1825
}
1772
1826
case UInt8 ( ascii: " \r " ) , UInt8 ( ascii: " \n " ) :
1773
1827
if stringLiteralKind == . multiLine {
0 commit comments