Skip to content

Commit b52d992

Browse files
committed
Allow lexer recovery for missing closing delimiter
Allow the C++ lexer to form a tok::regex_literal. This avoids generic fallback behavior, and better allows for things like code completion. The test case for this will be in the C++ repo.
1 parent 0e7ec99 commit b52d992

File tree

3 files changed

+41
-11
lines changed

3 files changed

+41
-11
lines changed

Sources/_MatchingEngine/Regex/Parse/DelimiterLexing.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,17 @@ fileprivate struct DelimiterLexer {
208208
/// Drop a set of regex delimiters from the input string, returning the contents
209209
/// and the delimiter used. The input string must have valid delimiters.
210210
func droppingRegexDelimiters(_ str: String) -> (String, Delimiter) {
211-
let utf8 = str.utf8
212211
func stripDelimiter(_ delim: Delimiter) -> String? {
213-
let prefix = delim.opening.utf8
214-
let suffix = delim.closing.utf8
215-
guard utf8.prefix(prefix.count).elementsEqual(prefix),
216-
utf8.suffix(suffix.count).elementsEqual(suffix) else { return nil }
217-
218-
return String(utf8.dropFirst(prefix.count).dropLast(suffix.count))
212+
// The opening delimiter must match.
213+
guard var slice = str.utf8.tryDropPrefix(delim.opening.utf8)
214+
else { return nil }
215+
216+
// The closing delimiter may optionally match, as it may not be present in
217+
// invalid code.
218+
if let newSlice = slice.tryDropSuffix(delim.closing.utf8) {
219+
slice = newSlice
220+
}
221+
return String(slice)
219222
}
220223
for d in Delimiter.allCases {
221224
if let contents = stripDelimiter(d) {

Sources/_MatchingEngine/Regex/Parse/Mocking.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,16 @@ func libswiftLexRegexLiteral(
6161
errOut.pointee = copyCString("\(error)")
6262
curPtrPtr.pointee = error.resumePtr.assumingMemoryBound(to: CChar.self)
6363

64-
// For now, treat every error as unrecoverable.
65-
// TODO: We should ideally be able to recover from a regex with missing
66-
// closing delimiters, which would help with code completion.
67-
return true
64+
switch error.kind {
65+
case .endOfString:
66+
// Missing closing delimiter can be recovered from.
67+
return false
68+
case .unprintableASCII, .invalidUTF8:
69+
// We don't currently have good recovery behavior for these.
70+
return true
71+
case .unknownDelimiter:
72+
fatalError("Already handled")
73+
}
6874
} catch {
6975
fatalError("Should be a DelimiterLexError")
7076
}

Sources/_MatchingEngine/Utility/Misc.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,28 @@ extension Collection {
108108
>(_ idx: Index, in c: C) -> C.Index {
109109
c.index(atOffset: offset(of: idx))
110110
}
111+
}
111112

113+
extension Collection where Element: Equatable {
114+
/// Attempt to drop a given prefix from the collection, returning the
115+
/// resulting subsequence, or `nil` if the prefix does not match.
116+
public func tryDropPrefix<C : Collection>(
117+
_ other: C
118+
) -> SubSequence? where C.Element == Element {
119+
let prefixCount = other.count
120+
guard prefix(prefixCount).elementsEqual(other) else { return nil }
121+
return dropFirst(prefixCount)
122+
}
123+
124+
/// Attempt to drop a given suffix from the collection, returning the
125+
/// resulting subsequence, or `nil` if the suffix does not match.
126+
public func tryDropSuffix<C : Collection>(
127+
_ other: C
128+
) -> SubSequence? where C.Element == Element {
129+
let suffixCount = other.count
130+
guard suffix(suffixCount).elementsEqual(other) else { return nil }
131+
return dropLast(suffixCount)
132+
}
112133
}
113134

114135
extension UnsafeMutableRawPointer {

0 commit comments

Comments
 (0)