Skip to content

Commit 30b2aaa

Browse files
committed
_sre: drive a non-ASCII str subject through the string's character index
The `&Wtf8` drive answers `count` and `create_cursor` by decoding from the start of the subject, so a scan that restarts at successive positions walks the subject once per position, and `slice` walks it again per extracted group. `Utf8Str` holds the `PyStr` and asks it instead: `count` is the cached character length, and `create_cursor` and `slice` resolve their positions through `char_index_to_byte`. Stepping is the `&Wtf8` drive's, unchanged. The table lives on the string, so a `Match` that outlives the scan shares it -- `group` has no cursor of its own to move relative to. `SreStr for &Wtf8` has no callers left; the `StrDrive` impl stays, since `Utf8Str` steps through it. The three subject helpers now share one downcast. Assisted-by: Claude
1 parent 0ebff92 commit 30b2aaa

1 file changed

Lines changed: 85 additions & 16 deletions

File tree

crates/vm/src/stdlib/_sre.rs

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)