Skip to content

Commit 3797c61

Browse files
committed
Diagnose quantifiers without operands
Avoid treating such cases as literal.
1 parent 88bd2df commit 3797c61

File tree

3 files changed

+27
-0
lines changed

3 files changed

+27
-0
lines changed

Sources/_MatchingEngine/Regex/Parse/Diagnostics.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ enum ParseError: Error, Hashable {
4343
case cannotReferToWholePattern
4444

4545
case notQuantifiable
46+
case quantifierRequiresOperand(String)
4647

4748
case backtrackingDirectiveMustHaveName(String)
4849

@@ -110,6 +111,8 @@ extension ParseError: CustomStringConvertible {
110111
return "cannot refer to whole pattern here"
111112
case .notQuantifiable:
112113
return "expression is not quantifiable"
114+
case .quantifierRequiresOperand(let q):
115+
return "quantifier '\(q)' must appear after expression"
113116
case .backtrackingDirectiveMustHaveName(let b):
114117
return "backtracking directive '\(b)' must include name"
115118
case let .tooManyBranchesInConditional(i):

Sources/_MatchingEngine/Regex/Parse/LexicalAnalysis.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,13 @@ extension Source {
17741774
return try src.expectGroupLikeAtom()
17751775
}
17761776

1777+
// A quantifier here is invalid.
1778+
if !customCC,
1779+
let q = try src.recordLoc({ try $0.lexQuantifier(context: context) }) {
1780+
throw ParseError.quantifierRequiresOperand(
1781+
String(src[q.location.range]))
1782+
}
1783+
17771784
let char = src.eat()
17781785
switch char {
17791786
case ")", "|":

Tests/RegexTests/ParseTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,9 @@ extension RegexTests {
415415
parseTest("[[:AL_NUM:]]", charClass(posixProp_m(.posix(.alnum))))
416416
parseTest("[[:script=Greek:]]", charClass(posixProp_m(.script(.greek))))
417417

418+
parseTest("[*]", charClass("*"))
419+
parseTest("[{0}]", charClass("{", "0", "}"))
420+
418421
// MARK: Operators
419422

420423
parseTest(
@@ -494,6 +497,9 @@ extension RegexTests {
494497

495498
// MARK: Quantification
496499

500+
parseTest("a*", zeroOrMore(of: "a"))
501+
parseTest(" +", oneOrMore(of: " "))
502+
497503
parseTest(
498504
#"a{1,2}"#,
499505
quantRange(1...2, of: "a"))
@@ -537,6 +543,8 @@ extension RegexTests {
537543

538544
// TODO: We should emit a diagnostic for this.
539545
parseTest("x{3, 5}", concat("x", "{", "3", ",", " ", "5", "}"))
546+
parseTest("{3, 5}", concat("{", "3", ",", " ", "5", "}"))
547+
parseTest("{3 }", concat("{", "3", " ", "}"))
540548

541549
// MARK: Groups
542550

@@ -1742,6 +1750,15 @@ extension RegexTests {
17421750

17431751
diagnosticTest("(?x)(? : )", .unknownGroupKind("? "))
17441752

1753+
// MARK: Quantifiers
1754+
1755+
diagnosticTest("*", .quantifierRequiresOperand("*"))
1756+
diagnosticTest("+", .quantifierRequiresOperand("+"))
1757+
diagnosticTest("?", .quantifierRequiresOperand("?"))
1758+
diagnosticTest("*?", .quantifierRequiresOperand("*?"))
1759+
diagnosticTest("{5}", .quantifierRequiresOperand("{5}"))
1760+
diagnosticTest("{1,3}", .quantifierRequiresOperand("{1,3}"))
1761+
17451762
// MARK: Matching options
17461763

17471764
diagnosticTest(#"(?^-"#, .cannotRemoveMatchingOptionsAfterCaret)

0 commit comments

Comments
 (0)