Skip to content

Commit 04b5c66

Browse files
committed
perf: ReplaceSource source
1 parent c90f574 commit 04b5c66

File tree

2 files changed

+104
-11
lines changed

2 files changed

+104
-11
lines changed

src/cached_source.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,14 @@ impl Source for CachedSource {
9090
// so transmuting Vec<&str> to Vec<&'static str> is safe in this context.
9191
// This allows us to store string slices in the cache without additional allocations.
9292
unsafe {
93-
std::mem::transmute::<(Vec<&str>, usize), (Vec<&'static str>, usize)>(self.rope())
93+
std::mem::transmute::<(Vec<&str>, usize), (Vec<&'static str>, usize)>(
94+
self.rope(),
95+
)
9496
}
9597
});
9698
let mut string = String::with_capacity(*len);
9799
for chunk in chunks {
98-
string.push_str(chunk);
100+
string.push_str(chunk);
99101
}
100102
SourceValue::String(Cow::Owned(string))
101103
}

src/replace_source.rs

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,11 @@ impl ReplaceSource {
161161

162162
impl Source for ReplaceSource {
163163
fn source(&self) -> SourceValue {
164-
let (chunks, len) = self.rope();
165-
if chunks.len() == 1 {
166-
SourceValue::String(Cow::Borrowed(chunks[0]))
164+
if self.replacements.len() == 0 {
165+
self.inner.source()
167166
} else {
168-
let mut string = String::with_capacity(len);
169-
for chunk in chunks {
170-
string.push_str(chunk);
171-
}
167+
let mut string = String::with_capacity(self.size());
168+
self.write_to_string(&mut string);
172169
SourceValue::String(Cow::Owned(string))
173170
}
174171
}
@@ -339,9 +336,103 @@ impl Source for ReplaceSource {
339336
get_map(&ObjectPool::default(), chunks.as_ref(), options)
340337
}
341338

339+
#[allow(unsafe_code)]
342340
fn write_to_string(&self, string: &mut String) {
343-
for chunk in self.rope().0 {
344-
string.push_str(chunk);
341+
let (inner_chunks, _) = self.inner.rope();
342+
343+
let mut pos: usize = 0;
344+
let mut replacement_idx: usize = 0;
345+
let mut replacement_end: Option<usize> = None;
346+
let mut next_replacement: Option<usize> = (replacement_idx
347+
< self.replacements.len())
348+
.then(|| self.replacements[replacement_idx].start as usize);
349+
350+
'chunk_loop: for chunk in inner_chunks {
351+
let mut chunk_pos = 0;
352+
let end_pos = pos + chunk.len();
353+
354+
// Skip over when it has been replaced
355+
if let Some(replacement_end) =
356+
replacement_end.filter(|replacement_end| *replacement_end > pos)
357+
{
358+
// Skip over the whole chunk
359+
if replacement_end >= end_pos {
360+
pos = end_pos;
361+
continue;
362+
}
363+
// Partially skip over chunk
364+
chunk_pos = replacement_end - pos;
365+
pos += chunk_pos;
366+
}
367+
368+
// Is a replacement in the chunk?
369+
while let Some(next_replacement_pos) = next_replacement
370+
.filter(|next_replacement_pos| *next_replacement_pos < end_pos)
371+
{
372+
if next_replacement_pos > pos {
373+
// Emit chunk until replacement
374+
let offset = next_replacement_pos - pos;
375+
let chunk_slice =
376+
unsafe { &chunk.get_unchecked(chunk_pos..(chunk_pos + offset)) };
377+
string.push_str(chunk_slice);
378+
chunk_pos += offset;
379+
pos = next_replacement_pos;
380+
}
381+
// Insert replacement content split into chunks by lines
382+
let replacement =
383+
unsafe { &self.replacements.get_unchecked(replacement_idx) };
384+
string.push_str(&replacement.content);
385+
386+
// Remove replaced content by settings this variable
387+
replacement_end = if let Some(replacement_end) = replacement_end {
388+
Some(replacement_end.max(replacement.end as usize))
389+
} else {
390+
Some(replacement.end as usize)
391+
};
392+
393+
// Move to next replacement
394+
replacement_idx += 1;
395+
next_replacement = if replacement_idx < self.replacements.len() {
396+
Some(unsafe {
397+
self.replacements.get_unchecked(replacement_idx).start as usize
398+
})
399+
} else {
400+
None
401+
};
402+
403+
// Skip over when it has been replaced
404+
let offset = chunk.len() as i64 - end_pos as i64
405+
+ replacement_end.unwrap() as i64
406+
- chunk_pos as i64;
407+
if offset > 0 {
408+
// Skip over whole chunk
409+
if replacement_end
410+
.is_some_and(|replacement_end| replacement_end >= end_pos)
411+
{
412+
pos = end_pos;
413+
continue 'chunk_loop;
414+
}
415+
416+
// Partially skip over chunk
417+
chunk_pos += offset as usize;
418+
pos += offset as usize;
419+
}
420+
}
421+
422+
// Emit remaining chunk
423+
if chunk_pos < chunk.len() {
424+
let chunk = unsafe { &chunk.get_unchecked(chunk_pos..) };
425+
string.push_str(chunk);
426+
}
427+
pos = end_pos;
428+
}
429+
430+
// Handle remaining replacements one by one
431+
while replacement_idx < self.replacements.len() {
432+
let content =
433+
unsafe { &self.replacements.get_unchecked(replacement_idx).content };
434+
string.push_str(content);
435+
replacement_idx += 1;
345436
}
346437
}
347438

0 commit comments

Comments
 (0)