diff --git a/README.md b/README.md index daef98f..27e5348 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Today with TypedArrays, it is possible to get the index of a specific single ele function findSubsequence(haystack, needle) { if (needle.length === 0) return 0; if (needle.length > haystack.length) return -1; - + outer: for (let i = 0; i <= haystack.length - needle.length; i++) { for (let j = 0; j < needle.length; j++) { if (haystack[i + j] !== needle[j]) continue outer; @@ -33,72 +33,55 @@ console.log(findSubsequence(int16, new Int16Array([3, 4]))); // 2 ## The Proposal -The proposal is to add an API to `TypedArray.prototype` to enable optimized searching for subsequences in three forms: `search` returns the starting index of the first occurrence, `searchLast` returns the starting index of the last occurrence, and `contains` returns a simple boolean true/false if the subsequence exists. All three methods accept an optional `position` parameter to control where the search begins. For `search` and `contains`, only matches starting at `position` or later are considered. For `searchLast`, only matches starting at `position` or earlier are considered. +The proposal is to add an API to `TypedArray.prototype` to enable optimized searching for subsequences: `indexOfSequence` returns the starting index of the first occurrence, `lastIndexOfSequence` returns the starting index of the last occurrence. Both methods accept an optional `position` parameter to control where the search begins. For `indexOfSequence` only matches starting at `position` or later are considered. For `lastIndexOfSequence`, only matches starting at `position` or earlier are considered. ```js const enc = new TextEncoder(); const u8 = enc.encode('Hello TC39, Hello TC39'); -console.log(u8.search(enc.encode('TC39'))); // 6 -console.log(u8.search(enc.encode('TC39'), 7)); // 17 -console.log(u8.searchLast(enc.encode('TC39'))); // 17 -console.log(u8.searchLast(enc.encode('TC39'), 16)); // 6 -console.log(u8.contains(enc.encode('TC39'))); // true -console.log(u8.contains(enc.encode('TC39'), 18)); // false +console.log(u8.indexOfSequence(enc.encode('TC39'))); // 6 +console.log(u8.indexOfSequence(enc.encode('TC39'), 7)); // 17 +console.log(u8.lastIndexOfSequence(enc.encode('TC39'))); // 17 +console.log(u8.lastIndexOfSequence(enc.encode('TC39'), 16)); // 6 ``` Exactly how to implement the subsequence search algorithm is intended to be left as an implementation specific detail. ### Needle types -The `needle` argument can be: +The `needle` argument must be a **TypedArray** (same or different element type). Elements are read directly from the needle's underlying buffer via `GetValueFromBuffer`, without calling `@@iterator`. This is consistent with how `%TypedArray%.prototype.set` handles TypedArray sources. The needle and haystack must have compatible content types (both Number-typed or both BigInt-typed); if not, the search returns `-1`. -* A **TypedArray** (same or different element type) — iterated via its `@@iterator` method. Each yielded value must be the correct type for the haystack (Number for non-BigInt TypedArrays, BigInt for BigInt TypedArrays); if any value is the wrong type, the search returns `-1`. This creates a snapshot of the needle's elements, which is necessary for correctness when the needle is backed by a SharedArrayBuffer. -* An **iterable object** (other than a String) — its elements are collected and type-checked against the haystack's element type. If any element is the wrong type, the search returns `-1`. -* A **String** — throws a `TypeError`. Although strings are iterable, their iteration yields code points, which is unlikely to be the intended behaviour when searching a TypedArray. -* Any other value — throws a `TypeError`. +Any other value — throws a `TypeError`. ```js const u8 = new Uint8Array([1, 2, 3, 4, 5]); // Same-type TypedArray -u8.search(new Uint8Array([3, 4])); // 2 - -// Iterable (e.g. plain Array) -u8.search([3, 4]); // 2 +u8.indexOfSequence(new Uint8Array([3, 4])); // 2 -// Different-type TypedArray (iterated via @@iterator) -u8.search(new Int16Array([3, 4])); // 2 +// Different-type TypedArray (read from buffer) +u8.indexOfSequence(new Int16Array([3, 4])); // 2 -// String throws -u8.search('hello'); // TypeError - -// Non-iterable throws -u8.search(42); // TypeError +// Non-TypedArray throws +u8.indexOfSequence([3, 4]); // TypeError +u8.indexOfSequence('hello'); // TypeError +u8.indexOfSequence(42); // TypeError ``` ### Cross-type floating-point precision -When a needle TypedArray has a narrower floating-point type than the haystack, precision loss during the round-trip through the narrower type can cause matches to fail. Needle elements are read back as JavaScript Numbers via `@@iterator`, and a value that was rounded when stored in a `Float32Array` will not SameValueZero-match the higher-precision representation in a `Float64Array`. +When a needle TypedArray has a narrower floating-point type than the haystack, precision loss can cause matches to fail. Needle elements are read from the buffer as the needle's element type and converted to JavaScript Numbers via `GetValueFromBuffer`. A value that was rounded when stored in a `Float32Array` will not SameValueZero-match the higher-precision representation in a `Float64Array`. ```js const f64 = new Float64Array([0.3]); // Float32 cannot represent 0.3 exactly — it rounds to ≈0.30000001192092896 -f64.search(new Float32Array([0.3])); // -1 (no match) +f64.indexOfSequence(new Float32Array([0.3])); // -1 (no match) // Values that are exact in Float32 (integers, powers of two, etc.) work fine const f64b = new Float64Array([0.25, 0.5, 42]); -f64b.search(new Float32Array([0.25])); // 0 -f64b.search(new Float32Array([42])); // 2 +f64b.indexOfSequence(new Float32Array([0.25])); // 0 +f64b.indexOfSequence(new Float32Array([42])); // 2 ``` This is not specific to this proposal — it is an inherent property of IEEE 754 floating-point arithmetic and applies equally to any cross-type element comparison. - -## Why just `TypedArray`? Why not all `Iterables` - -This proposal could generally address the same problem of searching for subsequences within any iterable. That's something the committee should decide. There are a few issues there however: - -* It will be easier to optimize the performance of searching for the `needle` in the `haystack` `TypedArray` specifically than it will be dealing with the iterable protocol in general. While it might make sense for this proposal to tackle iterables, there are a different set of performance and optimization path considerations in that approach. -* TypedArrays are homogenous in their member elements, as are strings. However, other types of iterables may yield any variety of types. While it is most common for iterables to always yield the same type of value, they are not required to do so. This also makes it difficult to optimize for the general case. - diff --git a/TEST_CASES.md b/TEST_CASES.md index 3830b31..d109973 100644 --- a/TEST_CASES.md +++ b/TEST_CASES.md @@ -1,12 +1,12 @@ # Comprehensive Test Cases -Test cases for `%TypedArray%.prototype.search`, `%TypedArray%.prototype.searchLast`, and `%TypedArray%.prototype.contains`. +Test cases for `%TypedArray%.prototype.indexOfSequence` and `%TypedArray%.prototype.lastIndexOfSequence`. ### Identifier Convention Each checkbox line has a unique identifier in the form `[section.subsection.number]`, e.g. `[2.1.1]`. -Where a test case is preceded by a "For each ..." qualifier (e.g. "For each method" or "For each TypedArray type"), each variation is referenced by appending a lowercase letter suffix: `[1.1.1a]` for `search`, `[1.1.1b]` for `searchLast`, `[1.1.1c]` for `contains`, etc. The letter ordering follows the order in which the items appear in the qualifier list. +Where a test case is preceded by a "For each ..." qualifier (e.g. "For each method" or "For each TypedArray type"), each variation is referenced by appending a lowercase letter suffix: `[1.1.1a]` for `indexOfSequence`, `[1.1.1b]` for `lastIndexOfSequence`, etc. The letter ordering follows the order in which the items appear in the qualifier list. --- @@ -14,7 +14,7 @@ Where a test case is preceded by a "For each ..." qualifier (e.g. "For each meth ### 1.1 Invalid `this` value -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): - [ ] [1.1.1] `this` = `undefined` → TypeError - [ ] [1.1.2] `this` = `null` → TypeError @@ -24,7 +24,7 @@ For each method (`search`, `searchLast`, `contains`): ### 1.2 Detached buffer -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): For each TypedArray type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`, `BigInt64Array`, `BigUint64Array`): @@ -32,160 +32,109 @@ For each TypedArray type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16 --- -## 2. Needle Validation (`ToCompatibleTypedArrayElementList`) +## 2. Needle Validation (`TypedArraySubsequenceFromTypedArray`) -### 2.1 Same-type TypedArray (iterated via @@iterator) +### 2.1 Same-type TypedArray (read from buffer via `TypedArraySubsequenceFromTypedArray`) -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [2.1.1] `new Int8Array([1,2,3]).search(new Int8Array([2,3]))` → 1 -- [ ] [2.1.2] `new Uint8Array([1,2,3]).search(new Uint8Array([2,3]))` → 1 -- [ ] [2.1.3] `new Uint8ClampedArray([1,2,3]).search(new Uint8ClampedArray([2,3]))` → 1 -- [ ] [2.1.4] `new Int16Array([1,2,3]).search(new Int16Array([2,3]))` → 1 -- [ ] [2.1.5] `new Uint16Array([1,2,3]).search(new Uint16Array([2,3]))` → 1 -- [ ] [2.1.6] `new Int32Array([1,2,3]).search(new Int32Array([2,3]))` → 1 -- [ ] [2.1.7] `new Uint32Array([1,2,3]).search(new Uint32Array([2,3]))` → 1 -- [ ] [2.1.8] `new Float16Array([1,2,3]).search(new Float16Array([2,3]))` → 1 -- [ ] [2.1.9] `new Float32Array([1,2,3]).search(new Float32Array([2,3]))` → 1 -- [ ] [2.1.10] `new Float64Array([1.5,2.5]).search(new Float64Array([1.5,2.5]))` → 0 -- [ ] [2.1.11] `new BigInt64Array([1n,2n,3n]).search(new BigInt64Array([2n,3n]))` → 1 -- [ ] [2.1.12] `new BigUint64Array([1n,2n]).search(new BigUint64Array([1n,2n]))` → 0 +- [ ] [2.1.1] `new Int8Array([1,2,3]).indexOfSequence(new Int8Array([2,3]))` → 1 +- [ ] [2.1.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]))` → 1 +- [ ] [2.1.3] `new Uint8ClampedArray([1,2,3]).indexOfSequence(new Uint8ClampedArray([2,3]))` → 1 +- [ ] [2.1.4] `new Int16Array([1,2,3]).indexOfSequence(new Int16Array([2,3]))` → 1 +- [ ] [2.1.5] `new Uint16Array([1,2,3]).indexOfSequence(new Uint16Array([2,3]))` → 1 +- [ ] [2.1.6] `new Int32Array([1,2,3]).indexOfSequence(new Int32Array([2,3]))` → 1 +- [ ] [2.1.7] `new Uint32Array([1,2,3]).indexOfSequence(new Uint32Array([2,3]))` → 1 +- [ ] [2.1.8] `new Float16Array([1,2,3]).indexOfSequence(new Float16Array([2,3]))` → 1 +- [ ] [2.1.9] `new Float32Array([1,2,3]).indexOfSequence(new Float32Array([2,3]))` → 1 +- [ ] [2.1.10] `new Float64Array([1.5,2.5]).indexOfSequence(new Float64Array([1.5,2.5]))` → 0 +- [ ] [2.1.11] `new BigInt64Array([1n,2n,3n]).indexOfSequence(new BigInt64Array([2n,3n]))` → 1 +- [ ] [2.1.12] `new BigUint64Array([1n,2n]).indexOfSequence(new BigUint64Array([1n,2n]))` → 0 -### 2.2 Different-type TypedArray (iterated via @@iterator) +### 2.2 Different-type TypedArray (read from buffer via `TypedArraySubsequenceFromTypedArray`) -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [2.2.1] `new Uint8Array([1,2,3]).search(new Int16Array([2,3]))` → 1 -- [ ] [2.2.2] `new Float64Array([1,2,3]).search(new Float32Array([2,3]))` → 1 -- [ ] [2.2.3] `new Uint8Array([1,2,3]).search(new Uint32Array([2,3]))` → 1 -- [ ] [2.2.4] `new Int16Array([1,2,3]).search(new Uint8Array([2,3]))` → 1 +- [ ] [2.2.1] `new Uint8Array([1,2,3]).indexOfSequence(new Int16Array([2,3]))` → 1 +- [ ] [2.2.2] `new Float64Array([1,2,3]).indexOfSequence(new Float32Array([2,3]))` → 1 +- [ ] [2.2.3] `new Uint8Array([1,2,3]).indexOfSequence(new Uint32Array([2,3]))` → 1 +- [ ] [2.2.4] `new Int16Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]))` → 1 ### 2.3 Different-type TypedArray — precision loss -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [2.3.1] Searching a `Float64Array` containing `1.1` (native Float64) with a `Float32Array` needle `[1.1]` → -1 (Float32 `1.1` is `1.100000023841858` as a Number, which does not SameValueZero-match the Float64 `1.1`) +- [ ] [2.3.1] Searching a `Float64Array` containing `1.1` (native Float64) with a `Float32Array` needle `[1.1]` → -1 (the needle element is read from the Float32 buffer via GetValueFromBuffer, yielding `1.100000023841858` as a Number, which does not SameValueZero-match the Float64 `1.1`) ### 2.4 BigInt / Number type mismatch → -1 (not TypeError) -- [ ] [2.4.1] `new BigInt64Array([1n,2n]).search(new Uint8Array([1,2]))` → -1 (needle yields Numbers, haystack expects BigInts) -- [ ] [2.4.2] `new Uint8Array([1,2]).search(new BigInt64Array([1n,2n]))` → -1 (needle yields BigInts, haystack expects Numbers) -- [ ] [2.4.3] `new BigInt64Array([1n,2n]).searchLast(new Uint8Array([1,2]))` → -1 -- [ ] [2.4.4] `new Uint8Array([1,2]).contains(new BigInt64Array([1n,2n]))` → false +The mismatch is detected via `[[ContentType]]` check in `TypedArraySubsequenceFromTypedArray`. -### 2.5 Iterable objects (non-TypedArray) +- [ ] [2.4.1] `new BigInt64Array([1n,2n]).indexOfSequence(new Uint8Array([1,2]))` → -1 (`[[ContentType]]` mismatch: ~bigint~ vs ~number~) +- [ ] [2.4.2] `new Uint8Array([1,2]).indexOfSequence(new BigInt64Array([1n,2n]))` → -1 (`[[ContentType]]` mismatch: ~number~ vs ~bigint~) +- [ ] [2.4.3] `new BigInt64Array([1n,2n]).lastIndexOfSequence(new Uint8Array([1,2]))` → -1 -For each method (`search`, `searchLast`, `contains`): +### 2.5 Non-TypedArray needle → TypeError -- [ ] [2.5.1] Plain Array: `new Uint8Array([1,2,3]).search([2,3])` → 1 -- [ ] [2.5.2] Generator: `new Uint8Array([1,2,3]).search(function*() { yield 2; yield 3; }())` → 1 -- [ ] [2.5.3] Set: `new Uint8Array([1,2,3]).search(new Set([2,3]))` → 1 -- [ ] [2.5.4] Custom iterable with `[Symbol.iterator]()`: - ```js - const iter = { [Symbol.iterator]() { return { i: 2, next() { return this.i <= 3 ? { value: this.i++, done: false } : { done: true }; } }; } }; - new Uint8Array([1,2,3]).search(iter) // → 1 - ``` -- [ ] [2.5.5] Empty iterable: `new Uint8Array([1,2,3]).search([])` → 0 (empty needle) - -### 2.6 Iterable with BigInt target - -For each method (`search`, `searchLast`, `contains`): - -- [ ] [2.6.1] `new BigInt64Array([1n,2n,3n]).search([1n,2n])` → 0 -- [ ] [2.6.2] `new BigInt64Array([1n,2n,3n]).search([1,2])` → -1 (Numbers are not BigInts, returns ~not-found~) -- [ ] [2.6.3] `new BigInt64Array([1n,2n,3n]).search(['1','2'])` → -1 (Strings are not BigInts, returns ~not-found~) - -### 2.7 Iterable with wrong-type elements → -1 - -For each method (`search`, `searchLast`, `contains`): - -- [ ] [2.7.1] `new Uint8Array([1,2,3]).search([1n, 2n])` → -1 (BigInts are not Numbers) -- [ ] [2.7.2] `new Uint8Array([1,2,3]).search(['1', '2'])` → -1 (Strings are not Numbers) -- [ ] [2.7.3] `new Uint8Array([1,2,3]).search([{}])` → -1 (Objects are not Numbers) -- [ ] [2.7.4] `new Uint8Array([1,2,3]).search([Symbol()])` → -1 (Symbols are not Numbers) -- [ ] [2.7.5] `new BigInt64Array([1n,2n]).search([1.5])` → -1 (Numbers are not BigInts) -- [ ] [2.7.6] `new Uint8Array([1,2,3]).search([1, 2, 3n])` → -1 (first two elements are Numbers but third is a BigInt; ~not-found~ returned on encountering the wrong type partway through) -- [ ] [2.7.7] `new BigInt64Array([1n,2n,3n]).search([1n, 2n, 3])` → -1 (first two elements are BigInts but third is a Number) -- [ ] [2.7.8] `new Uint8Array([1,2,3]).search([1, undefined, 3])` → -1 (undefined is not a Number) -- [ ] [2.7.9] `new Uint8Array([1,2,3]).search([1, null, 3])` → -1 (null is not a Number) - -### 2.8 Iterable that throws during iteration - -For each method (`search`, `searchLast`, `contains`): - -- [ ] [2.8.1] Iterator whose `next()` throws → the error propagates -- [ ] [2.8.2] Iterable whose `@@iterator` method throws → the error propagates - -### 2.9 String needle → TypeError - -- [ ] [2.9.1] `new Uint8Array([1,2,3]).search('hello')` → TypeError -- [ ] [2.9.2] `new Uint8Array([1,2,3]).searchLast('hello')` → TypeError -- [ ] [2.9.3] `new Uint8Array([1,2,3]).contains('hello')` → TypeError -- [ ] [2.9.4] `new Uint8Array([]).search('')` → TypeError - -### 2.10 Non-iterable Object → TypeError +For each method (`indexOfSequence`, `lastIndexOfSequence`): -For each method (`search`, `searchLast`, `contains`): +- [ ] [2.5.1] Plain Array: `new Uint8Array([1,2,3]).indexOfSequence([2,3])` → TypeError +- [ ] [2.5.2] String: `new Uint8Array([1,2,3]).indexOfSequence('hello')` → TypeError +- [ ] [2.5.3] Empty string: `new Uint8Array([]).indexOfSequence('')` → TypeError +- [ ] [2.5.4] Plain object: `new Uint8Array([1,2,3]).indexOfSequence({})` → TypeError +- [ ] [2.5.5] Array-like object: `new Uint8Array([1,2,3]).indexOfSequence({ length: 2, 0: 2, 1: 3 })` → TypeError +- [ ] [2.5.6] Number: `new Uint8Array([1,2,3]).indexOfSequence(42)` → TypeError +- [ ] [2.5.7] Boolean true: `new Uint8Array([1,2,3]).indexOfSequence(true)` → TypeError +- [ ] [2.5.8] Boolean false: `new Uint8Array([1,2,3]).indexOfSequence(false)` → TypeError +- [ ] [2.5.9] undefined: `new Uint8Array([1,2,3]).indexOfSequence(undefined)` → TypeError +- [ ] [2.5.10] null: `new Uint8Array([1,2,3]).indexOfSequence(null)` → TypeError +- [ ] [2.5.11] Symbol: `new Uint8Array([1,2,3]).indexOfSequence(Symbol())` → TypeError +- [ ] [2.5.12] BigInt: `new Uint8Array([1,2,3]).indexOfSequence(42n)` → TypeError +- [ ] [2.5.13] No argument: `new Uint8Array([1,2,3]).indexOfSequence()` → TypeError (needle is undefined) +- [ ] [2.5.14] No argument lastIndexOfSequence: `new Uint8Array([1,2,3]).lastIndexOfSequence()` → TypeError -- [ ] [2.10.1] `new Uint8Array([1,2,3]).search({})` → TypeError -- [ ] [2.10.2] `new Uint8Array([1,2,3]).search({ length: 2, 0: 2, 1: 3 })` → TypeError (array-like but not iterable) -- [ ] [2.10.3] `new Uint8Array([1,2,3]).search({ [Symbol.iterator]: 42 })` → TypeError (@@iterator is non-callable) -- [ ] [2.10.4] `new Uint8Array([1,2,3]).search({ [Symbol.iterator]: null })` → TypeError (GetMethod returns undefined, falls through to throw) +### 2.6 TypedArray needle — `Symbol.iterator` is NOT called -### 2.11 Non-Object primitives → TypeError +Since TypedArray needles are handled by `TypedArraySubsequenceFromTypedArray`, which reads directly from the underlying buffer, the `@@iterator` method is not called. Overriding `Symbol.iterator` on a TypedArray needle has no effect. This is consistent with how `%TypedArray%.prototype.set` handles TypedArray sources via `SetTypedArrayFromTypedArray`. -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [2.11.1] `new Uint8Array([1,2,3]).search(42)` → TypeError -- [ ] [2.11.2] `new Uint8Array([1,2,3]).search(true)` → TypeError -- [ ] [2.11.3] `new Uint8Array([1,2,3]).search(false)` → TypeError -- [ ] [2.11.4] `new Uint8Array([1,2,3]).search(undefined)` → TypeError -- [ ] [2.11.5] `new Uint8Array([1,2,3]).search(null)` → TypeError -- [ ] [2.11.6] `new Uint8Array([1,2,3]).search(Symbol())` → TypeError -- [ ] [2.11.7] `new Uint8Array([1,2,3]).search(42n)` → TypeError -- [ ] [2.11.8] `new Uint8Array([1,2,3]).search()` → TypeError (no argument; needle is undefined) -- [ ] [2.11.9] `new Uint8Array([1,2,3]).searchLast()` → TypeError (no argument) -- [ ] [2.11.10] `new Uint8Array([1,2,3]).contains()` → TypeError (no argument) - -### 2.12 TypedArray needle with overridden `Symbol.iterator` - -Since needle elements are collected via `@@iterator`, a TypedArray whose `Symbol.iterator` has been overridden will yield whatever the custom iterator produces, not the underlying buffer contents. - -For each method (`search`, `searchLast`, `contains`): - -- [ ] [2.12.1] Needle TypedArray with `Symbol.iterator` overridden to yield different values: +- [ ] [2.6.1] Needle TypedArray with `Symbol.iterator` overridden — override is ignored: ```js - const needle = new Uint8Array([99, 99]); - needle[Symbol.iterator] = function*() { yield 3; yield 4; }; - new Uint8Array([1, 2, 3, 4, 5]).search(needle) // → 2 (searches for [3,4], not [99,99]) + const needle = new Uint8Array([3, 4]); + needle[Symbol.iterator] = function*() { yield 99; yield 99; }; + new Uint8Array([1, 2, 3, 4, 5]).indexOfSequence(needle) // → 2 (searches for [3,4] from buffer, ignores override) ``` -- [ ] [2.12.2] Needle TypedArray with `Symbol.iterator` overridden to yield fewer elements: +- [ ] [2.6.2] Needle TypedArray with `Symbol.iterator` deleted — still works: ```js - const needle = new Uint8Array([1, 2, 3]); - needle[Symbol.iterator] = function*() { yield 2; }; - new Uint8Array([1, 2, 3]).search(needle) // → 1 (searches for [2], not [1,2,3]) + const needle = new Uint8Array([2, 3]); + delete needle[Symbol.iterator]; + // Even with prototype's @@iterator deleted: + const saved = Uint8Array.prototype[Symbol.iterator]; + delete Uint8Array.prototype[Symbol.iterator]; + try { + new Uint8Array([1, 2, 3]).indexOfSequence(needle) // → 1 (reads from buffer, @@iterator not needed) + } finally { + Uint8Array.prototype[Symbol.iterator] = saved; + } ``` -- [ ] [2.12.3] Needle TypedArray with `Symbol.iterator` overridden to yield nothing: +- [ ] [2.6.3] Verify @@iterator is not called (no observable side effect): ```js - const needle = new Uint8Array([1, 2, 3]); - needle[Symbol.iterator] = function*() {}; - new Uint8Array([1, 2, 3]).search(needle) // → 0 (empty needle returns position) - ``` -- [ ] [2.12.4] Needle TypedArray with `Symbol.iterator` overridden to yield more elements: - ```js - const needle = new Uint8Array([1]); - needle[Symbol.iterator] = function*() { yield 2; yield 3; yield 4; }; - new Uint8Array([1, 2, 3, 4, 5]).search(needle) // → 1 (searches for [2,3,4], not [1]) + let called = false; + const needle = new Uint8Array([2, 3]); + needle[Symbol.iterator] = function() { called = true; return [][Symbol.iterator](); }; + new Uint8Array([1, 2, 3]).indexOfSequence(needle); // → 1 + assert(called === false); // @@iterator was not invoked ``` -Note: Error cases for overridden `Symbol.iterator` (wrong types, non-callable, undefined/null, throws) are covered by the corresponding generic cases in sections 2.7, 2.8, and 2.10. The cases above focus on demonstrating that the override is respected for the yielded values. +### 2.7 Detached TypedArray needle -### 2.13 Detached TypedArray needle +TypedArray needles are handled by `TypedArraySubsequenceFromTypedArray`, which calls `MakeTypedArrayWithBufferWitnessRecord` and `IsTypedArrayOutOfBounds`. A detached buffer causes `IsTypedArrayOutOfBounds` to return *true*, resulting in a TypeError. -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [2.13.1] Same-type, detached: needle is a same-type TypedArray with a detached buffer → iteration via @@iterator; behaviour depends on the TypedArray's @@iterator implementation for detached buffers -- [ ] [2.13.2] Different-type, detached: needle is a different-type TypedArray with a detached buffer → iteration via @@iterator; behaviour depends on the TypedArray's @@iterator implementation for detached buffers +- [ ] [2.7.1] Same-type, detached: needle is a same-type TypedArray with a detached buffer → TypeError +- [ ] [2.7.2] Different-type, detached: needle is a different-type TypedArray with a detached buffer → TypeError --- @@ -193,591 +142,489 @@ For each method (`search`, `searchLast`, `contains`): ### 3.1 Default position -- [ ] [3.1.1] `search` with no position argument → defaults to 0 -- [ ] [3.1.2] `searchLast` with no position argument → defaults to `haystackLength - 1` -- [ ] [3.1.3] `contains` with no position argument → defaults to 0 +- [ ] [3.1.1] `indexOfSequence` with no position argument → defaults to 0 +- [ ] [3.1.2] `lastIndexOfSequence` with no position argument → defaults to `haystackLength - 1` ### 3.2 Explicit `undefined` → uses default -- [ ] [3.2.1] `new Uint8Array([1,2,3]).search([2,3], undefined)` → 1 (default 0) -- [ ] [3.2.2] `new Uint8Array([1,2,3]).searchLast([2,3], undefined)` → 1 (default haystackLength - 1) +- [ ] [3.2.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), undefined)` → 1 (default 0) +- [ ] [3.2.2] `new Uint8Array([1,2,3]).lastIndexOfSequence(new Uint8Array([2,3]), undefined)` → 1 (default haystackLength - 1) ### 3.3 Non-Number position → TypeError -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [3.3.1] `new Uint8Array([1,2,3]).search([2,3], 'hello')` → TypeError -- [ ] [3.3.2] `new Uint8Array([1,2,3]).search([2,3], {})` → TypeError -- [ ] [3.3.3] `new Uint8Array([1,2,3]).search([2,3], true)` → TypeError -- [ ] [3.3.4] `new Uint8Array([1,2,3]).search([2,3], null)` → TypeError -- [ ] [3.3.5] `new Uint8Array([1,2,3]).search([2,3], Symbol())` → TypeError -- [ ] [3.3.6] `new Uint8Array([1,2,3]).search([2,3], 1n)` → TypeError (BigInt is not a Number) +- [ ] [3.3.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 'hello')` → TypeError +- [ ] [3.3.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), {})` → TypeError +- [ ] [3.3.3] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), true)` → TypeError +- [ ] [3.3.4] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), null)` → TypeError +- [ ] [3.3.5] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), Symbol())` → TypeError +- [ ] [3.3.6] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 1n)` → TypeError (BigInt is not a Number) ### 3.4 NaN position → RangeError -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [3.4.1] `new Uint8Array([1,2,3]).search([2,3], NaN)` → RangeError +- [ ] [3.4.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), NaN)` → RangeError ### 3.5 Non-integral number → RangeError -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [3.5.1] `new Uint8Array([1,2,3]).search([2,3], 1.5)` → RangeError -- [ ] [3.5.2] `new Uint8Array([1,2,3]).search([2,3], 0.1)` → RangeError +- [ ] [3.5.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 1.5)` → RangeError +- [ ] [3.5.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 0.1)` → RangeError ### 3.6 Infinity → RangeError -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [3.6.1] `new Uint8Array([1,2,3]).search([2,3], Infinity)` → RangeError -- [ ] [3.6.2] `new Uint8Array([1,2,3]).search([2,3], -Infinity)` → RangeError +- [ ] [3.6.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), Infinity)` → RangeError +- [ ] [3.6.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), -Infinity)` → RangeError ### 3.7 Negative position (clamped to 0) -- [ ] [3.7.1] `new Uint8Array([1,2,3]).search([1,2], -5)` → 0 (clamped to 0) -- [ ] [3.7.2] `new Uint8Array([1,2,3]).searchLast([1,2], -1)` → 0 (clamped to 0) -- [ ] [3.7.3] `new Uint8Array([1,2,3]).contains([1,2], -10)` → true (clamped to 0) +- [ ] [3.7.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1,2]), -5)` → 0 (clamped to 0) +- [ ] [3.7.2] `new Uint8Array([1,2,3]).lastIndexOfSequence(new Uint8Array([1,2]), -1)` → 0 (clamped to 0) ### 3.8 Position beyond length (clamped) -- [ ] [3.8.1] `new Uint8Array([1,2,3,4,5]).search([3,4], 100)` → -1 (clamped to 5, can't match from index 5) -- [ ] [3.8.2] `new Uint8Array([1,2,3,4,5]).searchLast([3,4], 100)` → 2 (clamped to 4) -- [ ] [3.8.3] `new Uint8Array([1,2,3]).contains([2,3], 100)` → false (clamped to 3) +- [ ] [3.8.1] `new Uint8Array([1,2,3,4,5]).indexOfSequence(new Uint8Array([3,4]), 100)` → -1 (clamped to 5, can't match from index 5) +- [ ] [3.8.2] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([3,4]), 100)` → 2 (clamped to 4) ### 3.9 Valid integral positions -- [ ] [3.9.1] `new Uint8Array([1,2,3]).search([2,3], 0)` → 1 -- [ ] [3.9.2] `new Uint8Array([1,2,3]).search([2,3], 1)` → 1 -- [ ] [3.9.3] `new Uint8Array([1,2,3]).search([2,3], 2)` → -1 (not enough room) -- [ ] [3.9.4] `new Uint8Array([1,2,3]).search([1,2], -0)` → 0 (ℝ(-0) is 0) +- [ ] [3.9.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 0)` → 1 +- [ ] [3.9.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 1)` → 1 +- [ ] [3.9.3] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,3]), 2)` → -1 (not enough room) +- [ ] [3.9.4] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1,2]), -0)` → 0 (ℝ(-0) is 0) --- -## 4. `search` (Forward Search) +## 4. `indexOfSequence` (Forward Search) ### 4.1 Basic matching For each TypedArray type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`): -- [ ] [4.1.1] `new ([1,2,3,4,5]).search(new ([3,4]))` → 2 -- [ ] [4.1.2] Match at beginning: `new ([1,2,3]).search(new ([1,2]))` → 0 -- [ ] [4.1.3] Match at end: `new ([1,2,3]).search(new ([2,3]))` → 1 -- [ ] [4.1.4] Entire array as needle: `new ([1,2,3]).search(new ([1,2,3]))` → 0 +- [ ] [4.1.1] `new ([1,2,3,4,5]).indexOfSequence(new ([3,4]))` → 2 +- [ ] [4.1.2] Match at beginning: `new ([1,2,3]).indexOfSequence(new ([1,2]))` → 0 +- [ ] [4.1.3] Match at end: `new ([1,2,3]).indexOfSequence(new ([2,3]))` → 1 +- [ ] [4.1.4] Entire array as needle: `new ([1,2,3]).indexOfSequence(new ([1,2,3]))` → 0 For BigInt types (`BigInt64Array`, `BigUint64Array`): -- [ ] [4.1.5] `new ([1n,2n,3n,4n,5n]).search(new ([3n,4n]))` → 2 -- [ ] [4.1.6] Match at beginning: `new ([1n,2n,3n]).search(new ([1n,2n]))` → 0 -- [ ] [4.1.7] Match at end: `new ([1n,2n,3n]).search(new ([2n,3n]))` → 1 -- [ ] [4.1.8] Entire array as needle: `new ([1n,2n,3n]).search(new ([1n,2n,3n]))` → 0 +- [ ] [4.1.5] `new ([1n,2n,3n,4n,5n]).indexOfSequence(new ([3n,4n]))` → 2 +- [ ] [4.1.6] Match at beginning: `new ([1n,2n,3n]).indexOfSequence(new ([1n,2n]))` → 0 +- [ ] [4.1.7] Match at end: `new ([1n,2n,3n]).indexOfSequence(new ([2n,3n]))` → 1 +- [ ] [4.1.8] Entire array as needle: `new ([1n,2n,3n]).indexOfSequence(new ([1n,2n,3n]))` → 0 ### 4.2 No match -- [ ] [4.2.1] `new Uint8Array([1,2,3]).search(new Uint8Array([4,5]))` → -1 -- [ ] [4.2.2] Partial overlap but not full match: `new Uint8Array([1,2,3]).search(new Uint8Array([2,4]))` → -1 +- [ ] [4.2.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([4,5]))` → -1 +- [ ] [4.2.2] Partial overlap but not full match: `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2,4]))` → -1 ### 4.3 Empty needle -- [ ] [4.3.1] `new Uint8Array([1,2,3]).search(new Uint8Array([]))` → 0 (returns position, which defaults to 0) -- [ ] [4.3.2] `new Uint8Array([1,2,3]).search([], 2)` → 2 (returns position) -- [ ] [4.3.3] `new Uint8Array([]).search([])` → 0 +- [ ] [4.3.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([]))` → 0 (returns position, which defaults to 0) +- [ ] [4.3.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([]), 2)` → 2 (returns position) +- [ ] [4.3.3] `new Uint8Array([]).indexOfSequence(new Uint8Array([]))` → 0 ### 4.4 Empty haystack -- [ ] [4.4.1] `new Uint8Array([]).search(new Uint8Array([1]))` → -1 -- [ ] [4.4.2] `new Uint8Array([]).search(new Uint8Array([]))` → 0 +- [ ] [4.4.1] `new Uint8Array([]).indexOfSequence(new Uint8Array([1]))` → -1 +- [ ] [4.4.2] `new Uint8Array([]).indexOfSequence(new Uint8Array([]))` → 0 ### 4.5 Needle longer than haystack -- [ ] [4.5.1] `new Uint8Array([1,2]).search(new Uint8Array([1,2,3]))` → -1 +- [ ] [4.5.1] `new Uint8Array([1,2]).indexOfSequence(new Uint8Array([1,2,3]))` → -1 ### 4.6 Needle longer than remaining elements from position -- [ ] [4.6.1] `new Uint8Array([1,2,3,4]).search(new Uint8Array([3,4,5]), 2)` → -1 (position + needleLength > haystackLength) -- [ ] [4.6.2] `new Uint8Array([1,2,3,4]).search([3,4], 3)` → -1 +- [ ] [4.6.1] `new Uint8Array([1,2,3,4]).indexOfSequence(new Uint8Array([3,4,5]), 2)` → -1 (position + needleLength > haystackLength) +- [ ] [4.6.2] `new Uint8Array([1,2,3,4]).indexOfSequence(new Uint8Array([3,4]), 3)` → -1 ### 4.7 Position skips earlier matches -- [ ] [4.7.1] `new Uint8Array([1,2,1,2]).search([1,2], 1)` → 2 (skips match at 0) -- [ ] [4.7.2] `new Uint8Array([1,2,1,2,1,2]).search([1,2], 2)` → 2 -- [ ] [4.7.3] `new Uint8Array([1,2,1,2,1,2]).search([1,2], 3)` → 4 +- [ ] [4.7.1] `new Uint8Array([1,2,1,2]).indexOfSequence(new Uint8Array([1,2]), 1)` → 2 (skips match at 0) +- [ ] [4.7.2] `new Uint8Array([1,2,1,2,1,2]).indexOfSequence(new Uint8Array([1,2]), 2)` → 2 +- [ ] [4.7.3] `new Uint8Array([1,2,1,2,1,2]).indexOfSequence(new Uint8Array([1,2]), 3)` → 4 ### 4.8 Multiple occurrences — returns first from position -- [ ] [4.8.1] `new Uint8Array([1,2,3,1,2,3,1,2,3]).search([2,3])` → 1 -- [ ] [4.8.2] `new Uint8Array([1,2,3,1,2,3,1,2,3]).search([2,3], 2)` → 4 -- [ ] [4.8.3] `new Uint8Array([1,2,3,1,2,3,1,2,3]).search([2,3], 5)` → 7 +- [ ] [4.8.1] `new Uint8Array([1,2,3,1,2,3,1,2,3]).indexOfSequence(new Uint8Array([2,3]))` → 1 +- [ ] [4.8.2] `new Uint8Array([1,2,3,1,2,3,1,2,3]).indexOfSequence(new Uint8Array([2,3]), 2)` → 4 +- [ ] [4.8.3] `new Uint8Array([1,2,3,1,2,3,1,2,3]).indexOfSequence(new Uint8Array([2,3]), 5)` → 7 ### 4.9 Single-element needle -- [ ] [4.9.1] `new Uint8Array([1,2,3]).search([2])` → 1 -- [ ] [4.9.2] `new Uint8Array([1,2,3]).search([4])` → -1 +- [ ] [4.9.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([2]))` → 1 +- [ ] [4.9.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([4]))` → -1 --- -## 5. `searchLast` (Backward Search) +## 5. `lastIndexOfSequence` (Backward Search) ### 5.1 Basic matching For each TypedArray type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`): -- [ ] [5.1.1] `new ([1,2,3,4,5]).searchLast(new ([3,4]))` → 2 -- [ ] [5.1.2] `new ([1,2,3,1,2,3]).searchLast(new ([1,2,3]))` → 3 +- [ ] [5.1.1] `new ([1,2,3,4,5]).lastIndexOfSequence(new ([3,4]))` → 2 +- [ ] [5.1.2] `new ([1,2,3,1,2,3]).lastIndexOfSequence(new ([1,2,3]))` → 3 For BigInt types (`BigInt64Array`, `BigUint64Array`): -- [ ] [5.1.3] `new ([1n,2n,3n,4n,5n]).searchLast(new ([3n,4n]))` → 2 -- [ ] [5.1.4] `new ([1n,2n,3n,1n,2n,3n]).searchLast(new ([1n,2n,3n]))` → 3 +- [ ] [5.1.3] `new ([1n,2n,3n,4n,5n]).lastIndexOfSequence(new ([3n,4n]))` → 2 +- [ ] [5.1.4] `new ([1n,2n,3n,1n,2n,3n]).lastIndexOfSequence(new ([1n,2n,3n]))` → 3 ### 5.2 No match -- [ ] [5.2.1] `new Uint8Array([1,2,3]).searchLast(new Uint8Array([4,5]))` → -1 +- [ ] [5.2.1] `new Uint8Array([1,2,3]).lastIndexOfSequence(new Uint8Array([4,5]))` → -1 ### 5.3 Empty needle -- [ ] [5.3.1] `new Uint8Array([1,2,3]).searchLast([])` → 2 (returns position, default is haystackLength - 1) -- [ ] [5.3.2] `new Uint8Array([1,2,3]).searchLast([], 1)` → 1 +- [ ] [5.3.1] `new Uint8Array([1,2,3]).lastIndexOfSequence(new Uint8Array([]))` → 2 (returns position, default is haystackLength - 1) +- [ ] [5.3.2] `new Uint8Array([1,2,3]).lastIndexOfSequence(new Uint8Array([]), 1)` → 1 ### 5.4 Empty haystack -- [ ] [5.4.1] `new Uint8Array([]).searchLast(new Uint8Array([1]))` → -1 +- [ ] [5.4.1] `new Uint8Array([]).lastIndexOfSequence(new Uint8Array([1]))` → -1 ### 5.5 Needle longer than haystack -- [ ] [5.5.1] `new Uint8Array([1,2]).searchLast(new Uint8Array([1,2,3]))` → -1 +- [ ] [5.5.1] `new Uint8Array([1,2]).lastIndexOfSequence(new Uint8Array([1,2,3]))` → -1 ### 5.6 Position constrains search -- [ ] [5.6.1] `new Uint8Array([1,2,3,1,2,3,1,2,3]).searchLast([2,3])` → 7 -- [ ] [5.6.2] `new Uint8Array([1,2,3,1,2,3,1,2,3]).searchLast([2,3], 5)` → 4 -- [ ] [5.6.3] `new Uint8Array([1,2,3,1,2,3,1,2,3]).searchLast([2,3], 3)` → 1 -- [ ] [5.6.4] `new Uint8Array([1,2,3,1,2,3,1,2,3]).searchLast([2,3], 0)` → -1 +- [ ] [5.6.1] `new Uint8Array([1,2,3,1,2,3,1,2,3]).lastIndexOfSequence(new Uint8Array([2,3]))` → 7 +- [ ] [5.6.2] `new Uint8Array([1,2,3,1,2,3,1,2,3]).lastIndexOfSequence(new Uint8Array([2,3]), 5)` → 4 +- [ ] [5.6.3] `new Uint8Array([1,2,3,1,2,3,1,2,3]).lastIndexOfSequence(new Uint8Array([2,3]), 3)` → 1 +- [ ] [5.6.4] `new Uint8Array([1,2,3,1,2,3,1,2,3]).lastIndexOfSequence(new Uint8Array([2,3]), 0)` → -1 ### 5.7 Position at exact match start -- [ ] [5.7.1] `new Uint8Array([1,2,3,1,2,3]).searchLast([1,2,3], 3)` → 3 -- [ ] [5.7.2] `new Uint8Array([1,2,3,1,2,3]).searchLast([1,2,3], 2)` → 0 +- [ ] [5.7.1] `new Uint8Array([1,2,3,1,2,3]).lastIndexOfSequence(new Uint8Array([1,2,3]), 3)` → 3 +- [ ] [5.7.2] `new Uint8Array([1,2,3,1,2,3]).lastIndexOfSequence(new Uint8Array([1,2,3]), 2)` → 0 ### 5.8 Single-element needle -- [ ] [5.8.1] `new Uint8Array([1,2,3,2,1]).searchLast([2])` → 3 -- [ ] [5.8.2] `new Uint8Array([1,2,3,2,1]).searchLast([2], 2)` → 1 +- [ ] [5.8.1] `new Uint8Array([1,2,3,2,1]).lastIndexOfSequence(new Uint8Array([2]))` → 3 +- [ ] [5.8.2] `new Uint8Array([1,2,3,2,1]).lastIndexOfSequence(new Uint8Array([2]), 2)` → 1 ### 5.9 Match must start at or before position (not end at or before) -- [ ] [5.9.1] `new Uint8Array([1,2,3,4,5]).searchLast([3,4,5], 2)` → 2 (match starts at 2, which is ≤ position 2) -- [ ] [5.9.2] `new Uint8Array([1,2,3,4,5]).searchLast([3,4,5], 1)` → -1 (match would start at 2, which is > position 1) +- [ ] [5.9.1] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([3,4,5]), 2)` → 2 (match starts at 2, which is ≤ position 2) +- [ ] [5.9.2] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([3,4,5]), 1)` → -1 (match would start at 2, which is > position 1) ### 5.10 Position near end with multi-element needle (`k + needleLength ≤ haystackLength` constraint) -- [ ] [5.10.1] `new Uint8Array([1,2,3,4,5]).searchLast([4,5], 4)` → 3 (position is 4, but k + 2 ≤ 5 means k ≤ 3) -- [ ] [5.10.2] `new Uint8Array([1,2,3,4,5]).searchLast([3,4,5], 4)` → 2 (position is 4, but k + 3 ≤ 5 means k ≤ 2) -- [ ] [5.10.3] `new Uint8Array([1,2,3,4,5]).searchLast([1,2,3,4,5], 4)` → 0 (only one possible start index) -- [ ] [5.10.4] `new Uint8Array([1,2,3,4,5]).searchLast([1,2,3,4,5], 0)` → 0 +- [ ] [5.10.1] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([4,5]), 4)` → 3 (position is 4, but k + 2 ≤ 5 means k ≤ 3) +- [ ] [5.10.2] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([3,4,5]), 4)` → 2 (position is 4, but k + 3 ≤ 5 means k ≤ 2) +- [ ] [5.10.3] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([1,2,3,4,5]), 4)` → 0 (only one possible start index) +- [ ] [5.10.4] `new Uint8Array([1,2,3,4,5]).lastIndexOfSequence(new Uint8Array([1,2,3,4,5]), 0)` → 0 ### 5.11 Empty haystack with empty needle -- [ ] [5.11.1] `new Uint8Array([]).searchLast([])` → 0 (position defaults to haystackLength - 1 = -1, clamped to 0; empty needle returns position) +- [ ] [5.11.1] `new Uint8Array([]).lastIndexOfSequence(new Uint8Array([]))` → 0 (position defaults to haystackLength - 1 = -1, clamped to 0; empty needle returns position) -Note: the clamping range for `searchLast` is `[0, haystackLength - 1]`. When haystackLength is 0, this is `[0, -1]`, a degenerate range. The spec's clamping definition produces `lower` (0) when `x < lower`, so `clamp(-1, 0, -1)` → 0. This edge case may warrant spec clarification. +Note: the clamping range for `lastIndexOfSequence` is `[0, haystackLength - 1]`. When haystackLength is 0, this is `[0, -1]`, a degenerate range. The spec's clamping definition produces `lower` (0) when `x < lower`, so `clamp(-1, 0, -1)` → 0. This edge case may warrant spec clarification. --- -## 6. `contains` (Boolean Search) - -### 6.1 Basic matching - -For each TypedArray type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`): - -- [ ] [6.1.1] `new ([1,2,3,4,5]).contains(new ([3,4]))` → true -- [ ] [6.1.2] `new ([1,2,3]).contains(new ([4,5]))` → false - -For BigInt types (`BigInt64Array`, `BigUint64Array`): - -- [ ] [6.1.3] `new ([1n,2n,3n,4n,5n]).contains(new ([3n,4n]))` → true -- [ ] [6.1.4] `new ([1n,2n,3n]).contains(new ([4n,5n]))` → false - -### 6.2 Empty needle +## 6. SameValueZero Equality Semantics -- [ ] [6.2.1] `new Uint8Array([1,2,3]).contains([])` → true -- [ ] [6.2.2] `new Uint8Array([]).contains([])` → true - -### 6.3 Position parameter - -- [ ] [6.3.1] `new Uint8Array([1,2,3,4]).contains([3,4], 0)` → true -- [ ] [6.3.2] `new Uint8Array([1,2,3,4]).contains([3,4], 3)` → false (not enough room from position 3) - -### 6.4 Return type - -- [ ] [6.4.1] Verify `contains` return value is strictly `true` (not `1` or truthy) when match found -- [ ] [6.4.2] Verify `contains` return value is strictly `false` (not `0` or falsy) when no match - ---- - -## 7. SameValueZero Equality Semantics - -### 7.1 NaN matching +### 6.1 NaN matching For each floating-point type (`Float16Array`, `Float32Array`, `Float64Array`): -- [ ] [7.1.1] `new ([1, NaN, 3]).search(new ([NaN]))` → 1 -- [ ] [7.1.2] `new ([NaN, NaN]).search(new ([NaN, NaN]))` → 0 -- [ ] [7.1.3] `new ([NaN, 1, NaN]).searchLast(new ([NaN]))` → 2 -- [ ] [7.1.4] `new ([1, NaN, 3]).contains(new ([NaN]))` → true +- [ ] [6.1.1] `new ([1, NaN, 3]).indexOfSequence(new ([NaN]))` → 1 +- [ ] [6.1.2] `new ([NaN, NaN]).indexOfSequence(new ([NaN, NaN]))` → 0 +- [ ] [6.1.3] `new ([NaN, 1, NaN]).lastIndexOfSequence(new ([NaN]))` → 2 -### 7.2 +0 / -0 equivalence +### 6.2 +0 / -0 equivalence -For each floating-point type (`Float16Array`, `Float32Array`, `Float64Array`) and each method (`search`, `searchLast`, `contains`): +For each floating-point type (`Float16Array`, `Float32Array`, `Float64Array`) and each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [7.2.1] `new ([1, -0, 3]).search(new ([0]))` → 1 -- [ ] [7.2.2] `new ([1, 0, 3]).search(new ([-0]))` → 1 -- [ ] [7.2.3] `new ([-0]).search(new ([0]))` → 0 -- [ ] [7.2.4] `new ([0]).search(new ([-0]))` → 0 +- [ ] [6.2.1] `new ([1, -0, 3]).indexOfSequence(new ([0]))` → 1 +- [ ] [6.2.2] `new ([1, 0, 3]).indexOfSequence(new ([-0]))` → 1 +- [ ] [6.2.3] `new ([-0]).indexOfSequence(new ([0]))` → 0 +- [ ] [6.2.4] `new ([0]).indexOfSequence(new ([-0]))` → 0 -### 7.3 NaN in subsequence +### 6.3 NaN in subsequence -For each floating-point type (`Float16Array`, `Float32Array`, `Float64Array`) and each method (`search`, `searchLast`, `contains`): +For each floating-point type (`Float16Array`, `Float32Array`, `Float64Array`) and each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [7.3.1] `new ([1, NaN, 3, 4]).search(new ([NaN, 3]))` → 1 +- [ ] [6.3.1] `new ([1, NaN, 3, 4]).indexOfSequence(new ([NaN, 3]))` → 1 -### 7.4 Integer TypedArrays (no NaN / -0 concerns) +### 6.4 Integer TypedArrays (no NaN / -0 concerns) -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [7.4.1] `new Int32Array([-1, 0, 1]).search(new Int32Array([0, 1]))` → 1 +- [ ] [6.4.1] `new Int32Array([-1, 0, 1]).indexOfSequence(new Int32Array([0, 1]))` → 1 --- -## 8. TypedArray Type Coverage +## 7. TypedArray Type Coverage -### 8.1 Basic search/searchLast/contains per type +### 7.1 Basic indexOfSequence/lastIndexOfSequence per type For each non-BigInt type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`): -- [ ] [8.1.1] `new ([1,2,3]).search(new ([2,3]))` → 1 -- [ ] [8.1.2] `new ([1,2,3]).searchLast(new ([2,3]))` → 1 -- [ ] [8.1.3] `new ([1,2,3]).contains(new ([2,3]))` → true +- [ ] [7.1.1] `new ([1,2,3]).indexOfSequence(new ([2,3]))` → 1 +- [ ] [7.1.2] `new ([1,2,3]).lastIndexOfSequence(new ([2,3]))` → 1 For each BigInt type (`BigInt64Array`, `BigUint64Array`): -- [ ] [8.1.4] `new ([1n,2n,3n]).search(new ([2n,3n]))` → 1 -- [ ] [8.1.5] `new ([1n,2n,3n]).searchLast(new ([2n,3n]))` → 1 -- [ ] [8.1.6] `new ([1n,2n,3n]).contains(new ([2n,3n]))` → true - -### 8.2 BigInt array with iterable needle +- [ ] [7.1.3] `new ([1n,2n,3n]).indexOfSequence(new ([2n,3n]))` → 1 +- [ ] [7.1.4] `new ([1n,2n,3n]).lastIndexOfSequence(new ([2n,3n]))` → 1 -- [ ] [8.2.1] `new BigInt64Array([1n, 2n, 3n]).search([2n, 3n])` → 1 -- [ ] [8.2.2] `new BigUint64Array([1n, 2n, 3n]).search([2n, 3n])` → 1 +### 7.2 Cross-type with BigInt -### 8.3 Cross-type with BigInt - -- [ ] [8.3.1] `new BigInt64Array([1n, 2n]).search(new BigUint64Array([1n, 2n]))` → 0 (both yield BigInts) -- [ ] [8.3.2] `new BigInt64Array([1n, 2n]).search(new Uint8Array([1, 2]))` → -1 (Uint8Array yields Numbers, BigInt64Array expects BigInts) +- [ ] [7.2.1] `new BigInt64Array([1n, 2n]).indexOfSequence(new BigUint64Array([1n, 2n]))` → 0 (both have BigInt content type) +- [ ] [7.2.2] `new BigInt64Array([1n, 2n]).indexOfSequence(new Uint8Array([1, 2]))` → -1 (content type mismatch: BigInt vs Number) --- -## 9. Evaluation Order and Observable Side Effects +## 8. Evaluation Order and Observable Side Effects -### 9.1 ValidateTypedArray before ToCompatibleTypedArrayElementList +### 8.1 ValidateTypedArray before needle validation -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [9.1.1] Detached buffer with an iterable needle that has observable side effects → TypeError from validation, iterator never called +- [ ] [8.1.1] Detached buffer with a TypedArray needle → TypeError from haystack validation, needle's buffer not accessed -### 9.2 ToCompatibleTypedArrayElementList before ValidateIntegralNumber +### 8.2 Needle validation before ValidateIntegralNumber -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [9.2.1] Valid TypedArray, invalid needle type, invalid position → TypeError from needle validation (position never validated) +- [ ] [8.2.1] Valid TypedArray, invalid needle type, invalid position → TypeError from needle validation (position never validated) ```js - new Uint8Array([1,2,3]).search(42, 'bad') // → TypeError (from needle, not position) + new Uint8Array([1,2,3]).indexOfSequence(42, 'bad') // → TypeError (from needle, not position) ``` -### 9.3 ValidateIntegralNumber after needle validation +### 8.3 ValidateIntegralNumber after needle validation -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [9.3.1] Valid TypedArray, valid iterable needle, invalid position → error from position validation +- [ ] [8.3.1] Valid TypedArray, valid TypedArray needle, invalid position → error from position validation ```js - new Uint8Array([1,2,3]).search([1,2], NaN) // → RangeError - new Uint8Array([1,2,3]).search([1,2], 'bad') // → TypeError + new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1,2]), NaN) // → RangeError + new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1,2]), 'bad') // → TypeError ``` -### 9.4 Iterator side effects - -For each method (`search`, `searchLast`, `contains`): - -- [ ] [9.4.1] Iterable whose iterator modifies global state → verify iteration happens exactly once -- [ ] [9.4.2] Iterable whose iterator throws midway → error propagates, partial iteration observable - --- -## 10. Property and Prototype +## 9. Property and Prototype -### 10.1 Method existence +### 9.1 Method existence -- [ ] [10.1.1] `typeof Uint8Array.prototype.search` → `'function'` -- [ ] [10.1.2] `typeof Uint8Array.prototype.searchLast` → `'function'` -- [ ] [10.1.3] `typeof Uint8Array.prototype.contains` → `'function'` +- [ ] [9.1.1] `typeof Uint8Array.prototype.indexOfSequence` → `'function'` +- [ ] [9.1.2] `typeof Uint8Array.prototype.lastIndexOfSequence` → `'function'` -### 10.2 Method `.length` property +### 9.2 Method `.length` property -- [ ] [10.2.1] `Uint8Array.prototype.search.length` → 1 -- [ ] [10.2.2] `Uint8Array.prototype.searchLast.length` → 1 -- [ ] [10.2.3] `Uint8Array.prototype.contains.length` → 1 +- [ ] [9.2.1] `Uint8Array.prototype.indexOfSequence.length` → 1 +- [ ] [9.2.2] `Uint8Array.prototype.lastIndexOfSequence.length` → 1 -### 10.3 Method `.name` property +### 9.3 Method `.name` property -- [ ] [10.3.1] `Uint8Array.prototype.search.name` → `'search'` -- [ ] [10.3.2] `Uint8Array.prototype.searchLast.name` → `'searchLast'` -- [ ] [10.3.3] `Uint8Array.prototype.contains.name` → `'contains'` +- [ ] [9.3.1] `Uint8Array.prototype.indexOfSequence.name` → `'indexOfSequence'` +- [ ] [9.3.2] `Uint8Array.prototype.lastIndexOfSequence.name` → `'lastIndexOfSequence'` -### 10.4 Methods are on %TypedArray%.prototype +### 9.4 Methods are on %TypedArray%.prototype -- [ ] [10.4.1] `Uint8Array.prototype.search === Int32Array.prototype.search` → true -- [ ] [10.4.2] `Uint8Array.prototype.searchLast === Float64Array.prototype.searchLast` → true -- [ ] [10.4.3] `Uint8Array.prototype.contains === BigInt64Array.prototype.contains` → true +- [ ] [9.4.1] `Uint8Array.prototype.indexOfSequence === Int32Array.prototype.indexOfSequence` → true +- [ ] [9.4.2] `Uint8Array.prototype.lastIndexOfSequence === Float64Array.prototype.lastIndexOfSequence` → true -### 10.5 Not enumerable +### 9.5 Not enumerable -- [ ] [10.5.1] `Object.getOwnPropertyDescriptor(Uint8Array.prototype, 'search').enumerable` → false -- [ ] [10.5.2] `Object.getOwnPropertyDescriptor(Uint8Array.prototype, 'searchLast').enumerable` → false -- [ ] [10.5.3] `Object.getOwnPropertyDescriptor(Uint8Array.prototype, 'contains').enumerable` → false +- [ ] [9.5.1] `Object.getOwnPropertyDescriptor(Uint8Array.prototype, 'indexOfSequence').enumerable` → false +- [ ] [9.5.2] `Object.getOwnPropertyDescriptor(Uint8Array.prototype, 'lastIndexOfSequence').enumerable` → false --- -## 11. Boundary and Stress Cases - -### 11.1 Single-element haystack +## 10. Boundary and Stress Cases -- [ ] [11.1.1] `new Uint8Array([5]).search([5])` → 0 -- [ ] [11.1.2] `new Uint8Array([5]).search([6])` → -1 -- [ ] [11.1.3] `new Uint8Array([5]).searchLast([5])` → 0 +### 10.1 Single-element haystack -### 11.2 Needle same length as haystack +- [ ] [10.1.1] `new Uint8Array([5]).indexOfSequence(new Uint8Array([5]))` → 0 +- [ ] [10.1.2] `new Uint8Array([5]).indexOfSequence(new Uint8Array([6]))` → -1 +- [ ] [10.1.3] `new Uint8Array([5]).lastIndexOfSequence(new Uint8Array([5]))` → 0 -- [ ] [11.2.1] `new Uint8Array([1,2,3]).search([1,2,3])` → 0 -- [ ] [11.2.2] `new Uint8Array([1,2,3]).search([1,2,4])` → -1 -- [ ] [11.2.3] `new Uint8Array([1,2,3]).searchLast([1,2,3])` → 0 +### 10.2 Needle same length as haystack -### 11.3 Overlapping pattern in haystack +- [ ] [10.2.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1,2,3]))` → 0 +- [ ] [10.2.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1,2,4]))` → -1 +- [ ] [10.2.3] `new Uint8Array([1,2,3]).lastIndexOfSequence(new Uint8Array([1,2,3]))` → 0 -- [ ] [11.3.1] `new Uint8Array([1,1,1,2]).search([1,1,2])` → 1 -- [ ] [11.3.2] `new Uint8Array([1,1,1,1]).search([1,1])` → 0 -- [ ] [11.3.3] `new Uint8Array([1,1,1,1]).searchLast([1,1])` → 2 +### 10.3 Overlapping pattern in haystack -### 11.4 Repeated single value +- [ ] [10.3.1] `new Uint8Array([1,1,1,2]).indexOfSequence(new Uint8Array([1,1,2]))` → 1 +- [ ] [10.3.2] `new Uint8Array([1,1,1,1]).indexOfSequence(new Uint8Array([1,1]))` → 0 +- [ ] [10.3.3] `new Uint8Array([1,1,1,1]).lastIndexOfSequence(new Uint8Array([1,1]))` → 2 -- [ ] [11.4.1] `new Uint8Array([0,0,0,0,0]).search([0,0])` → 0 -- [ ] [11.4.2] `new Uint8Array([0,0,0,0,0]).searchLast([0,0])` → 3 -- [ ] [11.4.3] `new Uint8Array([0,0,0,0,0]).search([0,0], 2)` → 2 -- [ ] [11.4.4] `new Uint8Array([0,0,0,0,0]).searchLast([0,0], 2)` → 2 +### 10.4 Repeated single value -### 11.5 Large TypedArrays +- [ ] [10.4.1] `new Uint8Array([0,0,0,0,0]).indexOfSequence(new Uint8Array([0,0]))` → 0 +- [ ] [10.4.2] `new Uint8Array([0,0,0,0,0]).lastIndexOfSequence(new Uint8Array([0,0]))` → 3 +- [ ] [10.4.3] `new Uint8Array([0,0,0,0,0]).indexOfSequence(new Uint8Array([0,0]), 2)` → 2 +- [ ] [10.4.4] `new Uint8Array([0,0,0,0,0]).lastIndexOfSequence(new Uint8Array([0,0]), 2)` → 2 -- [ ] [11.5.1] Search in a large TypedArray (e.g., 10,000+ elements) with needle at the end → correct index -- [ ] [11.5.2] Search in a large TypedArray with no match → -1 -- [ ] [11.5.3] searchLast in a large TypedArray with needle at the beginning → correct index +### 10.5 Large TypedArrays -### 11.6 Position equals haystack length +- [ ] [10.5.1] Search in a large TypedArray (e.g., 10,000+ elements) with needle at the end → correct index +- [ ] [10.5.2] Search in a large TypedArray with no match → -1 +- [ ] [10.5.3] lastIndexOfSequence in a large TypedArray with needle at the beginning → correct index -- [ ] [11.6.1] `new Uint8Array([1,2,3]).search([], 3)` → 3 (empty needle returns position) -- [ ] [11.6.2] `new Uint8Array([1,2,3]).search([1], 3)` → -1 (position + needleLength > haystackLength) +### 10.6 Position equals haystack length -### 11.7 Uint8ClampedArray behaviour +- [ ] [10.6.1] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([]), 3)` → 3 (empty needle returns position) +- [ ] [10.6.2] `new Uint8Array([1,2,3]).indexOfSequence(new Uint8Array([1]), 3)` → -1 (position + needleLength > haystackLength) -Note: `ToCompatibleTypedArrayElementList` validates that iterable elements are Numbers (for non-BigInt TypedArrays) but does not clamp them. The needle List contains the raw Number values as yielded by the iterator. A value like 300 is a valid Number, but will not SameValueZero-match any stored element (which was clamped to 255 on write). +### 10.7 Uint8ClampedArray behaviour -- [ ] [11.7.1] `new Uint8ClampedArray([255, 0]).search([255])` → 0 -- [ ] [11.7.2] `new Uint8ClampedArray([255, 0]).search([300])` → -1 (300 does not SameValueZero-equal 255) -- [ ] [11.7.3] `new Uint8ClampedArray([255, 0]).search(new Uint8ClampedArray([255]))` → 0 (iterated value is 255, matches stored 255) +- [ ] [10.7.1] `new Uint8ClampedArray([255, 0]).indexOfSequence(new Uint8ClampedArray([255]))` → 0 +- [ ] [10.7.2] `new Uint8ClampedArray([255, 0]).indexOfSequence(new Uint8ClampedArray([128]))` → -1 -### 11.8 Typed integer overflow boundaries +### 10.8 Typed integer overflow boundaries -- [ ] [11.8.1] `new Uint8Array([255, 0, 1]).search([255, 0])` → 0 -- [ ] [11.8.2] `new Int8Array([-128, 127]).search([-128, 127])` → 0 -- [ ] [11.8.3] `new Uint16Array([65535, 0]).search([65535, 0])` → 0 -- [ ] [11.8.4] `new Int32Array([-2147483648, 2147483647]).search([-2147483648])` → 0 +- [ ] [10.8.1] `new Uint8Array([255, 0, 1]).indexOfSequence(new Uint8Array([255, 0]))` → 0 +- [ ] [10.8.2] `new Int8Array([-128, 127]).indexOfSequence(new Int8Array([-128, 127]))` → 0 +- [ ] [10.8.3] `new Uint16Array([65535, 0]).indexOfSequence(new Uint16Array([65535, 0]))` → 0 +- [ ] [10.8.4] `new Int32Array([-2147483648, 2147483647]).indexOfSequence(new Int32Array([-2147483648]))` → 0 -### 11.9 Methods called via `.call()` on different TypedArray subtypes +### 10.9 Methods called via `.call()` on different TypedArray subtypes -- [ ] [11.9.1] `Uint8Array.prototype.search.call(new Int32Array([1,2,3]), new Int32Array([2,3]))` → 1 (this is Int32Array, needle is same-type relative to this) -- [ ] [11.9.2] `Uint8Array.prototype.search.call(new Int32Array([1,2,3]), new Uint8Array([2,3]))` → 1 (needle is different-type, iterated via @@iterator) +- [ ] [10.9.1] `Uint8Array.prototype.indexOfSequence.call(new Int32Array([1,2,3]), new Int32Array([2,3]))` → 1 (this is Int32Array, needle is same-type relative to this) +- [ ] [10.9.2] `Uint8Array.prototype.indexOfSequence.call(new Int32Array([1,2,3]), new Uint8Array([2,3]))` → 1 (needle is different-type, read from buffer) -### 11.10 Empty haystack with non-empty needle (both directions) +### 10.10 Empty haystack with non-empty needle (both directions) -- [ ] [11.10.1] `new Uint8Array([]).search([1])` → -1 -- [ ] [11.10.2] `new Uint8Array([]).searchLast([1])` → -1 -- [ ] [11.10.3] `new Uint8Array([]).contains([1])` → false +- [ ] [10.10.1] `new Uint8Array([]).indexOfSequence(new Uint8Array([1]))` → -1 +- [ ] [10.10.2] `new Uint8Array([]).lastIndexOfSequence(new Uint8Array([1]))` → -1 --- -## 12. SharedArrayBuffer Considerations +## 11. SharedArrayBuffer Considerations -The needle is always snapshotted into a List via `@@iterator` before the search begins, so concurrent modifications to the needle's underlying buffer cannot affect the search. The haystack is *not* snapshotted — its elements are read directly during the search, consistent with `indexOf` and `lastIndexOf`. This means another agent may modify haystack elements during the search. +TypedArray needles are read directly from their underlying buffer via `TypedArraySubsequenceFromTypedArray` using `GetValueFromBuffer` with ~unordered~ ordering. Each element is read individually, so for SAB-backed needles, another agent may modify elements between reads. The haystack is *not* snapshotted — its elements are read directly during the search, consistent with `indexOf` and `lastIndexOf`. -### 12.1 Needle backed by SharedArrayBuffer (snapshotted) +### 11.1 TypedArray needle backed by SharedArrayBuffer (not snapshotted) -- [ ] [12.1.1] Needle elements are read via `@@iterator` before the search begins, producing a fixed snapshot List. Concurrent writes to the needle's SharedArrayBuffer by another agent after iteration completes do not affect the search result. -- [ ] [12.1.2] Example: needle `[2, 3]` is iterated and snapshotted; even if another agent changes the needle's buffer to `[9, 9]` mid-search, the search still looks for `[2, 3]`. +- [ ] [11.1.1] TypedArray needle elements are read individually from the SAB via `GetValueFromBuffer`. Another agent may modify needle elements between reads, potentially yielding an incoherent needle List. +- [ ] [11.1.2] Example: needle starts as `[2, 3]`; another agent changes element 0 to `9` after it is read but before element 1 is read. The search proceeds with the needle List `[2, 3]` or `[9, 3]` depending on timing. -### 12.2 Haystack backed by SharedArrayBuffer (not snapshotted) +### 11.2 Haystack backed by SharedArrayBuffer (not snapshotted) -- [ ] [12.2.1] Haystack elements are read individually during the search (via Get). Another agent may modify elements of the haystack concurrently. This is the same behaviour as `%TypedArray%.prototype.indexOf` and `%TypedArray%.prototype.lastIndexOf`. -- [ ] [12.2.2] A search may return `-1` even if the needle was present at the start of the search, if another agent modifies the haystack during the search. -- [ ] [12.2.3] A search may return an index where the needle no longer exists by the time the result is observed, if another agent modifies the haystack after the match is found. +- [ ] [11.2.1] Haystack elements are read individually during the search (via Get). Another agent may modify elements of the haystack concurrently. This is the same behaviour as `%TypedArray%.prototype.indexOf` and `%TypedArray%.prototype.lastIndexOf`. +- [ ] [11.2.2] A search may return `-1` even if the needle was present at the start of the search, if another agent modifies the haystack during the search. +- [ ] [11.2.3] A search may return an index where the needle no longer exists by the time the result is observed, if another agent modifies the haystack after the match is found. -### 12.3 Both haystack and needle backed by SharedArrayBuffer +### 11.3 Both haystack and needle backed by SharedArrayBuffer -- [ ] [12.3.1] The needle is snapshotted before the search. The haystack is read live. The combination of these two behaviours is well-defined: the search compares a frozen needle List against live haystack reads. +- [ ] [11.3.1] Both needle and haystack elements are read live (neither is snapshotted). Another agent may modify either during the search. Users must synchronize access externally. --- -## 13. Resizable ArrayBuffer Considerations +## 12. Resizable ArrayBuffer Considerations TypedArrays can be backed by resizable ArrayBuffers (created via `new ArrayBuffer(n, { maxByteLength: m })`). These buffers can be grown or shrunk via `resize()`, which can cause a TypedArray to go out of bounds. Auto-length TypedArrays (created without an explicit length) track the buffer size; fixed-length TypedArrays over resizable buffers have a fixed element count but can go out of bounds if the buffer shrinks. -The `@@iterator` for TypedArrays checks `IsTypedArrayOutOfBounds` on **every** `.next()` call and throws a `TypeError` if the TypedArray is out of bounds or detached at that point. +### 12.1 Haystack backed by a resizable ArrayBuffer (basic operation) -### 13.1 Haystack backed by a resizable ArrayBuffer (basic operation) +For each method (`indexOfSequence`, `lastIndexOfSequence`): -For each method (`search`, `searchLast`, `contains`): - -- [ ] [13.1.1] Auto-length haystack, no resize during operation → normal search behaviour, length tracks the buffer +- [ ] [12.1.1] Auto-length haystack, no resize during operation → normal search behaviour, length tracks the buffer ```js const rab = new ArrayBuffer(5, { maxByteLength: 10 }); const u8 = new Uint8Array(rab); u8.set([1, 2, 3, 4, 5]); - u8.search([3, 4]) // → 2 + u8.indexOfSequence(new Uint8Array([3, 4])) // → 2 ``` -- [ ] [13.1.2] Fixed-length haystack over resizable buffer, no resize → normal search behaviour +- [ ] [12.1.2] Fixed-length haystack over resizable buffer, no resize → normal search behaviour ```js const rab = new ArrayBuffer(8, { maxByteLength: 16 }); const u8 = new Uint8Array(rab, 0, 5); u8.set([1, 2, 3, 4, 5]); - u8.search([3, 4]) // → 2 + u8.indexOfSequence(new Uint8Array([3, 4])) // → 2 ``` -- [ ] [13.1.3] Auto-length haystack, buffer grown before calling `search` → search sees the new length -- [ ] [13.1.4] Fixed-length haystack, buffer shrunk below the fixed length before calling `search` → `ValidateTypedArray` throws TypeError (TypedArray is out of bounds) - -### 13.2 Needle backed by a resizable ArrayBuffer (basic operation) - -For each method (`search`, `searchLast`, `contains`): - -- [ ] [13.2.1] Auto-length needle TypedArray over resizable buffer, no resize → normal iteration, produces correct snapshot List -- [ ] [13.2.2] Fixed-length needle TypedArray over resizable buffer, no resize → normal iteration, produces correct snapshot List - -### 13.3 Needle iteration detaches the haystack's buffer - -A custom iterable whose iterator detaches the haystack's `ArrayBuffer` during iteration. `ValidateTypedArray` was already called (step 2), so `_taRecord_` has a pre-detachment buffer witness. After `ToCompatibleTypedArrayElementList` returns, `TypedArrayLength(_taRecord_)` uses the stale witness. The search proceeds but element reads from the detached buffer return `undefined`, which will not SameValueZero-match any numeric needle element. - -- [ ] [13.3.1] Custom iterable that detaches the haystack's ArrayBuffer during iteration → search returns -1 (element reads return `undefined`) - ```js - const ab = new ArrayBuffer(5); - const u8 = new Uint8Array(ab); - u8.set([1, 2, 3, 4, 5]); - const evilNeedle = { - [Symbol.iterator]() { - return { - i: 0, - next() { - if (this.i === 0) { - // Detach the haystack's buffer via transfer - ab.transfer(); - } - if (this.i < 2) return { value: this.i++ + 3, done: false }; - return { done: true }; - } - }; - } - }; - u8.search(evilNeedle) // → -1 (haystack buffer is now detached) - ``` -- [ ] [13.3.2] Same scenario with `contains` → false -- [ ] [13.3.3] Same scenario with `searchLast` → -1 - -### 13.4 Needle's own buffer shrunk or detached during its iteration +- [ ] [12.1.3] Auto-length haystack, buffer grown before calling `indexOfSequence` → search sees the new length +- [ ] [12.1.4] Fixed-length haystack, buffer shrunk below the fixed length before calling `indexOfSequence` → `ValidateTypedArray` throws TypeError (TypedArray is out of bounds) -When the needle is a TypedArray backed by a resizable ArrayBuffer, the TypedArray `@@iterator` checks `IsTypedArrayOutOfBounds` on every `.next()` call. If the buffer is shrunk or detached such that the needle goes out of bounds, `.next()` throws a TypeError. This error propagates through `IteratorToList` and `ToCompatibleTypedArrayElementList`. +### 12.2 Needle backed by a resizable ArrayBuffer (basic operation) -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [13.4.1] Needle is auto-length over a resizable buffer; a custom `next()` wrapper shrinks the buffer below the needle's byte offset mid-iteration → TypeError from `@@iterator`'s `.next()` -- [ ] [13.4.2] Needle is fixed-length over a resizable buffer; buffer is shrunk below `byteOffset + length * elementSize` mid-iteration → TypeError from `@@iterator`'s `.next()` -- [ ] [13.4.3] Needle TypedArray's buffer is detached during its own iteration → TypeError from `@@iterator`'s `.next()` +- [ ] [12.2.1] Auto-length needle TypedArray over resizable buffer, no resize → produces correct element List +- [ ] [12.2.2] Fixed-length needle TypedArray over resizable buffer, no resize → produces correct element List -### 13.5 Needle's buffer grown during its iteration (auto-length needle) +### 12.3 Needle TypedArray's own buffer shrunk or detached before read -When the needle is an auto-length TypedArray and its resizable buffer is grown during iteration, the `@@iterator` sees the new larger `TypedArrayLength` and continues iterating into the newly available elements. This produces a longer-than-expected needle List. +Since TypedArray needles are read via `TypedArraySubsequenceFromTypedArray`, `IsTypedArrayOutOfBounds` is checked once at the start. If the needle is already out of bounds or detached, a TypeError is thrown. Since no user code runs during the buffer reads, the buffer cannot be shrunk or detached *during* the read. -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [13.5.1] Auto-length needle over a resizable buffer; buffer is grown during iteration → needle List contains more elements than initially expected; search uses the full (longer) snapshot +- [ ] [12.3.1] Needle is auto-length over a resizable buffer; buffer shrunk below the needle's range before calling the method → TypeError from `IsTypedArrayOutOfBounds` +- [ ] [12.3.2] Needle is fixed-length over a resizable buffer; buffer shrunk below `byteOffset + length * elementSize` before calling the method → TypeError from `IsTypedArrayOutOfBounds` +- [ ] [12.3.3] Needle TypedArray's buffer is detached before calling the method → TypeError from `IsTypedArrayOutOfBounds` -### 13.6 Haystack buffer resized during needle iteration +### 12.4 Needle TypedArray's buffer grown (auto-length needle) -The `_taRecord_` is created by `ValidateTypedArray` before needle iteration begins. If the haystack's resizable buffer is resized during needle iteration: +Since TypedArray needles are read via `TypedArraySubsequenceFromTypedArray`, `TypedArrayLength` is computed once from the buffer witness record created by `MakeTypedArrayWithBufferWitnessRecord`. If the buffer is grown before the method is called, the auto-length needle reflects the new size. The buffer cannot be grown *during* the read since no user code runs. -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [13.6.1] Haystack is auto-length; buffer is grown during needle iteration → `TypedArrayLength(_taRecord_)` returns the pre-growth length (witness is stale); search only scans the original range. Elements beyond the original length are not searched. -- [ ] [13.6.2] Haystack is auto-length; buffer is shrunk during needle iteration (but TypedArray remains in bounds) → `TypedArrayLength(_taRecord_)` returns the pre-shrink length (witness is stale); search may read `undefined` for elements beyond the new length. Those reads will not match, effectively returning -1 for needles that would have matched only in the now-truncated region. -- [ ] [13.6.3] Haystack is auto-length; buffer is shrunk below the haystack's byte offset during needle iteration → `TypedArrayLength(_taRecord_)` uses stale witness; search may read `undefined` for all elements. Returns -1. -- [ ] [13.6.4] Haystack is fixed-length; buffer is shrunk below the fixed range during needle iteration → same stale-witness behaviour; element reads return `undefined`. +- [ ] [12.4.1] Auto-length needle over a resizable buffer; buffer is grown before calling the method → needle length reflects the new buffer size; search uses all elements -### 13.7 Both haystack and needle backed by resizable ArrayBuffers +### 12.5 Both haystack and needle backed by resizable ArrayBuffers -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [13.7.1] Both are auto-length over separate resizable buffers, no resize during operation → normal behaviour -- [ ] [13.7.2] Both share the same resizable ArrayBuffer (different views); no resize → needle is snapshotted via iteration, search proceeds normally -- [ ] [13.7.3] Both share the same resizable ArrayBuffer; buffer is grown during needle iteration → needle snapshot may include new elements; haystack length uses stale witness +- [ ] [12.5.1] Both are auto-length over separate resizable buffers, no resize during operation → normal behaviour +- [ ] [12.5.2] Both share the same resizable ArrayBuffer (different views); no resize → needle is read from buffer, search proceeds normally --- -## 14. Self-Search and Overlapping Views +## 13. Self-Search and Overlapping Views -### 14.1 Self-search (searching a TypedArray for itself) +### 13.1 Self-search (searching a TypedArray for itself) -The needle is snapshotted into a List via `@@iterator` before the search begins, so the search compares the live haystack against an independent copy of its own elements. +The needle elements are read from the buffer via `TypedArraySubsequenceFromTypedArray` into a List before the search begins. The search then compares this List against the live haystack elements read via `! Get`. Since both read from the same buffer and the method does not modify the buffer, self-search is safe. -- [ ] [14.1.1] `const u8 = new Uint8Array([1,2,3]); u8.search(u8)` → 0 (entire array matches at index 0) -- [ ] [14.1.2] `const u8 = new Uint8Array([1,2,3]); u8.searchLast(u8)` → 0 (only one possible match position) -- [ ] [14.1.3] `const u8 = new Uint8Array([1,2,3]); u8.contains(u8)` → true -- [ ] [14.1.4] `const u8 = new Uint8Array([1,2,3]); u8.search(u8, 1)` → -1 (needle length 3, only 2 elements from position 1) -- [ ] [14.1.5] Self-search on empty TypedArray: `const u8 = new Uint8Array([]); u8.search(u8)` → 0 (empty needle returns position) -- [ ] [14.1.6] Self-search on single-element TypedArray: `const u8 = new Uint8Array([42]); u8.search(u8)` → 0 -- [ ] [14.1.7] Self-search with BigInt TypedArray: `const b = new BigInt64Array([1n,2n]); b.search(b)` → 0 +- [ ] [13.1.1] `const u8 = new Uint8Array([1,2,3]); u8.indexOfSequence(u8)` → 0 (entire array matches at index 0) +- [ ] [13.1.2] `const u8 = new Uint8Array([1,2,3]); u8.lastIndexOfSequence(u8)` → 0 (only one possible match position) +- [ ] [13.1.3] `const u8 = new Uint8Array([1,2,3]); u8.indexOfSequence(u8, 1)` → -1 (needle length 3, only 2 elements from position 1) +- [ ] [13.1.4] Self-search on empty TypedArray: `const u8 = new Uint8Array([]); u8.indexOfSequence(u8)` → 0 (empty needle returns position) +- [ ] [13.1.5] Self-search on single-element TypedArray: `const u8 = new Uint8Array([42]); u8.indexOfSequence(u8)` → 0 +- [ ] [13.1.6] Self-search with BigInt TypedArray: `const b = new BigInt64Array([1n,2n]); b.indexOfSequence(b)` → 0 -### 14.2 Needle and haystack sharing the same underlying ArrayBuffer (overlapping views) +### 13.2 Needle and haystack sharing the same underlying ArrayBuffer (overlapping views) -Two TypedArrays backed by the same ArrayBuffer but with different byte offsets or lengths. The needle is snapshotted via iteration, so the search is safe regardless of overlap. +Two TypedArrays backed by the same ArrayBuffer but with different byte offsets or lengths. The needle elements are read from the buffer into a List before the search begins, so the search is safe regardless of overlap. -For each method (`search`, `searchLast`, `contains`): +For each method (`indexOfSequence`, `lastIndexOfSequence`): -- [ ] [14.2.1] Overlapping views, needle is a subview of the haystack: +- [ ] [13.2.1] Overlapping views, needle is a subview of the haystack: ```js const ab = new ArrayBuffer(5); const haystack = new Uint8Array(ab); // [1,2,3,4,5] haystack.set([1, 2, 3, 4, 5]); const needle = new Uint8Array(ab, 2, 2); // [3,4] - haystack.search(needle) // → 2 + haystack.indexOfSequence(needle) // → 2 ``` -- [ ] [14.2.2] Overlapping views, haystack is a subview: +- [ ] [13.2.2] Overlapping views, haystack is a subview: ```js const ab = new ArrayBuffer(5); const full = new Uint8Array(ab); full.set([1, 2, 3, 4, 5]); const haystack = new Uint8Array(ab, 1, 3); // [2,3,4] const needle = new Uint8Array(ab, 0, 2); // [1,2] - haystack.search(needle) // → -1 (haystack is [2,3,4], needle is [1,2]) + haystack.indexOfSequence(needle) // → -1 (haystack is [2,3,4], needle is [1,2]) ``` -- [ ] [14.2.3] Same buffer, same offset, different element types: +- [ ] [13.2.3] Same buffer, same offset, different element types: ```js const ab = new ArrayBuffer(8); const u8 = new Uint8Array(ab); u8.set([1, 0, 2, 0, 3, 0, 4, 0]); const u16 = new Uint16Array(ab); // [1, 2, 3, 4] on little-endian - u16.search(new Uint16Array([2, 3])) // → 1 + u16.indexOfSequence(new Uint16Array([2, 3])) // → 1 ``` diff --git a/spec.emu b/spec.emu index 2dde406..7e8c5a1 100644 --- a/spec.emu +++ b/spec.emu @@ -4,7 +4,7 @@ @@ -32,65 +32,46 @@ contributors: James M Snell 1. If _value_ is *undefined*, return _default_. 1. If _value_ is not a Number, throw a *TypeError* exception. - 1. If _value_ is *NaN*, throw a *RangeError* exception. - 1. If truncate(ℝ(_value_)) is not ℝ(_value_), throw a *RangeError* exception. + 1. If _value_ is not an integral Number, throw a *RangeError* exception. 1. Return ℝ(_value_). - +

- SequenceSameValueZeroEqual ( - _a_: either a List of Numbers or a List of BigInts, - _b_: either a List of Numbers or a List of BigInts, - ): a Boolean -

-
-
description
-
It determines whether two Lists have the same length and element-wise equal values according to the SameValueZero algorithm.
-
- - 1. Let _aLength_ be the number of elements in _a_. - 1. Let _bLength_ be the number of elements in _b_. - 1. If _aLength_ is not _bLength_, return *false*. - 1. Let _i_ be 0. - 1. Repeat, while _i_ < _aLength_, - 1. If SameValueZero(_a_[_i_], _b_[_i_]) is *false*, return *false*. - 1. Set _i_ to _i_ + 1. - 1. Return *true*. - -
- - -

- ToCompatibleTypedArrayElementList ( - _typedArray_: a TypedArray, - _needle_: an ECMAScript language value, + TypedArraySubsequenceFromTypedArray ( + _haystack_: a TypedArray, + _needle_: a TypedArray, ): either a normal completion containing either a List of Numbers, a List of BigInts, or ~not-found~, or a throw completion

description
-
It produces a List of element values compatible with _typedArray_ from _needle_. If _needle_ is an iterable Object, its elements are collected and validated against the element type of _typedArray_. If any element is not the expected type, ~not-found~ is returned. Non-Object values (including Strings, which would otherwise be boxed into iterable String objects) and non-iterable Objects throw a *TypeError*.
+
It produces a List of element values by reading directly from _needle_'s underlying buffer. If _haystack_ and _needle_ have incompatible content types (Number vs BigInt), ~not-found~ is returned.
- 1. If _needle_ is not an Object, throw a *TypeError* exception. - 1. Let _iteratorMethod_ be ? GetMethod(_needle_, @@iterator). - 1. If _iteratorMethod_ is *undefined*, throw a *TypeError* exception. - 1. Let _values_ be ? IteratorToList(? GetIteratorFromMethod(_needle_, _iteratorMethod_)). - 1. Let _elementType_ be TypedArrayElementType(_typedArray_). + 1. Assert: _haystack_ is an Object that has a [[TypedArrayName]] internal slot. + 1. Let _needleRecord_ be MakeTypedArrayWithBufferWitnessRecord(_needle_, ~seq-cst~). + 1. If IsTypedArrayOutOfBounds(_needleRecord_) is *true*, throw a *TypeError* exception. + 1. Let _needleLength_ be TypedArrayLength(_needleRecord_). + 1. If _haystack_.[[ContentType]] is not _needle_.[[ContentType]], return ~not-found~. + 1. Let _needleBuffer_ be _needle_.[[ViewedArrayBuffer]]. + 1. Let _needleType_ be TypedArrayElementType(_needle_). + 1. Let _needleElementSize_ be TypedArrayElementSize(_needle_). + 1. Let _needleByteOffset_ be _needle_.[[ByteOffset]]. 1. Let _result_ be a new empty List. - 1. For each element _v_ of _values_, do - 1. If IsBigIntElementType(_elementType_) is *true* and _v_ is not a BigInt, or if IsBigIntElementType(_elementType_) is *false* and _v_ is not a Number, return ~not-found~. - 1. Append _v_ to _result_. + 1. Let _k_ be 0. + 1. Repeat, while _k_ < _needleLength_, + 1. Let _byteIndex_ be (_k_ × _needleElementSize_) + _needleByteOffset_. + 1. Let _value_ be GetValueFromBuffer(_needleBuffer_, _byteIndex_, _needleType_, *true*, ~unordered~). + 1. Append _value_ to _result_. + 1. Set _k_ to _k_ + 1. 1. Return _result_. -

All TypedArray needles (whether same-type or different-type) are iterated via their @@iterator method. This ensures that the resulting List is a snapshot of the needle's elements at the time of the call, which is necessary for correctness when the needle is backed by a SharedArrayBuffer (see issue #8).

-

If the iterable yields values of the wrong type (e.g., BigInts when the haystack is a non-BigInt TypedArray, or Numbers when it is a BigInt TypedArray), ~not-found~ is returned rather than throwing. Such values can never SameValueZero-match any element of the haystack, so returning ~not-found~ (which callers interpret as *-1*𝔽) is the correct result without the cost of performing the search. Alternatively we could - throw a TypeError in these cases.

+

This operation reads the needle's elements directly from its underlying buffer without invoking user code. The @@iterator method of _needle_ is not called. This is consistent with how %TypedArray%.prototype.set handles TypedArray sources via SetTypedArrayFromTypedArray.

- -

The acceptance of iterable objects as needles is intended to improve ergonomics (see issue #1). String primitives are not Objects and so fall through to the *TypeError* in the first step. Non-iterable objects and non-String primitives also throw to avoid silent misuse (see issue #7).

+ +

When _needle_ is backed by a SharedArrayBuffer, another agent may modify the needle's elements during the read. Each element is read individually via GetValueFromBuffer with ~unordered~ ordering. Users who need stronger guarantees must synchronize access externally. See issue #8 for discussion.

@@ -113,14 +94,39 @@ contributors: James M Snell 1. If _needleLength_ is 0, return 𝔽(_position_). 1. If _direction_ is ~first~, then 1. If _position_ + _needleLength_ > _haystackLength_, return *-1*𝔽. - 1. [id="step-search-first"] Return the smallest integer _k_ such that _k_ ≥ _position_ and SequenceSameValueZeroEqual(the List of elements of _typedArray_ from index _k_ to _k_ + _needleLength_ - 1, _needle_) is *true*, or *-1*𝔽 if no such _k_ exists. + 1. Let _k_ be _position_. + 1. Let _limit_ be _haystackLength_ - _needleLength_. + 1. Repeat, while _k_ ≤ _limit_, + 1. Let _matched_ be *true*. + 1. Let _j_ be 0. + 1. Repeat, while _j_ < _needleLength_ and _matched_ is *true*, + 1. Let _Pk_ be ! ToString(𝔽(_k_ + _j_)). + 1. Let _haystackValue_ be ! Get(_typedArray_, _Pk_). + 1. If SameValueZero(_haystackValue_, _needle_[_j_]) is *false*, set _matched_ to *false*. + 1. Set _j_ to _j_ + 1. + 1. If _matched_ is *true*, return 𝔽(_k_). + 1. Set _k_ to _k_ + 1. + 1. Return *-1*𝔽. 1. Else, 1. If _needleLength_ > _haystackLength_, return *-1*𝔽. - 1. [id="step-search-last"] Return the largest integer _k_ such that _k_ ≤ _position_ and _k_ + _needleLength_ ≤ _haystackLength_ and SequenceSameValueZeroEqual(the List of elements of _typedArray_ from index _k_ to _k_ + _needleLength_ - 1, _needle_) is *true*, or *-1*𝔽 if no such _k_ exists. + 1. Let _k_ be min(_position_, _haystackLength_ - _needleLength_). + 1. Repeat, while _k_ ≥ 0, + 1. Let _matched_ be *true*. + 1. Let _j_ be 0. + 1. Repeat, while _j_ < _needleLength_ and _matched_ is *true*, + 1. Let _Pk_ be ! ToString(𝔽(_k_ + _j_)). + 1. Let _haystackValue_ be ! Get(_typedArray_, _Pk_). + 1. If SameValueZero(_haystackValue_, _needle_[_j_]) is *false*, set _matched_ to *false*. + 1. Set _j_ to _j_ + 1. + 1. If _matched_ is *true*, return 𝔽(_k_). + 1. Set _k_ to _k_ - 1. + 1. Return *-1*𝔽. -

The _needle_ is produced by ToCompatibleTypedArrayElementList, which validates that element values are the correct type for _typedArray_ and are a snapshot that cannot be modified during the search. The elements of _typedArray_ (the haystack) are not snapshotted, consistent with how %TypedArray%.prototype.indexOf and %TypedArray%.prototype.lastIndexOf scan the haystack directly. When _typedArray_ is backed by a SharedArrayBuffer, another agent may modify elements of _typedArray_ during the search.

-

Implementations may use any technique to search for the subsequence, such as naive search, Boyer-Moore, or other matching algorithms, provided the observable result is correct.

+

The elements of _typedArray_ (the haystack) are not snapshotted, consistent with how %TypedArray%.prototype.indexOf and %TypedArray%.prototype.lastIndexOf scan the haystack directly. When _typedArray_ is backed by a SharedArrayBuffer, another agent may modify elements of _typedArray_ during the search.

+
+ +

The algorithm above describes a naive linear search for clarity. Implementations may use any technique to search for the subsequence, such as Boyer-Moore, two-way, or other matching algorithms, provided the observable result is equivalent.

@@ -128,54 +134,41 @@ contributors: James M Snell

Properties of the %TypedArray% Prototype Object

- -

%TypedArray%.prototype.search ( _needle_ [ , _position_ ] )

+ +

%TypedArray%.prototype.indexOfSequence ( _needle_ [ , _position_ ] )

This method searches for the first occurrence of the subsequence _needle_ within this TypedArray, starting the search at index _position_. It performs the following steps when called:

1. Let _typedArray_ be the *this* value. 1. Let _taRecord_ be ? ValidateTypedArray(_typedArray_, ~seq-cst~). - 1. Let _needleList_ be ? ToCompatibleTypedArrayElementList(_typedArray_, _needle_). + 1. If _needle_ is not an Object that has a [[TypedArrayName]] internal slot, throw a *TypeError* exception. + 1. Let _needleList_ be ? TypedArraySubsequenceFromTypedArray(_typedArray_, _needle_). 1. If _needleList_ is ~not-found~, return *-1*𝔽. 1. Let _haystackLength_ be TypedArrayLength(_taRecord_). 1. Let _start_ be ? ValidateIntegralNumber(_position_, 0). 1. Let _startFrom_ be the result of clamping _start_ between 0 and _haystackLength_. 1. Return TypedArraySearchSubsequence(_typedArray_, _haystackLength_, _needleList_, ~first~, _startFrom_). +

This method is not generic. The *this* value must be an object with a [[TypedArrayName]] internal slot.

- -

%TypedArray%.prototype.searchLast ( _needle_ [ , _position_ ] )

+ +

%TypedArray%.prototype.lastIndexOfSequence ( _needle_ [ , _position_ ] )

This method searches for the last occurrence of the subsequence _needle_ within this TypedArray whose starting index is at or before _position_. It performs the following steps when called:

1. Let _typedArray_ be the *this* value. 1. Let _taRecord_ be ? ValidateTypedArray(_typedArray_, ~seq-cst~). - 1. Let _needleList_ be ? ToCompatibleTypedArrayElementList(_typedArray_, _needle_). + 1. If _needle_ is not an Object that has a [[TypedArrayName]] internal slot, throw a *TypeError* exception. + 1. Let _needleList_ be ? TypedArraySubsequenceFromTypedArray(_typedArray_, _needle_). 1. If _needleList_ is ~not-found~, return *-1*𝔽. 1. Let _haystackLength_ be TypedArrayLength(_taRecord_). + 1. If _haystackLength_ is 0, then + 1. If _needleList_ is not empty, return *-1*𝔽. + 1. Return *+0*𝔽. 1. Let _start_ be ? ValidateIntegralNumber(_position_, _haystackLength_ - 1). 1. Let _startFrom_ be the result of clamping _start_ between 0 and _haystackLength_ - 1. 1. Return TypedArraySearchSubsequence(_typedArray_, _haystackLength_, _needleList_, ~last~, _startFrom_). -
- - -

%TypedArray%.prototype.contains ( _needle_ [ , _position_ ] )

-

This method determines whether the subsequence _needle_ exists within this TypedArray, starting the search at index _position_. It performs the following steps when called:

- - 1. Let _typedArray_ be the *this* value. - 1. Let _taRecord_ be ? ValidateTypedArray(_typedArray_, ~seq-cst~). - 1. Let _needleList_ be ? ToCompatibleTypedArrayElementList(_typedArray_, _needle_). - 1. If _needleList_ is ~not-found~, return *false*. - 1. Let _haystackLength_ be TypedArrayLength(_taRecord_). - 1. Let _start_ be ? ValidateIntegralNumber(_position_, 0). - 1. Let _startFrom_ be the result of clamping _start_ between 0 and _haystackLength_. - 1. Let _index_ be TypedArraySearchSubsequence(_typedArray_, _haystackLength_, _needleList_, ~first~, _startFrom_). - 1. If _index_ is *-1*𝔽, return *false*. - 1. Return *true*. - - -

Whether `contains` should exist as a separate method or whether users should simply use `search(_needle_) !== -1` is an open question. See issue #6 for discussion.

-
+

This method is not generic. The *this* value must be an object with a [[TypedArrayName]] internal slot.