Find Buffer search candidates with memchr

This commit is contained in:
Chris Tate
2026-10-22 08:57:59 -05:00
parent 8db39e6c63
commit 17b70e6d6a
4 changed files with 45 additions and 3 deletions
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7249-buffer-index-of-candidates.ts"
],
"diags": []
}
+11 -3
View File
@@ -3271,16 +3271,24 @@ double scr_bytes_index_of(const ScrBytes *b, const ScrBytes *needle, double off,
}
if (nlen == 0) return o > (double)len ? (double)len : o; /* '' matches at the clamp */
if (nlen > len) return -1;
/* Candidates are the positions holding the needle's first byte; only
* those compare the rest. */
const uint8_t first = needle->data[0];
if (fwd) {
size_t start = o > (double)len ? len : (size_t)o;
for (size_t i = start; i + nlen <= len; i++) {
if (memcmp(b->data + i, needle->data, nlen) == 0) return (double)i;
const uint8_t *at = b->data + start, *end = b->data + (len - nlen) + 1;
while (at < end) {
at = memchr(at, first, (size_t)(end - at));
if (!at) return -1;
if (nlen == 1 || memcmp(at + 1, needle->data + 1, nlen - 1) == 0) return (double)(at - b->data);
at++;
}
return -1;
}
size_t start = o > (double)(len - nlen) ? len - nlen : (size_t)o;
for (size_t i = start;; i--) {
if (memcmp(b->data + i, needle->data, nlen) == 0) return (double)i;
if (b->data[i] == first && (nlen == 1 || memcmp(b->data + i + 1, needle->data + 1, nlen - 1) == 0))
return (double)i;
if (i == 0) break;
}
return -1;
@@ -0,0 +1,27 @@
// Buffer indexOf, lastIndexOf and includes with needles whose first byte
// recurs: a candidate whose later bytes differ is skipped, matches at the
// first and last positions are found, byteOffset clamps and counts from the
// end, and the search reads only the buffer's own window of its memory.
const text = Buffer.from("aab aaab aaaab ab\nab");
console.log(text.indexOf("ab"), text.indexOf("aab"), text.indexOf("aaab"), text.indexOf("aaaab"), text.indexOf("aaaaab"));
console.log(text.lastIndexOf("ab"), text.lastIndexOf("aab"), text.lastIndexOf("aaab"), text.lastIndexOf("b\na"));
console.log(text.indexOf("ab", 2), text.indexOf("ab", -3), text.indexOf("ab", 100), text.indexOf("ab", -100));
console.log(text.lastIndexOf("ab", 5), text.lastIndexOf("ab", -4), text.lastIndexOf("ab", 0), text.lastIndexOf("aa", 1));
console.log(text.indexOf(10), text.indexOf(97, 3), text.lastIndexOf(98), text.lastIndexOf(97, -2), text.includes("b a"));
console.log(text.indexOf(""), text.indexOf("", 4), text.indexOf("", 100), text.lastIndexOf(""), text.indexOf("x"));
console.log(text.indexOf(Buffer.from("a\nab")), text.indexOf("ab\nab"), text.indexOf("ab\nabc"), text.lastIndexOf("aab", 4));
// A window into a larger buffer never sees the bytes outside it.
const whole = Buffer.from("xxneedlexx needle needlexx");
const window = whole.subarray(3, 22);
console.log(window.toString(), window.indexOf("needle"), window.lastIndexOf("needle"), window.indexOf("xx"), window.includes("xne"));
console.log(window.indexOf("eedle", 1), window.lastIndexOf("eed"), window.indexOf("needlex"), window.lastIndexOf("n", 0));
// A long search with many first-byte candidates.
const long = Buffer.alloc(20000, "ab");
long.write("abc", 19990);
console.log(long.indexOf("abc"), long.lastIndexOf("aba"), long.indexOf("abc", 19991), long.lastIndexOf("abc"), long.indexOf("bb"));
const lines: number[] = [];
const fasta = Buffer.from(">ONE\nACGT\nTTGA\n>TWO\nGGCC\n");
for (let at = fasta.indexOf(10); at !== -1; at = fasta.indexOf(10, at + 1)) lines.push(at);
console.log(lines.join(","), fasta.indexOf(62, 1), fasta.lastIndexOf(62));
@@ -0,0 +1 @@
{ "plain": 2870, "san": 3656 }