Skip to content

Commit 7c97208

Browse files
committed
perf: convert remaining char operations to byte-based for performance
1 parent 50b2153 commit 7c97208

5 files changed

Lines changed: 87 additions & 38 deletions

File tree

‎src/formatter/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ trait NewlineCount {
10481048

10491049
impl NewlineCount for &str {
10501050
fn newline_count(&self) -> usize {
1051-
self.chars().filter(|c| c == &'\n').count()
1051+
self.as_bytes().iter().filter(|&&b| b == b'\n').count()
10521052
}
10531053
}
10541054

‎src/lexer.rs‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,20 @@ impl<'source, Token: LexerToken<'source>> Iterator for Lexer<'source, Token> {
4343
self.current_span = start..self.pos;
4444
Some(Ok(token))
4545
} else {
46-
// Error case - consume one char
47-
let c = remainder.chars().next()?;
48-
self.pos += c.len_utf8();
46+
// Error case - consume one UTF-8 character
47+
let bytes = remainder.as_bytes();
48+
let first_byte = *bytes.first()?;
49+
// Determine UTF-8 sequence length from first byte
50+
let char_len = if first_byte < 0x80 {
51+
1 // ASCII
52+
} else if first_byte < 0xE0 {
53+
2 // 2-byte sequence
54+
} else if first_byte < 0xF0 {
55+
3 // 3-byte sequence
56+
} else {
57+
4 // 4-byte sequence
58+
};
59+
self.pos += char_len;
4960
self.current_span = start..self.pos;
5061
Some(Err(()))
5162
}

‎src/parser/mod.rs‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -889,25 +889,45 @@ impl<'p> Parser<'p> {
889889
}
890890

