11
11
//===----------------------------------------------------------------------===//
12
12
13
13
import SwiftSyntax
14
- import Foundation
15
-
16
- /// A regular expression matching sequences of `#`s with an adjacent quote or
17
- /// interpolation. Used to determine the number of `#`s for a raw string literal.
18
- private let rawStringPotentialEscapesPattern = try ! NSRegularExpression (
19
- pattern: [
20
- #""(#*)"# , // Match potential opening delimiters
21
- #"(#*)""# , // Match potential closing delimiters
22
- #"\\(#*)"# , // Match potential interpolations
23
- ] . joined ( separator: " | " )
24
- )
25
14
26
15
extension StringLiteralExpr {
27
16
/// Creates a string literal, optionally specifying quotes and delimiters.
@@ -41,17 +30,12 @@ extension StringLiteralExpr {
41
30
var openDelimiter = openDelimiter
42
31
var closeDelimiter = closeDelimiter
43
32
if openDelimiter == nil , closeDelimiter == nil {
44
- // Match potential escapes in the string
45
- let matches = rawStringPotentialEscapesPattern. matches ( in: content, range: NSRange ( content. startIndex... , in: content) )
46
-
47
- // Find longest sequence of `#`s by taking the maximum length over all captures
48
- let poundCount = matches
49
- . compactMap { match in ( 1 ..< match. numberOfRanges) . map { match. range ( at: $0) . length + 1 } . max ( ) }
50
- . max ( ) ?? 0
51
-
52
- // Use a delimiter that is exactly one longer
53
- openDelimiter = Token . rawStringDelimiter ( String ( repeating: " # " , count: poundCount) )
54
- closeDelimiter = openDelimiter
33
+ let ( requiresEscaping, poundCount) = requiresEscaping ( content)
34
+ if requiresEscaping {
35
+ // Use a delimiter that is exactly one longer
36
+ openDelimiter = Token . rawStringDelimiter ( String ( repeating: " # " , count: poundCount + 1 ) )
37
+ closeDelimiter = openDelimiter
38
+ }
55
39
}
56
40
57
41
self . init (
@@ -63,3 +47,41 @@ extension StringLiteralExpr {
63
47
)
64
48
}
65
49
}
50
+
51
+ private enum PoundState {
52
+ case afterQuote, afterBackslash, none
53
+ }
54
+
55
+ private func requiresEscaping( _ content: String ) -> ( Bool , poundCount: Int ) {
56
+ var state : PoundState = . none
57
+ var consecutivePounds = 0
58
+ var maxPounds = 0
59
+ var requiresEscaping = false
60
+
61
+ for c in content {
62
+ switch c {
63
+ case " # " :
64
+ consecutivePounds += 1
65
+ case " \" " :
66
+ state = . afterQuote
67
+ consecutivePounds = 0
68
+ case " \\ " :
69
+ state = . afterBackslash
70
+ consecutivePounds = 0
71
+ case " ( " where state == . afterBackslash:
72
+ maxPounds = max ( maxPounds, consecutivePounds)
73
+ fallthrough
74
+ default :
75
+ consecutivePounds = 0
76
+ state = . none
77
+ }
78
+
79
+ if state == . afterQuote {
80
+ maxPounds = max ( maxPounds, consecutivePounds)
81
+ }
82
+
83
+ requiresEscaping = requiresEscaping || state != . none
84
+ }
85
+
86
+ return ( requiresEscaping, poundCount: maxPounds)
87
+ }
0 commit comments