@@ -44,6 +44,176 @@ extension Parser {
44
44
}
45
45
}
46
46
47
+ /// If `text` only consists of indentation whitespace (space and tab), return
48
+ /// the trivia pieces that represent `text`, otherwise return `nil`.
49
+ private func parseIndentationTrivia( text: SyntaxText ) -> [ RawTriviaPiece ] ? {
50
+ let trivia = TriviaParser . parseTrivia ( text, position: . leading)
51
+ if trivia. allSatisfy ( { $0. isIndentationWhitespace } ) {
52
+ return trivia
53
+ } else {
54
+ return nil
55
+ }
56
+ }
57
+
58
+ // FIXME: Handle \r and \r\n if needed in here
59
+ private func postProcessMultilineStringLiteral(
60
+ openQuote: RawTokenSyntax ,
61
+ segments allSegments: [ RawStringLiteralSegmentsSyntax . Element ] ,
62
+ closeQuote: RawTokenSyntax
63
+ ) -> (
64
+ unexpectedBeforeOpenQuote: [ RawTokenSyntax ] ,
65
+ openQuote: RawTokenSyntax ,
66
+ segments: [ RawStringLiteralSegmentsSyntax . Element ] ,
67
+ unexpectedBeforeCloseQuote: [ RawTokenSyntax ] ,
68
+ closeQuote: RawTokenSyntax
69
+ ) {
70
+ // -------------------------------------------------------------------------
71
+ // Precondition
72
+
73
+ assert ( openQuote. trailingTriviaByteLength == 0 , " Open quote produced by the lexer should not have trailing trivia because we would drop it during post-processing " )
74
+ assert ( closeQuote. leadingTriviaByteLength == 0 , " Closing quote produced by the lexer should not have leading trivia because we would drop it during post-processing " )
75
+ assert (
76
+ allSegments. allSatisfy {
77
+ if case . stringSegment( let segment) = $0 {
78
+ return segment. unexpectedBeforeContent == nil
79
+ && segment. unexpectedAfterContent == nil
80
+ && segment. content. leadingTriviaByteLength == 0
81
+ && segment. content. trailingTriviaByteLength == 0
82
+ } else {
83
+ return true
84
+ }
85
+ } ,
86
+ " String segement produced by the lexer should not have unexpected text or trivia because we would drop it during post-processing "
87
+ )
88
+
89
+ // -------------------------------------------------------------------------
90
+ // Variables
91
+
92
+ var middleSegments = allSegments
93
+ let lastSegment = !middleSegments. isEmpty ? middleSegments. removeLast ( ) . as ( RawStringSegmentSyntax . self) : nil
94
+ let firstSegment = !middleSegments. isEmpty ? middleSegments. removeFirst ( ) . as ( RawStringSegmentSyntax . self) : nil
95
+
96
+ let indentation : SyntaxText
97
+ let indentationTrivia : [ RawTriviaPiece ]
98
+
99
+ var unexpectedBeforeOpenQuote : [ RawTokenSyntax ] = [ ]
100
+ var openQuote = openQuote
101
+ var unexpectedBeforeCloseQuote : [ RawTokenSyntax ] = [ ]
102
+ var closeQuote = closeQuote
103
+
104
+ // -------------------------------------------------------------------------
105
+ // Check close quote is on new line
106
+
107
+ let closeDelimiterOnNewLine : Bool
108
+ switch middleSegments. last {
109
+ case . stringSegment( let lastMiddleSegment) :
110
+ if lastMiddleSegment. content. tokenText. hasSuffix ( " \n " ) {
111
+ // The newline at the end of the last line in the string literal is not part of the represented string.
112
+ // Mark it as trivia.
113
+ middleSegments [ middleSegments. count - 1 ] = . stringSegment(
114
+ RawStringSegmentSyntax (
115
+ content: lastMiddleSegment. content. reclassifyAsTrailingTrivia ( [ . newlines( 1 ) ] , arena: self . arena) ,
116
+ arena: self . arena
117
+ )
118
+ )
119
+ closeDelimiterOnNewLine = true
120
+ } else {
121
+ closeDelimiterOnNewLine = false
122
+ }
123
+ case . expressionSegment:
124
+ closeDelimiterOnNewLine = false
125
+ case nil :
126
+ closeDelimiterOnNewLine = firstSegment? . content. tokenText. hasSuffix ( " \n " ) ?? false
127
+ }
128
+
129
+ if !closeDelimiterOnNewLine {
130
+ unexpectedBeforeCloseQuote = [ closeQuote]
131
+ closeQuote = RawTokenSyntax ( missing: closeQuote. tokenKind, leadingTriviaPieces: [ . newlines( 1 ) ] , arena: self . arena)
132
+
133
+ // The closing delimiter doesn't start on a new line and thus it doesn't
134
+ // make sense to try and extract indentation from it.
135
+ return ( unexpectedBeforeOpenQuote, openQuote, allSegments, unexpectedBeforeCloseQuote, closeQuote)
136
+ }
137
+
138
+ // -------------------------------------------------------------------------
139
+ // Parse indentation
140
+
141
+ if let lastSegment = lastSegment,
142
+ let parsedTrivia = parseIndentationTrivia ( text: lastSegment. content. tokenText)
143
+ {
144
+ indentationTrivia = parsedTrivia
145
+ indentation = lastSegment. content. tokenText
146
+ closeQuote = closeQuote. extendingLeadingTrivia ( by: parsedTrivia, arena: self . arena)
147
+ } else {
148
+ if let lastSegment = lastSegment {
149
+ indentationTrivia = TriviaParser . parseTrivia ( lastSegment. content. tokenText, position: . leading) . prefix ( while: { $0. isIndentationWhitespace } )
150
+ let indentationByteLength = indentationTrivia. reduce ( 0 , { $0 + $1. byteLength } )
151
+ indentation = SyntaxText ( rebasing: lastSegment. content. tokenText [ 0 ..< indentationByteLength] )
152
+ middleSegments. append ( . stringSegment( lastSegment) )
153
+ } else {
154
+ indentationTrivia = [ ]
155
+ indentation = " "
156
+ }
157
+
158
+ unexpectedBeforeCloseQuote = [ closeQuote]
159
+ closeQuote = RawTokenSyntax ( missing: closeQuote. tokenKind, leadingTriviaPieces: [ . newlines( 1 ) ] + indentationTrivia, arena: self . arena)
160
+ }
161
+
162
+ // -------------------------------------------------------------------------
163
+ // Check open quote followed by newline
164
+
165
+ if firstSegment? . content. tokenText == " \n " {
166
+ openQuote = openQuote. extendingTrailingTrivia ( by: [ . newlines( 1 ) ] , arena: self . arena)
167
+ } else {
168
+ if let firstSegment = firstSegment {
169
+ middleSegments. insert ( . stringSegment( firstSegment) , at: 0 )
170
+ }
171
+ unexpectedBeforeOpenQuote = [ openQuote]
172
+ openQuote = RawTokenSyntax ( missing: openQuote. tokenKind, trailingTriviaPieces: [ . newlines( 1 ) ] + indentationTrivia, arena: self . arena)
173
+ }
174
+
175
+ // -------------------------------------------------------------------------
176
+ // Check indentation of segments
177
+
178
+ for (index, segment) in middleSegments. enumerated ( ) {
179
+ switch segment {
180
+ case . stringSegment( var segment) :
181
+ assert ( segment. unexpectedBeforeContent == nil , " Segment should not have unexpected before content " )
182
+ assert ( segment. content. leadingTriviaByteLength == 0 , " Segment should not have leading trivia " )
183
+ if segment. content. tokenText. hasPrefix ( indentation) {
184
+ segment = RawStringSegmentSyntax (
185
+ content: segment. content. reclassifyAsLeadingTrivia ( indentationTrivia, arena: self . arena) ,
186
+ arena: self . arena
187
+ )
188
+ } else {
189
+ // TODO: Diagnose
190
+ }
191
+ if segment. content. tokenText. hasSuffix ( " \\ \n " ) {
192
+ // TODO: Add a backslash trivia kind
193
+ segment = RawStringSegmentSyntax (
194
+ content: segment. content. reclassifyAsTrailingTrivia ( [ . unexpectedText( " \\ " ) , . newlines( 1 ) ] , arena: self . arena) ,
195
+ arena: self . arena
196
+ )
197
+ }
198
+ middleSegments [ index] = . stringSegment( segment)
199
+ case . expressionSegment:
200
+ // TODO: Check indentation
201
+ break
202
+ }
203
+ }
204
+
205
+ // -------------------------------------------------------------------------
206
+ // Done
207
+
208
+ return (
209
+ unexpectedBeforeOpenQuote,
210
+ openQuote,
211
+ middleSegments,
212
+ unexpectedBeforeCloseQuote,
213
+ closeQuote
214
+ )
215
+ }
216
+
47
217
/// Parse a string literal expression.
48
218
@_spi ( RawSyntax)
49
219
public mutating func parseStringLiteral( ) -> RawStringLiteralExprSyntax {
@@ -106,21 +276,34 @@ extension Parser {
106
276
}
107
277
108
278
/// Parse close quote.
109
- let ( unexpectedBeforeCloseQuote , closeQuote) = self . expect ( openQuote. tokenKind)
279
+ let closeQuote = self . expectWithoutRecovery ( openQuote. tokenKind)
110
280
111
281
let ( unexpectedBeforeCloseDelimiter, closeDelimiter) = self . parseStringDelimiter ( openDelimiter: openDelimiter)
112
282
113
- /// Construct the literal expression.
114
- return RawStringLiteralExprSyntax (
115
- openDelimiter: openDelimiter,
116
- unexpectedBeforeOpenQuote,
117
- openQuote: openQuote,
118
- segments: RawStringLiteralSegmentsSyntax ( elements: segments, arena: self . arena) ,
119
- unexpectedBeforeCloseQuote,
120
- closeQuote: closeQuote,
121
- unexpectedBeforeCloseDelimiter,
122
- closeDelimiter: closeDelimiter,
123
- arena: self . arena
124
- )
283
+ if openQuote. tokenKind == . multilineStringQuote, !openQuote. isMissing, !closeQuote. isMissing {
284
+ let postProcessed = postProcessMultilineStringLiteral ( openQuote: openQuote, segments: segments, closeQuote: closeQuote)
285
+ return RawStringLiteralExprSyntax (
286
+ openDelimiter: openDelimiter,
287
+ RawUnexpectedNodesSyntax ( combining: unexpectedBeforeOpenQuote, postProcessed. unexpectedBeforeOpenQuote, arena: self . arena) ,
288
+ openQuote: postProcessed. openQuote,
289
+ segments: RawStringLiteralSegmentsSyntax ( elements: postProcessed. segments, arena: self . arena) ,
290
+ RawUnexpectedNodesSyntax ( postProcessed. unexpectedBeforeCloseQuote, arena: self . arena) ,
291
+ closeQuote: postProcessed. closeQuote,
292
+ unexpectedBeforeCloseDelimiter,
293
+ closeDelimiter: closeDelimiter,
294
+ arena: self . arena
295
+ )
296
+ } else {
297
+ return RawStringLiteralExprSyntax (
298
+ openDelimiter: openDelimiter,
299
+ unexpectedBeforeOpenQuote,
300
+ openQuote: openQuote,
301
+ segments: RawStringLiteralSegmentsSyntax ( elements: segments, arena: self . arena) ,
302
+ closeQuote: closeQuote,
303
+ unexpectedBeforeCloseDelimiter,
304
+ closeDelimiter: closeDelimiter,
305
+ arena: self . arena
306
+ )
307
+ }
125
308
}
126
309
}
0 commit comments