@@ -259,17 +259,22 @@ extension Lexer {
259
259
/// error occurred
260
260
let error : ( kind: LexerError . Kind , position: Lexer . Cursor ) ?
261
261
let stateTransition : StateTransition ?
262
+ /// If set, overritdes the trailing trivia lexing mode of the current state
263
+ /// for this lexeme.
264
+ let trailingTriviaLexingMode : Lexer . Cursor . TriviaLexingMode ?
262
265
263
266
init (
264
267
_ tokenKind: RawTokenKind ,
265
268
flags: Lexer . Lexeme . Flags = [ ] ,
266
269
error: ( kind: LexerError . Kind , position: Cursor ) ? = nil ,
267
- stateTransition: StateTransition ? = nil
270
+ stateTransition: StateTransition ? = nil ,
271
+ trailingTriviaLexingMode: Lexer . Cursor . TriviaLexingMode ? = nil
268
272
) {
269
273
self . tokenKind = tokenKind
270
274
self . flags = flags
271
275
self . error = error
272
276
self . stateTransition = stateTransition
277
+ self . trailingTriviaLexingMode = trailingTriviaLexingMode
273
278
}
274
279
}
275
280
}
@@ -317,16 +322,9 @@ extension Lexer.Cursor {
317
322
318
323
// Trailing trivia.
319
324
let trailingTriviaStart = self
320
- let newlineInTrailingTrivia : NewlinePresence
321
- if let trailingTriviaMode = currentState. trailingTriviaLexingMode ( cursor: self ) {
322
- newlineInTrailingTrivia = self . lexTrivia ( mode: trailingTriviaMode)
323
- } else {
324
- newlineInTrailingTrivia = . absent
325
+ if let trailingTriviaMode = result. trailingTriviaLexingMode ?? currentState. trailingTriviaLexingMode ( cursor: self ) {
326
+ _ = self . lexTrivia ( mode: trailingTriviaMode)
325
327
}
326
- assert (
327
- newlineInTrailingTrivia == . absent,
328
- " trailingTrivia should not have a newline "
329
- )
330
328
331
329
if self . currentState. shouldPopStateWhenReachingNewlineInTrailingTrivia && self . is ( at: " \r " , " \n " ) {
332
330
self . stateStack. perform ( stateTransition: . pop, stateAllocator: stateAllocator)
@@ -853,7 +851,7 @@ extension Lexer.Cursor {
853
851
UInt8 ( ascii: " 9 " ) :
854
852
return self . lexNumber ( )
855
853
case UInt8 ( ascii: #"'"# ) , UInt8 ( ascii: #"""# ) :
856
- return self . lexStringQuote ( leadingDelimiterLength: 0 )
854
+ return self . lexStringQuote ( isOpening : true , leadingDelimiterLength: 0 )
857
855
858
856
case UInt8 ( ascii: " ` " ) :
859
857
return self . lexEscapedIdentifier ( )
@@ -878,7 +876,7 @@ extension Lexer.Cursor {
878
876
private mutating func lexAfterRawStringDelimiter( delimiterLength: Int ) -> Lexer . Result {
879
877
switch self . peek ( ) {
880
878
case UInt8 ( ascii: #"'"# ) , UInt8 ( ascii: #"""# ) :
881
- return self . lexStringQuote ( leadingDelimiterLength: delimiterLength)
879
+ return self . lexStringQuote ( isOpening : true , leadingDelimiterLength: delimiterLength)
882
880
case nil :
883
881
return Lexer . Result ( . eof)
884
882
default :
@@ -889,7 +887,7 @@ extension Lexer.Cursor {
889
887
private mutating func lexAfterStringLiteral( ) -> Lexer . Result {
890
888
switch self . peek ( ) {
891
889
case UInt8 ( ascii: #"'"# ) , UInt8 ( ascii: #"""# ) :
892
- return self . lexStringQuote ( leadingDelimiterLength: 0 )
890
+ return self . lexStringQuote ( isOpening : false , leadingDelimiterLength: 0 )
893
891
case nil :
894
892
return Lexer . Result ( . eof)
895
893
default :
@@ -984,9 +982,28 @@ extension Lexer.Cursor {
984
982
case normal
985
983
/// Don't lex newlines (`\r` and `\r`) as trivia
986
984
case noNewlines
985
+ /// Lex the characters that escape a newline in a multi-line string literal
986
+ /// as trivia.
987
+ ///
988
+ /// Matches the following regex: `\\?#*[ \t]*(\r\n|\r|\n)
989
+ case escapedNewlineInMultiLineStringLiteral
987
990
}
988
991
989
992
fileprivate mutating func lexTrivia( mode: TriviaLexingMode ) -> NewlinePresence {
993
+ if mode == . escapedNewlineInMultiLineStringLiteral {
994
+ _ = self . advance ( matching: " \\ " )
995
+ self . advance ( while: { $0 == " # " } )
996
+ self . advance ( while: { $0 == " " || $0 == " \t " } )
997
+ if self . advance ( matching: " \r " ) {
998
+ _ = self . advance ( matching: " \n " )
999
+ return . present
1000
+ } else if self . advance ( matching: " \n " ) {
1001
+ return . present
1002
+ } else {
1003
+ return . absent
1004
+ }
1005
+ }
1006
+
990
1007
var hasNewline = false
991
1008
while true {
992
1009
let start = self
@@ -1662,7 +1679,9 @@ extension Lexer.Cursor {
1662
1679
}
1663
1680
}
1664
1681
1665
- mutating func lexStringQuote( leadingDelimiterLength: Int ) -> Lexer . Result {
1682
+ /// `isOpening` is `true` if this string quote is the opening quote of a string
1683
+ /// literal and `false` if we are lexing the closing quote of a string literal.
1684
+ mutating func lexStringQuote( isOpening: Bool , leadingDelimiterLength: Int ) -> Lexer . Result {
1666
1685
if self . advance ( matching: " ' " ) {
1667
1686
return Lexer . Result ( . singleQuote, stateTransition: stateTransitionAfterLexingStringQuote ( kind: . singleQuote) )
1668
1687
}
@@ -1696,7 +1715,20 @@ extension Lexer.Cursor {
1696
1715
}
1697
1716
1698
1717
self = lookingForMultilineString
1699
- return Lexer . Result ( . multilineStringQuote, stateTransition: stateTransitionAfterLexingStringQuote ( kind: . multiLine) )
1718
+ let trailingTriviaLexingMode : TriviaLexingMode ?
1719
+ if isOpening && self . is ( at: " \n " , " \r " ) {
1720
+ // The opening quote of a multi-line string literal must be followed by
1721
+ // a newline that's not part of the represented string.
1722
+ trailingTriviaLexingMode = . escapedNewlineInMultiLineStringLiteral
1723
+ } else {
1724
+ trailingTriviaLexingMode = nil
1725
+ }
1726
+
1727
+ return Lexer . Result (
1728
+ . multilineStringQuote,
1729
+ stateTransition: stateTransitionAfterLexingStringQuote ( kind: . multiLine) ,
1730
+ trailingTriviaLexingMode: trailingTriviaLexingMode
1731
+ )
1700
1732
} else {
1701
1733
return Lexer . Result ( . stringQuote, stateTransition: stateTransitionAfterLexingStringQuote ( kind: . singleLine) )
1702
1734
}
@@ -1714,6 +1746,23 @@ extension Lexer.Cursor {
1714
1746
return tmp. advanceIfStringDelimiter ( delimiterLength: delimiterLength) && tmp. is ( at: " ( " )
1715
1747
}
1716
1748
1749
+ /// Returns `true` if we are positioned at a backslash that escapes the newline
1750
+ /// character in a multi-line string literal.
1751
+ private func isAtEscapedNewline( delimiterLength: Int ) -> Bool {
1752
+ guard self . is ( at: " \\ " ) else {
1753
+ return false
1754
+ }
1755
+
1756
+ var tmp = self
1757
+ let backslashConsumed = tmp. advance ( matching: " \\ " ) // Skip over the '\' to look for '#' and '('
1758
+ assert ( backslashConsumed)
1759
+ guard tmp. advanceIfStringDelimiter ( delimiterLength: delimiterLength) else {
1760
+ return false
1761
+ }
1762
+ tmp. advance ( while: { $0 == " " || $0 == " \t " } )
1763
+ return tmp. is ( at: " \r " , " \n " )
1764
+ }
1765
+
1717
1766
mutating func lexInStringLiteral( stringLiteralKind: StringLiteralKind , delimiterLength: Int ) -> Lexer . Result {
1718
1767
/*
1719
1768
if IsMultilineString && *CurPtr != '\n' && *CurPtr != '\r' {
@@ -1729,6 +1778,11 @@ extension Lexer.Cursor {
1729
1778
. stringSegment,
1730
1779
stateTransition: . push( newState: . inStringInterpolationStart( stringLiteralKind: stringLiteralKind) )
1731
1780
)
1781
+ } else if self . isAtEscapedNewline ( delimiterLength: delimiterLength) {
1782
+ return Lexer . Result (
1783
+ . stringSegment,
1784
+ trailingTriviaLexingMode: . escapedNewlineInMultiLineStringLiteral
1785
+ )
1732
1786
}
1733
1787
case UInt8 ( ascii: " \r " ) , UInt8 ( ascii: " \n " ) :
1734
1788
if stringLiteralKind == . multiLine {
0 commit comments