@@ -150,23 +150,44 @@ where
150
150
Parsing ( parse:: Error < Input > ) ,
151
151
152
152
/// Expansion error.
153
- #[ display( fmt = "Regex expansion failed : {}" , _0) ]
154
- Expansion ( UnknownParameterError < Input > ) ,
153
+ #[ display( fmt = "Failed to expand regex : {}" , _0) ]
154
+ Expansion ( ParameterError < Input > ) ,
155
155
156
156
/// [`Regex`] creation error.
157
157
#[ display( fmt = "Regex creation failed: {}" , _0) ]
158
158
Regex ( regex:: Error ) ,
159
159
}
160
160
161
- /// Error of an unknown [`Parameter`] being used in an [`Expression`].
162
- #[ derive( Clone , Copy , Debug , Display , Error ) ]
163
- #[ display( fmt = "Parameter '{}' not found." , not_found) ]
164
- pub struct UnknownParameterError < Input >
161
+ /// Possible [`Parameter`] errors being used in an [`Expression`].
162
+ #[ derive( Clone , Debug , Display , Error ) ]
163
+ pub enum ParameterError < Input >
165
164
where
166
165
Input : fmt:: Display ,
167
166
{
168
167
/// [`Parameter`] not found.
169
- pub not_found : Input ,
168
+ #[ display( fmt = "Parameter `{}` not found." , _0) ]
169
+ NotFound ( Input ) ,
170
+
171
+ /// Failed to rename [`Regex`] capturing group.
172
+ #[ display(
173
+ fmt = "Failed to rename capturing groups in regex `{}` of \
174
+ parameter `{}`: {}",
175
+ re,
176
+ parameter,
177
+ err
178
+ ) ]
179
+ RenameRegexGroup {
180
+ /// [`Parameter`] name.
181
+ parameter : Input ,
182
+
183
+ /// [`Regex`] of the [`Parameter`].
184
+ re : String ,
185
+
186
+ /// [`Error`] of parsing the [`Regex`] with renamed capturing groups.
187
+ ///
188
+ /// [`Error`]: regex_syntax::Error
189
+ err : regex_syntax:: Error ,
190
+ } ,
170
191
}
171
192
172
193
/// Expansion of a [Cucumber Expressions][0] [AST] element into a [`Regex`] by
@@ -177,7 +198,7 @@ where
177
198
/// [AST]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
178
199
pub trait IntoRegexCharIter < Input : fmt:: Display > {
179
200
/// Type of an [`Iterator`] performing the expansion.
180
- type Iter : Iterator < Item = Result < char , UnknownParameterError < Input > > > ;
201
+ type Iter : Iterator < Item = Result < char , ParameterError < Input > > > ;
181
202
182
203
/// Consumes this [AST] element returning an [`Iterator`] over [`char`]s
183
204
/// transformable into a [`Regex`].
@@ -208,7 +229,7 @@ where
208
229
/// [`IntoRegexCharIter::Iter`] for an [`Expression`].
209
230
type ExpressionIter < Input > = iter:: Chain <
210
231
iter:: Chain <
211
- iter:: Once < Result < char , UnknownParameterError < Input > > > ,
232
+ iter:: Once < Result < char , ParameterError < Input > > > ,
212
233
iter:: FlatMap <
213
234
vec:: IntoIter < SingleExpression < Input > > ,
214
235
<SingleExpression < Input > as IntoRegexCharIter < Input > >:: Iter ,
@@ -218,7 +239,7 @@ type ExpressionIter<Input> = iter::Chain<
218
239
-> <SingleExpression < Input > as IntoRegexCharIter < Input > >:: Iter ,
219
240
> ,
220
241
> ,
221
- iter:: Once < Result < char , UnknownParameterError < Input > > > ,
242
+ iter:: Once < Result < char , ParameterError < Input > > > ,
222
243
> ;
223
244
224
245
impl < Input > IntoRegexCharIter < Input > for SingleExpression < Input >
@@ -307,7 +328,7 @@ type AlternationIter<I> = iter::Chain<
307
328
> ,
308
329
> ,
309
330
> ,
310
- iter:: Once < Result < char , UnknownParameterError < I > > > ,
331
+ iter:: Once < Result < char , ParameterError < I > > > ,
311
332
> ;
312
333
313
334
// TODO: Replace with TAIT, once stabilized:
@@ -319,7 +340,7 @@ type AlternationIterInner<I> = iter::Chain<
319
340
<Alternative < I > as IntoRegexCharIter < I > >:: Iter ,
320
341
fn ( Alternative < I > ) -> <Alternative < I > as IntoRegexCharIter < I > >:: Iter ,
321
342
> ,
322
- iter:: Once < Result < char , UnknownParameterError < I > > > ,
343
+ iter:: Once < Result < char , ParameterError < I > > > ,
323
344
> ;
324
345
325
346
impl < Input > IntoRegexCharIter < Input > for Alternative < Input >
@@ -397,7 +418,7 @@ type OptionalIter<Input> = iter::Map<
397
418
> ;
398
419
399
420
/// Function pointer describing [`Ok`].
400
- type MapOkChar < Input > = fn ( char ) -> Result < char , UnknownParameterError < Input > > ;
421
+ type MapOkChar < Input > = fn ( char ) -> Result < char , ParameterError < Input > > ;
401
422
402
423
impl < Input > IntoRegexCharIter < Input > for Parameter < Input >
403
424
where
@@ -413,36 +434,41 @@ where
413
434
i. iter_elements ( ) . map ( AsChar :: as_char) . eq ( str. chars ( ) )
414
435
} ;
415
436
416
- if eq ( & self . 0 , "int" ) {
417
- Left ( r#"((?:-?\d+)|(?:\d+))"# . chars ( ) . map ( Ok ) )
418
- } else if eq ( & self . 0 , "float" ) {
437
+ if eq ( & self . input , "int" ) {
438
+ Left ( Left ( r#"((?:-?\d+)|(?:\d+))"# . chars ( ) . map ( Ok ) ) )
439
+ } else if eq ( & self . input , "float" ) {
419
440
// Regex in other implementations has lookaheads. As `regex` crate
420
441
// doesn't support them, we use `f32`/`f64` grammar instead:
421
442
// https://doc.rust-lang.org/stable/std/primitive.f64.html#grammar
422
443
// Provided grammar is a superset of the original one:
423
444
// - supports `e` as exponent in addition to `E`
424
445
// - supports trailing comma: `1.`
425
446
// - supports `inf` and `NaN`
426
- Left (
447
+ Left ( Left (
427
448
"([+-]?(?:inf\
428
449
|NaN\
429
450
|(?:\\ d+|\\ d+\\ .\\ d*|\\ d*\\ .\\ d+)(?:[eE][+-]?\\ d+)?\
430
451
))"
431
452
. chars ( )
432
453
. map ( Ok ) ,
433
- )
434
- } else if eq ( & self . 0 , "word" ) {
435
- Left ( r#"([^\s]+)"# . chars ( ) . map ( Ok ) )
436
- } else if eq ( & self . 0 , "string" ) {
437
- Left (
438
- r#"("(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')"#
439
- . chars ( )
440
- . map ( Ok ) ,
441
- )
442
- } else if eq ( & self . 0 , "" ) {
443
- Left ( r#"(.*)"# . chars ( ) . map ( Ok ) )
454
+ ) )
455
+ } else if eq ( & self . input , "word" ) {
456
+ Left ( Left ( r#"([^\s]+)"# . chars ( ) . map ( Ok ) ) )
457
+ } else if eq ( & self . input , "string" ) {
458
+ Left ( Right (
459
+ OwnedChars :: new ( format ! (
460
+ "(?:\
461
+ \" (?P<__{id}_0>[^\" \\ \\ ]*(?:\\ \\ .[^\" \\ \\ ]*)*)\" \
462
+ |'(?P<__{id}_1>[^'\\ \\ ]*(?:\\ \\ .[^'\\ \\ ]*)*)'\
463
+ )",
464
+ id = self . id,
465
+ ) )
466
+ . map ( Ok ) ,
467
+ ) )
468
+ } else if eq ( & self . input , "" ) {
469
+ Left ( Left ( r#"(.*)"# . chars ( ) . map ( Ok ) ) )
444
470
} else {
445
- Right ( iter:: once ( Err ( UnknownParameterError { not_found : self . 0 } ) ) )
471
+ Right ( iter:: once ( Err ( ParameterError :: NotFound ( self . input ) ) ) )
446
472
}
447
473
}
448
474
}
@@ -451,11 +477,14 @@ where
451
477
// https://github.com/rust-lang/rust/issues/63063
452
478
/// [`IntoRegexCharIter::Iter`] for a [`Parameter`].
453
479
type ParameterIter < Input > = Either <
454
- iter:: Map <
455
- str:: Chars < ' static > ,
456
- fn ( char ) -> Result < char , UnknownParameterError < Input > > ,
480
+ Either <
481
+ iter:: Map <
482
+ str:: Chars < ' static > ,
483
+ fn ( char ) -> Result < char , ParameterError < Input > > ,
484
+ > ,
485
+ iter:: Map < OwnedChars , fn ( char ) -> Result < char , ParameterError < Input > > > ,
457
486
> ,
458
- iter:: Once < Result < char , UnknownParameterError < Input > > > ,
487
+ iter:: Once < Result < char , ParameterError < Input > > > ,
459
488
> ;
460
489
461
490
/// [`Iterator`] for skipping a last [`Item`].
@@ -513,6 +542,36 @@ where
513
542
}
514
543
}
515
544
545
+ // TODO: Make private, once TAIT stabilized:
546
+ // https://github.com/rust-lang/rust/issues/63063
547
+ /// Like [`str::Chars`] [`Iterator`], but owns its [`String`].
548
+ #[ derive( Clone , Debug ) ]
549
+ pub struct OwnedChars {
550
+ /// Iterated [`String`].
551
+ str : String ,
552
+
553
+ /// Current char number.
554
+ cur : usize ,
555
+ }
556
+
557
+ impl OwnedChars {
558
+ /// Creates a new [`OwnedChars`] [`Iterator`].
559
+ #[ must_use]
560
+ pub const fn new ( str : String ) -> Self {
561
+ Self { str, cur : 0 }
562
+ }
563
+ }
564
+
565
+ impl Iterator for OwnedChars {
566
+ type Item = char ;
567
+
568
+ fn next ( & mut self ) -> Option < Self :: Item > {
569
+ let char = self . str . chars ( ) . nth ( self . cur ) ?;
570
+ self . cur += 1 ;
571
+ Some ( char)
572
+ }
573
+ }
574
+
516
575
/// [`Iterator`] for escaping `^`, `$`, `[`, `]`, `(`, `)`, `{`, `}`, `.`, `|`,
517
576
/// `?`, `*`, `+` with `\`, and removing it for other [`char`]s.
518
577
///
@@ -586,7 +645,7 @@ where
586
645
// Naming of test cases is preserved.
587
646
#[ cfg( test) ]
588
647
mod spec {
589
- use super :: { Error , Expression , UnknownParameterError } ;
648
+ use super :: { Error , Expression , ParameterError } ;
590
649
591
650
#[ test]
592
651
fn alternation_with_optional ( ) {
@@ -699,7 +758,10 @@ mod spec {
699
758
700
759
assert_eq ! (
701
760
expr. as_str( ) ,
702
- r#"^("(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')$"# ,
761
+ "^(?:\
762
+ \" (?P<__0_0>[^\" \\ \\ ]*(?:\\ \\ .[^\" \\ \\ ]*)*)\" \
763
+ |'(?P<__0_1>[^'\\ \\ ]*(?:\\ \\ .[^'\\ \\ ]*)*)'\
764
+ )$",
703
765
) ;
704
766
assert ! ( expr. is_match( "\" \" " ) ) ;
705
767
assert ! ( expr. is_match( "''" ) ) ;
@@ -710,6 +772,30 @@ mod spec {
710
772
assert ! ( !expr. is_match( "word" ) ) ;
711
773
}
712
774
775
+ #[ test]
776
+ fn multiple_string_parameters ( ) {
777
+ // TODO: Use "{e}" syntax once MSRV bumps above 1.58.
778
+ let expr = Expression :: regex ( "{string} {string}" )
779
+ . unwrap_or_else ( |e| panic ! ( "failed: {}" , e) ) ;
780
+
781
+ assert_eq ! (
782
+ expr. as_str( ) ,
783
+ "^(?:\
784
+ \" (?P<__0_0>[^\" \\ \\ ]*(?:\\ \\ .[^\" \\ \\ ]*)*)\" \
785
+ |'(?P<__0_1>[^'\\ \\ ]*(?:\\ \\ .[^'\\ \\ ]*)*)'\
786
+ ) (?:\
787
+ \" (?P<__1_0>[^\" \\ \\ ]*(?:\\ \\ .[^\" \\ \\ ]*)*)\" \
788
+ |'(?P<__1_1>[^'\\ \\ ]*(?:\\ \\ .[^'\\ \\ ]*)*)'\
789
+ )$",
790
+ ) ;
791
+ assert ! ( expr. is_match( "\" \" ''" ) ) ;
792
+ assert ! ( expr. is_match( "'' \" \" " ) ) ;
793
+ assert ! ( expr. is_match( "'with \" ' \" \" " ) ) ;
794
+ assert ! ( expr. is_match( "\" with '\" '\" '" ) ) ;
795
+ assert ! ( expr. is_match( "\" with \\ \" escaped\" 'with \\ ' escaped'" ) ) ;
796
+ assert ! ( expr. is_match( "'with \\ ' escaped' \" with \\ \" escaped\" " ) ) ;
797
+ }
798
+
713
799
#[ test]
714
800
fn parameter_all ( ) {
715
801
// TODO: Use "{e}" syntax once MSRV bumps above 1.58.
@@ -747,10 +833,10 @@ mod spec {
747
833
#[ test]
748
834
fn unknown_parameter ( ) {
749
835
match Expression :: regex ( "{custom}" ) . unwrap_err ( ) {
750
- Error :: Expansion ( UnknownParameterError { not_found } ) => {
836
+ Error :: Expansion ( ParameterError :: NotFound ( not_found) ) => {
751
837
assert_eq ! ( * not_found, "custom" ) ;
752
838
}
753
- e @ ( Error :: Parsing ( _) | Error :: Regex ( _) ) => {
839
+ e @ ( Error :: Parsing ( _) | Error :: Regex ( _) | Error :: Expansion ( _ ) ) => {
754
840
// TODO: Use "{e}" syntax once MSRV bumps above 1.58.
755
841
panic ! ( "wrong err: {}" , e) ;
756
842
}
0 commit comments