Skip to content

Commit a5535f5

Browse files
committed
Add some additional alternation test cases
Including a couple of test cases that make sure we have correct source ranges for them, as a later commit will change how we compute their source range.
1 parent d3a0469 commit a5535f5

File tree

3 files changed

+124
-25
lines changed

3 files changed

+124
-25
lines changed

Sources/_MatchingEngine/Utility/Misc_2.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ extension Collection {
9393
distance(from: startIndex, to: i)
9494
}
9595

96+
public func offsets(of r: Range<Index>) -> Range<Int> {
97+
offset(of: r.lowerBound) ..< offset(of: r.upperBound)
98+
}
99+
96100
public func convertByOffset<
97101
C: Collection
98102
>(_ range: Range<Index>, in c: C) -> Range<C.Index> {

Tests/RegexTests/MatchTests.swift

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ func matchTest(
99
syntax: SyntaxOptions = .traditional,
1010
enableTracing: Bool = false,
1111
dumpAST: Bool = false,
12-
xfail: Bool = false
12+
xfail: Bool = false,
13+
file: StaticString = #filePath,
14+
line: UInt = #line
1315
) {
1416
do {
1517
var consumer = try RegexConsumer<String>(parsing: regex)
@@ -22,13 +24,13 @@ func matchTest(
2224
}
2325

2426
if xfail {
25-
XCTAssertNotEqual(String(input[range]), match)
27+
XCTAssertNotEqual(String(input[range]), match, file: file, line: line)
2628
} else {
27-
XCTAssertEqual(String(input[range]), match)
29+
XCTAssertEqual(String(input[range]), match, file: file, line: line)
2830
}
2931
} catch {
3032
if !xfail {
31-
XCTFail("\(error)")
33+
XCTFail("\(error)", file: file, line: line)
3234
}
3335
return
3436
}
@@ -64,6 +66,14 @@ extension RegexTests {
6466
#"abc\+d*"#, input: "123abc+dddxyz", match: "abc+ddd")
6567
matchTest(
6668
"a(b)", input: "123abcxyz", match: "ab")
69+
70+
matchTest(
71+
"(.)*(.*)", input: "123abcxyz", match: "123abcxyz")
72+
matchTest(
73+
#"abc\d"#, input: "xyzabc123", match: "abc1")
74+
75+
// MARK: Alternations
76+
6777
matchTest(
6878
"abc(?:de)+fghi*k|j", input: "123abcdefghijxyz", match: "j")
6979
matchTest(
@@ -84,10 +94,25 @@ extension RegexTests {
8494
"a|b?c", input: "123bcxyz", match: "bc")
8595
matchTest(
8696
"(a|b)c", input: "123abcxyz", match: "bc")
87-
matchTest(
88-
"(.)*(.*)", input: "123abcxyz", match: "123abcxyz")
89-
matchTest(
90-
#"abc\d"#, input: "xyzabc123", match: "abc1")
97+
98+
// Alternations with empty branches are permitted.
99+
matchTest("|", input: "ab", match: "")
100+
matchTest("(|)", input: "ab", match: "")
101+
matchTest("a|", input: "ab", match: "a")
102+
matchTest("a|", input: "ba", match: "")
103+
matchTest("|b", input: "ab", match: "")
104+
matchTest("|b", input: "ba", match: "")
105+
matchTest("|b|", input: "ab", match: "")
106+
matchTest("|b|", input: "ba", match: "")
107+
matchTest("a|b|", input: "ab", match: "a")
108+
matchTest("a|b|", input: "ba", match: "b")
109+
matchTest("a|b|", input: "ca", match: "")
110+
matchTest("||c|", input: "ab", match: "")
111+
matchTest("||c|", input: "cb", match: "")
112+
matchTest("|||", input: "ab", match: "")
113+
matchTest("a|||d", input: "bc", match: "")
114+
matchTest("a|||d", input: "abc", match: "a")
115+
matchTest("a|||d", input: "d", match: "")
91116

92117
// MARK: Unicode scalars
93118

Tests/RegexTests/ParseTests.swift

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,26 @@ func parseNotEqualTest(
124124
}
125125
}
126126

127+
func rangeTest(
128+
_ input: String, syntax: SyntaxOptions = .traditional,
129+
_ expectedRange: (String) -> Range<Int>,
130+
at locFn: (AST) -> SourceLocation = \.location,
131+
file: StaticString = #file, line: UInt = #line
132+
) {
133+
let ast = try! parse(input, syntax)
134+
let range = input.offsets(of: locFn(ast).range)
135+
let expected = expectedRange(input)
136+
137+
guard range == expected else {
138+
XCTFail("""
139+
Expected range: "\(expected)"
140+
Found range: "\(range)"
141+
""",
142+
file: file, line: line)
143+
return
144+
}
145+
}
146+
127147
extension RegexTests {
128148
func testParse() {
129149
parseTest(
@@ -153,6 +173,27 @@ extension RegexTests {
153173
zeroOrOne(.eager, "a"), zeroOrOne(.reluctant, "b"),
154174
oneOrMore(.eager, "c"), oneOrMore(.reluctant, "d"),
155175
zeroOrMore(.eager, "e"), zeroOrMore(.reluctant, "f")))
176+
177+
parseTest(
178+
"(.)*(.*)",
179+
concat(
180+
zeroOrMore(.eager, capture(atom(.any))),
181+
capture(zeroOrMore(.eager, atom(.any)))),
182+
captures: .tuple([.array(.atom()), .atom()]))
183+
parseTest(
184+
"((.))*((.)?)",
185+
concat(
186+
zeroOrMore(.eager, capture(capture(atom(.any)))),
187+
capture(zeroOrOne(.eager, capture(atom(.any))))),
188+
captures: .tuple([
189+
.array(.atom()), .array(.atom()), .atom(), .optional(.atom())
190+
]))
191+
parseTest(
192+
#"abc\d"#,
193+
concat("a", "b", "c", escaped(.decimalDigit)))
194+
195+
// MARK: Alternations
196+
156197
parseTest(
157198
"a|b?c",
158199
alt("a", concat(zeroOrOne(.eager, "b"), "c")))
@@ -182,23 +223,17 @@ extension RegexTests {
182223
"(a)|b|(c)d",
183224
alt(capture("a"), "b", concat(capture("c"), "d")),
184225
captures: .tuple([.optional(.atom()), .optional(.atom())]))
185-
parseTest(
186-
"(.)*(.*)",
187-
concat(
188-
zeroOrMore(.eager, capture(atom(.any))),
189-
capture(zeroOrMore(.eager, atom(.any)))),
190-
captures: .tuple([.array(.atom()), .atom()]))
191-
parseTest(
192-
"((.))*((.)?)",
193-
concat(
194-
zeroOrMore(.eager, capture(capture(atom(.any)))),
195-
capture(zeroOrOne(.eager, capture(atom(.any))))),
196-
captures: .tuple([
197-
.array(.atom()), .array(.atom()), .atom(), .optional(.atom())
198-
]))
199-
parseTest(
200-
#"abc\d"#,
201-
concat("a", "b", "c", escaped(.decimalDigit)))
226+
227+
// Alternations with empty branches are permitted.
228+
parseTest("|", alt(empty(), empty()))
229+
parseTest("(|)", capture(alt(empty(), empty())), captures: .atom())
230+
parseTest("a|", alt("a", empty()))
231+
parseTest("|b", alt(empty(), "b"))
232+
parseTest("|b|", alt(empty(), "b", empty()))
233+
parseTest("a|b|", alt("a", "b", empty()))
234+
parseTest("||c|", alt(empty(), empty(), "c", empty()))
235+
parseTest("|||", alt(empty(), empty(), empty(), empty()))
236+
parseTest("a|||d", alt("a", empty(), empty(), "d"))
202237

203238
// MARK: Unicode scalars
204239

@@ -827,6 +862,10 @@ extension RegexTests {
827862
parseWithDelimitersTest("'/a b/'", concat("a", " ", "b"))
828863
parseWithDelimitersTest("'|a b|'", concat("a", "b"))
829864

865+
parseWithDelimitersTest("'|||'", alt(empty(), empty()))
866+
parseWithDelimitersTest("'||||'", alt(empty(), empty(), empty()))
867+
parseWithDelimitersTest("'|a||'", alt("a", empty()))
868+
830869
// Make sure dumping output correctly reflects differences in AST.
831870
parseNotEqualTest(#"abc"#, #"abd"#)
832871

@@ -857,9 +896,40 @@ extension RegexTests {
857896
parseNotEqualTest("(?i-s:)", ("(?i-m:)"))
858897
parseNotEqualTest("(?y{w}:)", ("(?y{g}:)"))
859898

899+
parseNotEqualTest("|", "||")
900+
parseNotEqualTest("a|", "|")
901+
parseNotEqualTest("a|b", "|")
902+
860903
// TODO: failure tests
861904
}
862905

906+
func testParseSourceLocations() throws {
907+
func entireRange(input: String) -> Range<Int> {
908+
0 ..< input.count
909+
}
910+
func insetRange(by i: Int) -> (String) -> Range<Int> {
911+
{ i ..< $0.count - i }
912+
}
913+
func range(_ indices: Range<Int>) -> (String) -> Range<Int> {
914+
{ _ in indices }
915+
}
916+
917+
// MARK: Alternations
918+
919+
typealias Alt = AST.Alternation
920+
921+
let alternations = [
922+
"|", "a|", "|b", "a|b", "abc|def", "a|b|c|d", "a|b|", "|||", "a|||d",
923+
"||c|"
924+
]
925+
926+
// Make sure we correctly compute source ranges for alternations.
927+
for alt in alternations {
928+
rangeTest(alt, entireRange)
929+
rangeTest("(\(alt))", insetRange(by: 1), at: \.children![0].location)
930+
}
931+
}
932+
863933
func testParseErrors() {
864934

865935
func performErrorTest(_ input: String, _ expecting: String) {

0 commit comments

Comments
 (0)