@@ -960,6 +960,10 @@ pub const Regex = struct {
960960 return .{ .count = run , .end = p };
961961 }
962962
963+ fn anchoredStartSkipped (self : * const Regex , start : usize ) bool {
964+ return start > 0 and self .opt_info .anchored_start and ! self .flags .multiline ;
965+ }
966+
963967 fn nextCodepointStart (input : []const u8 , pos : usize ) usize {
964968 if (pos >= input .len ) return input .len + 1 ;
965969 const len = common .ecmaCodePointLen (input , pos ) orelse return pos + 1 ;
@@ -1470,15 +1474,66 @@ pub const Regex = struct {
14701474 pub fn findFrom (self : * const Regex , input : []const u8 , start : usize ) ! ? Match {
14711475 if (start == 0 ) return self .find (input );
14721476 if (self .requiredAbsent (input )) return null ;
1477+ const from = @min (start , input .len );
1478+ if (self .anchoredStartSkipped (from )) return null ;
14731479
14741480 if (self .opt_info .exact_literal ) | lit | {
14751481 const found = if (self .flags .case_insensitive ) blk : {
1476- var sc = CiLiteralScanner .init (input , lit , @min ( start , input . len ) );
1482+ var sc = CiLiteralScanner .init (input , lit , from );
14771483 break :blk sc .next ();
1478- } else literalSearch (input , lit , @min ( start , input . len ) );
1484+ } else literalSearch (input , lit , from );
14791485 if (found ) | i | return Match { .slice = input [i .. i + lit .len ], .start = i , .end = i + lit .len };
14801486 return null ;
14811487 }
1488+ if (self .engine_type == .thompson_nfa ) if (self .opt_info .repeat_atom ) | ra | if (self .repeatAtomFastPathOk ()) {
1489+ const table = repeatTable (ra , self .flags .case_insensitive );
1490+ const max = ra .max orelse std .math .maxInt (usize );
1491+ if (self .opt_info .anchored_end ) {
1492+ var p = from ;
1493+ while (p < input .len and ! table [input [p ]]) p += 1 ;
1494+ if (p >= input .len ) return null ;
1495+ const run = repeatRunAt (input , table , p , max );
1496+ const end = p + run ;
1497+ if (end == input .len and repeatBoundsOk (run , ra .min , max ))
1498+ return Match { .slice = input [p .. end ], .start = p , .end = end };
1499+ return null ;
1500+ }
1501+ var p = from ;
1502+ while (p < input .len ) {
1503+ while (p < input .len and ! table [input [p ]]) p += 1 ;
1504+ if (p >= input .len ) break ;
1505+ const match_start = p ;
1506+ var run : usize = 0 ;
1507+ while (p < input .len and table [input [p ]] and run < max ) : (p += 1 ) run += 1 ;
1508+ if (run >= ra .min ) return Match { .slice = input [match_start .. p ], .start = match_start , .end = p };
1509+ }
1510+ return null ;
1511+ };
1512+ if (self .opt_info .unicode_repeat_atom ) | ura | if (! self .flags .case_insensitive and self .repeatAtomFastPathOk ()) {
1513+ const max = ura .max orelse std .math .maxInt (usize );
1514+ if (self .opt_info .anchored_end ) {
1515+ var p = from ;
1516+ while (p < input .len ) {
1517+ const run = unicodeRepeatRunAt (input , ura .property , p , max );
1518+ if (run .count >= ura .min ) {
1519+ if (run .end == input .len )
1520+ return Match { .slice = input [p .. run .end ], .start = p , .end = run .end };
1521+ return null ;
1522+ }
1523+ p = nextCodepointStart (input , p );
1524+ }
1525+ return null ;
1526+ }
1527+ var p = from ;
1528+ while (p < input .len ) {
1529+ const run = unicodeRepeatRunAt (input , ura .property , p , max );
1530+ if (run .count >= ura .min ) {
1531+ return Match { .slice = input [p .. run .end ], .start = p , .end = run .end };
1532+ }
1533+ p = nextCodepointStart (input , p );
1534+ }
1535+ return null ;
1536+ };
14821537
14831538 switch (self .engine_type ) {
14841539 .thompson_nfa = > {
@@ -1488,7 +1543,7 @@ pub const Regex = struct {
14881543
14891544 if (self .onepass ) | plan | {
14901545 const fb = self .opt_info .first_bytes ;
1491- var scan : usize = @min ( start , input . len ) ;
1546+ var scan : usize = from ;
14921547 while (scan <= input .len ) {
14931548 if (fb ) | t | {
14941549 while (scan < input .len and ! t [input [scan ]]) scan += 1 ;
@@ -1504,7 +1559,7 @@ pub const Regex = struct {
15041559
15051560 if (self .opt_info .literal_prefix ) | prefix | {
15061561 if (! self .flags .case_insensitive ) {
1507- var search_from : usize = @min ( start , input . len ) ;
1562+ var search_from : usize = from ;
15081563 while (std .mem .indexOf (u8 , input [search_from .. ], prefix )) | rel_pos | {
15091564 const prefix_pos = search_from + rel_pos ;
15101565 if (try virtual_machine .matchAt (input , prefix_pos )) | result | {
@@ -1518,7 +1573,7 @@ pub const Regex = struct {
15181573
15191574 if (self .opt_info .first_bytes ) | fb | {
15201575 if (! self .flags .case_insensitive ) {
1521- var scan : usize = @min ( start , input . len ) ;
1576+ var scan : usize = from ;
15221577 while (scan < input .len ) {
15231578 while (scan < input .len and ! fb [input [scan ]]) scan += 1 ;
15241579 if (scan >= input .len ) break ;
@@ -1531,7 +1586,7 @@ pub const Regex = struct {
15311586 }
15321587 }
15331588
1534- var scan : usize = @min ( start , input . len ) ;
1589+ var scan : usize = from ;
15351590 while (scan <= input .len ) {
15361591 if (try virtual_machine .matchAt (input , scan )) | result | {
15371592 return try self .buildMatch (input , result );
@@ -3057,6 +3112,32 @@ test "unicode surrogate escapes compile to WTF-8 code units" {
30573112 try std .testing .expect (try regex .isMatch (&.{ 0xED , 0xBC , 0x86 }));
30583113}
30593114
3115+ test "findFrom uses repeated atom fast paths after nonzero start" {
3116+ const allocator = std .testing .allocator ;
3117+
3118+ var digits = try Regex .compile (allocator , "\\ d+" );
3119+ defer digits .deinit ();
3120+ if (try digits .findFrom ("xx123yy" , 2 )) | match_result | {
3121+ try std .testing .expectEqualStrings ("123" , match_result .slice );
3122+ } else try std .testing .expect (false );
3123+ if (try digits .findFrom ("xx123yy" , 4 )) | match_result | {
3124+ try std .testing .expectEqualStrings ("3" , match_result .slice );
3125+ } else try std .testing .expect (false );
3126+
3127+ var letters = try Regex .compileWithFlags (allocator , "\\ p{Letter}+" , .{ .unicode = true });
3128+ defer letters .deinit ();
3129+ if (try letters .findFrom ("12abc" , 2 )) | match_result | {
3130+ try std .testing .expectEqualStrings ("abc" , match_result .slice );
3131+ } else try std .testing .expect (false );
3132+ if (try letters .findFrom ("12abc" , 3 )) | match_result | {
3133+ try std .testing .expectEqualStrings ("bc" , match_result .slice );
3134+ } else try std .testing .expect (false );
3135+
3136+ var anchored = try Regex .compileWithFlags (allocator , "^\\ p{Letter}+$" , .{ .unicode = true });
3137+ defer anchored .deinit ();
3138+ try std .testing .expect ((try anchored .findFrom ("abc" , 1 )) == null );
3139+ }
3140+
30603141test "capture spans give per-group byte offsets" {
30613142 const allocator = std .testing .allocator ;
30623143 var regex = try Regex .compile (allocator , "(\\ d+)-(\\ d+)" );
0 commit comments