Skip to content

Commit a9a71c4

Browse files
committed
Allow escaped quotes in experimental syntax
Allow e.g `"\""`
1 parent 3334fa0 commit a9a71c4

File tree

3 files changed

+31
-6
lines changed

3 files changed

+31
-6
lines changed

Sources/_MatchingEngine/Regex/Parse/LexicalAnalysis.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,24 @@ extension Source {
394394
try lexUntil(eating: String(end))
395395
}
396396

397-
/// Expect a linear run of non-nested non-empty content
397+
/// Expect a linear run of non-nested non-empty content ending with a given
398+
/// delimiter. If `ignoreEscaped` is true, escaped characters will not be
399+
/// considered for the ending delimiter.
398400
private mutating func expectQuoted(
399-
endingWith end: String
401+
endingWith end: String, ignoreEscaped: Bool = false
400402
) throws -> Located<String> {
401403
try recordLoc { src in
402-
let result = try src.lexUntil(eating: end).value
404+
let result = try src.lexUntil { src in
405+
if try src.tryEatNonEmpty(sequence: end) {
406+
return true
407+
}
408+
// Ignore escapes if we're allowed to. lexUntil will consume the next
409+
// character.
410+
if ignoreEscaped, src.tryEat("\\") {
411+
try src.expectNonEmpty()
412+
}
413+
return false
414+
}.value
403415
guard !result.isEmpty else {
404416
throw ParseError.misc("Expected non-empty contents")
405417
}
@@ -413,7 +425,7 @@ extension Source {
413425
///
414426
/// With `SyntaxOptions.experimentalQuotes`, also accepts
415427
///
416-
/// ExpQuote -> '"' [^"]* '"'
428+
/// ExpQuote -> '"' ('\"' | [^"])* '"'
417429
///
418430
/// Future: Experimental quotes are full fledged Swift string literals
419431
///
@@ -425,8 +437,7 @@ extension Source {
425437
return try src.expectQuoted(endingWith: #"\E"#).value
426438
}
427439
if src.experimentalQuotes, src.tryEat("\"") {
428-
// TODO: escaped `"`, etc...
429-
return try src.expectQuoted(endingWith: "\"").value
440+
return try src.expectQuoted(endingWith: "\"", ignoreEscaped: true).value
430441
}
431442
return nil
432443
}

Tests/RegexTests/LexTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ extension RegexTests {
133133
_ = try $0.lexGroupStart()
134134
}
135135

136+
diagnose(#"\Qab"#, expecting: .expected("\\E")) { _ = try $0.lexQuote() }
137+
diagnose(#"\Qab\"#, expecting: .expected("\\E")) { _ = try $0.lexQuote() }
138+
diagnose(#""ab"#, expecting: .expected("\""), .experimental) { _ = try $0.lexQuote() }
139+
diagnose(#""ab\""#, expecting: .expected("\""), .experimental) { _ = try $0.lexQuote() }
140+
diagnose(#""ab\"#, expecting: .unexpectedEndOfInput, .experimental) { _ = try $0.lexQuote() }
141+
136142
// TODO: want to dummy print out source ranges, etc, test that.
137143
}
138144

Tests/RegexTests/ParseTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@ extension RegexTests {
414414
#"a\Q \Q \\.\Eb"#,
415415
concat("a", quote(#" \Q \\."#), "b"))
416416

417+
parseTest(#"a" ."b"#, concat("a", quote(" ."), "b"),
418+
syntax: .experimental)
419+
parseTest(#"a" .""b""#, concat("a", quote(" ."), quote("b")),
420+
syntax: .experimental)
421+
parseTest(#"a" .\"\"b""#, concat("a", quote(" .\"\"b")),
422+
syntax: .experimental)
423+
parseTest(#""\"""#, quote("\""), syntax: .experimental)
424+
417425
// MARK: Comments
418426

419427
parseTest(

0 commit comments

Comments
 (0)