@@ -124,6 +124,26 @@ func parseNotEqualTest(
124
124
}
125
125
}
126
126
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
+
127
147
extension RegexTests {
128
148
func testParse( ) {
129
149
parseTest (
@@ -153,6 +173,27 @@ extension RegexTests {
153
173
zeroOrOne ( . eager, " a " ) , zeroOrOne ( . reluctant, " b " ) ,
154
174
oneOrMore ( . eager, " c " ) , oneOrMore ( . reluctant, " d " ) ,
155
175
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
+
156
197
parseTest (
157
198
" a|b?c " ,
158
199
alt ( " a " , concat ( zeroOrOne ( . eager, " b " ) , " c " ) ) )
@@ -182,23 +223,17 @@ extension RegexTests {
182
223
" (a)|b|(c)d " ,
183
224
alt ( capture ( " a " ) , " b " , concat ( capture ( " c " ) , " d " ) ) ,
184
225
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 " ) )
202
237
203
238
// MARK: Unicode scalars
204
239
@@ -827,6 +862,10 @@ extension RegexTests {
827
862
parseWithDelimitersTest ( " '/a b/' " , concat ( " a " , " " , " b " ) )
828
863
parseWithDelimitersTest ( " '|a b|' " , concat ( " a " , " b " ) )
829
864
865
+ parseWithDelimitersTest ( " '|||' " , alt ( empty ( ) , empty ( ) ) )
866
+ parseWithDelimitersTest ( " '||||' " , alt ( empty ( ) , empty ( ) , empty ( ) ) )
867
+ parseWithDelimitersTest ( " '|a||' " , alt ( " a " , empty ( ) ) )
868
+
830
869
// Make sure dumping output correctly reflects differences in AST.
831
870
parseNotEqualTest ( #"abc"# , #"abd"# )
832
871
@@ -857,9 +896,40 @@ extension RegexTests {
857
896
parseNotEqualTest ( " (?i-s:) " , ( " (?i-m:) " ) )
858
897
parseNotEqualTest ( " (?y{w}:) " , ( " (?y{g}:) " ) )
859
898
899
+ parseNotEqualTest ( " | " , " || " )
900
+ parseNotEqualTest ( " a| " , " | " )
901
+ parseNotEqualTest ( " a|b " , " | " )
902
+
860
903
// TODO: failure tests
861
904
}
862
905
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
+
863
933
func testParseErrors( ) {
864
934
865
935
func performErrorTest( _ input: String , _ expecting: String ) {
0 commit comments