Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
**Learning:** In tight parsing loops in Rust (e.g., `normalize_keywords` and `remove_numeric_separators`), using `.chars().collect::<Vec<_>>()` introduces severe performance overhead due to O(N) memory allocation per operation. Similarly, repeatedly allocating new `String` instances for word extraction and `.to_ascii_lowercase()` within loops causes significant heap traffic.

**Action:** When performing string transformations or extractions in high-frequency functions, prefer slicing directly with `&str`, checking bytes directly (e.g. `value.as_bytes()`) where possible (such as when scanning for single-byte ASCII characters), and using non-allocating comparison methods like `eq_ignore_ascii_case()` instead of allocating a lowercase string. Always avoid `.chars().collect::<Vec<_>>()` when a streaming iterator (`chars()` or `as_bytes()`) suffices.
## 2026-08-12 - [Avoid String allocation during initial parsing segment split]
**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.
12 changes: 6 additions & 6 deletions compiler/rockql-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ impl Display for Diagnostic {
}

#[derive(Debug)]
struct Segment {
text: String,
struct Segment<'a> {
text: &'a str,
span: Span,
}

Expand All @@ -47,7 +47,7 @@ pub fn parse(source: &str) -> Result<Query, Vec<Diagnostic>> {
let mut diagnostics = Vec::new();

for segment in segments {
match parse_transform(&segment.text, segment.span) {
match parse_transform(segment.text, segment.span) {
Ok(transform) => transforms.push(SpannedTransform::new(segment.span, transform)),
Err(diagnostic) => diagnostics.push(diagnostic),
}
Expand All @@ -64,7 +64,7 @@ pub fn format_source(source: &str) -> Result<String, Vec<Diagnostic>> {
parse(source).map(|query| format!("{query}\n"))
}

fn split_segments(source: &str) -> Vec<Segment> {
fn split_segments(source: &str) -> Vec<Segment<'_>> {
let mut segments = Vec::new();

for (line_index, line) in source.lines().enumerate() {
Expand All @@ -88,15 +88,15 @@ fn split_segments(source: &str) -> Vec<Segment> {
segments
}

fn push_segment(segments: &mut Vec<Segment>, raw: &str, line: usize, byte_start: usize) {
fn push_segment<'a>(segments: &mut Vec<Segment<'a>>, raw: &'a str, line: usize, byte_start: usize) {
let text = raw.trim();
if text.is_empty() {
return;
}

let leading_bytes = raw.find(text).unwrap_or(0);
segments.push(Segment {
text: text.to_owned(),
text,
span: Span::new(line, byte_start + leading_bytes + 1),
});
}
Expand Down
Loading