891891
fn check_underscores(s: &str, radix: u32) -> bool {
892-
if s.starts_with('_') || s.ends_with('_') {
892+
let bytes = s.as_bytes();
893+
if bytes.first() == Some(&b'_') || bytes.last() == Some(&b'_') {
893894
return false;
894895
}
895896

896-
let mut last_char = 0 as char;
897+
let mut last_byte = 0_u8;
897898

898-
for c in s.chars() {
899-
if c == '_' && !last_char.is_digit(radix) {
899+
for &b in bytes {
900+
let is_digit = match radix {
901+
2 => b == b'0' || b == b'1',
902+
8 => (b'0'..=b'7').contains(&b),
903+
10 => b.is_ascii_digit(),
904+
16 => b.is_ascii_hexdigit(),
905+
_ => unreachable!(),
906+
};
907+
908+
if b == b'_' && !is_digit_byte(last_byte, radix) {
900909
return false;
901910
}
902-
if !c.is_digit(radix) && last_char == '_' {
911+
if !is_digit && last_byte == b'_' {
903912
return false;
904913
}
905-
last_char = c;
914+
last_byte = b;
906915
}
907916

908917
true
909918
}
910919

920+
#[inline]
921+
fn is_digit_byte(b: u8, radix: u32) -> bool {
922+
match radix {
923+
2 => b == b'0' || b == b'1',
924+
8 => (b'0'..=b'7').contains(&b),
925+
10 => b.is_ascii_digit(),
926+
16 => b.is_ascii_hexdigit(),
927+
_ => unreachable!(),
928+
}
929+
}
930+
911931
/// The final results of a parsing.
912932
/// It contains the green tree, and
913933
/// the errors that occurred during parsing.

‎src/util/escape.rs‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ impl<'source> LexerToken<'source> for Escape {
5454

5555
// Newline escape: backslash followed by optional whitespace and newline
5656
if let Some(rest) = input.strip_prefix('\\') {
57-
let ws_len: usize =
58-
rest.chars().take_while(|&c| c == ' ' || c == '\t').map(|c| c.len_utf8()).sum();
57+
let bytes = rest.as_bytes();
58+
let ws_len = bytes.iter().take_while(|&&b| b == b' ' || b == b'\t').count();
5959
let after_ws = &rest[ws_len..];
6060
if after_ws.starts_with('\n') {
6161
return Some((Newline, 1 + ws_len + 1));
@@ -67,16 +67,16 @@ impl<'source> LexerToken<'source> for Escape {
6767

6868
// Unicode escape \uXXXX
6969
if input.starts_with("\\u") && input.len() >= 6 {
70-
let hex_chars = &input[2..6];
71-
if hex_chars.chars().all(|c| c.is_ascii_hexdigit() || c == '_') {
70+
let hex_bytes = &input.as_bytes()[2..6];
71+
if hex_bytes.iter().all(|&b| b.is_ascii_hexdigit() || b == b'_') {
7272
return Some((Unicode, 6));
7373
}
7474
}
7575

7676
// Unicode escape \UXXXXXXXX
7777
if input.starts_with("\\U") && input.len() >= 10 {
78-
let hex_chars = &input[2..10];
79-
if hex_chars.chars().all(|c| c.is_ascii_hexdigit() || c == '_') {
78+
let hex_bytes = &input.as_bytes()[2..10];
79+
if hex_bytes.iter().all(|&b| b.is_ascii_hexdigit() || b == b'_') {
8080
return Some((UnicodeLarge, 10));
8181
}
8282
}
@@ -86,9 +86,20 @@ impl<'source> LexerToken<'source> for Escape {
8686
return Some((Unknown, 2));
8787
}
8888

89-
// Unescaped character
90-
if let Some(c) = input.chars().next() {
91-
return Some((UnEscaped, c.len_utf8()));
89+
// Unescaped character - need to handle UTF-8 here
90+
let bytes = input.as_bytes();
91+
if let Some(&first_byte) = bytes.first() {
92+
// Determine UTF-8 sequence length from first byte
93+
let len = if first_byte < 0x80 {
94+
1 // ASCII
95+
} else if first_byte < 0xE0 {
96+
2 // 2-byte sequence
97+
} else if first_byte < 0xF0 {
98+
3 // 3-byte sequence
99+
} else {
100+
4 // 4-byte sequence
101+
};
102+
return Some((UnEscaped, len));
92103
}
93104

94105
None

‎src/util/mod.rs‎

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ pub use escape::check_escape;
77
pub(crate) mod allowed_chars {
88
pub(crate) fn comment(s: &str) -> Result<(), Vec<usize>> {
99
let mut err_indices = Vec::new();
10+
let bytes = s.as_bytes();
1011

11-
for (i, c) in s.chars().enumerate() {
12-
if c != '\t' && c.is_control() {
12+
for (i, &b) in bytes.iter().enumerate() {
13+
// Tab is 0x09, control chars are 0x00-0x1F and 0x7F (all ASCII)
14+
if b != b'\t' && (b < 0x20 || b == 0x7F) {
1315
err_indices.push(i);
1416
}
1517
}
@@ -19,12 +21,13 @@ pub(crate) mod allowed_chars {
1921

2022
pub(crate) fn string(s: &str) -> Result<(), Vec<usize>> {
2123
let mut err_indices = Vec::new();
24+
let bytes = s.as_bytes();
2225

23-
for (i, c) in s.chars().enumerate() {
24-
if c != '\t'
25-
&& (('\u{0000}'..='\u{0008}').contains(&c)
26-
|| ('\u{000A}'..='\u{001F}').contains(&c)
27-
|| c == '\u{007F}')
26+
for (i, &b) in bytes.iter().enumerate() {
27+
// Check for disallowed control chars: 0x00-0x08, 0x0A-0x1F, 0x7F
28+
// Tab (0x09) is allowed
29+
if b != b'\t'
30+
&& ((0x00..=0x08).contains(&b) || (0x0A..=0x1F).contains(&b) || b == 0x7F)
2831
{
2932
err_indices.push(i);
3033
}
@@ -35,14 +38,14 @@ pub(crate) mod allowed_chars {
3538

3639
pub(crate) fn multi_line_string(s: &str) -> Result<(), Vec<usize>> {
3740
let mut err_indices = Vec::new();
38-
39-
for (i, c) in s.chars().enumerate() {
40-
if c != '\t'
41-
&& c != '\n'
42-
&& c != '\r'
43-
&& (('\u{0000}'..='\u{0008}').contains(&c)
44-
|| ('\u{000A}'..='\u{001F}').contains(&c)
45-
|| c == '\u{007F}')
41+
let bytes = s.as_bytes();
42+
43+
for (i, &b) in bytes.iter().enumerate() {
44+
// Tab (0x09), LF (0x0A), CR (0x0D) are allowed
45+
if b != b'\t'
46+
&& b != b'\n'
47+
&& b != b'\r'
48+
&& ((0x00..=0x08).contains(&b) || (0x0A..=0x1F).contains(&b) || b == 0x7F)
4649
{
4750
err_indices.push(i);
4851
}
@@ -53,9 +56,11 @@ pub(crate) mod allowed_chars {
5356

5457
pub(crate) fn string_literal(s: &str) -> Result<(), Vec<usize>> {
5558
let mut err_indices = Vec::new();
59+
let bytes = s.as_bytes();
5660

57-
for (i, c) in s.chars().enumerate() {
58-
if c != '\t' && c.is_control() {
61+
for (i, &b) in bytes.iter().enumerate() {
62+
// Tab is 0x09, control chars are 0x00-0x1F and 0x7F
63+
if b != b'\t' && (b < 0x20 || b == 0x7F) {
5964
err_indices.push(i);
6065
}
6166
}
@@ -65,9 +70,11 @@ pub(crate) mod allowed_chars {
6570

6671
pub(crate) fn multi_line_string_literal(s: &str) -> Result<(), Vec<usize>> {
6772
let mut err_indices = Vec::new();
73+
let bytes = s.as_bytes();
6874

69-
for (i, c) in s.chars().enumerate() {
70-
if c != '\t' && c != '\n' && c != '\r' && c.is_control() {
75+
for (i, &b) in bytes.iter().enumerate() {
76+
// Tab (0x09), LF (0x0A), CR (0x0D) are allowed
77+
if b != b'\t' && b != b'\n' && b != b'\r' && (b < 0x20 || b == 0x7F) {
7178
err_indices.push(i);
7279
}
7380
}

0 commit comments

Comments
 (0)