diff --git a/.jules/bolt.md b/.jules/bolt.md index 3644ef5..aca28d8 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -6,3 +6,6 @@ **Learning:** In `split_segments`, constructing `Segment` previously performed `text.to_owned()` for every split segment. This caused unnecessary memory allocation, as the parsed segment string could just borrow from the original input `&str`. **Action:** Update parsing intermediate structs (like `Segment`) to carry string slices (`&'a str`) representing chunks of the input string rather than owning `String`s when they are only used briefly to route segments to transformation parsers. +## 2024-08-14 - [Rust String Parsing - Whitespace Semantics Regression] +**Learning:** When optimizing whitespace scanning in Rust parsing loops by replacing `.char_indices()` with byte-level ASCII checks (e.g., `as_bytes().iter().position(|b| b.is_ascii_whitespace())`), it can introduce a subtle functional regression. Rust's `char::is_whitespace()` matches all Unicode whitespace characters (like non-breaking spaces), whereas `is_ascii_whitespace()` only matches standard ASCII whitespace. +**Action:** When exact Unicode semantics must be preserved while optimizing, use `.find()` (e.g., `text.find(char::is_whitespace)`) instead of dropping down to byte-level operations. This leverages internal optimizations while preserving the exact semantic meaning of the original code. diff --git a/compiler/rockql-parser/src/lib.rs b/compiler/rockql-parser/src/lib.rs index 1ddfaa9..312944d 100644 --- a/compiler/rockql-parser/src/lib.rs +++ b/compiler/rockql-parser/src/lib.rs @@ -70,16 +70,16 @@ fn split_segments(source: &str) -> Vec> { for (line_index, line) in source.lines().enumerate() { let mut start = 0; - for (byte_index, character) in line.char_indices() { - if character == '|' { - push_segment( - &mut segments, - &line[start..byte_index], - line_index + 1, - start, - ); - start = byte_index + character.len_utf8(); - } + // ⚡ Bolt Optimization: Use `match_indices` instead of `char_indices` + // to avoid UTF-8 decoding overhead when searching for an ASCII character. + for (byte_index, _) in line.match_indices('|') { + push_segment( + &mut segments, + &line[start..byte_index], + line_index + 1, + start, + ); + start = byte_index + 1; // '|' is 1 byte } push_segment(&mut segments, &line[start..], line_index + 1, start); @@ -102,10 +102,9 @@ fn push_segment<'a>(segments: &mut Vec>, raw: &'a str, line: usize, } fn parse_transform(text: &str, span: Span) -> Result { - let keyword_end = text - .char_indices() - .find_map(|(index, character)| character.is_whitespace().then_some(index)) - .unwrap_or(text.len()); + // ⚡ Bolt Optimization: Use `find(char::is_whitespace)` instead of `char_indices` + // to optimize whitespace scanning while preserving exact Unicode whitespace semantics. + let keyword_end = text.find(char::is_whitespace).unwrap_or(text.len()); let keyword = &text[..keyword_end]; let rest = text[keyword_end..].trim();