Skip to content

Commit db58c1b

Browse files
committed
Reserve <{...}> for interpolation syntax
Ban a balanced set of `<{...}>` delimiters for a potential future interpolation syntax.
1 parent a4a4a9a commit db58c1b

File tree

10 files changed

+88
-3
lines changed

10 files changed

+88
-3
lines changed

Sources/_RegexParser/Regex/AST/AST.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ extension AST {
5353
/// Comments, non-semantic whitespace, etc
5454
case trivia(Trivia)
5555

56+
/// Intepolation `<{...}>`, currently reserved for future use.
57+
case interpolation(Interpolation)
58+
5659
case atom(Atom)
5760

5861
case customCharacterClass(CustomCharacterClass)
@@ -78,6 +81,7 @@ extension AST.Node {
7881
case let .quantification(v): return v
7982
case let .quote(v): return v
8083
case let .trivia(v): return v
84+
case let .interpolation(v): return v
8185
case let .atom(v): return v
8286
case let .customCharacterClass(v): return v
8387
case let .empty(v): return v
@@ -128,7 +132,7 @@ extension AST.Node {
128132
case .group, .conditional, .customCharacterClass, .absentFunction:
129133
return true
130134
case .alternation, .concatenation, .quantification, .quote, .trivia,
131-
.empty:
135+
.empty, .interpolation:
132136
return false
133137
}
134138
}
@@ -192,6 +196,16 @@ extension AST {
192196
}
193197
}
194198

199+
public struct Interpolation: Hashable, _ASTNode {
200+
public let contents: String
201+
public let location: SourceLocation
202+
203+
public init(_ contents: String, _ location: SourceLocation) {
204+
self.contents = contents
205+
self.location = location
206+
}
207+
}
208+
195209
public struct Empty: Hashable, _ASTNode {
196210
public let location: SourceLocation
197211

Sources/_RegexParser/Regex/AST/Atom.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ extension AST.Node {
822822
case .alternation, .concatenation, .group,
823823
.conditional, .quantification, .quote,
824824
.trivia, .customCharacterClass, .empty,
825-
.absentFunction:
825+
.absentFunction, .interpolation:
826826
return nil
827827
}
828828
}

Sources/_RegexParser/Regex/Parse/CaptureList.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ extension AST.Node {
103103
break
104104
}
105105

106-
case .quote, .trivia, .atom, .customCharacterClass, .empty:
106+
case .quote, .trivia, .atom, .customCharacterClass, .empty, .interpolation:
107107
break
108108
}
109109
}

Sources/_RegexParser/Regex/Parse/LexicalAnalysis.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,26 @@ extension Source {
589589
return AST.Quote(str.value, str.location)
590590
}
591591

592+
/// Try to consume an interpolation sequence.
593+
///
594+
/// Interpolation -> '<{' String '}>'
595+
///
596+
mutating func lexInterpolation() throws -> AST.Interpolation? {
597+
let contents = try recordLoc { src -> String? in
598+
try src.tryEating { src in
599+
guard src.tryEat(sequence: "<{") else { return nil }
600+
_ = src.lexUntil { $0.isEmpty || $0.starts(with: "}>") }
601+
guard src.tryEat(sequence: "}>") else { return nil }
602+
603+
// Not currently supported. We error here instead of during Sema to
604+
// get a better error for something like `(<{)}>`.
605+
throw ParseError.unsupported("interpolation")
606+
}
607+
}
608+
guard let contents = contents else { return nil }
609+
return .init(contents.value, contents.location)
610+
}
611+
592612
/// Try to consume a comment
593613
///
594614
/// Comment -> '(?#' [^')']* ')'

