Skip to content

Commit 7c20056

Browse files
committed
perf(regex): reduce string allocations in Display impls (#6528)
There's still room for improvement here.
1 parent 2c32dac commit 7c20056

File tree

1 file changed

+42
-45
lines changed
  • crates/oxc_regular_expression/src/ast_impl

1 file changed

+42
-45
lines changed

crates/oxc_regular_expression/src/ast_impl/display.rs

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
borrow::Cow,
23
fmt::{self, Display},
34
iter::Peekable,
45
};
@@ -31,7 +32,7 @@ impl<'a> Display for Alternative<'a> {
3132

3233
write_join_with(f, "", &self.body, |iter| {
3334
let next = iter.next()?;
34-
let Some(next) = as_character(next) else { return Some(next.to_string()) };
35+
let Some(next) = as_character(next) else { return Some(Cow::Owned(next.to_string())) };
3536

3637
let peek = iter.peek().and_then(|it| as_character(it));
3738
let (result, eat) = character_to_string(next, peek);
@@ -106,10 +107,12 @@ impl<'a> Display for Quantifier<'a> {
106107
(1, None) => write!(f, "+")?,
107108
(0, Some(1)) => write!(f, "?")?,
108109
(min, Some(max)) if min == max => write!(f, "{{{min}}}",)?,
109-
(min, max) => {
110-
let max = max.map_or_else(String::default, |it| it.to_string());
110+
(min, Some(max)) => {
111111
write!(f, "{{{min},{max}}}",)?;
112112
}
113+
(min, None) => {
114+
write!(f, "{{{min},}}",)?;
115+
}
113116
}
114117

115118
if !self.greedy {
@@ -194,7 +197,9 @@ impl<'a> Display for CharacterClass<'a> {
194197

195198
write_join_with(f, sep, &self.body, |iter| {
196199
let next = iter.next()?;
197-
let Some(next) = as_character(next) else { return Some(next.to_string()) };
200+
let Some(next) = as_character(next) else {
201+
return Some(Cow::Owned(next.to_string()));
202+
};
198203

199204
let peek = iter.peek().and_then(|it| as_character(it));
200205
let (result, eat) = character_to_string(next, peek);
@@ -304,76 +309,62 @@ impl<'a> Display for NamedReference<'a> {
304309
fn character_to_string(
305310
this: &Character,
306311
peek: Option<&Character>,
307-
) -> (/* result */ String, /* true of peek should be consumed */ bool) {
312+
) -> (/* result */ Cow<'static, str>, /* true of peek should be consumed */ bool) {
308313
let cp = this.value;
309314

310315
if matches!(this.kind, CharacterKind::Symbol | CharacterKind::UnicodeEscape) {
311316
// Trail only
312317
if is_trail_surrogate(cp) {
313-
return (format!(r"\u{cp:X}"), false);
318+
return (Cow::Owned(format!(r"\u{cp:X}")), false);
314319
}
315320

316321
if is_lead_surrogate(cp) {
317322
if let Some(peek) = peek.filter(|peek| is_trail_surrogate(peek.value)) {
318323
// Lead+Trail
319324
let cp = combine_surrogate_pair(cp, peek.value);
320325
let ch = char::from_u32(cp).expect("Invalid surrogate pair `Character`!");
321-
return (format!("{ch}"), true);
326+
return (Cow::Owned(format!("{ch}")), true);
322327
}
323328

324329
// Lead only
325-
return (format!(r"\u{cp:X}"), false);
330+
return (Cow::Owned(format!(r"\u{cp:X}")), false);
326331
}
327332
}
328333

329334
let ch = char::from_u32(cp).expect("Invalid `Character`!");
330335
let result = match this.kind {
331336
// Not a surrogate, like BMP, or all units in unicode mode
332-
CharacterKind::Symbol => format!("{ch}"),
337+
CharacterKind::Symbol => Cow::Owned(ch.to_string()),
333338
CharacterKind::ControlLetter => match ch {
334-
'\n' => r"\cJ".to_string(),
335-
'\r' => r"\cM".to_string(),
336-
'\t' => r"\cI".to_string(),
337-
_ => format!(r"\c{ch}"),
339+
'\n' => Cow::Borrowed(r"\cJ"),
340+
'\r' => Cow::Borrowed(r"\cM"),
341+
'\t' => Cow::Borrowed(r"\cI"),
342+
_ => Cow::Owned(format!(r"\c{ch}")),
338343
},
339-
CharacterKind::Identifier => {
340-
format!(r"\{ch}")
341-
}
344+
CharacterKind::Identifier => Cow::Owned(format!(r"\{ch}")),
342345
CharacterKind::SingleEscape => match ch {
343-
'\n' => String::from(r"\n"),
344-
'\r' => String::from(r"\r"),
345-
'\t' => String::from(r"\t"),
346-
'\u{b}' => String::from(r"\v"),
347-
'\u{c}' => String::from(r"\f"),
348-
'\u{8}' => String::from(r"\b"),
349-
'\u{2D}' => String::from(r"\-"),
350-
_ => format!(r"\{ch}"),
346+
'\n' => Cow::Borrowed(r"\n"),
347+
'\r' => Cow::Borrowed(r"\r"),
348+
'\t' => Cow::Borrowed(r"\t"),
349+
'\u{b}' => Cow::Borrowed(r"\v"),
350+
'\u{c}' => Cow::Borrowed(r"\f"),
351+
'\u{8}' => Cow::Borrowed(r"\b"),
352+
'\u{2D}' => Cow::Borrowed(r"\-"),
353+
_ => Cow::Owned(format!(r"\{ch}")),
351354
},
352-
CharacterKind::Null => String::from(r"\0"),
355+
CharacterKind::Null => Cow::Borrowed(r"\0"),
353356
CharacterKind::UnicodeEscape => {
354357
let hex = &format!("{cp:04X}");
355358
if hex.len() <= 4 {
356-
format!(r"\u{hex}")
359+
Cow::Owned(format!(r"\u{hex}"))
357360
} else {
358-
format!(r"\u{{{hex}}}")
361+
Cow::Owned(format!(r"\u{{{hex}}}"))
359362
}
360363
}
361-
CharacterKind::HexadecimalEscape => {
362-
let hex = &format!("{cp:02X}");
363-
format!(r"\x{hex}")
364-
}
365-
CharacterKind::Octal1 => {
366-
let octal = format!("{cp:o}");
367-
format!(r"\{octal}")
368-
}
369-
CharacterKind::Octal2 => {
370-
let octal = format!("{cp:02o}");
371-
format!(r"\{octal}")
372-
}
373-
CharacterKind::Octal3 => {
374-
let octal = format!("{cp:03o}");
375-
format!(r"\{octal}")
376-
}
364+
CharacterKind::HexadecimalEscape => Cow::Owned(format!(r"\x{cp:02X}")),
365+
CharacterKind::Octal1 => Cow::Owned(format!(r"\{cp:o}")),
366+
CharacterKind::Octal2 => Cow::Owned(format!(r"\{cp:02o}")),
367+
CharacterKind::Octal3 => Cow::Owned(format!(r"\{cp:03o}")),
377368
};
378369

379370
(result, false)
@@ -390,12 +381,18 @@ where
390381
write_join_with(f, sep, items, |iter| iter.next().map(|it| it.to_string()))
391382
}
392383

393-
fn write_join_with<S, I, E, F>(f: &mut fmt::Formatter<'_>, sep: S, items: I, next: F) -> fmt::Result
384+
fn write_join_with<S, I, E, F, D>(
385+
f: &mut fmt::Formatter<'_>,
386+
sep: S,
387+
items: I,
388+
next: F,
389+
) -> fmt::Result
394390
where
395391
S: AsRef<str>,
396392
E: Display,
397393
I: IntoIterator<Item = E>,
398-
F: Fn(&mut Peekable<I::IntoIter>) -> Option<String>,
394+
F: Fn(&mut Peekable<I::IntoIter>) -> Option<D>,
395+
D: fmt::Display,
399396
{
400397
let sep = sep.as_ref();
401398
let iter = &mut items.into_iter().peekable();

0 commit comments

Comments
 (0)