@@ -70,15 +70,76 @@ mod _sre {
7070 }
7171 }
7272
73- impl SreStr for & Wtf8 {
73+ /// A `str` subject with non-ASCII characters, driven through the string's
74+ /// own character-index table.
75+ ///
76+ /// The `&Wtf8` drive answers `count` and `create_cursor` by decoding from
77+ /// the start of the subject, so both are O(n) and a scan that restarts at
78+ /// successive positions walks the subject once per position. `PyStr`
79+ /// already caches its character length and can resolve a character index to
80+ /// a byte offset in constant time, so this drive asks the string instead of
81+ /// re-deriving: the table it builds on the first lookup is shared by every
82+ /// later one, including by `Match` objects that outlive the scan and have
83+ /// no cursor of their own to move relative to.
84+ ///
85+ /// Stepping is the `&Wtf8` drive's, unchanged -- the subject is the same
86+ /// buffer, decoded the same way. Only the two operations that resolve a
87+ /// position from scratch differ.
88+ #[ derive( Clone , Copy ) ]
89+ struct Utf8Str < ' a > ( & ' a Py < PyStr > ) ;
90+
91+ impl StrDrive for Utf8Str < ' _ > {
92+ fn count ( & self ) -> usize {
93+ self . 0 . char_len ( )
94+ }
95+
96+ fn create_cursor ( & self , n : usize ) -> StringCursor {
97+ // `StringCursor`'s pointer is private to the engine, so the cursor
98+ // is taken from the `&Wtf8` drive at the start of the suffix that
99+ // begins at `n` -- an O(1) reslice -- rather than built here.
100+ let suffix = & self . 0 . as_wtf8 ( ) [ self . 0 . char_index_to_byte ( n) ..] ;
101+ let mut cursor = <& Wtf8 as StrDrive >:: create_cursor ( & suffix, 0 ) ;
102+ cursor. position = n;
103+ cursor
104+ }
105+
106+ fn adjust_cursor ( & self , cursor : & mut StringCursor , n : usize ) {
107+ // Rebuilding is O(1), so it is never the slower branch and the
108+ // `&Wtf8` drive's walk-or-restart choice does not apply.
109+ * cursor = self . create_cursor ( n) ;
110+ }
111+
112+ fn advance ( cursor : & mut StringCursor ) -> u32 {
113+ <& Wtf8 as StrDrive >:: advance ( cursor)
114+ }
115+
116+ fn peek ( cursor : & StringCursor ) -> u32 {
117+ <& Wtf8 as StrDrive >:: peek ( cursor)
118+ }
119+
120+ fn skip ( cursor : & mut StringCursor , n : usize ) {
121+ <& Wtf8 as StrDrive >:: skip ( cursor, n)
122+ }
123+
124+ fn back_advance ( cursor : & mut StringCursor ) -> u32 {
125+ <& Wtf8 as StrDrive >:: back_advance ( cursor)
126+ }
127+
128+ fn back_peek ( cursor : & StringCursor ) -> u32 {
129+ <& Wtf8 as StrDrive >:: back_peek ( cursor)
130+ }
131+
132+ fn back_skip ( cursor : & mut StringCursor , n : usize ) {
133+ <& Wtf8 as StrDrive >:: back_skip ( cursor, n)
134+ }
135+ }
136+
137+ impl SreStr for Utf8Str < ' _ > {
74138 fn slice ( & self , start : usize , end : usize , vm : & VirtualMachine ) -> PyObjectRef {
139+ let end = self . 0 . char_index_to_byte ( end) ;
140+ let start = self . 0 . char_index_to_byte ( start) . min ( end) ;
75141 vm. ctx
76- . new_str (
77- self . code_points ( )
78- . take ( end)
79- . skip ( start)
80- . collect :: < Wtf8Buf > ( ) ,
81- )
142+ . new_str ( self . 0 . as_wtf8 ( ) [ start..end] . to_owned ( ) )
82143 . into ( )
83144 }
84145 }
@@ -275,28 +336,31 @@ mod _sre {
275336 } else if Pattern :: is_ascii_str( subject) {
276337 Pattern :: with_ascii_str( subject, $vm, $f)
277338 } else {
278- Pattern :: with_str ( subject, $vm, $f)
339+ Pattern :: with_utf8_str ( subject, $vm, $f)
279340 }
280341 } } ;
281342 }
282343
283344 #[ pyclass( with( Hashable , Comparable , Representable ) , flags( HAS_WEAKREF ) ) ]
284345 impl Pattern {
346+ fn downcast_str < ' a > ( string : & ' a PyObject , vm : & VirtualMachine ) -> PyResult < & ' a Py < PyStr > > {
347+ string. downcast_ref :: < PyStr > ( ) . ok_or_else ( || {
348+ vm. new_type_error ( format ! ( "expected string got '{}'" , string. class( ) ) )
349+ } )
350+ }
351+
285352 fn with_str < F , R > ( string : & PyObject , vm : & VirtualMachine , f : F ) -> PyResult < R >
286353 where
287354 F : FnOnce ( & Wtf8 ) -> PyResult < R > ,
288355 {
289- let string = string. downcast_ref :: < PyStr > ( ) . ok_or_else ( || {
290- vm. new_type_error ( format ! ( "expected string got '{}'" , string. class( ) ) )
291- } ) ?;
292- f ( string. as_wtf8 ( ) )
356+ f ( Self :: downcast_str ( string, vm) ?. as_wtf8 ( ) )
293357 }
294358
295359 /// Whether a `str` subject can take the [`AsciiStr`] drive.
296360 ///
297361 /// `PyStr` already knows: `StrKind` is decided when the string is
298362 /// built, so this is a field load rather than a scan. A non-`str`
299- /// argument answers `false` and is reported by [`Self::with_str `].
363+ /// argument answers `false` and is reported by [`Self::with_utf8_str `].
300364 fn is_ascii_str ( string : & PyObject ) -> bool {
301365 string
302366 . downcast_ref :: < PyStr > ( )
@@ -307,12 +371,17 @@ mod _sre {
307371 where
308372 F : FnOnce ( AsciiStr < ' _ > ) -> PyResult < R > ,
309373 {
310- let string = string. downcast_ref :: < PyStr > ( ) . ok_or_else ( || {
311- vm. new_type_error ( format ! ( "expected string got '{}'" , string. class( ) ) )
312- } ) ?;
374+ let string = Self :: downcast_str ( string, vm) ?;
313375 f ( AsciiStr ( string. as_wtf8 ( ) . as_bytes ( ) ) )
314376 }
315377
378+ fn with_utf8_str < F , R > ( string : & PyObject , vm : & VirtualMachine , f : F ) -> PyResult < R >
379+ where
380+ F : FnOnce ( Utf8Str < ' _ > ) -> PyResult < R > ,
381+ {
382+ f ( Utf8Str ( Self :: downcast_str ( string, vm) ?) )
383+ }
384+
316385 fn with_bytes < F , R > ( string : & PyObject , vm : & VirtualMachine , f : F ) -> PyResult < R >
317386 where
318387 F : FnOnce ( & [ u8 ] ) -> PyResult < R > ,
0 commit comments