@@ -109,6 +109,16 @@ extension SyntaxStringInterpolation: StringInterpolationProtocol {
109
109
) {
110
110
self . appendInterpolation ( buildable. formatted ( using: format) )
111
111
}
112
+
113
+ public mutating func appendInterpolation< Literal: ExpressibleByLiteralSyntax > (
114
+ literal value: Literal ,
115
+ format: BasicFormat = BasicFormat ( )
116
+ ) {
117
+ self . appendInterpolation (
118
+ ExprSyntax ( fromProtocol: value. makeLiteralSyntax ( ) ) ,
119
+ format: format
120
+ )
121
+ }
112
122
}
113
123
114
124
/// Syntax nodes that can be formed by a string interpolation involve source
@@ -135,6 +145,26 @@ enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
135
145
}
136
146
}
137
147
148
+ /// A Swift type whose value can be represented directly in source code by a Swift literal.
149
+ ///
150
+ /// Conforming types do not *contain* Swift source code; rather, they can be *expressed* in Swift source code, and this protocol can be used to get whatever source code would do that. For example, `String` is `ExpressibleByLiteralSyntax` but `StringLiteralExprSyntax` is not.
151
+ ///
152
+ /// Conforming types can be interpolated into a Swift source code literal with the syntax `\(literal: <value>)`:
153
+ ///
154
+ /// let greeting = "Hello, world!"
155
+ /// let expr1 = ExprSyntax("print(\(literal: greeting))")
156
+ /// // `expr1` is a syntax tree for `print("Hello, world!")`
157
+ ///
158
+ /// Note that quote marks are automatically added around the contents; you don't have to write them yourself. The conformance will automatically ensure the contents are correctly escaped, possibly by using raw literals or other language features:
159
+ ///
160
+ /// let msPath = "c:\\windows\\system32"
161
+ /// let expr2 = ExprSyntax("open(\(literal: msPath))")
162
+ /// // `expr2` might be a syntax tree for `open(#"c:\windows\system32"#)`
163
+ /// // or for `open("c:\\windows\\system32")`.
164
+ public protocol ExpressibleByLiteralSyntax {
165
+ func makeLiteralSyntax( ) -> ExprSyntaxProtocol
166
+ }
167
+
138
168
extension SyntaxExpressibleByStringInterpolation {
139
169
/// Initialize a syntax node by parsing the contents of the interpolation.
140
170
/// This function is marked `@_transparent` so that fatalErrors raised here
@@ -166,3 +196,218 @@ extension SyntaxExpressibleByStringInterpolation {
166
196
try self . init ( stringInterpolationOrThrow: interpolation)
167
197
}
168
198
}
199
+
200
+ // MARK: ExpressibleByLiteralSyntax conformances
201
+
202
+ extension Collection {
203
+ fileprivate func allIndices( where predicate: @escaping ( Element ) -> Bool ) -> UnfoldSequence < Index , Index > {
204
+ sequence ( state: startIndex) { i in
205
+ guard let newI = self [ i... ] . firstIndex ( where: predicate) else {
206
+ return nil
207
+ }
208
+ i = index ( after: newI)
209
+ return newI
210
+ }
211
+ }
212
+ }
213
+
214
+ extension Substring : ExpressibleByLiteralSyntax {
215
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
216
+ // TODO: Choose whether to use a single-line or multi-line literal.
217
+ let quote = TokenSyntax . stringQuote
218
+
219
+ // Select a raw delimiter long enough that we won't need to escape quotes or backslashes.
220
+ // Locate backslashes and quotes...
221
+ let problemIndices = allIndices ( where: #"\""# . contains ( _: ) )
222
+ // Count adjacent hashes and compute the largest number (-1 = no problem chars)...
223
+ let maxPoundCount = problemIndices. reduce ( - 1 ) { prevCount, i in
224
+ // Technically we don't need to check leading pounds for a backslash, but this is easier.
225
+ Swift . max (
226
+ prevCount,
227
+ self [ index ( after: i) ... ] . prefix ( while: { $0 == " # " } ) . count,
228
+ self [ ..< i] . reversed ( ) . prefix ( while: { $0 == " # " } ) . count
229
+ )
230
+ }
231
+ // And create the delimiter.
232
+ let rawDelimiter = String ( repeating: " # " , count: maxPoundCount + 1 )
233
+
234
+ // Assemble the string with newlines escaped.
235
+ var segment = " "
236
+ var previousStart = startIndex
237
+
238
+ // Scan for next newline; if found, append text up to newline, then an escape sequence for the newline, then continue at the next character.
239
+ for i in allIndices ( where: \. isNewline) {
240
+ segment += self [ previousStart..< i]
241
+
242
+ for scalar in self [ i] . unicodeScalars {
243
+ segment += " \\ " + rawDelimiter
244
+ switch scalar {
245
+ case " \r " :
246
+ segment += " r "
247
+ case " \n " :
248
+ segment += " n "
249
+ default :
250
+ segment += " u{ \( String ( scalar. value, radix: 16 ) ) } "
251
+ }
252
+ }
253
+
254
+ previousStart = index ( after: i)
255
+ }
256
+
257
+ // Append remainder of string.
258
+ segment += self [ previousStart... ]
259
+
260
+ // Now make these into syntax nodes.
261
+ let optRawDelimiter = rawDelimiter. isEmpty ? nil : rawDelimiter
262
+ return StringLiteralExpr (
263
+ openDelimiter: optRawDelimiter,
264
+ openQuote: quote,
265
+ segments: StringLiteralSegments {
266
+ StringSegment ( content: segment)
267
+ } ,
268
+ closeQuote: quote,
269
+ closeDelimiter: optRawDelimiter
270
+ )
271
+ }
272
+ }
273
+
274
+ extension String : ExpressibleByLiteralSyntax {
275
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
276
+ self [ ... ] . makeLiteralSyntax ( )
277
+ }
278
+ }
279
+
280
+ extension ExpressibleByLiteralSyntax where Self: BinaryInteger {
281
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
282
+ // TODO: Radix selection? Thousands separators?
283
+ let digits = String ( self , radix: 10 )
284
+ return IntegerLiteralExpr ( digits: digits)
285
+ }
286
+ }
287
+ extension Int : ExpressibleByLiteralSyntax { }
288
+ extension Int8 : ExpressibleByLiteralSyntax { }
289
+ extension Int16 : ExpressibleByLiteralSyntax { }
290
+ extension Int32 : ExpressibleByLiteralSyntax { }
291
+ extension Int64 : ExpressibleByLiteralSyntax { }
292
+ extension UInt : ExpressibleByLiteralSyntax { }
293
+ extension UInt8 : ExpressibleByLiteralSyntax { }
294
+ extension UInt16 : ExpressibleByLiteralSyntax { }
295
+ extension UInt32 : ExpressibleByLiteralSyntax { }
296
+ extension UInt64 : ExpressibleByLiteralSyntax { }
297
+
298
+ extension ExpressibleByLiteralSyntax where Self: FloatingPoint , Self: LosslessStringConvertible {
299
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
300
+ switch floatingPointClass {
301
+ case . positiveInfinity:
302
+ return MemberAccessExpr ( name: " infinity " )
303
+
304
+ case . quietNaN:
305
+ return MemberAccessExpr ( name: " nan " )
306
+
307
+ case . signalingNaN:
308
+ return MemberAccessExpr ( name: " signalingNaN " )
309
+
310
+ case . negativeInfinity, . negativeZero:
311
+ return PrefixOperatorExpr (
312
+ operatorToken: " - " ,
313
+ postfixExpression: ( - self ) . makeLiteralSyntax ( )
314
+ )
315
+
316
+ case . negativeNormal, . negativeSubnormal, . positiveZero, . positiveSubnormal, . positiveNormal:
317
+ // TODO: Thousands separators?
318
+ let digits = String ( self )
319
+ return FloatLiteralExpr ( floatingDigits: digits)
320
+ }
321
+
322
+ }
323
+ }
324
+ extension Float : ExpressibleByLiteralSyntax { }
325
+ extension Double : ExpressibleByLiteralSyntax { }
326
+
327
+ #if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
328
+ @available ( macOS 11 . 0 , iOS 14 . 0 , watchOS 7 . 0 , tvOS 14 . 0 , * )
329
+ extension Float16 : ExpressibleByLiteralSyntax { }
330
+ #endif
331
+
332
+ extension Bool : ExpressibleByLiteralSyntax {
333
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
334
+ BooleanLiteralExpr ( self )
335
+ }
336
+ }
337
+
338
+ extension ArraySlice : ExpressibleByLiteralSyntax where Element: ExpressibleByLiteralSyntax {
339
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
340
+ ArrayExpr (
341
+ leftSquare: . leftSquareBracket,
342
+ elements: ArrayElementList {
343
+ for elem in self {
344
+ ArrayElement ( expression: elem. makeLiteralSyntax ( ) )
345
+ }
346
+ } ,
347
+ rightSquare: . rightSquareBracket
348
+ )
349
+ }
350
+ }
351
+
352
+ extension Array : ExpressibleByLiteralSyntax where Element: ExpressibleByLiteralSyntax {
353
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
354
+ self [ ... ] . makeLiteralSyntax ( )
355
+ }
356
+ }
357
+
358
+ extension Set : ExpressibleByLiteralSyntax where Element: ExpressibleByLiteralSyntax {
359
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
360
+ // Sets are unordered. Sort the elements by their source-code representation to emit them in a stable order.
361
+ let elemSyntaxes = map {
362
+ $0. makeLiteralSyntax ( )
363
+ } . sorted {
364
+ $0. syntaxTextBytes. lexicographicallyPrecedes ( $1. syntaxTextBytes)
365
+ }
366
+
367
+ return ArrayExpr (
368
+ leftSquare: . leftSquareBracket,
369
+ elements: ArrayElementList {
370
+ for elemSyntax in elemSyntaxes {
371
+ ArrayElement ( expression: elemSyntax)
372
+ }
373
+ } ,
374
+ rightSquare: . rightSquareBracket
375
+ )
376
+ }
377
+ }
378
+
379
+ extension KeyValuePairs : ExpressibleByLiteralSyntax where Key: ExpressibleByLiteralSyntax , Value: ExpressibleByLiteralSyntax {
380
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
381
+ DictionaryExpr ( leftSquare: . leftSquareBracket, rightSquare: . rightSquareBracket) {
382
+ for elem in self {
383
+ DictionaryElement (
384
+ keyExpression: elem. key. makeLiteralSyntax ( ) ,
385
+ colon: . colon,
386
+ valueExpression: elem. value. makeLiteralSyntax ( )
387
+ )
388
+ }
389
+ }
390
+ }
391
+ }
392
+
393
+ extension Dictionary : ExpressibleByLiteralSyntax where Key: ExpressibleByLiteralSyntax , Value: ExpressibleByLiteralSyntax {
394
+ public func makeLiteralSyntax( ) -> ExprSyntaxProtocol {
395
+ // Dictionaries are unordered. Sort the elements by their keys' source-code representation to emit them in a stable order.
396
+ let elemSyntaxes = map {
397
+ ( key: $0. key. makeLiteralSyntax ( ) , value: $0. value. makeLiteralSyntax ( ) )
398
+ } . sorted {
399
+ $0. key. syntaxTextBytes. lexicographicallyPrecedes ( $1. key. syntaxTextBytes)
400
+ }
401
+
402
+ return DictionaryExpr ( leftSquare: . leftSquareBracket, rightSquare: . rightSquareBracket) {
403
+ for elemSyntax in elemSyntaxes {
404
+ DictionaryElement (
405
+ keyExpression: elemSyntax. key,
406
+ colon: . colon,
407
+ valueExpression: elemSyntax. value
408
+ )
409
+ }
410
+ }
411
+ }
412
+ }
413
+
0 commit comments