Sources/_RegexParser/Regex/Parse/Parse.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ extension Parser {
222222
result.append(.quote(quote))
223223
continue
224224
}
225+
226+
// Interpolation -> `lexInterpolation`
227+
if let interpolation = try source.lexInterpolation() {
228+
result.append(.interpolation(interpolation))
229+
continue
230+
}
231+
225232
// Quantification -> QuantOperand Quantifier?
226233
if let operand = try parseQuantifierOperand() {
227234
if let (amt, kind, trivia) =

Sources/_RegexParser/Regex/Parse/Sema.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,11 @@ extension RegexValidator {
395395
// These are Oniguruma specific.
396396
throw error(.unsupported("absent function"), at: a.location)
397397

398+
case .interpolation(let i):
399+
// This is currently rejected in the parser for better diagnostics, but
400+
// reject here too until we get runtime support.
401+
throw error(.unsupported("interpolation"), at: i.location)
402+
398403
case .quote, .trivia, .empty:
399404
break
400405
}

Sources/_RegexParser/Regex/Printing/DumpAST.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ extension AST.Trivia {
101101
}
102102
}
103103

104+
extension AST.Interpolation {
105+
public var _dumpBase: String { "interpolation <\(contents)>" }
106+
}
107+
104108
extension AST.Empty {
105109
public var _dumpBase: String { "" }
106110
}

Sources/_RegexParser/Regex/Printing/PrintAsCanonical.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ extension PrettyPrinter {
9797
case let .trivia(t):
9898
output(t._canonicalBase)
9999

100+
case let .interpolation(i):
101+
output(i._canonicalBase)
102+
100103
case let .atom(a):
101104
output(a._canonicalBase)
102105

@@ -178,6 +181,12 @@ extension AST.Quote {
178181
}
179182
}
180183

184+
extension AST.Interpolation {
185+
var _canonicalBase: String {
186+
"<{\(contents)}>"
187+
}
188+
}
189+
181190
extension AST.Group.Kind {
182191
var _canonicalBase: String {
183192
switch self {

Sources/_StringProcessing/Regex/ASTConversion.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ extension AST.Node {
137137
case let .trivia(v):
138138
return .trivia(v.contents)
139139

140+
case .interpolation:
141+
throw Unsupported("TODO: interpolation")
142+
140143
case let .atom(v):
141144
switch v.kind {
142145
case .scalarSequence(let seq):

Tests/RegexTests/ParseTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,20 @@ extension RegexTests {
804804
#"a(?#. comment)b"#,
805805
concat("a", "b"))
806806

807+
// MARK: Interpolation
808+
809+
// These are literal as there's no closing '}>'
810+
parseTest("<{", concat("<", "{"))
811+
parseTest("<{a", concat("<", "{", "a"))
812+
parseTest("<{a}", concat("<", "{", "a", "}"))
813+
parseTest("<{<{}", concat("<", "{", "<", "{", "}"))
814+
815+
// Literal as escaped
816+
parseTest(#"\<{}>"#, concat("<", "{", "}", ">"))
817+
818+
// A quantification
819+
parseTest(#"<{2}"#, exactly(2, of: "<"))
820+
807821
// MARK: Quantification
808822

809823
parseTest("a*", zeroOrMore(of: "a"))
@@ -2574,6 +2588,15 @@ extension RegexTests {
25742588
diagnosticTest("|\u{360}", .confusableCharacter("|\u{360}"))
25752589
diagnosticTest(" \u{361}", .confusableCharacter(" \u{361}"))
25762590

2591+
// MARK: Interpolation (currently unsupported)
2592+
2593+
diagnosticTest("<{}>", .unsupported("interpolation"))
2594+
diagnosticTest("<{...}>", .unsupported("interpolation"))
2595+
diagnosticTest("<{)}>", .unsupported("interpolation"))
2596+
diagnosticTest("<{}}>", .unsupported("interpolation"))
2597+
diagnosticTest("<{<{}>", .unsupported("interpolation"))
2598+
diagnosticTest("(<{)}>", .unsupported("interpolation"))
2599+
25772600
// MARK: Character properties
25782601

25792602
diagnosticTest(#"\p{Lx}"#, .unknownProperty(key: nil, value: "Lx"))

0 commit comments

Comments
 (0)