Skip to content

Commit 9b3fbd4

Browse files
authored
tokenizer: Make consume_name faster. (#449)
Skip over runs of ascii and such in a tight loop. This is one of the hottest functions and match_byte! adds extra branches for the common cases.
1 parent c050a07 commit 9b3fbd4

1 file changed

Lines changed: 47 additions & 15 deletions

File tree

src/tokenizer.rs

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,16 @@ impl<'a> Tokenizer<'a> {
312312
unsafe { self.input.get_unchecked(range.start.0..range.end.0) }
313313
}
314314

315+
#[inline]
316+
pub(crate) fn byte_slice(&self, range: Range<usize>) -> &'a [u8] {
317+
&self.input.as_bytes()[range]
318+
}
319+
320+
#[inline]
321+
pub(crate) fn byte_slice_from(&self, start: usize) -> &'a [u8] {
322+
self.byte_slice(start..self.position)
323+
}
324+
315325
pub fn current_source_line(&self) -> &'a str {
316326
let current = self.position();
317327
let start = self
@@ -365,6 +375,26 @@ impl<'a> Tokenizer<'a> {
365375
self.position += n
366376
}
367377

378+
/// Equivalent to calling advance() for runs of bytes for which `matches` returns true.
379+
/// Returns the byte slice advanced over.
380+
fn advance_while(&mut self, mut matches: impl FnMut(u8) -> bool) -> &[u8] {
381+
let start = self.position;
382+
let mut position = start;
383+
384+
let bytes = &self.input.as_bytes()[start..];
385+
for b in bytes {
386+
if !matches(*b) {
387+
break;
388+
}
389+
position += 1;
390+
}
391+
392+
// Equivalent to self.position = position, but with advance()'s debug_assert!s
393+
self.advance(position - self.position);
394+
395+
self.byte_slice_from(start)
396+
}
397+
368398
// Assumes non-EOF
369399
#[inline]
370400
fn next_byte_unchecked(&self) -> u8 {
@@ -917,15 +947,26 @@ fn consume_ident_like<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
917947
}
918948

919949
fn consume_name<'a>(tokenizer: &mut Tokenizer<'a>) -> CowRcStr<'a> {
950+
// These are the overwhelmingly common bytes, that we can just skip over in a tight loop.
951+
static IS_SIMPLE_NAME_BYTE: [bool; 256] = {
952+
let mut table = [false; 256];
953+
let mut i = 0;
954+
while i < 256 {
955+
table[i as usize] = matches!(i as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' | b'\xC0'..=b'\xEF');
956+
i += 1;
957+
}
958+
table
959+
};
960+
920961
// start_pos is the end of the previous token, therefore at a code point boundary
921962
let start_pos = tokenizer.position();
922963
let mut value_bytes;
923964
loop {
965+
tokenizer.advance_while(|b| IS_SIMPLE_NAME_BYTE[b as usize]);
924966
if tokenizer.is_eof() {
925967
return tokenizer.slice_from(start_pos).into();
926968
}
927969
match_byte! { tokenizer.next_byte_unchecked(),
928-
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' => tokenizer.advance(1),
929970
b'\\' | b'\0' => {
930971
// * The tokenizer’s input is UTF-8 since it’s `&str`.
931972
// * start_pos is at a code point boundary
@@ -936,21 +977,20 @@ fn consume_name<'a>(tokenizer: &mut Tokenizer<'a>) -> CowRcStr<'a> {
936977
break
937978
}
938979
b'\x80'..=b'\xBF' => { tokenizer.consume_continuation_byte(); }
939-
b'\xC0'..=b'\xEF' => { tokenizer.advance(1); }
940980
b'\xF0'..=b'\xFF' => { tokenizer.consume_4byte_intro(); }
941981
_b => {
942982
return tokenizer.slice_from(start_pos).into();
943983
}
944984
}
945985
}
946986

947-
while !tokenizer.is_eof() {
987+
loop {
988+
value_bytes.extend(tokenizer.advance_while(|b| IS_SIMPLE_NAME_BYTE[b as usize]));
989+
if tokenizer.is_eof() {
990+
break;
991+
}
948992
let b = tokenizer.next_byte_unchecked();
949993
match_byte! { b,
950-
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' => {
951-
tokenizer.advance(1);
952-
value_bytes.push(b) // ASCII
953-
}
954994
b'\\' => {
955995
if tokenizer.has_newline_at(1) { break }
956996
tokenizer.advance(1);
@@ -962,17 +1002,9 @@ fn consume_name<'a>(tokenizer: &mut Tokenizer<'a>) -> CowRcStr<'a> {
9621002
value_bytes.extend("\u{FFFD}".as_bytes());
9631003
},
9641004
b'\x80'..=b'\xBF' => {
965-
// This byte *is* part of a multi-byte code point,
966-
// we’ll end up copying the whole code point before this loop does something else.
9671005
tokenizer.consume_continuation_byte();
9681006
value_bytes.push(b)
9691007
}
970-
b'\xC0'..=b'\xEF' => {
971-
// This byte *is* part of a multi-byte code point,
972-
// we’ll end up copying the whole code point before this loop does something else.
973-
tokenizer.advance(1);
974-
value_bytes.push(b)
975-
}
9761008
b'\xF0'..=b'\xFF' => {
9771009
tokenizer.consume_4byte_intro();
9781010
value_bytes.push(b)

0 commit comments

Comments
 (0)