| // ignore-tidy-filelength |
| /** |
| * @import * as stringdex from "./stringdex.d.ts" |
| */ |
| |
| const EMPTY_UINT8 = new Uint8Array(); |
| |
| /** |
| * @property {Uint8Array} keysAndCardinalities |
| * @property {Uint8Array[]} containers |
| */ |
| class RoaringBitmap { |
| /** |
| * @param {Uint8Array|null} u8array |
| * @param {number} [startingOffset] |
| */ |
| constructor(u8array, startingOffset) { |
| const start = startingOffset ? startingOffset : 0; |
| let i = start; |
| /** @type {Uint8Array} */ |
| this.keysAndCardinalities = EMPTY_UINT8; |
| /** @type {(RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun)[]} */ |
| this.containers = []; |
| /** @type {number} */ |
| this.consumed_len_bytes = 0; |
| if (u8array === null || u8array.length === i || u8array[i] === 0) { |
| return this; |
| } else if (u8array[i] > 0xf0) { |
| // Special representation of tiny sets that are close together |
| const lspecial = u8array[i] & 0x0f; |
| this.keysAndCardinalities = new Uint8Array(lspecial * 4); |
| let pspecial = i + 1; |
| let key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8); |
| let value = u8array[pspecial] | (u8array[pspecial + 1] << 8); |
| let entry = (key << 16) | value; |
| let container; |
| container = new RoaringBitmapArray(1, new Uint8Array(4)); |
| container.array[0] = value & 0xFF; |
| container.array[1] = (value >> 8) & 0xFF; |
| this.containers.push(container); |
| this.keysAndCardinalities[0] = key; |
| this.keysAndCardinalities[1] = key >> 8; |
| pspecial += 4; |
| for (let ispecial = 1; ispecial < lspecial; ispecial += 1) { |
| entry += u8array[pspecial] | (u8array[pspecial + 1] << 8); |
| value = entry & 0xffff; |
| key = entry >> 16; |
| container = this.addToArrayAt(key); |
| const cardinalityOld = container.cardinality; |
| container.array[cardinalityOld * 2] = value & 0xFF; |
| container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF; |
| container.cardinality = cardinalityOld + 1; |
| pspecial += 2; |
| } |
| this.consumed_len_bytes = pspecial - i; |
| return this; |
| } else if (u8array[i] < 0x3a) { |
| // Special representation of tiny sets with arbitrary 32-bit integers |
| const lspecial = u8array[i]; |
| this.keysAndCardinalities = new Uint8Array(lspecial * 4); |
| let pspecial = i + 1; |
| for (let ispecial = 0; ispecial < lspecial; ispecial += 1) { |
| const key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8); |
| const value = u8array[pspecial] | (u8array[pspecial + 1] << 8); |
| const container = this.addToArrayAt(key); |
| const cardinalityOld = container.cardinality; |
| container.array[cardinalityOld * 2] = value & 0xFF; |
| container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF; |
| container.cardinality = cardinalityOld + 1; |
| pspecial += 4; |
| } |
| this.consumed_len_bytes = pspecial - i; |
| return this; |
| } |
| // https://github.com/RoaringBitmap/RoaringFormatSpec |
| // |
| // Roaring bitmaps are used for flags that can be kept in their |
| // compressed form, even when loaded into memory. This decoder |
| // turns the containers into objects, but uses byte array |
| // slices of the original format for the data payload. |
| const has_runs = u8array[i] === 0x3b; |
| if (u8array[i] !== 0x3a && u8array[i] !== 0x3b) { |
| throw new Error("not a roaring bitmap: " + u8array[i]); |
| } |
| const size = has_runs ? |
| ((u8array[i + 2] | (u8array[i + 3] << 8)) + 1) : |
| ((u8array[i + 4] | (u8array[i + 5] << 8) | |
| (u8array[i + 6] << 16) | (u8array[i + 7] << 24))); |
| i += has_runs ? 4 : 8; |
| let is_run; |
| if (has_runs) { |
| const is_run_len = (size + 7) >> 3; |
| is_run = new Uint8Array(u8array.buffer, i + u8array.byteOffset, is_run_len); |
| i += is_run_len; |
| } else { |
| is_run = EMPTY_UINT8; |
| } |
| this.keysAndCardinalities = u8array.subarray(i, i + (size * 4)); |
| i += size * 4; |
| let offsets = null; |
| if (!has_runs || size >= 4) { |
| offsets = []; |
| for (let j = 0; j < size; ++j) { |
| offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) | |
| (u8array[i + 3] << 24)); |
| i += 4; |
| } |
| } |
| for (let j = 0; j < size; ++j) { |
| if (offsets && offsets[j] !== i - start) { |
| throw new Error(`corrupt bitmap ${j}: ${i - start} / ${offsets[j]}`); |
| } |
| const cardinality = (this.keysAndCardinalities[(j * 4) + 2] | |
| (this.keysAndCardinalities[(j * 4) + 3] << 8)) + 1; |
| if (is_run[j >> 3] & (1 << (j & 0x7))) { |
| const runcount = (u8array[i] | (u8array[i + 1] << 8)); |
| i += 2; |
| this.containers.push(new RoaringBitmapRun( |
| runcount, |
| new Uint8Array(u8array.buffer, i + u8array.byteOffset, runcount * 4), |
| )); |
| i += runcount * 4; |
| } else if (cardinality >= 4096) { |
| this.containers.push(new RoaringBitmapBits(new Uint8Array( |
| u8array.buffer, |
| i + u8array.byteOffset, 8192, |
| ))); |
| i += 8192; |
| } else { |
| const end = cardinality * 2; |
| this.containers.push(new RoaringBitmapArray( |
| cardinality, |
| new Uint8Array(u8array.buffer, i + u8array.byteOffset, end), |
| )); |
| i += end; |
| } |
| } |
| this.consumed_len_bytes = i - start; |
| } |
| /** |
| * @param {number} number |
| * @returns {RoaringBitmap} |
| */ |
| static makeSingleton(number) { |
| const result = new RoaringBitmap(null, 0); |
| result.keysAndCardinalities = Uint8Array.of( |
| (number >> 16), (number >> 24), |
| 0, 0, // keysAndCardinalities stores the true cardinality minus 1 |
| ); |
| result.containers.push(new RoaringBitmapArray( |
| 1, |
| Uint8Array.of(number, number >> 8), |
| )); |
| return result; |
| } |
| /** @returns {RoaringBitmap} */ |
| static everything() { |
| if (EVERYTHING_BITMAP.isEmpty()) { |
| let i = 0; |
| const l = 1 << 16; |
| const everything_range = new RoaringBitmapRun(1, Uint8Array.of(0, 0, 0xff, 0xff)); |
| EVERYTHING_BITMAP.keysAndCardinalities = new Uint8Array(l * 4); |
| while (i < l) { |
| EVERYTHING_BITMAP.containers.push(everything_range); |
| // key |
| EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 0] = i; |
| EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 1] = i >> 8; |
| // cardinality (minus one) |
| EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 2] = 0xff; |
| EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 3] = 0xff; |
| i += 1; |
| } |
| } |
| return EVERYTHING_BITMAP; |
| } |
| /** @returns {RoaringBitmap} */ |
| static empty() { |
| return EMPTY_BITMAP; |
| } |
| /** @returns {boolean} */ |
| isEmpty() { |
| return this.containers.length === 0; |
| } |
| /** |
| * Helper function used when constructing bitmaps from lists. |
| * Returns an array container with at least two free byte slots |
| * and bumps `this.cardinalities`. |
| * @param {number} key |
| * @returns {RoaringBitmapArray} |
| */ |
| addToArrayAt(key) { |
| let mid = this.getContainerId(key); |
| /** @type {RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun} */ |
| let container; |
| if (mid === -1) { |
| container = new RoaringBitmapArray(0, new Uint8Array(2)); |
| mid = this.containers.length; |
| this.containers.push(container); |
| if (mid * 4 > this.keysAndCardinalities.length) { |
| const keysAndContainers = new Uint8Array(mid * 8); |
| keysAndContainers.set(this.keysAndCardinalities); |
| this.keysAndCardinalities = keysAndContainers; |
| } |
| this.keysAndCardinalities[(mid * 4) + 0] = key; |
| this.keysAndCardinalities[(mid * 4) + 1] = key >> 8; |
| } else { |
| container = this.containers[mid]; |
| const cardinalityOld = |
| this.keysAndCardinalities[(mid * 4) + 2] | |
| (this.keysAndCardinalities[(mid * 4) + 3] << 8); |
| const cardinality = cardinalityOld + 1; |
| this.keysAndCardinalities[(mid * 4) + 2] = cardinality; |
| this.keysAndCardinalities[(mid * 4) + 3] = cardinality >> 8; |
| } |
| // the logic for handing this number is annoying, because keysAndCardinalities stores |
| // the cardinality *minus one*, so that it can count up to 65536 with only two bytes |
| // (because empty containers are never stored). |
| // |
| // So, if this is a new container, the stored cardinality contains `0 0`, which is |
| // the proper value of the old cardinality (an imaginary empty container existed). |
| // If this is adding to an existing container, then the above `else` branch bumps it |
| // by one, leaving us with a proper value of `cardinality - 1`. |
| const cardinalityOld = |
| this.keysAndCardinalities[(mid * 4) + 2] | |
| (this.keysAndCardinalities[(mid * 4) + 3] << 8); |
| if (!(container instanceof RoaringBitmapArray) || |
| container.array.byteLength < ((cardinalityOld + 1) * 2) |
| ) { |
| const newBuf = new Uint8Array((cardinalityOld + 1) * 4); |
| let idx = 0; |
| for (const cvalue of container.values()) { |
| newBuf[idx] = cvalue & 0xFF; |
| newBuf[idx + 1] = (cvalue >> 8) & 0xFF; |
| idx += 2; |
| } |
| if (container instanceof RoaringBitmapArray) { |
| container.cardinality = cardinalityOld; |
| container.array = newBuf; |
| return container; |
| } |
| const newcontainer = new RoaringBitmapArray(cardinalityOld, newBuf); |
| this.containers[mid] = newcontainer; |
| return newcontainer; |
| } else { |
| return container; |
| } |
| } |
| /** |
| * @param {RoaringBitmap} that |
| * @returns {RoaringBitmap} |
| */ |
| union(that) { |
| if (this.isEmpty()) { |
| return that; |
| } |
| if (that.isEmpty()) { |
| return this; |
| } |
| if (this === RoaringBitmap.everything() || that === RoaringBitmap.everything()) { |
| return RoaringBitmap.everything(); |
| } |
| let i = 0; |
| const il = this.containers.length; |
| let j = 0; |
| const jl = that.containers.length; |
| const result = new RoaringBitmap(null, 0); |
| result.keysAndCardinalities = new Uint8Array((il + jl) * 4); |
| while (i < il || j < jl) { |
| const ik = i * 4; |
| const jk = j * 4; |
| const k = result.containers.length * 4; |
| if (j >= jl || (i < il && ( |
| (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) || |
| (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] && |
| this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk]) |
| ))) { |
| result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0]; |
| result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1]; |
| result.keysAndCardinalities[k + 2] = this.keysAndCardinalities[ik + 2]; |
| result.keysAndCardinalities[k + 3] = this.keysAndCardinalities[ik + 3]; |
| result.containers.push(this.containers[i]); |
| i += 1; |
| } else if (i >= il || (j < jl && ( |
| (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) || |
| (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] && |
| that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik]) |
| ))) { |
| result.keysAndCardinalities[k + 0] = that.keysAndCardinalities[jk + 0]; |
| result.keysAndCardinalities[k + 1] = that.keysAndCardinalities[jk + 1]; |
| result.keysAndCardinalities[k + 2] = that.keysAndCardinalities[jk + 2]; |
| result.keysAndCardinalities[k + 3] = that.keysAndCardinalities[jk + 3]; |
| result.containers.push(that.containers[j]); |
| j += 1; |
| } else { |
| // this key is not smaller than that key |
| // that key is not smaller than this key |
| // they must be equal |
| const thisContainer = this.containers[i]; |
| const thatContainer = that.containers[j]; |
| let card = 0; |
| if (thisContainer instanceof RoaringBitmapBits && |
| thatContainer instanceof RoaringBitmapBits |
| ) { |
| const resultArray = new Uint8Array( |
| thisContainer.array.length > thatContainer.array.length ? |
| thisContainer.array.length : |
| thatContainer.array.length, |
| ); |
| let k = 0; |
| const kl = resultArray.length; |
| while (k < kl) { |
| const c = thisContainer.array[k] | thatContainer.array[k]; |
| resultArray[k] = c; |
| card += bitCount(c); |
| k += 1; |
| } |
| result.containers.push(new RoaringBitmapBits(resultArray)); |
| } else { |
| const thisValues = thisContainer.values(); |
| const thatValues = thatContainer.values(); |
| let thisResult = thisValues.next(); |
| let thatResult = thatValues.next(); |
| /** @type {Array<number>} */ |
| const resultValues = []; |
| while (!thatResult.done || !thisResult.done) { |
| // generator will definitely implement the iterator protocol correctly |
| /** @type {number} */ |
| const thisValue = thisResult.value; |
| /** @type {number} */ |
| const thatValue = thatResult.value; |
| if (thatResult.done || thisValue < thatValue) { |
| resultValues.push(thisValue); |
| thisResult = thisValues.next(); |
| } else if (thisResult.done || thatValue < thisValue) { |
| resultValues.push(thatValue); |
| thatResult = thatValues.next(); |
| } else { |
| // this value is not smaller than that value |
| // that value is not smaller than this value |
| // they must be equal |
| resultValues.push(thisValue); |
| thisResult = thisValues.next(); |
| thatResult = thatValues.next(); |
| } |
| } |
| const resultArray = new Uint8Array(resultValues.length * 2); |
| let k = 0; |
| for (const value of resultValues) { |
| // roaring bitmap is little endian |
| resultArray[k] = value & 0xFF; |
| resultArray[k + 1] = (value >> 8) & 0xFF; |
| k += 2; |
| } |
| result.containers.push(new RoaringBitmapArray( |
| resultValues.length, |
| resultArray, |
| )); |
| card = resultValues.length; |
| } |
| result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0]; |
| result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1]; |
| card -= 1; |
| result.keysAndCardinalities[k + 2] = card; |
| result.keysAndCardinalities[k + 3] = card >> 8; |
| i += 1; |
| j += 1; |
| } |
| } |
| return result; |
| } |
| /** |
| * @param {RoaringBitmap} that |
| * @returns {RoaringBitmap} |
| */ |
| intersection(that) { |
| if (this.isEmpty() || that.isEmpty()) { |
| return EMPTY_BITMAP; |
| } |
| if (this === RoaringBitmap.everything()) { |
| return that; |
| } |
| if (that === RoaringBitmap.everything()) { |
| return this; |
| } |
| let i = 0; |
| const il = this.containers.length; |
| let j = 0; |
| const jl = that.containers.length; |
| const result = new RoaringBitmap(null, 0); |
| result.keysAndCardinalities = new Uint8Array((il > jl ? il : jl) * 4); |
| while (i < il && j < jl) { |
| const ik = i * 4; |
| const jk = j * 4; |
| const k = result.containers.length * 4; |
| if (j >= jl || (i < il && ( |
| (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) || |
| (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] && |
| this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk]) |
| ))) { |
| i += 1; |
| } else if (i >= il || (j < jl && ( |
| (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) || |
| (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] && |
| that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik]) |
| ))) { |
| j += 1; |
| } else { |
| // this key is not smaller than that key |
| // that key is not smaller than this key |
| // they must be equal |
| const thisContainer = this.containers[i]; |
| const thatContainer = that.containers[j]; |
| let card = 0; |
| if (thisContainer instanceof RoaringBitmapBits && |
| thatContainer instanceof RoaringBitmapBits |
| ) { |
| const resultArray = new Uint8Array( |
| thisContainer.array.length > thatContainer.array.length ? |
| thisContainer.array.length : |
| thatContainer.array.length, |
| ); |
| let k = 0; |
| const kl = resultArray.length; |
| while (k < kl) { |
| const c = thisContainer.array[k] & thatContainer.array[k]; |
| resultArray[k] = c; |
| card += bitCount(c); |
| k += 1; |
| } |
| if (card !== 0) { |
| result.containers.push(new RoaringBitmapBits(resultArray)); |
| } |
| } else { |
| const thisValues = thisContainer.values(); |
| const thatValues = thatContainer.values(); |
| let thisValue = thisValues.next(); |
| let thatValue = thatValues.next(); |
| const resultValues = []; |
| while (!thatValue.done && !thisValue.done) { |
| if (thisValue.value < thatValue.value) { |
| thisValue = thisValues.next(); |
| } else if (thatValue.value < thisValue.value) { |
| thatValue = thatValues.next(); |
| } else { |
| // this value is not smaller than that value |
| // that value is not smaller than this value |
| // they must be equal |
| resultValues.push(thisValue.value); |
| thisValue = thisValues.next(); |
| thatValue = thatValues.next(); |
| } |
| } |
| card = resultValues.length; |
| if (card !== 0) { |
| const resultArray = new Uint8Array(resultValues.length * 2); |
| let k = 0; |
| for (const value of resultValues) { |
| // roaring bitmap is little endian |
| resultArray[k] = value & 0xFF; |
| resultArray[k + 1] = (value >> 8) & 0xFF; |
| k += 2; |
| } |
| result.containers.push(new RoaringBitmapArray( |
| resultValues.length, |
| resultArray, |
| )); |
| } |
| } |
| if (card !== 0) { |
| result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0]; |
| result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1]; |
| card -= 1; |
| result.keysAndCardinalities[k + 2] = card; |
| result.keysAndCardinalities[k + 3] = card >> 8; |
| } |
| i += 1; |
| j += 1; |
| } |
| } |
| return result; |
| } |
| /** @param {number} keyvalue */ |
| contains(keyvalue) { |
| const key = keyvalue >> 16; |
| const value = keyvalue & 0xFFFF; |
| const mid = this.getContainerId(key); |
| return mid === -1 ? false : this.containers[mid].contains(value); |
| } |
| /** |
| * @param {number} keyvalue |
| * @returns {RoaringBitmap} |
| */ |
| remove(keyvalue) { |
| const key = keyvalue >> 16; |
| const value = keyvalue & 0xFFFF; |
| const mid = this.getContainerId(key); |
| if (mid === -1) { |
| return this; |
| } |
| const container = this.containers[mid]; |
| if (!container.contains(value)) { |
| return this; |
| } |
| const newCardinality = (this.keysAndCardinalities[(mid * 4) + 2] | |
| (this.keysAndCardinalities[(mid * 4) + 3] << 8)); |
| const l = this.containers.length; |
| const m = l - (newCardinality === 0 ? 1 : 0); |
| const result = new RoaringBitmap(null, 0); |
| result.keysAndCardinalities = new Uint8Array(m * 4); |
| let j = 0; |
| for (let i = 0; i < l; i += 1) { |
| if (i === mid) { |
| if (newCardinality !== 0) { |
| result.keysAndCardinalities[(j * 4) + 0] = key; |
| result.keysAndCardinalities[(j * 4) + 1] = key >> 8; |
| const card = newCardinality - 1; |
| result.keysAndCardinalities[(j * 4) + 2] = card; |
| result.keysAndCardinalities[(j * 4) + 3] = card >> 8; |
| const newContainer = new RoaringBitmapArray( |
| newCardinality, |
| new Uint8Array(newCardinality * 2), |
| ); |
| let newContainerSlot = 0; |
| for (const containerValue of container.values()) { |
| if (containerValue !== value) { |
| newContainer.array[newContainerSlot] = value & 0xFF; |
| newContainerSlot += 1; |
| newContainer.array[newContainerSlot] = value >> 8; |
| newContainerSlot += 1; |
| } |
| } |
| result.containers.push(newContainer); |
| j += 1; |
| } |
| } else { |
| result.keysAndCardinalities[(j * 4) + 0] = this.keysAndCardinalities[(i * 4) + 0]; |
| result.keysAndCardinalities[(j * 4) + 1] = this.keysAndCardinalities[(i * 4) + 1]; |
| result.keysAndCardinalities[(j * 4) + 2] = this.keysAndCardinalities[(i * 4) + 2]; |
| result.keysAndCardinalities[(j * 4) + 3] = this.keysAndCardinalities[(i * 4) + 3]; |
| result.containers.push(this.containers[i]); |
| j += 1; |
| } |
| } |
| return result; |
| } |
| /** |
| * @param {number} key |
| * @returns {number} |
| */ |
| getContainerId(key) { |
| // Binary search algorithm copied from |
| // https://en.wikipedia.org/wiki/Binary_search#Procedure |
| // |
| // Format is required by specification to be sorted. |
| // Because keys are 16 bits and unique, length can't be |
| // bigger than 2**16, and because we have 32 bits of safe int, |
| // left + right can't overflow. |
| let left = 0; |
| let right = this.containers.length - 1; |
| while (left <= right) { |
| const mid = Math.floor((left + right) / 2); |
| const x = this.keysAndCardinalities[(mid * 4)] | |
| (this.keysAndCardinalities[(mid * 4) + 1] << 8); |
| if (x < key) { |
| left = mid + 1; |
| } else if (x > key) { |
| right = mid - 1; |
| } else { |
| return mid; |
| } |
| } |
| return -1; |
| } |
| * entries() { |
| const l = this.containers.length; |
| for (let i = 0; i < l; ++i) { |
| const key = this.keysAndCardinalities[i * 4] | |
| (this.keysAndCardinalities[(i * 4) + 1] << 8); |
| for (const value of this.containers[i].values()) { |
| yield (key << 16) | value; |
| } |
| } |
| } |
| /** |
| * @returns {number|null} |
| */ |
| first() { |
| for (const entry of this.entries()) { |
| return entry; |
| } |
| return null; |
| } |
| /** |
| * @returns {number} |
| */ |
| cardinality() { |
| let result = 0; |
| const l = this.containers.length; |
| for (let i = 0; i < l; ++i) { |
| const card = this.keysAndCardinalities[(i * 4) + 2] | |
| (this.keysAndCardinalities[(i * 4) + 3] << 8); |
| result += card + 1; |
| } |
| return result; |
| } |
| } |
| |
| class RoaringBitmapRun { |
| /** |
| * @param {number} runcount |
| * @param {Uint8Array} array |
| */ |
| constructor(runcount, array) { |
| this.runcount = runcount; |
| this.array = array; |
| } |
| /** @param {number} value */ |
| contains(value) { |
| // Binary search algorithm copied from |
| // https://en.wikipedia.org/wiki/Binary_search#Procedure |
| // |
| // Since runcount is stored as 16 bits, left + right |
| // can't overflow. |
| let left = 0; |
| let right = this.runcount - 1; |
| while (left <= right) { |
| const mid = (left + right) >> 1; |
| const i = mid * 4; |
| const start = this.array[i] | (this.array[i + 1] << 8); |
| const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8); |
| if ((start + lenm1) < value) { |
| left = mid + 1; |
| } else if (start > value) { |
| right = mid - 1; |
| } else { |
| return true; |
| } |
| } |
| return false; |
| } |
| * values() { |
| let i = 0; |
| while (i < this.runcount) { |
| const start = this.array[i * 4] | (this.array[(i * 4) + 1] << 8); |
| const lenm1 = this.array[(i * 4) + 2] | (this.array[(i * 4) + 3] << 8); |
| let value = start; |
| let j = 0; |
| while (j <= lenm1) { |
| yield value; |
| value += 1; |
| j += 1; |
| } |
| i += 1; |
| } |
| } |
| } |
| class RoaringBitmapArray { |
| /** |
| * @param {number} cardinality |
| * @param {Uint8Array} array |
| */ |
| constructor(cardinality, array) { |
| this.cardinality = cardinality; |
| this.array = array; |
| } |
| /** @param {number} value */ |
| contains(value) { |
| // Binary search algorithm copied from |
| // https://en.wikipedia.org/wiki/Binary_search#Procedure |
| // |
| // Since cardinality can't be higher than 4096, left + right |
| // cannot overflow. |
| let left = 0; |
| let right = this.cardinality - 1; |
| while (left <= right) { |
| const mid = (left + right) >> 1; |
| const i = mid * 2; |
| const x = this.array[i] | (this.array[i + 1] << 8); |
| if (x < value) { |
| left = mid + 1; |
| } else if (x > value) { |
| right = mid - 1; |
| } else { |
| return true; |
| } |
| } |
| return false; |
| } |
| /** @returns {Generator<number>} */ |
| * values() { |
| let i = 0; |
| const l = this.cardinality * 2; |
| while (i < l) { |
| yield this.array[i] | (this.array[i + 1] << 8); |
| i += 2; |
| } |
| } |
| } |
| class RoaringBitmapBits { |
| /** |
| * @param {Uint8Array} array |
| */ |
| constructor(array) { |
| this.array = array; |
| } |
| /** @param {number} value */ |
| contains(value) { |
| return !!(this.array[value >> 3] & (1 << (value & 7))); |
| } |
| * values() { |
| let i = 0; |
| const l = this.array.length << 3; |
| while (i < l) { |
| if (this.contains(i)) { |
| yield i; |
| } |
| i += 1; |
| } |
| } |
| } |
| |
| const EMPTY_BITMAP = new RoaringBitmap(null, 0); |
| EMPTY_BITMAP.consumed_len_bytes = 0; |
| const EMPTY_BITMAP1 = new RoaringBitmap(null, 0); |
| EMPTY_BITMAP1.consumed_len_bytes = 1; |
| const EVERYTHING_BITMAP = new RoaringBitmap(null, 0); |
| |
| /** |
| * A mapping from six byte nodeids to an arbitrary value. |
| * We don't just use `Map` because that requires double hashing. |
| * @template T |
| * @property {Uint8Array} keys |
| * @property {T[]} values |
| * @property {number} size |
| * @property {number} capacityClass |
| */ |
| class HashTable { |
| /** |
| * Construct an empty hash table. |
| */ |
| constructor() { |
| this.keys = EMPTY_UINT8; |
| /** @type {(T|undefined)[]} */ |
| this.values = []; |
| this.size = 0; |
| this.capacityClass = 0; |
| } |
| /** |
| * @returns {Generator<[Uint8Array, T]>} |
| */ |
| * entries() { |
| const keys = this.keys; |
| const values = this.values; |
| const l = this.values.length; |
| for (let i = 0; i < l; i += 1) { |
| const value = values[i]; |
| if (value !== undefined) { |
| yield [keys.subarray(i * 6, (i + 1) * 6), value]; |
| } |
| } |
| } |
| /** |
| * Add a value to the hash table. |
| * @param {Uint8Array} key |
| * @param {T} value |
| */ |
| set(key, value) { |
| // 90 % load factor |
| if (this.size * 10 >= this.values.length * 9) { |
| const keys = this.keys; |
| const values = this.values; |
| const l = values.length; |
| this.capacityClass += 1; |
| const capacity = 1 << this.capacityClass; |
| this.keys = new Uint8Array(capacity * 6); |
| this.values = []; |
| for (let i = 0; i < capacity; i += 1) { |
| this.values.push(undefined); |
| } |
| this.size = 0; |
| for (let i = 0; i < l; i += 1) { |
| const oldValue = values[i]; |
| if (oldValue !== undefined) { |
| this.setNoGrow(keys, i * 6, oldValue); |
| } |
| } |
| } |
| this.setNoGrow(key, 0, value); |
| } |
| /** |
| * @param {Uint8Array} key |
| * @param {number} start |
| * @param {T} value |
| */ |
| setNoGrow(key, start, value) { |
| const mask = ~(0xffffffff << this.capacityClass); |
| const keys = this.keys; |
| const values = this.values; |
| const l = 1 << this.capacityClass; |
| // because we know that our values are already hashed, |
| // just chop off the lower four bytes |
| let slot = ( |
| (key[start + 2] << 24) | |
| (key[start + 3] << 16) | |
| (key[start + 4] << 8) | |
| key[start + 5] |
| ) & mask; |
| for (let distance = 0; distance < l; ) { |
| const j = slot * 6; |
| const otherValue = values[slot]; |
| if (otherValue === undefined) { |
| values[slot] = value; |
| const keysStart = slot * 6; |
| keys[keysStart + 0] = key[start + 0]; |
| keys[keysStart + 1] = key[start + 1]; |
| keys[keysStart + 2] = key[start + 2]; |
| keys[keysStart + 3] = key[start + 3]; |
| keys[keysStart + 4] = key[start + 4]; |
| keys[keysStart + 5] = key[start + 5]; |
| this.size += 1; |
| break; |
| } else if ( |
| key[start + 0] === keys[j + 0] && |
| key[start + 1] === keys[j + 1] && |
| key[start + 2] === keys[j + 2] && |
| key[start + 3] === keys[j + 3] && |
| key[start + 4] === keys[j + 4] && |
| key[start + 5] === keys[j + 5] |
| ) { |
| values[slot] = value; |
| break; |
| } else { |
| const otherPreferredSlot = ( |
| (keys[j + 2] << 24) | (keys[j + 3] << 16) | |
| (keys[j + 4] << 8) | keys[j + 5] |
| ) & mask; |
| const otherDistance = otherPreferredSlot <= slot ? |
| slot - otherPreferredSlot : |
| (l - otherPreferredSlot) + slot; |
| if (distance > otherDistance) { |
| // if the other key is closer to its preferred slot than this one, |
| // then insert our node in its place and swap |
| // |
| // https://cglab.ca/~abeinges/blah/robinhood-part-1/ |
| const otherKey = keys.slice(j, j + 6); |
| values[slot] = value; |
| value = otherValue; |
| keys[j + 0] = key[start + 0]; |
| keys[j + 1] = key[start + 1]; |
| keys[j + 2] = key[start + 2]; |
| keys[j + 3] = key[start + 3]; |
| keys[j + 4] = key[start + 4]; |
| keys[j + 5] = key[start + 5]; |
| key = otherKey; |
| start = 0; |
| distance = otherDistance; |
| } |
| distance += 1; |
| slot = (slot + 1) & mask; |
| } |
| } |
| } |
| /** |
| * Retrieve a value |
| * @param {Uint8Array} key |
| * @returns {T|undefined} |
| */ |
| get(key) { |
| if (key.length !== 6) { |
| throw "invalid key"; |
| } |
| return this.getWithOffsetKey(key, 0); |
| } |
| /** |
| * Retrieve a value |
| * @param {Uint8Array} key |
| * @param {number} start |
| * @returns {T|undefined} |
| */ |
| getWithOffsetKey(key, start) { |
| const mask = ~(0xffffffff << this.capacityClass); |
| const keys = this.keys; |
| const values = this.values; |
| const l = 1 << this.capacityClass; |
| // because we know that our values are already hashed, |
| // just chop off the lower four bytes |
| let slot = ( |
| (key[start + 2] << 24) | |
| (key[start + 3] << 16) | |
| (key[start + 4] << 8) | |
| key[start + 5] |
| ) & mask; |
| for (let distance = 0; distance < l; distance += 1) { |
| const j = slot * 6; |
| const value = values[slot]; |
| if (value === undefined) { |
| break; |
| } else if ( |
| key[start + 0] === keys[j + 0] && |
| key[start + 1] === keys[j + 1] && |
| key[start + 2] === keys[j + 2] && |
| key[start + 3] === keys[j + 3] && |
| key[start + 4] === keys[j + 4] && |
| key[start + 5] === keys[j + 5] |
| ) { |
| return value; |
| } else { |
| const otherPreferredSlot = ( |
| (keys[j + 2] << 24) | (keys[j + 3] << 16) | |
| (keys[j + 4] << 8) | keys[j + 5] |
| ) & mask; |
| const otherDistance = otherPreferredSlot <= slot ? |
| slot - otherPreferredSlot : |
| (l - otherPreferredSlot) + slot; |
| if (distance > otherDistance) { |
| break; |
| } |
| } |
| slot = (slot + 1) & mask; |
| } |
| return undefined; |
| } |
| } |
| |
| /*eslint-disable */ |
| // ignore-tidy-linelength |
| /** <https://stackoverflow.com/questions/43122082/efficiently-count-the-number-of-bits-in-an-integer-in-javascript> |
| * @param {number} n |
| * @returns {number} |
| */ |
| function bitCount(n) { |
| n = (~~n) - ((n >> 1) & 0x55555555); |
| n = (n & 0x33333333) + ((n >> 2) & 0x33333333); |
| return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; |
| } |
| /*eslint-enable */ |
| |
| /** |
| * https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm |
| */ |
| class Uint8ArraySearchPattern { |
| /** @param {Uint8Array} needle */ |
| constructor(needle) { |
| this.needle = needle; |
| this.skipTable = []; |
| const m = needle.length; |
| for (let i = 0; i < 256; i += 1) { |
| this.skipTable.push(m); |
| } |
| for (let i = 0; i < m - 1; i += 1) { |
| this.skipTable[needle[i]] = m - 1 - i; |
| } |
| } |
| /** |
| * @param {Uint8Array} haystack |
| * @returns {boolean} |
| */ |
| matches(haystack) { |
| const needle = this.needle; |
| const skipTable = this.skipTable; |
| const m = needle.length; |
| const n = haystack.length; |
| |
| let skip = 0; |
| search: while (n - skip >= m) { |
| for (let i = m - 1; i >= 0; i -= 1) { |
| if (haystack[skip + i] !== needle[i]) { |
| skip += skipTable[haystack[skip + m - 1]]; |
| continue search; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * @param {stringdex.Hooks} hooks |
| * @returns {Promise<stringdex.Database>} |
| */ |
| function loadDatabase(hooks) { |
| /** @type {stringdex.Callbacks} */ |
| const callbacks = { |
| rr_: function(data) { |
| const dataObj = JSON.parse(data); |
| for (const colName of Object.keys(dataObj)) { |
| if (Object.hasOwn(dataObj[colName], "N")) { |
| const counts = []; |
| const countsstring = dataObj[colName]["N"]; |
| let i = 0; |
| const l = countsstring.length; |
| while (i < l) { |
| let n = 0; |
| let c = countsstring.charCodeAt(i); |
| while (c < 96) { // 96 = "`" |
| n = (n << 4) | (c & 0xF); |
| i += 1; |
| c = countsstring.charCodeAt(i); |
| } |
| n = (n << 4) | (c & 0xF); |
| counts.push(n); |
| i += 1; |
| } |
| registry.dataColumns.set(colName, new DataColumn( |
| counts, |
| makeUint8ArrayFromBase64(dataObj[colName]["H"]), |
| new RoaringBitmap(makeUint8ArrayFromBase64(dataObj[colName]["E"]), 0), |
| colName, |
| Object.hasOwn(dataObj[colName], "I") ? |
| makeSearchTreeFromBase64(dataObj[colName].I)[1] : |
| null, |
| )); |
| } |
| } |
| const cb = registry.searchTreeRootCallback; |
| if (cb) { |
| cb(null, new Database(registry.searchTreeRoots, registry.dataColumns)); |
| } |
| }, |
| err_rr_: function(err) { |
| const cb = registry.searchTreeRootCallback; |
| if (cb) { |
| cb(err, null); |
| } |
| }, |
| rd_: function(dataString) { |
| const l = dataString.length; |
| const data = new Uint8Array(l); |
| for (let i = 0; i < l; ++i) { |
| data[i] = dataString.charCodeAt(i); |
| } |
| loadColumnFromBytes(data); |
| }, |
| err_rd_: function(filename, err) { |
| const nodeid = makeUint8ArrayFromHex(filename); |
| const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid); |
| if (cb) { |
| cb(err, null); |
| } |
| }, |
| rb_: function(dataString64) { |
| loadColumnFromBytes(makeUint8ArrayFromBase64(dataString64)); |
| }, |
| err_rb_: function(filename, err) { |
| const nodeid = makeUint8ArrayFromHex(filename); |
| const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid); |
| if (cb) { |
| cb(err, null); |
| } |
| }, |
| rn_: function(inputBase64) { |
| const [nodeid, tree] = makeSearchTreeFromBase64(inputBase64); |
| const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid); |
| if (cb) { |
| cb(null, tree); |
| registry.searchTreeLoadPromiseCallbacks.set(nodeid, null); |
| } |
| }, |
| err_rn_: function(filename, err) { |
| const nodeid = makeUint8ArrayFromHex(filename); |
| const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid); |
| if (cb) { |
| cb(err, null); |
| } |
| }, |
| }; |
| |
| /** |
| * @type {{ |
| * searchTreeRoots: Map<string, SearchTree>; |
| * searchTreeLoadPromiseCallbacks: HashTable<(function(any, SearchTree?): any)|null>; |
| * searchTreePromises: HashTable<Promise<SearchTree>>; |
| * dataColumnLoadPromiseCallbacks: HashTable<function(any, Uint8Array[]?): any>; |
| * dataColumns: Map<string, DataColumn>; |
| * dataColumnsBuckets: HashTable<Promise<Uint8Array[]>>; |
| * searchTreeLoadByNodeID: function(Uint8Array): Promise<SearchTree>; |
| * searchTreeRootCallback?: function(any, Database?): any; |
| * dataLoadByNameAndHash: function(string, Uint8Array): Promise<Uint8Array[]>; |
| * }} |
| */ |
| const registry = { |
| searchTreeRoots: new Map(), |
| searchTreeLoadPromiseCallbacks: new HashTable(), |
| searchTreePromises: new HashTable(), |
| dataColumnLoadPromiseCallbacks: new HashTable(), |
| dataColumns: new Map(), |
| dataColumnsBuckets: new HashTable(), |
| searchTreeLoadByNodeID: function(nodeid) { |
| const existingPromise = registry.searchTreePromises.get(nodeid); |
| if (existingPromise) { |
| return existingPromise; |
| } |
| /** @type {Promise<SearchTree>} */ |
| let newPromise; |
| if ((nodeid[0] & 0x80) !== 0) { |
| const isWhole = (nodeid[0] & 0x40) !== 0; |
| let leaves; |
| if ((nodeid[0] & 0x10) !== 0) { |
| let id1 = (nodeid[2] << 8) | nodeid[3]; |
| if ((nodeid[0] & 0x20) !== 0) { |
| // when data is present, id1 can be up to 20 bits |
| id1 |= ((nodeid[1] & 0x0f) << 16); |
| } else { |
| // otherwise, we fit in 28 |
| id1 |= ((nodeid[0] & 0x0f) << 24) | (nodeid[1] << 16); |
| } |
| const id2 = id1 + ((nodeid[4] << 8) | nodeid[5]); |
| leaves = RoaringBitmap.makeSingleton(id1) |
| .union(RoaringBitmap.makeSingleton(id2)); |
| } else { |
| leaves = RoaringBitmap.makeSingleton( |
| (nodeid[2] << 24) | (nodeid[3] << 16) | |
| (nodeid[4] << 8) | nodeid[5], |
| ); |
| } |
| const data = (nodeid[0] & 0x20) !== 0 ? |
| Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : |
| EMPTY_UINT8; |
| newPromise = Promise.resolve(new PrefixSearchTree( |
| EMPTY_SEARCH_TREE_BRANCHES, |
| EMPTY_SEARCH_TREE_BRANCHES, |
| data, |
| isWhole ? leaves : EMPTY_BITMAP, |
| isWhole ? EMPTY_BITMAP : leaves, |
| )); |
| } else { |
| const hashHex = makeHexFromUint8Array(nodeid); |
| newPromise = new Promise((resolve, reject) => { |
| const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid); |
| if (cb) { |
| registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => { |
| cb(err, data); |
| if (data) { |
| resolve(data); |
| } else { |
| reject(err); |
| } |
| }); |
| } else { |
| registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => { |
| if (data) { |
| resolve(data); |
| } else { |
| reject(err); |
| } |
| }); |
| hooks.loadTreeByHash(hashHex); |
| } |
| }); |
| } |
| registry.searchTreePromises.set(nodeid, newPromise); |
| return newPromise; |
| }, |
| dataLoadByNameAndHash: function(name, hash) { |
| const existingBucket = registry.dataColumnsBuckets.get(hash); |
| if (existingBucket) { |
| return existingBucket; |
| } |
| const hashHex = makeHexFromUint8Array(hash); |
| /** @type {Promise<Uint8Array[]>} */ |
| const newBucket = new Promise((resolve, reject) => { |
| const cb = registry.dataColumnLoadPromiseCallbacks.get(hash); |
| if (cb) { |
| registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => { |
| cb(err, data); |
| if (data) { |
| resolve(data); |
| } else { |
| reject(err); |
| } |
| }); |
| } else { |
| registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => { |
| if (data) { |
| resolve(data); |
| } else { |
| reject(err); |
| } |
| }); |
| hooks.loadDataByNameAndHash(name, hashHex); |
| } |
| }); |
| registry.dataColumnsBuckets.set(hash, newBucket); |
| return newBucket; |
| }, |
| }; |
| |
| /** |
| * The set of child subtrees. |
| * @template ST |
| * @type {{ |
| * nodeids: Uint8Array, |
| * subtrees: Array<Promise<ST>|null>, |
| * }} |
| */ |
| class SearchTreeBranches { |
| /** |
| * Construct the subtree list with `length` nulls |
| * @param {number} length |
| * @param {Uint8Array} nodeids |
| */ |
| constructor(length, nodeids) { |
| this.nodeids = nodeids; |
| this.subtrees = []; |
| for (let i = 0; i < length; ++i) { |
| this.subtrees.push(null); |
| } |
| } |
| /** |
| * @param {number} i |
| * @returns {Uint8Array} |
| */ |
| getNodeID(i) { |
| return new Uint8Array( |
| this.nodeids.buffer, |
| this.nodeids.byteOffset + (i * 6), |
| 6, |
| ); |
| } |
| // https://github.com/microsoft/TypeScript/issues/17227 |
| /** @returns {Generator<[number, Promise<ST>|null]>} */ |
| entries() { |
| throw new Error(); |
| } |
| /** |
| * @param {number} _k |
| * @returns {number} |
| */ |
| getIndex(_k) { |
| throw new Error(); |
| } |
| /** |
| * @param {number} _i |
| * @returns {number} |
| */ |
| getKey(_i) { |
| throw new Error(); |
| } |
| /** |
| * @returns {Uint8Array} |
| */ |
| getKeys() { |
| throw new Error(); |
| } |
| } |
| |
| /** |
| * A sorted array of search tree branches. |
| * |
| * @template ST |
| * @extends SearchTreeBranches<ST> |
| * @type {{ |
| * keys: Uint8Array, |
| * nodeids: Uint8Array, |
| * subtrees: Array<Promise<ST>|null>, |
| * }} |
| */ |
| class SearchTreeBranchesArray extends SearchTreeBranches { |
| /** |
| * @param {Uint8Array} keys |
| * @param {Uint8Array} nodeids |
| */ |
| constructor(keys, nodeids) { |
| super(keys.length, nodeids); |
| this.keys = keys; |
| let i = 1; |
| while (i < this.keys.length) { |
| if (this.keys[i - 1] >= this.keys[i]) { |
| throw new Error("HERE"); |
| } |
| i += 1; |
| } |
| } |
| /** @returns {Generator<[number, Promise<ST>|null]>} */ |
| * entries() { |
| let i = 0; |
| const l = this.keys.length; |
| while (i < l) { |
| yield [this.keys[i], this.subtrees[i]]; |
| i += 1; |
| } |
| } |
| /** |
| * @param {number} k |
| * @returns {number} |
| */ |
| getIndex(k) { |
| // Since length can't be bigger than 256, |
| // left + right can't overflow. |
| let left = 0; |
| let right = this.keys.length - 1; |
| while (left <= right) { |
| const mid = (left + right) >> 1; |
| if (this.keys[mid] < k) { |
| left = mid + 1; |
| } else if (this.keys[mid] > k) { |
| right = mid - 1; |
| } else { |
| return mid; |
| } |
| } |
| return -1; |
| } |
| /** |
| * @param {number} i |
| * @returns {number} |
| */ |
| getKey(i) { |
| return this.keys[i]; |
| } |
| /** |
| * @returns {Uint8Array} |
| */ |
| getKeys() { |
| return this.keys; |
| } |
| } |
| |
| const EMPTY_SEARCH_TREE_BRANCHES = new SearchTreeBranchesArray( |
| EMPTY_UINT8, |
| EMPTY_UINT8, |
| ); |
| |
| /** @type {number[]} */ |
| const SHORT_ALPHABITMAP_CHARS = []; |
| for (let i = 0x61; i <= 0x7A; ++i) { |
| if (i === 0x76 || i === 0x71) { |
| // 24 entries, 26 letters, so we skip q and v |
| continue; |
| } |
| SHORT_ALPHABITMAP_CHARS.push(i); |
| } |
| |
| /** @type {number[]} */ |
| const LONG_ALPHABITMAP_CHARS = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36]; |
| for (let i = 0x61; i <= 0x7A; ++i) { |
| LONG_ALPHABITMAP_CHARS.push(i); |
| } |
| |
| /** |
| * @template ST |
| * @param {number[]} alphabitmap_chars |
| * @param {number} width |
| * @return {(typeof SearchTreeBranches<ST>)&{"ALPHABITMAP_CHARS": number[], "width": number}} |
| */ |
| function makeSearchTreeBranchesAlphaBitmapClass(alphabitmap_chars, width) { |
| const bitwidth = width * 8; |
| /** |
| * @extends SearchTreeBranches<ST> |
| */ |
| const cls = class SearchTreeBranchesAlphaBitmap extends SearchTreeBranches { |
| /** |
| * @param {number} bitmap |
| * @param {Uint8Array} nodeids |
| */ |
| constructor(bitmap, nodeids) { |
| super(nodeids.length / 6, nodeids); |
| if (nodeids.length / 6 !== bitCount(bitmap)) { |
| throw new Error(`mismatch ${bitmap} ${nodeids}`); |
| } |
| this.bitmap = bitmap; |
| this.nodeids = nodeids; |
| } |
| /** @returns {Generator<[number, Promise<ST>|null]>} */ |
| * entries() { |
| let i = 0; |
| let j = 0; |
| while (i < bitwidth) { |
| if (this.bitmap & (1 << i)) { |
| yield [alphabitmap_chars[i], this.subtrees[j]]; |
| j += 1; |
| } |
| i += 1; |
| } |
| } |
| /** |
| * @param {number} k |
| * @returns {number} |
| */ |
| getIndex(k) { |
| //return this.getKeys().indexOf(k); |
| const ix = alphabitmap_chars.indexOf(k); |
| if (ix < 0) { |
| return ix; |
| } |
| const result = bitCount(~(0xffffffff << ix) & this.bitmap); |
| return result >= this.subtrees.length ? -1 : result; |
| } |
| /** |
| * @param {number} branch_index |
| * @returns {number} |
| */ |
| getKey(branch_index) { |
| return this.getKeys()[branch_index]; |
| } |
| /** |
| * @returns {Uint8Array} |
| */ |
| getKeys() { |
| const length = bitCount(this.bitmap); |
| const result = new Uint8Array(length); |
| let result_index = 0; |
| for (let alpha_index = 0; alpha_index < bitwidth; ++alpha_index) { |
| if (this.bitmap & (1 << alpha_index)) { |
| result[result_index] = alphabitmap_chars[alpha_index]; |
| result_index += 1; |
| } |
| } |
| return result; |
| } |
| }; |
| cls.ALPHABITMAP_CHARS = alphabitmap_chars; |
| cls.width = width; |
| return cls; |
| } |
| |
| /** |
| * @template ST |
| * @type {(typeof SearchTreeBranches<any>)&{"ALPHABITMAP_CHARS": number[], "width": number}} |
| */ |
| const SearchTreeBranchesShortAlphaBitmap = |
| makeSearchTreeBranchesAlphaBitmapClass(SHORT_ALPHABITMAP_CHARS, 3); |
| |
| /** |
| * @template ST |
| * @type {(typeof SearchTreeBranches<any>)&{"ALPHABITMAP_CHARS": number[], "width": number}} |
| */ |
| const SearchTreeBranchesLongAlphaBitmap = |
| makeSearchTreeBranchesAlphaBitmapClass(LONG_ALPHABITMAP_CHARS, 4); |
| |
| /** |
| * @typedef {PrefixSearchTree|SuffixSearchTree} SearchTree |
| * @typedef {PrefixTrie|SuffixTrie} Trie |
| */ |
| |
| /** |
| * An interleaved [prefix] and [suffix tree], |
| * used for name-based search. |
| * |
| * This data structure is used to drive prefix matches, |
| * such as matching the query "link" to `LinkedList`, |
| * and Lev-distance matches, such as matching the |
| * query "hahsmap" to `HashMap`. |
| * |
| * [prefix tree]: https://en.wikipedia.org/wiki/Prefix_tree |
| * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree |
| * |
| * branches |
| * : A sorted-array map of subtrees. |
| * |
| * data |
| * : The substring represented by this node. The root node |
| * is always empty. |
| * |
| * leaves_suffix |
| * : The IDs of every entry that matches. Levenshtein matches |
| * won't include these. |
| * |
| * leaves_whole |
| * : The IDs of every entry that matches exactly. Levenshtein matches |
| * will include these. |
| * |
| * @type {{ |
| * might_have_prefix_branches: SearchTreeBranches<SearchTree>, |
| * branches: SearchTreeBranches<SearchTree>, |
| * data: Uint8Array, |
| * leaves_suffix: RoaringBitmap, |
| * leaves_whole: RoaringBitmap, |
| * }} |
| */ |
| class PrefixSearchTree { |
| /** |
| * @param {SearchTreeBranches<SearchTree>} branches |
| * @param {SearchTreeBranches<SearchTree>} might_have_prefix_branches |
| * @param {Uint8Array} data |
| * @param {RoaringBitmap} leaves_whole |
| * @param {RoaringBitmap} leaves_suffix |
| */ |
| constructor( |
| branches, |
| might_have_prefix_branches, |
| data, |
| leaves_whole, |
| leaves_suffix, |
| ) { |
| this.might_have_prefix_branches = might_have_prefix_branches; |
| this.branches = branches; |
| this.data = data; |
| this.leaves_suffix = leaves_suffix; |
| this.leaves_whole = leaves_whole; |
| } |
| /** |
| * Returns the Trie for the root node. |
| * |
| * A Trie pointer refers to a single node in a logical decompressed search tree |
| * (the real search tree is compressed). |
| * |
| * @param {DataColumn} dataColumn |
| * @param {Uint8ArraySearchPattern} searchPattern |
| * @return {PrefixTrie} |
| */ |
| trie(dataColumn, searchPattern) { |
| return new PrefixTrie(this, 0, dataColumn, searchPattern); |
| } |
| |
| /** |
| * Return the trie representing `name` |
| * @param {Uint8Array|string} name |
| * @param {DataColumn} dataColumn |
| * @returns {Promise<Trie?>} |
| */ |
| async search(name, dataColumn) { |
| if (typeof name === "string") { |
| const utf8encoder = new TextEncoder(); |
| name = utf8encoder.encode(name); |
| } |
| const searchPattern = new Uint8ArraySearchPattern(name); |
| /** @type {Trie} */ |
| let trie = this.trie(dataColumn, searchPattern); |
| for (const datum of name) { |
| // code point definitely exists |
| /** @type {Promise<Trie>?} */ |
| const newTrie = trie.child(datum); |
| if (newTrie) { |
| trie = await newTrie; |
| } else { |
| return null; |
| } |
| } |
| return trie; |
| } |
| |
| /** |
| * @param {Uint8Array|string} name |
| * @param {DataColumn} dataColumn |
| * @returns {AsyncGenerator<Trie>} |
| */ |
| async* searchLev(name, dataColumn) { |
| if (typeof name === "string") { |
| const utf8encoder = new TextEncoder(); |
| name = utf8encoder.encode(name); |
| } |
| const w = name.length; |
| if (w < 3) { |
| const trie = await this.search(name, dataColumn); |
| if (trie !== null) { |
| yield trie; |
| } |
| return; |
| } |
| const searchPattern = new Uint8ArraySearchPattern(name); |
| const levParams = w >= 6 ? |
| new Lev2TParametricDescription(w) : |
| new Lev1TParametricDescription(w); |
| /** @type {Array<[Promise<Trie>, number]>} */ |
| const stack = [[Promise.resolve(this.trie(dataColumn, searchPattern)), 0]]; |
| const n = levParams.n; |
| while (stack.length !== 0) { |
| // It's not empty |
| /** @type {[Promise<Trie>, number]} */ |
| //@ts-expect-error |
| const [triePromise, levState] = stack.pop(); |
| const trie = await triePromise; |
| for (const byte of trie.keysExcludeSuffixOnly()) { |
| const levPos = levParams.getPosition(levState); |
| const vector = levParams.getVector( |
| name, |
| byte, |
| levPos, |
| Math.min(w, levPos + (2 * n) + 1), |
| ); |
| const newLevState = levParams.transition( |
| levState, |
| levPos, |
| vector, |
| ); |
| if (newLevState >= 0) { |
| const child = trie.child(byte); |
| if (child) { |
| stack.push([child, newLevState]); |
| if (levParams.isAccept(newLevState)) { |
| yield child; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** @returns {RoaringBitmap} */ |
| getCurrentLeaves() { |
| return this.leaves_whole.union(this.leaves_suffix); |
| } |
| } |
| |
| /** |
| * A representation of a set of strings in the search index, |
| * as a subset of the entire tree. |
| */ |
| class PrefixTrie { |
| /** |
| * @param {PrefixSearchTree} tree |
| * @param {number} offset |
| * @param {DataColumn} dataColumn |
| * @param {Uint8ArraySearchPattern} searchPattern |
| */ |
| constructor(tree, offset, dataColumn, searchPattern) { |
| this.tree = tree; |
| this.offset = offset; |
| this.dataColumn = dataColumn; |
| this.searchPattern = searchPattern; |
| } |
| |
| /** |
| * All exact matches for the string represented by this node. |
| * @returns {RoaringBitmap} |
| */ |
| matches() { |
| if (this.offset === this.tree.data.length) { |
| return this.tree.leaves_whole; |
| } else { |
| return EMPTY_BITMAP; |
| } |
| } |
| |
| /** |
| * All matches for strings that contain the string represented by this node. |
| * @returns {AsyncGenerator<RoaringBitmap>} |
| */ |
| async* substringMatches() { |
| /** @type {Promise<SearchTree>[]} */ |
| let layer = [Promise.resolve(this.tree)]; |
| while (layer.length) { |
| const current_layer = layer; |
| layer = []; |
| for await (const tree of current_layer) { |
| /** @type {number[]?} */ |
| let rejected = null; |
| let leaves = tree.getCurrentLeaves(); |
| for (const leaf of leaves.entries()) { |
| const haystack = await this.dataColumn.at(leaf); |
| if (haystack === undefined || !this.searchPattern.matches(haystack)) { |
| if (!rejected) { |
| rejected = []; |
| } |
| rejected.push(leaf); |
| } |
| } |
| if (rejected) { |
| if (leaves.cardinality() !== rejected.length) { |
| for (const rej of rejected) { |
| leaves = leaves.remove(rej); |
| } |
| yield leaves; |
| } |
| } else { |
| yield leaves; |
| } |
| } |
| /** @type {HashTable<[number, SearchTree][]>} */ |
| const subnodes = new HashTable(); |
| for await (const node of current_layer) { |
| const branches = node.branches; |
| const l = branches.subtrees.length; |
| for (let i = 0; i < l; ++i) { |
| const subtree = branches.subtrees[i]; |
| if (subtree) { |
| layer.push(subtree); |
| } else if (subtree === null) { |
| const byte = branches.getKey(i); |
| const newnode = branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no node for key ${byte}`); |
| } else { |
| let subnode_list = subnodes.get(newnode); |
| if (!subnode_list) { |
| subnode_list = [[byte, node]]; |
| subnodes.set(newnode, subnode_list); |
| } else { |
| subnode_list.push([byte, node]); |
| } |
| } |
| } else { |
| throw new Error(`malformed tree; index ${i} does not exist`); |
| } |
| } |
| } |
| for (const [newnode, subnode_list] of subnodes.entries()) { |
| const res = registry.searchTreeLoadByNodeID(newnode); |
| for (const [byte, node] of subnode_list) { |
| const branches = node.branches; |
| const i = branches.getIndex(byte); |
| branches.subtrees[i] = res; |
| if (node instanceof PrefixSearchTree) { |
| const might_have_prefix_branches = node.might_have_prefix_branches; |
| const mhpI = might_have_prefix_branches.getIndex(byte); |
| if (mhpI !== -1) { |
| might_have_prefix_branches.subtrees[mhpI] = res; |
| } |
| } |
| } |
| layer.push(res); |
| } |
| } |
| } |
| |
| /** |
| * All matches for strings that start with the string represented by this node. |
| * @returns {AsyncGenerator<RoaringBitmap>} |
| */ |
| async* prefixMatches() { |
| /** @type {{node: Promise<SearchTree>, len: number}[]} */ |
| let layer = [{node: Promise.resolve(this.tree), len: 0}]; |
| // https://en.wikipedia.org/wiki/Heap_(data_structure)#Implementation_using_arrays |
| /** @type {{bitmap: RoaringBitmap, length: number}[]} */ |
| const backlog = []; |
| while (layer.length !== 0 || backlog.length !== 0) { |
| const current_layer = layer; |
| layer = []; |
| let minLength = null; |
| // push every entry in the current layer into the backlog, |
| // a min-heap of result entries |
| // we then yield the smallest ones (can't yield bigger ones |
| // if we want to do them in order) |
| for (const {node, len} of current_layer) { |
| const tree = await node; |
| if (!(tree instanceof PrefixSearchTree)) { |
| continue; |
| } |
| const length = len + tree.data.length; |
| if (minLength === null || length < minLength) { |
| minLength = length; |
| } |
| let backlogSlot = backlog.length; |
| backlog.push({bitmap: tree.leaves_whole, length}); |
| while (backlogSlot > 0 && |
| backlog[backlogSlot].length < backlog[(backlogSlot - 1) >> 1].length |
| ) { |
| const parentSlot = (backlogSlot - 1) >> 1; |
| const parent = backlog[parentSlot]; |
| backlog[parentSlot] = backlog[backlogSlot]; |
| backlog[backlogSlot] = parent; |
| backlogSlot = parentSlot; |
| } |
| } |
| // yield nodes in length order, smallest one first |
| // we know that, whatever the smallest item is |
| // every child will be bigger than that |
| while (backlog.length !== 0) { |
| const backlogEntry = backlog[0]; |
| if (minLength !== null && backlogEntry.length > minLength) { |
| break; |
| } |
| if (!backlogEntry.bitmap.isEmpty()) { |
| yield backlogEntry.bitmap; |
| } |
| backlog[0] = backlog[backlog.length - 1]; |
| backlog.length -= 1; |
| let backlogSlot = 0; |
| const backlogLength = backlog.length; |
| while (backlogSlot < backlogLength) { |
| const leftSlot = (backlogSlot << 1) + 1; |
| const rightSlot = (backlogSlot << 1) + 2; |
| let smallest = backlogSlot; |
| if (leftSlot < backlogLength && |
| backlog[leftSlot].length < backlog[smallest].length |
| ) { |
| smallest = leftSlot; |
| } |
| if (rightSlot < backlogLength && |
| backlog[rightSlot].length < backlog[smallest].length |
| ) { |
| smallest = rightSlot; |
| } |
| if (smallest === backlogSlot) { |
| break; |
| } else { |
| const tmp = backlog[backlogSlot]; |
| backlog[backlogSlot] = backlog[smallest]; |
| backlog[smallest] = tmp; |
| backlogSlot = smallest; |
| } |
| } |
| } |
| // if we still have more subtrees to walk, then keep going |
| /** @type {HashTable<{byte: number, tree: PrefixSearchTree, len: number}[]>} */ |
| const subnodes = new HashTable(); |
| for await (const {node, len} of current_layer) { |
| const tree = await node; |
| if (!(tree instanceof PrefixSearchTree)) { |
| continue; |
| } |
| const length = len + tree.data.length; |
| const mhp_branches = tree.might_have_prefix_branches; |
| const l = mhp_branches.subtrees.length; |
| for (let i = 0; i < l; ++i) { |
| const len = length + 1; |
| const subtree = mhp_branches.subtrees[i]; |
| if (subtree) { |
| layer.push({node: subtree, len}); |
| } else if (subtree === null) { |
| const byte = mhp_branches.getKey(i); |
| const newnode = mhp_branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no node for key ${byte}`); |
| } else { |
| let subnode_list = subnodes.get(newnode); |
| if (!subnode_list) { |
| subnode_list = [{byte, tree, len}]; |
| subnodes.set(newnode, subnode_list); |
| } else { |
| subnode_list.push({byte, tree, len}); |
| } |
| } |
| } |
| } |
| } |
| for (const [newnode, subnode_list] of subnodes.entries()) { |
| const res = registry.searchTreeLoadByNodeID(newnode); |
| let len = Number.MAX_SAFE_INTEGER; |
| for (const {byte, tree, len: subtreelen} of subnode_list) { |
| if (subtreelen < len) { |
| len = subtreelen; |
| } |
| const mhp_branches = tree.might_have_prefix_branches; |
| const i = mhp_branches.getIndex(byte); |
| mhp_branches.subtrees[i] = res; |
| const branches = tree.branches; |
| const bi = branches.getIndex(byte); |
| branches.subtrees[bi] = res; |
| } |
| layer.push({node: res, len}); |
| } |
| } |
| } |
| |
| /** |
| * Returns all keys that are children of this node. |
| * @returns {Uint8Array} |
| */ |
| keys() { |
| const data = this.tree.data; |
| if (this.offset === data.length) { |
| return this.tree.branches.getKeys(); |
| } else { |
| return Uint8Array.of(data[this.offset]); |
| } |
| } |
| |
| /** |
| * Returns all nodes that are direct children of this node. |
| * @returns {[number, Promise<Trie>][]} |
| */ |
| children() { |
| const data = this.tree.data; |
| if (this.offset === data.length) { |
| /** @type {[number, Promise<Trie>][]} */ |
| const nodes = []; |
| let i = 0; |
| for (const [k, v] of this.tree.branches.entries()) { |
| /** @type {Promise<SearchTree>} */ |
| let node; |
| if (v) { |
| node = v; |
| } else { |
| const newnode = this.tree.branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no hash for key ${k}: ${newnode} \ |
| ${this.tree.branches.nodeids} ${this.tree.branches.getKeys()}`); |
| } |
| node = registry.searchTreeLoadByNodeID(newnode); |
| this.tree.branches.subtrees[i] = node; |
| const mhpI = this.tree.might_have_prefix_branches.getIndex(k); |
| if (mhpI !== -1) { |
| this.tree.might_have_prefix_branches.subtrees[mhpI] = node; |
| } |
| } |
| nodes.push([k, node.then(node => { |
| return node.trie(this.dataColumn, this.searchPattern); |
| })]); |
| i += 1; |
| } |
| return nodes; |
| } else { |
| /** @type {number} */ |
| const codePoint = data[this.offset]; |
| const trie = new PrefixTrie( |
| this.tree, |
| this.offset + 1, |
| this.dataColumn, |
| this.searchPattern, |
| ); |
| return [[codePoint, Promise.resolve(trie)]]; |
| } |
| } |
| |
| /** |
| * Returns all keys that are children of this node. |
| * @returns {Uint8Array} |
| */ |
| keysExcludeSuffixOnly() { |
| const data = this.tree.data; |
| if (this.offset === data.length) { |
| return this.tree.might_have_prefix_branches.getKeys(); |
| } else { |
| return Uint8Array.of(data[this.offset]); |
| } |
| } |
| |
| /** |
| * Returns all nodes that are direct children of this node. |
| * @returns {[number, Promise<Trie>][]} |
| */ |
| childrenExcludeSuffixOnly() { |
| const data = this.tree.data; |
| if (this.offset === data.length) { |
| /** @type {[number, Promise<Trie>][]} */ |
| const nodes = []; |
| let i = 0; |
| for (const [k, v] of this.tree.might_have_prefix_branches.entries()) { |
| /** @type {Promise<SearchTree>} */ |
| let node; |
| if (v) { |
| node = v; |
| } else { |
| const newnode = this.tree.might_have_prefix_branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no node for key ${k}`); |
| } |
| node = registry.searchTreeLoadByNodeID(newnode); |
| this.tree.might_have_prefix_branches.subtrees[i] = node; |
| this.tree.branches.subtrees[this.tree.branches.getIndex(k)] = node; |
| } |
| nodes.push([k, node.then(node => { |
| return node.trie(this.dataColumn, this.searchPattern); |
| })]); |
| i += 1; |
| } |
| return nodes; |
| } else { |
| /** @type {number} */ |
| const codePoint = data[this.offset]; |
| const trie = new PrefixTrie( |
| this.tree, |
| this.offset + 1, |
| this.dataColumn, |
| this.searchPattern, |
| ); |
| return [[codePoint, Promise.resolve(trie)]]; |
| } |
| } |
| |
| /** |
| * Returns a single node that is a direct child of this node. |
| * @param {number} byte |
| * @returns {Promise<Trie>?} |
| */ |
| child(byte) { |
| if (this.offset === this.tree.data.length) { |
| const i = this.tree.branches.getIndex(byte); |
| if (i !== -1) { |
| let branch = this.tree.branches.subtrees[i]; |
| if (branch === null) { |
| const newnode = this.tree.branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no node for key ${byte}`); |
| } |
| branch = registry.searchTreeLoadByNodeID(newnode); |
| this.tree.branches.subtrees[i] = branch; |
| const mhpI = this.tree.might_have_prefix_branches.getIndex(byte); |
| if (mhpI !== -1) { |
| this.tree.might_have_prefix_branches.subtrees[mhpI] = branch; |
| } |
| } |
| return branch.then(branch => branch.trie(this.dataColumn, this.searchPattern)); |
| } |
| } else if (this.tree.data[this.offset] === byte) { |
| return Promise.resolve(new PrefixTrie( |
| this.tree, |
| this.offset + 1, |
| this.dataColumn, |
| this.searchPattern, |
| )); |
| } |
| return null; |
| } |
| } |
| /** |
| * A [suffix tree], used for name-based search. |
| * |
| * This data structure is used to drive substring matches, |
| * such as matching the query "inked" to `LinkedList`. |
| * |
| * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree |
| * |
| * Suffix trees do not actually carry the intermediate data |
| * between branches, so, in order to validate the results, |
| * they must go through a filtering step at the end. |
| * Suffix trees also cannot match lev and exact matches, |
| * so those just return empty sets. |
| * |
| * branches |
| * : A sorted-array map of subtrees. |
| * |
| * dataLen |
| * : The length of the substring used by this node. |
| * |
| * leaves_suffix |
| * : The IDs of every entry that matches. Levenshtein matches |
| * won't include these. |
| * |
| * @type {{ |
| * branches: SearchTreeBranches<SearchTree>, |
| * dataLen: number, |
| * leaves_suffix: RoaringBitmap, |
| * }} |
| */ |
| class SuffixSearchTree { |
| /** |
| * @param {SearchTreeBranches<SearchTree>} branches |
| * @param {number} dataLen |
| * @param {RoaringBitmap} leaves_suffix |
| */ |
| constructor( |
| branches, |
| dataLen, |
| leaves_suffix, |
| ) { |
| this.branches = branches; |
| this.dataLen = dataLen; |
| this.leaves_suffix = leaves_suffix; |
| } |
| /** |
| * Returns the Trie for the root node. |
| * |
| * A Trie pointer refers to a single node in a logical decompressed search tree |
| * (the real search tree is compressed). |
| * |
| * @param {DataColumn} dataColumn |
| * @param {Uint8ArraySearchPattern} searchPattern |
| * @return {Trie} |
| */ |
| trie(dataColumn, searchPattern) { |
| return new SuffixTrie(this, 0, dataColumn, searchPattern); |
| } |
| |
| /** |
| * Return the trie representing `name` |
| * @param {Uint8Array|string} name |
| * @param {DataColumn} dataColumn |
| * @returns {Promise<Trie?>} |
| */ |
| async search(name, dataColumn) { |
| if (typeof name === "string") { |
| const utf8encoder = new TextEncoder(); |
| name = utf8encoder.encode(name); |
| } |
| const searchPattern = new Uint8ArraySearchPattern(name); |
| let trie = this.trie(dataColumn, searchPattern); |
| for (const datum of name) { |
| // code point definitely exists |
| const newTrie = trie.child(datum); |
| if (newTrie) { |
| trie = await newTrie; |
| } else { |
| return null; |
| } |
| } |
| return trie; |
| } |
| |
| /** |
| * @param {Uint8Array|string} _name |
| * @param {DataColumn} _dataColumn |
| * @returns {AsyncGenerator<Trie>} |
| */ |
| async* searchLev(_name, _dataColumn) { |
| // this function only returns whole-string matches, |
| // which pure-suffix nodes don't have, so is |
| // intentionally blank |
| } |
| |
| /** @returns {RoaringBitmap} */ |
| getCurrentLeaves() { |
| return this.leaves_suffix; |
| } |
| } |
| |
| /** |
| * A representation of a set of strings in the search index, |
| * as a subset of the entire tree (suffix-only). |
| */ |
| class SuffixTrie { |
| /** |
| * @param {SuffixSearchTree} tree |
| * @param {number} offset |
| * @param {DataColumn} dataColumn |
| * @param {Uint8ArraySearchPattern} searchPattern |
| */ |
| constructor(tree, offset, dataColumn, searchPattern) { |
| this.tree = tree; |
| this.offset = offset; |
| this.dataColumn = dataColumn; |
| this.searchPattern = searchPattern; |
| } |
| |
| /** |
| * All exact matches for the string represented by this node. |
| * Since pure-suffix nodes have no exactly-matching children, |
| * this function returns the empty bitmap. |
| * @returns {RoaringBitmap} |
| */ |
| matches() { |
| return EMPTY_BITMAP; |
| } |
| |
| /** |
| * All matches for strings that contain the string represented by this node. |
| * @returns {AsyncGenerator<RoaringBitmap>} |
| */ |
| async* substringMatches() { |
| /** @type {Promise<SearchTree>[]} */ |
| let layer = [Promise.resolve(this.tree)]; |
| while (layer.length) { |
| const current_layer = layer; |
| layer = []; |
| for await (const tree of current_layer) { |
| /** @type {number[]?} */ |
| let rejected = null; |
| let leaves = tree.getCurrentLeaves(); |
| for (const leaf of leaves.entries()) { |
| const haystack = await this.dataColumn.at(leaf); |
| if (haystack === undefined || !this.searchPattern.matches(haystack)) { |
| if (!rejected) { |
| rejected = []; |
| } |
| rejected.push(leaf); |
| } |
| } |
| if (rejected) { |
| if (leaves.cardinality() !== rejected.length) { |
| for (const rej of rejected) { |
| leaves = leaves.remove(rej); |
| } |
| yield leaves; |
| } |
| } else { |
| yield leaves; |
| } |
| } |
| /** @type {HashTable<[number, SearchTree][]>} */ |
| const subnodes = new HashTable(); |
| for await (const node of current_layer) { |
| const branches = node.branches; |
| const l = branches.subtrees.length; |
| for (let i = 0; i < l; ++i) { |
| const subtree = branches.subtrees[i]; |
| if (subtree) { |
| layer.push(subtree); |
| } else if (subtree === null) { |
| const newnode = branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no node for index ${i}`); |
| } else { |
| let subnode_list = subnodes.get(newnode); |
| if (!subnode_list) { |
| subnode_list = [[i, node]]; |
| subnodes.set(newnode, subnode_list); |
| } else { |
| subnode_list.push([i, node]); |
| } |
| } |
| } else { |
| throw new Error(`malformed tree; index ${i} does not exist`); |
| } |
| } |
| } |
| for (const [newnode, subnode_list] of subnodes.entries()) { |
| const res = registry.searchTreeLoadByNodeID(newnode); |
| for (const [i, node] of subnode_list) { |
| const branches = node.branches; |
| branches.subtrees[i] = res; |
| } |
| layer.push(res); |
| } |
| } |
| } |
| |
| /** |
| * All matches for strings that start with the string represented by this node. |
| * Since this is a pure-suffix node, there aren't any. |
| * @returns {AsyncGenerator<RoaringBitmap>} |
| */ |
| async* prefixMatches() { |
| // this function only returns prefix matches, |
| // which pure-suffix nodes don't have, so is |
| // intentionally blank |
| } |
| |
| /** |
| * Returns all keys that are children of this node. |
| * @returns {Uint8Array} |
| */ |
| keysExcludeSuffixOnly() { |
| return EMPTY_UINT8; |
| } |
| |
| /** |
| * Returns all nodes that are direct children of this node. |
| * @returns {[number, Promise<Trie>][]} |
| */ |
| childrenExcludeSuffixOnly() { |
| return []; |
| } |
| |
| /** |
| * Returns a single node that is a direct child of this node. |
| * @param {number} byte |
| * @returns {Promise<Trie>?} |
| */ |
| child(byte) { |
| if (this.offset === this.tree.dataLen) { |
| const i = this.tree.branches.getIndex(byte); |
| if (i !== -1) { |
| /** @type {Promise<SearchTree>?} */ |
| let branch = this.tree.branches.subtrees[i]; |
| if (branch === null) { |
| const newnode = this.tree.branches.getNodeID(i); |
| if (!newnode) { |
| throw new Error(`malformed tree; no node for key ${byte}`); |
| } |
| branch = registry.searchTreeLoadByNodeID(newnode); |
| this.tree.branches.subtrees[i] = branch; |
| } |
| return branch.then(branch => branch.trie(this.dataColumn, this.searchPattern)); |
| } |
| } else { |
| return Promise.resolve(new SuffixTrie( |
| this.tree, |
| this.offset + 1, |
| this.dataColumn, |
| this.searchPattern, |
| )); |
| } |
| return null; |
| } |
| } |
| |
| class DataColumn { |
| /** |
| * Construct the wrapper object for a data column. |
| * @param {number[]} counts |
| * @param {Uint8Array} hashes |
| * @param {RoaringBitmap} emptyset |
| * @param {string} name |
| * @param {SearchTree?} searchTree |
| */ |
| constructor(counts, hashes, emptyset, name, searchTree) { |
| this.searchTree = searchTree; |
| this.hashes = hashes; |
| this.emptyset = emptyset; |
| this.name = name; |
| /** @type {{"hash": Uint8Array, "data": Uint8Array[]?, "end": number}[]} */ |
| this.buckets = []; |
| this.bucket_keys = []; |
| const l = counts.length; |
| let k = 0; |
| let totalLength = 0; |
| for (let i = 0; i < l; ++i) { |
| const count = counts[i]; |
| totalLength += count; |
| const start = k; |
| for (let j = 0; j < count; ++j) { |
| if (emptyset.contains(k)) { |
| j -= 1; |
| } |
| k += 1; |
| } |
| const end = k; |
| const bucket = {hash: hashes.subarray(i * 6, (i + 1) * 6), data: null, end, count}; |
| this.buckets.push(bucket); |
| this.bucket_keys.push(start); |
| } |
| this.length = totalLength; |
| } |
| /** |
| * Check if a cell contains the empty string. |
| * @param {number} id |
| * @returns {boolean} |
| */ |
| isEmpty(id) { |
| return this.emptyset.contains(id); |
| } |
| /** |
| * Look up a cell by row ID. |
| * @param {number} id |
| * @returns {Promise<Uint8Array>|Uint8Array|undefined} |
| */ |
| at(id) { |
| if (this.emptyset.contains(id)) { |
| return EMPTY_UINT8; |
| } else { |
| let idx = -1; |
| while (this.bucket_keys[idx + 1] <= id) { |
| idx += 1; |
| } |
| if (idx === -1 || idx >= this.bucket_keys.length) { |
| return undefined; |
| } else { |
| const start = this.bucket_keys[idx]; |
| const bucket = this.buckets[idx]; |
| const data = this.buckets[idx].data; |
| if (data === null) { |
| return this.atAsyncFetch(id, start, bucket); |
| } else { |
| return data[id - start]; |
| } |
| } |
| } |
| } |
| /** |
| * Look up a cell by row ID. |
| * @param {number} id |
| * @param {number} start |
| * @param {{hash: Uint8Array, data: Uint8Array[] | null, end: number}} bucket |
| * @returns {Promise<Uint8Array>} |
| */ |
| async atAsyncFetch(id, start, bucket) { |
| const {hash, end} = bucket; |
| const dataSansEmptysetOrig = await registry.dataLoadByNameAndHash( |
| this.name, |
| hash, |
| ); |
| // After the `await` resolves, another task might fill |
| // in the data. If so, we should use that. |
| let data = bucket.data; |
| if (data !== null) { |
| return data[id - start]; |
| } |
| const dataSansEmptyset = [...dataSansEmptysetOrig]; |
| /** @type {(Uint8Array[])|null} */ |
| let dataWithEmptyset = null; |
| let pos = start; |
| let insertCount = 0; |
| while (pos < end) { |
| if (this.emptyset.contains(pos)) { |
| if (dataWithEmptyset === null) { |
| dataWithEmptyset = dataSansEmptyset.splice(0, insertCount); |
| } else if (insertCount !== 0) { |
| dataWithEmptyset.push( |
| ...dataSansEmptyset.splice(0, insertCount), |
| ); |
| } |
| insertCount = 0; |
| dataWithEmptyset.push(EMPTY_UINT8); |
| } else { |
| insertCount += 1; |
| } |
| pos += 1; |
| } |
| data = dataWithEmptyset === null ? |
| dataSansEmptyset : |
| dataWithEmptyset.concat(dataSansEmptyset); |
| bucket.data = data; |
| return data[id - start]; |
| } |
| /** |
| * Search by exact substring |
| * @param {Uint8Array|string} name |
| * @returns {Promise<Trie?>} |
| */ |
| async search(name) { |
| return await (this.searchTree ? this.searchTree.search(name, this) : null); |
| } |
| /** |
| * Search by whole, inexact match |
| * @param {Uint8Array|string} name |
| * @returns {AsyncGenerator<Trie>} |
| */ |
| async *searchLev(name) { |
| if (this.searchTree) { |
| yield* this.searchTree.searchLev(name, this); |
| } |
| } |
| } |
| |
| class Database { |
| /** |
| * The primary frontend for accessing data in this index. |
| * |
| * @param {Map<string, SearchTree>} searchTreeRoots |
| * @param {Map<string, DataColumn>} dataColumns |
| */ |
| constructor(searchTreeRoots, dataColumns) { |
| this.searchTreeRoots = searchTreeRoots; |
| this.dataColumns = dataColumns; |
| } |
| /** |
| * Search a column by name, returning verbatim matched IDs. |
| * @param {string} colname |
| * @returns {SearchTree|undefined} |
| */ |
| getIndex(colname) { |
| return this.searchTreeRoots.get(colname); |
| } |
| /** |
| * Look up a cell by column ID and row ID. |
| * @param {string} colname |
| * @returns {DataColumn|undefined} |
| */ |
| getData(colname) { |
| return this.dataColumns.get(colname); |
| } |
| } |
| |
| /** |
| * Load a data column. |
| * @param {Uint8Array} data |
| */ |
| function loadColumnFromBytes(data) { |
| const hashBuf = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0); |
| const truncatedHash = hashBuf.subarray(2, 8); |
| siphashOfBytes(data, 0, 0, 0, 0, hashBuf); |
| const cb = registry.dataColumnLoadPromiseCallbacks.get(truncatedHash); |
| if (cb) { |
| const backrefs = []; |
| const dataSansEmptyset = []; |
| let i = 0; |
| const l = data.length; |
| while (i < l) { |
| let c = data[i]; |
| if (c >= 48 && c <= 63) { // 48 = "0", 63 = "?" |
| dataSansEmptyset.push(backrefs[c - 48]); |
| i += 1; |
| } else { |
| let n = 0; |
| while (c < 96) { // 96 = "`" |
| n = (n << 4) | (c & 0xF); |
| i += 1; |
| c = data[i]; |
| } |
| n = (n << 4) | (c & 0xF); |
| i += 1; |
| const item = data.subarray(i, i + n); |
| dataSansEmptyset.push(item); |
| i += n; |
| backrefs.unshift(item); |
| if (backrefs.length > 16) { |
| backrefs.pop(); |
| } |
| } |
| } |
| cb(null, dataSansEmptyset); |
| } |
| } |
| |
| /** |
| * @param {string} inputBase64 |
| * @returns {[Uint8Array, SearchTree]} |
| */ |
| function makeSearchTreeFromBase64(inputBase64) { |
| const input = makeUint8ArrayFromBase64(inputBase64); |
| let i = 0; |
| const l = input.length; |
| /** @type {HashTable<SearchTree>} */ |
| const stash = new HashTable(); |
| const hash = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0); |
| const truncatedHash = new Uint8Array(hash.buffer, 2, 6); |
| // used for handling compressed (that is, relative-offset) nodes |
| /** @type {{hash: Uint8Array, used: boolean}[]} */ |
| const hash_history = []; |
| /** @type {Uint8Array[]} */ |
| const data_history = []; |
| let canonical = EMPTY_UINT8; |
| /** @type {SearchTree} */ |
| let tree = new PrefixSearchTree( |
| EMPTY_SEARCH_TREE_BRANCHES, |
| EMPTY_SEARCH_TREE_BRANCHES, |
| EMPTY_UINT8, |
| EMPTY_BITMAP, |
| EMPTY_BITMAP, |
| ); |
| /** |
| * @param {Uint8Array} input |
| * @param {number} i |
| * @param {number} compression_tag |
| * @returns {{ |
| * "cpbranches": Uint8Array, |
| * "csbranches": Uint8Array, |
| * "might_have_prefix_branches": SearchTreeBranches<SearchTree>, |
| * "branches": SearchTreeBranches<SearchTree>, |
| * "cpnodes": Uint8Array, |
| * "csnodes": Uint8Array, |
| * "consumed_len_bytes": number, |
| * }} |
| */ |
| function makeBranchesFromBinaryData( |
| input, |
| i, |
| compression_tag, |
| ) { |
| const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0x00; |
| const is_stack_compressed = (compression_tag & 0x02) !== 0; |
| const is_long_compressed = (compression_tag & 0x04) !== 0; |
| const all_children_are_compressed = |
| (compression_tag & 0xF0) === 0xF0 && !is_long_compressed; |
| const any_children_are_compressed = |
| (compression_tag & 0xF0) !== 0x00 || is_long_compressed; |
| const start_point = i; |
| let cplen; |
| let cslen; |
| /** |
| * @type {( |
| * typeof SearchTreeBranches<SearchTree> & |
| * {"ALPHABITMAP_CHARS": number[], "width": number} |
| * )?} |
| */ |
| let alphabitmap = null; |
| if (is_pure_suffixes_only_node) { |
| cplen = 0; |
| cslen = input[i]; |
| i += 1; |
| if (cslen >= 0xc0) { |
| alphabitmap = SearchTreeBranchesLongAlphaBitmap; |
| cslen = cslen & 0x3F; |
| } else if (cslen >= 0x80) { |
| alphabitmap = SearchTreeBranchesShortAlphaBitmap; |
| cslen = cslen & 0x7F; |
| } |
| } else { |
| cplen = input[i]; |
| i += 1; |
| cslen = input[i]; |
| i += 1; |
| if (cplen === 0xff && cslen === 0xff) { |
| cplen = 0x100; |
| cslen = 0; |
| } else if (cplen >= 0xc0 && cslen >= 0xc0) { |
| alphabitmap = SearchTreeBranchesLongAlphaBitmap; |
| cplen = cplen & 0x3F; |
| cslen = cslen & 0x3F; |
| } else if (cplen >= 0x80 && cslen >= 0x80) { |
| alphabitmap = SearchTreeBranchesShortAlphaBitmap; |
| cplen = cplen & 0x7F; |
| cslen = cslen & 0x7F; |
| } |
| } |
| let j = 0; |
| /** @type {Uint8Array} */ |
| let cpnodes; |
| if (any_children_are_compressed) { |
| cpnodes = cplen === 0 ? EMPTY_UINT8 : new Uint8Array(cplen * 6); |
| while (j < cplen) { |
| const is_compressed = all_children_are_compressed || |
| ((0x10 << j) & compression_tag) !== 0; |
| if (is_compressed) { |
| let slot = hash_history.length - 1; |
| if (is_stack_compressed) { |
| while (hash_history[slot].used) { |
| slot -= 1; |
| } |
| } else { |
| slot -= input[i]; |
| i += 1; |
| } |
| hash_history[slot].used = true; |
| cpnodes.set( |
| hash_history[slot].hash, |
| j * 6, |
| ); |
| } else { |
| const joff = j * 6; |
| cpnodes[joff + 0] = input[i + 0]; |
| cpnodes[joff + 1] = input[i + 1]; |
| cpnodes[joff + 2] = input[i + 2]; |
| cpnodes[joff + 3] = input[i + 3]; |
| cpnodes[joff + 4] = input[i + 4]; |
| cpnodes[joff + 5] = input[i + 5]; |
| i += 6; |
| } |
| j += 1; |
| } |
| } else { |
| cpnodes = cplen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cplen * 6)); |
| i += cplen * 6; |
| } |
| j = 0; |
| /** @type {Uint8Array} */ |
| let csnodes; |
| if (any_children_are_compressed) { |
| csnodes = cslen === 0 ? EMPTY_UINT8 : new Uint8Array(cslen * 6); |
| while (j < cslen) { |
| const is_compressed = all_children_are_compressed || |
| ((0x10 << (cplen + j)) & compression_tag) !== 0; |
| if (is_compressed) { |
| let slot = hash_history.length - 1; |
| if (is_stack_compressed) { |
| while (hash_history[slot].used) { |
| slot -= 1; |
| } |
| } else { |
| slot -= input[i]; |
| i += 1; |
| } |
| hash_history[slot].used = true; |
| csnodes.set( |
| hash_history[slot].hash, |
| j * 6, |
| ); |
| } else { |
| const joff = j * 6; |
| csnodes[joff + 0] = input[i + 0]; |
| csnodes[joff + 1] = input[i + 1]; |
| csnodes[joff + 2] = input[i + 2]; |
| csnodes[joff + 3] = input[i + 3]; |
| csnodes[joff + 4] = input[i + 4]; |
| csnodes[joff + 5] = input[i + 5]; |
| i += 6; |
| } |
| j += 1; |
| } |
| } else { |
| csnodes = cslen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cslen * 6)); |
| i += cslen * 6; |
| } |
| let cpbranches; |
| let might_have_prefix_branches; |
| if (cplen === 0) { |
| cpbranches = EMPTY_UINT8; |
| might_have_prefix_branches = EMPTY_SEARCH_TREE_BRANCHES; |
| } else if (alphabitmap) { |
| cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width); |
| const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) | |
| (input[i + 2] << 16) | |
| (input[i + 1] << 8) | |
| input[i]; |
| might_have_prefix_branches = new alphabitmap(branchset, cpnodes); |
| i += alphabitmap.width; |
| } else { |
| cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, cplen); |
| might_have_prefix_branches = new SearchTreeBranchesArray(cpbranches, cpnodes); |
| i += cplen; |
| } |
| let csbranches; |
| let branches; |
| if (cslen === 0) { |
| csbranches = EMPTY_UINT8; |
| branches = might_have_prefix_branches; |
| } else if (alphabitmap) { |
| csbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width); |
| const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) | |
| (input[i + 2] << 16) | |
| (input[i + 1] << 8) | |
| input[i]; |
| if (cplen === 0) { |
| branches = new alphabitmap(branchset, csnodes); |
| } else { |
| const cpoffset = i - alphabitmap.width; |
| const cpbranchset = |
| (alphabitmap.width === 4 ? (input[cpoffset + 3] << 24) : 0) | |
| (input[cpoffset + 2] << 16) | |
| (input[cpoffset + 1] << 8) | |
| input[cpoffset]; |
| const hashes = new Uint8Array((cplen + cslen) * 6); |
| let cpi = 0; |
| let csi = 0; |
| let j = 0; |
| for (let k = 0; k < alphabitmap.ALPHABITMAP_CHARS.length; k += 1) { |
| if (branchset & (1 << k)) { |
| hashes[j + 0] = csnodes[csi + 0]; |
| hashes[j + 1] = csnodes[csi + 1]; |
| hashes[j + 2] = csnodes[csi + 2]; |
| hashes[j + 3] = csnodes[csi + 3]; |
| hashes[j + 4] = csnodes[csi + 4]; |
| hashes[j + 5] = csnodes[csi + 5]; |
| j += 6; |
| csi += 6; |
| } else if (cpbranchset & (1 << k)) { |
| hashes[j + 0] = cpnodes[cpi + 0]; |
| hashes[j + 1] = cpnodes[cpi + 1]; |
| hashes[j + 2] = cpnodes[cpi + 2]; |
| hashes[j + 3] = cpnodes[cpi + 3]; |
| hashes[j + 4] = cpnodes[cpi + 4]; |
| hashes[j + 5] = cpnodes[cpi + 5]; |
| j += 6; |
| cpi += 6; |
| } |
| } |
| branches = new alphabitmap(branchset | cpbranchset, hashes); |
| } |
| i += alphabitmap.width; |
| } else { |
| csbranches = new Uint8Array(input.buffer, i + input.byteOffset, cslen); |
| if (cplen === 0) { |
| branches = new SearchTreeBranchesArray(csbranches, csnodes); |
| } else { |
| const branchset = new Uint8Array(cplen + cslen); |
| const hashes = new Uint8Array((cplen + cslen) * 6); |
| let cpi = 0; |
| let csi = 0; |
| let j = 0; |
| while (cpi < cplen || csi < cslen) { |
| if (cpi >= cplen || (csi < cslen && cpbranches[cpi] > csbranches[csi])) { |
| branchset[j] = csbranches[csi]; |
| const joff = j * 6; |
| const csioff = csi * 6; |
| hashes[joff + 0] = csnodes[csioff + 0]; |
| hashes[joff + 1] = csnodes[csioff + 1]; |
| hashes[joff + 2] = csnodes[csioff + 2]; |
| hashes[joff + 3] = csnodes[csioff + 3]; |
| hashes[joff + 4] = csnodes[csioff + 4]; |
| hashes[joff + 5] = csnodes[csioff + 5]; |
| csi += 1; |
| } else { |
| branchset[j] = cpbranches[cpi]; |
| const joff = j * 6; |
| const cpioff = cpi * 6; |
| hashes[joff + 0] = cpnodes[cpioff + 0]; |
| hashes[joff + 1] = cpnodes[cpioff + 1]; |
| hashes[joff + 2] = cpnodes[cpioff + 2]; |
| hashes[joff + 3] = cpnodes[cpioff + 3]; |
| hashes[joff + 4] = cpnodes[cpioff + 4]; |
| hashes[joff + 5] = cpnodes[cpioff + 5]; |
| cpi += 1; |
| } |
| j += 1; |
| } |
| branches = new SearchTreeBranchesArray(branchset, hashes); |
| } |
| i += cslen; |
| } |
| return { |
| consumed_len_bytes: i - start_point, |
| cpbranches, |
| csbranches, |
| cpnodes, |
| csnodes, |
| branches, |
| might_have_prefix_branches, |
| }; |
| } |
| while (i < l) { |
| const start = i; |
| let data; |
| // compression_tag = 1 means pure-suffixes-only, |
| // which is not considered "compressed" for the purposes of this loop |
| // because that's the canonical, hashed version of the data |
| let compression_tag = input[i]; |
| const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0; |
| if (compression_tag > 1) { |
| // compressed node |
| const is_long_compressed = (compression_tag & 0x04) !== 0; |
| const is_data_compressed = (compression_tag & 0x08) !== 0; |
| i += 1; |
| if (is_long_compressed) { |
| compression_tag |= input[i] << 8; |
| i += 1; |
| compression_tag |= input[i] << 16; |
| i += 1; |
| } |
| let dlen = input[i]; |
| i += 1; |
| if (is_data_compressed) { |
| data = data_history[data_history.length - dlen - 1]; |
| dlen = data.length; |
| } else if (is_pure_suffixes_only_node) { |
| data = EMPTY_UINT8; |
| } else { |
| data = dlen === 0 ? |
| EMPTY_UINT8 : |
| new Uint8Array(input.buffer, i + input.byteOffset, dlen); |
| i += dlen; |
| } |
| const coffset = i; |
| const { |
| cpbranches, |
| csbranches, |
| cpnodes, |
| csnodes, |
| consumed_len_bytes: branches_consumed_len_bytes, |
| branches, |
| might_have_prefix_branches, |
| } = makeBranchesFromBinaryData(input, i, compression_tag); |
| i += branches_consumed_len_bytes; |
| let whole; |
| let suffix; |
| if (is_pure_suffixes_only_node) { |
| suffix = input[i] === 0 ? |
| EMPTY_BITMAP1 : |
| new RoaringBitmap(input, i); |
| i += suffix.consumed_len_bytes; |
| tree = new SuffixSearchTree( |
| branches, |
| dlen, |
| suffix, |
| ); |
| const clen = ( |
| 3 + // lengths of children and data |
| csnodes.length + |
| csbranches.length + |
| suffix.consumed_len_bytes |
| ); |
| if (canonical.length < clen) { |
| canonical = new Uint8Array(clen); |
| } |
| let ci = 0; |
| canonical[ci] = 1; |
| ci += 1; |
| canonical[ci] = dlen; |
| ci += 1; |
| canonical[ci] = input[coffset]; // suffix child count |
| ci += 1; |
| canonical.set(csnodes, ci); |
| ci += csnodes.length; |
| canonical.set(csbranches, ci); |
| ci += csbranches.length; |
| const leavesOffset = i - suffix.consumed_len_bytes; |
| for (let j = leavesOffset; j < i; j += 1) { |
| canonical[ci + j - leavesOffset] = input[j]; |
| } |
| siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); |
| } else { |
| if (input[i] === 0xff) { |
| whole = EMPTY_BITMAP; |
| suffix = EMPTY_BITMAP1; |
| i += 1; |
| } else { |
| whole = input[i] === 0 ? |
| EMPTY_BITMAP1 : |
| new RoaringBitmap(input, i); |
| i += whole.consumed_len_bytes; |
| suffix = input[i] === 0 ? |
| EMPTY_BITMAP1 : |
| new RoaringBitmap(input, i); |
| i += suffix.consumed_len_bytes; |
| } |
| tree = new PrefixSearchTree( |
| branches, |
| might_have_prefix_branches, |
| data, |
| whole, |
| suffix, |
| ); |
| const clen = ( |
| 4 + // lengths of children and data |
| dlen + |
| cpnodes.length + csnodes.length + |
| cpbranches.length + csbranches.length + |
| whole.consumed_len_bytes + |
| suffix.consumed_len_bytes |
| ); |
| if (canonical.length < clen) { |
| canonical = new Uint8Array(clen); |
| } |
| let ci = 0; |
| canonical[ci] = 0; |
| ci += 1; |
| canonical[ci] = dlen; |
| ci += 1; |
| canonical.set(data, ci); |
| ci += data.length; |
| canonical[ci] = input[coffset]; // prefix child count |
| ci += 1; |
| canonical[ci] = input[coffset + 1]; // suffix child count |
| ci += 1; |
| canonical.set(cpnodes, ci); |
| ci += cpnodes.length; |
| canonical.set(csnodes, ci); |
| ci += csnodes.length; |
| canonical.set(cpbranches, ci); |
| ci += cpbranches.length; |
| canonical.set(csbranches, ci); |
| ci += csbranches.length; |
| const leavesOffset = i - whole.consumed_len_bytes - suffix.consumed_len_bytes; |
| for (let j = leavesOffset; j < i; j += 1) { |
| canonical[ci + j - leavesOffset] = input[j]; |
| } |
| siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); |
| } |
| hash[2] &= 0x7f; |
| } else { |
| // uncompressed node |
| const dlen = input [i + 1]; |
| i += 2; |
| if (dlen === 0 || is_pure_suffixes_only_node) { |
| data = EMPTY_UINT8; |
| } else { |
| data = new Uint8Array(input.buffer, i + input.byteOffset, dlen); |
| i += dlen; |
| } |
| const { |
| consumed_len_bytes: branches_consumed_len_bytes, |
| branches, |
| might_have_prefix_branches, |
| } = makeBranchesFromBinaryData(input, i, compression_tag); |
| i += branches_consumed_len_bytes; |
| let whole; |
| let suffix; |
| if (is_pure_suffixes_only_node) { |
| whole = EMPTY_BITMAP; |
| suffix = input[i] === 0 ? |
| EMPTY_BITMAP1 : |
| new RoaringBitmap(input, i); |
| i += suffix.consumed_len_bytes; |
| } else if (input[i] === 0xff) { |
| whole = EMPTY_BITMAP; |
| suffix = EMPTY_BITMAP; |
| i += 1; |
| } else { |
| whole = input[i] === 0 ? |
| EMPTY_BITMAP1 : |
| new RoaringBitmap(input, i); |
| i += whole.consumed_len_bytes; |
| suffix = input[i] === 0 ? |
| EMPTY_BITMAP1 : |
| new RoaringBitmap(input, i); |
| i += suffix.consumed_len_bytes; |
| } |
| siphashOfBytes(new Uint8Array( |
| input.buffer, |
| start + input.byteOffset, |
| i - start, |
| ), 0, 0, 0, 0, hash); |
| hash[2] &= 0x7f; |
| tree = is_pure_suffixes_only_node ? |
| new SuffixSearchTree( |
| branches, |
| dlen, |
| suffix, |
| ) : |
| new PrefixSearchTree( |
| branches, |
| might_have_prefix_branches, |
| data, |
| whole, |
| suffix, |
| ); |
| } |
| hash_history.push({hash: truncatedHash.slice(), used: false}); |
| if (data.length !== 0) { |
| data_history.push(data); |
| } |
| const tree_branch_nodeids = tree.branches.nodeids; |
| const tree_branch_subtrees = tree.branches.subtrees; |
| let j = 0; |
| let lb = tree.branches.subtrees.length; |
| while (j < lb) { |
| // node id with a 1 in its most significant bit is inlined, and, so |
| // it won't be in the stash |
| if ((tree_branch_nodeids[j * 6] & 0x80) === 0) { |
| const subtree = stash.getWithOffsetKey(tree_branch_nodeids, j * 6); |
| if (subtree !== undefined) { |
| tree_branch_subtrees[j] = Promise.resolve(subtree); |
| } |
| } |
| j += 1; |
| } |
| if (tree instanceof PrefixSearchTree) { |
| const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids; |
| const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees; |
| j = 0; |
| lb = tree.might_have_prefix_branches.subtrees.length; |
| while (j < lb) { |
| // node id with a 1 in its most significant bit is inlined, and, so |
| // it won't be in the stash |
| if ((tree_mhp_branch_nodeids[j * 6] & 0x80) === 0) { |
| const subtree = stash.getWithOffsetKey(tree_mhp_branch_nodeids, j * 6); |
| if (subtree !== undefined) { |
| tree_mhp_branch_subtrees[j] = Promise.resolve(subtree); |
| } |
| } |
| j += 1; |
| } |
| } |
| if (i !== l) { |
| stash.set(truncatedHash, tree); |
| } |
| } |
| return [truncatedHash, tree]; |
| } |
| |
| return new Promise((resolve, reject) => { |
| registry.searchTreeRootCallback = (error, data) => { |
| if (data) { |
| resolve(data); |
| } else { |
| reject(error); |
| } |
| }; |
| hooks.loadRoot(callbacks); |
| }); |
| } |
| |
| if (typeof window !== "undefined") { |
| window.Stringdex = { |
| loadDatabase, |
| }; |
| window.RoaringBitmap = RoaringBitmap; |
| if (window.StringdexOnload) { |
| window.StringdexOnload.forEach(cb => cb(window.Stringdex)); |
| } |
| } else { |
| /** @type {stringdex.Stringdex} */ |
| // eslint-disable-next-line no-undef |
| module.exports.Stringdex = { |
| loadDatabase, |
| }; |
| /** @type {stringdex.RoaringBitmap} */ |
| // eslint-disable-next-line no-undef |
| module.exports.RoaringBitmap = RoaringBitmap; |
| } |
| |
| // eslint-disable-next-line max-len |
| // polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64 |
| /** |
| * @type {function(string): Uint8Array} base64 |
| */ |
| //@ts-expect-error |
| const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => { |
| const bytes_as_string = atob(string); |
| const l = bytes_as_string.length; |
| const bytes = new Uint8Array(l); |
| for (let i = 0; i < l; ++i) { |
| bytes[i] = bytes_as_string.charCodeAt(i); |
| } |
| return bytes; |
| }); |
| /** |
| * @type {function(string): Uint8Array} base64 |
| */ |
| //@ts-expect-error |
| const makeUint8ArrayFromHex = Uint8Array.fromHex ? Uint8Array.fromHex : (string => { |
| /** @type {Object<string, number>} */ |
| const alpha = { |
| "0": 0, "1": 1, |
| "2": 2, "3": 3, |
| "4": 4, "5": 5, |
| "6": 6, "7": 7, |
| "8": 8, "9": 9, |
| "a": 10, "b": 11, |
| "A": 10, "B": 11, |
| "c": 12, "d": 13, |
| "C": 12, "D": 13, |
| "e": 14, "f": 15, |
| "E": 14, "F": 15, |
| }; |
| const l = string.length >> 1; |
| const bytes = new Uint8Array(l); |
| for (let i = 0; i < l; i += 1) { |
| const top = string[i << 1]; |
| const bottom = string[(i << 1) + 1]; |
| bytes[i] = (alpha[top] << 4) | alpha[bottom]; |
| } |
| return bytes; |
| }); |
| |
| /** |
| * @type {function(Uint8Array): string} base64 |
| */ |
| //@ts-expect-error |
| const makeHexFromUint8Array = Uint8Array.prototype.toHex ? (array => array.toHex()) : (array => { |
| /** @type {string[]} */ |
| const alpha = [ |
| "0", "1", |
| "2", "3", |
| "4", "5", |
| "6", "7", |
| "8", "9", |
| "a", "b", |
| "c", "d", |
| "e", "f", |
| ]; |
| const l = array.length; |
| const v = []; |
| for (let i = 0; i < l; ++i) { |
| v.push(alpha[array[i] >> 4]); |
| v.push(alpha[array[i] & 0xf]); |
| } |
| return v.join(""); |
| }); |
| |
| ////////////// |
| |
| /** |
| * SipHash 1-3 |
| * @param {Uint8Array} input data to be hashed; all codepoints in string should be less than 256 |
| * @param {number} k0lo first word of key |
| * @param {number} k0hi second word of key |
| * @param {number} k1lo third word of key |
| * @param {number} k1hi fourth word of key |
| * @param {Uint8Array} output the data to write (clobber the first eight bytes) |
| */ |
| function siphashOfBytes(input, k0lo, k0hi, k1lo, k1hi, output) { |
| // hash state |
| // While siphash uses 64 bit state, js only has native support |
| // for 32 bit numbers. BigInt, unfortunately, doesn't count. |
| // It's too slow. |
| let v0lo = k0lo ^ 0x70736575; |
| let v0hi = k0hi ^ 0x736f6d65; |
| let v1lo = k1lo ^ 0x6e646f6d; |
| let v1hi = k1hi ^ 0x646f7261; |
| let v2lo = k0lo ^ 0x6e657261; |
| let v2hi = k0hi ^ 0x6c796765; |
| let v3lo = k1lo ^ 0x79746573; |
| let v3hi = k1hi ^ 0x74656462; |
| const inputLength = input.length; |
| let inputI = 0; |
| // main hash loop |
| const left = inputLength & 0x7; |
| let milo = 0; |
| let mihi = 0; |
| while (inputI < inputLength - left) { |
| u8ToU64le(inputI, inputI + 8); |
| v3lo ^= milo; |
| v3hi ^= mihi; |
| siphashCompress(); |
| v0lo ^= milo; |
| v0hi ^= mihi; |
| inputI += 8; |
| } |
| u8ToU64le(inputI, inputI + left); |
| // finish |
| const blo = milo; |
| const bhi = ((inputLength & 0xff) << 24) | mihi; |
| v3lo ^= blo; |
| v3hi ^= bhi; |
| siphashCompress(); |
| v0lo ^= blo; |
| v0hi ^= bhi; |
| v2lo ^= 0xff; |
| siphashCompress(); |
| siphashCompress(); |
| siphashCompress(); |
| output[7] = (v0lo ^ v1lo ^ v2lo ^ v3lo) & 0xff; |
| output[6] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 8; |
| output[5] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 16; |
| output[4] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 24; |
| output[3] = (v0hi ^ v1hi ^ v2hi ^ v3hi) & 0xff; |
| output[2] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 8; |
| output[1] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 16; |
| output[0] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 24; |
| /** |
| * Convert eight bytes to a single 64-bit number |
| * @param {number} offset |
| * @param {number} length |
| */ |
| function u8ToU64le(offset, length) { |
| const n0 = offset < length ? input[offset] & 0xff : 0; |
| const n1 = offset + 1 < length ? input[offset + 1] & 0xff : 0; |
| const n2 = offset + 2 < length ? input[offset + 2] & 0xff : 0; |
| const n3 = offset + 3 < length ? input[offset + 3] & 0xff : 0; |
| const n4 = offset + 4 < length ? input[offset + 4] & 0xff : 0; |
| const n5 = offset + 5 < length ? input[offset + 5] & 0xff : 0; |
| const n6 = offset + 6 < length ? input[offset + 6] & 0xff : 0; |
| const n7 = offset + 7 < length ? input[offset + 7] & 0xff : 0; |
| milo = n0 | (n1 << 8) | (n2 << 16) | (n3 << 24); |
| mihi = n4 | (n5 << 8) | (n6 << 16) | (n7 << 24); |
| } |
| function siphashCompress() { |
| // v0 += v1; |
| v0hi = (v0hi + v1hi + (((v0lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; |
| v0lo = (v0lo + v1lo) | 0; |
| // rotl(v1, 13) |
| let v1lo_ = v1lo; |
| let v1hi_ = v1hi; |
| v1lo = (v1lo_ << 13) | (v1hi_ >>> 19); |
| v1hi = (v1hi_ << 13) | (v1lo_ >>> 19); |
| // v1 ^= v0 |
| v1lo ^= v0lo; |
| v1hi ^= v0hi; |
| // rotl(v0, 32) |
| const v0lo_ = v0lo; |
| const v0hi_ = v0hi; |
| v0lo = v0hi_; |
| v0hi = v0lo_; |
| // v2 += v3 |
| v2hi = (v2hi + v3hi + (((v2lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; |
| v2lo = (v2lo + v3lo) | 0; |
| // rotl(v3, 16) |
| let v3lo_ = v3lo; |
| let v3hi_ = v3hi; |
| v3lo = (v3lo_ << 16) | (v3hi_ >>> 16); |
| v3hi = (v3hi_ << 16) | (v3lo_ >>> 16); |
| // v3 ^= v2 |
| v3lo ^= v2lo; |
| v3hi ^= v2hi; |
| // v0 += v3 |
| v0hi = (v0hi + v3hi + (((v0lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; |
| v0lo = (v0lo + v3lo) | 0; |
| // rotl(v3, 21) |
| v3lo_ = v3lo; |
| v3hi_ = v3hi; |
| v3lo = (v3lo_ << 21) | (v3hi_ >>> 11); |
| v3hi = (v3hi_ << 21) | (v3lo_ >>> 11); |
| // v3 ^= v0 |
| v3lo ^= v0lo; |
| v3hi ^= v0hi; |
| // v2 += v1 |
| v2hi = (v2hi + v1hi + (((v2lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; |
| v2lo = (v2lo + v1lo) | 0; |
| // rotl(v1, 17) |
| v1lo_ = v1lo; |
| v1hi_ = v1hi; |
| v1lo = (v1lo_ << 17) | (v1hi_ >>> 15); |
| v1hi = (v1hi_ << 17) | (v1lo_ >>> 15); |
| // v1 ^= v2 |
| v1lo ^= v2lo; |
| v1hi ^= v2hi; |
| // rotl(v2, 32) |
| const v2lo_ = v2lo; |
| const v2hi_ = v2hi; |
| v2lo = v2hi_; |
| v2hi = v2lo_; |
| } |
| } |
| |
| ////////////// |
| |
| |
| // Parts of this code are based on Lucene, which is licensed under the |
| // Apache/2.0 license. |
| // More information found here: |
| // https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/ |
| // LevenshteinAutomata.java |
| class ParametricDescription { |
| /** |
| * @param {number} w |
| * @param {number} n |
| * @param {Int32Array} minErrors |
| */ |
| constructor(w, n, minErrors) { |
| this.w = w; |
| this.n = n; |
| this.minErrors = minErrors; |
| } |
| /** |
| * @param {number} absState |
| * @returns {boolean} |
| */ |
| isAccept(absState) { |
| const state = Math.floor(absState / (this.w + 1)); |
| const offset = absState % (this.w + 1); |
| return this.w - offset + this.minErrors[state] <= this.n; |
| } |
| /** |
| * @param {number} absState |
| * @returns {number} |
| */ |
| getPosition(absState) { |
| return absState % (this.w + 1); |
| } |
| /** |
| * @param {Uint8Array} name |
| * @param {number} charCode |
| * @param {number} pos |
| * @param {number} end |
| * @returns {number} |
| */ |
| getVector(name, charCode, pos, end) { |
| let vector = 0; |
| for (let i = pos; i < end; i += 1) { |
| vector = vector << 1; |
| if (name[i] === charCode) { |
| vector |= 1; |
| } |
| } |
| return vector; |
| } |
| /** |
| * @param {Int32Array} data |
| * @param {number} index |
| * @param {number} bitsPerValue |
| * @returns {number} |
| */ |
| unpack(data, index, bitsPerValue) { |
| const bitLoc = (bitsPerValue * index); |
| const dataLoc = bitLoc >> 5; |
| const bitStart = bitLoc & 31; |
| if (bitStart + bitsPerValue <= 32) { |
| // not split |
| return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]); |
| } else { |
| // split |
| const part = 32 - bitStart; |
| return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) + |
| ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part)); |
| } |
| } |
| } |
| ParametricDescription.prototype.MASKS = new Int32Array([ |
| 0x1, 0x3, 0x7, 0xF, |
| 0x1F, 0x3F, 0x7F, 0xFF, |
| 0x1FF, 0x3F, 0x7FF, 0xFFF, |
| 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, |
| 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF, |
| 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF, |
| 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, |
| 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, |
| ]); |
| |
| // The following code was generated with the moman/finenight pkg |
| // This package is available under the MIT License, see NOTICE.txt |
| // for more details. |
| // This class is auto-generated, Please do not modify it directly. |
| // You should modify the https://gitlab.com/notriddle/createAutomata.py instead. |
| // The following code was generated with the moman/finenight pkg |
| // This package is available under the MIT License, see NOTICE.txt |
| // for more details. |
| // This class is auto-generated, Please do not modify it directly. |
| // You should modify https://gitlab.com/notriddle/moman-rustdoc instead. |
| |
| class Lev2TParametricDescription extends ParametricDescription { |
| /** |
| * @param {number} absState |
| * @param {number} position |
| * @param {number} vector |
| * @returns {number} |
| */ |
| transition(absState, position, vector) { |
| let state = Math.floor(absState / (this.w + 1)); |
| let offset = absState % (this.w + 1); |
| |
| if (position === this.w) { |
| if (state < 3) { |
| const loc = Math.imul(vector, 3) + state; |
| offset += this.unpack(this.offsetIncrs0, loc, 1); |
| state = this.unpack(this.toStates0, loc, 2) - 1; |
| } |
| } else if (position === this.w - 1) { |
| if (state < 5) { |
| const loc = Math.imul(vector, 5) + state; |
| offset += this.unpack(this.offsetIncrs1, loc, 1); |
| state = this.unpack(this.toStates1, loc, 3) - 1; |
| } |
| } else if (position === this.w - 2) { |
| if (state < 13) { |
| const loc = Math.imul(vector, 13) + state; |
| offset += this.unpack(this.offsetIncrs2, loc, 2); |
| state = this.unpack(this.toStates2, loc, 4) - 1; |
| } |
| } else if (position === this.w - 3) { |
| if (state < 28) { |
| const loc = Math.imul(vector, 28) + state; |
| offset += this.unpack(this.offsetIncrs3, loc, 2); |
| state = this.unpack(this.toStates3, loc, 5) - 1; |
| } |
| } else if (position === this.w - 4) { |
| if (state < 45) { |
| const loc = Math.imul(vector, 45) + state; |
| offset += this.unpack(this.offsetIncrs4, loc, 3); |
| state = this.unpack(this.toStates4, loc, 6) - 1; |
| } |
| } else { |
| // eslint-disable-next-line no-lonely-if |
| if (state < 45) { |
| const loc = Math.imul(vector, 45) + state; |
| offset += this.unpack(this.offsetIncrs5, loc, 3); |
| state = this.unpack(this.toStates5, loc, 6) - 1; |
| } |
| } |
| |
| if (state === -1) { |
| // null state |
| return -1; |
| } else { |
| // translate back to abs |
| return Math.imul(state, this.w + 1) + offset; |
| } |
| } |
| |
| // state map |
| // 0 -> [(0, 0)] |
| // 1 -> [(0, 1)] |
| // 2 -> [(0, 2)] |
| // 3 -> [(0, 1), (1, 1)] |
| // 4 -> [(0, 2), (1, 2)] |
| // 5 -> [(0, 1), (1, 1), (2, 1)] |
| // 6 -> [(0, 2), (1, 2), (2, 2)] |
| // 7 -> [(0, 1), (2, 1)] |
| // 8 -> [(0, 1), (2, 2)] |
| // 9 -> [(0, 2), (2, 1)] |
| // 10 -> [(0, 2), (2, 2)] |
| // 11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)] |
| // 12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)] |
| // 13 -> [(0, 2), (1, 2), (2, 2), (3, 2)] |
| // 14 -> [(0, 1), (1, 1), (3, 2)] |
| // 15 -> [(0, 1), (2, 2), (3, 2)] |
| // 16 -> [(0, 1), (3, 2)] |
| // 17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)] |
| // 18 -> [(0, 2), (1, 2), (3, 1)] |
| // 19 -> [(0, 2), (1, 2), (3, 2)] |
| // 20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)] |
| // 21 -> [(0, 2), (2, 1), (3, 1)] |
| // 22 -> [(0, 2), (2, 2), (3, 2)] |
| // 23 -> [(0, 2), (3, 1)] |
| // 24 -> [(0, 2), (3, 2)] |
| // 25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)] |
| // 26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)] |
| // 27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)] |
| // 28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)] |
| // 29 -> [(0, 2), (1, 2), (2, 2), (4, 2)] |
| // 30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)] |
| // 31 -> [(0, 2), (1, 2), (3, 2), (4, 2)] |
| // 32 -> [(0, 2), (1, 2), (4, 2)] |
| // 33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)] |
| // 34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)] |
| // 35 -> [(0, 2), (2, 1), (4, 2)] |
| // 36 -> [(0, 2), (2, 2), (3, 2), (4, 2)] |
| // 37 -> [(0, 2), (2, 2), (4, 2)] |
| // 38 -> [(0, 2), (3, 2), (4, 2)] |
| // 39 -> [(0, 2), (4, 2)] |
| // 40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)] |
| // 41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)] |
| // 42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)] |
| // 43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)] |
| // 44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)] |
| |
| |
| /** @param {number} w - length of word being checked */ |
| constructor(w) { |
| super(w, 2, new Int32Array([ |
| 0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2, |
| -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2, |
| ])); |
| } |
| } |
| |
| Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([ |
| 0xe, |
| ]); |
| Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([ |
| 0x0, |
| ]); |
| |
| Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([ |
| 0x1a688a2c, |
| ]); |
| Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([ |
| 0x3e0, |
| ]); |
| |
| Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([ |
| 0x70707054,0xdc07035,0x3dd3a3a,0x2323213a, |
| 0x15435223,0x22545432,0x5435, |
| ]); |
| Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([ |
| 0x80000,0x55582088,0x55555555,0x55, |
| ]); |
| |
| Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([ |
| 0x1c0380a4,0x700a570,0xca529c0,0x180a00, |
| 0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8, |
| 0xac18c601,0xd8d43501,0x863500ad,0x51976d6a, |
| 0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5, |
| 0x18c4519,0xc41294a,0xe248d231,0x1086520c, |
| 0xce31ac42,0x13946358,0x2d0348c4,0x6732d494, |
| 0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948, |
| 0x22110a52,0x58ce729d,0xc41394e3,0x941cc520, |
| 0x90e732d4,0x4729d224,0x39ce35ad, |
| ]); |
| Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([ |
| 0x80000,0xc0c830,0x300f3c30,0x2200fcff, |
| 0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555, |
| 0x55555555,0x55555555,0x55555555,0x55555555, |
| 0x55555555,0x55555555, |
| ]); |
| |
| Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([ |
| 0x801c0144,0x1453803,0x14700038,0xc0005145, |
| 0x1401,0x14,0x140000,0x0, |
| 0x510000,0x6301f007,0x301f00d1,0xa186178, |
| 0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd, |
| 0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34, |
| 0x8300550,0x430c0143,0x50c31,0xc30850c, |
| 0xc3143000,0x50053c50,0x5130d301,0x850d30c2, |
| 0x30a08608,0xc214414,0x43142145,0x21450031, |
| 0x1400c314,0x4c143145,0x32832803,0x28014d6c, |
| 0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3, |
| 0x1431,0xc300500,0xca00d303,0xd36d0e40, |
| 0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c, |
| 0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280, |
| 0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401, |
| 0x1430c714,0xc3087,0x71451450,0xca00d30, |
| 0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144, |
| 0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51, |
| 0x24324308,0xc51031c2,0x70820820,0x5c33830d, |
| 0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3, |
| 0x20c20c20,0xda0920d,0x5145914f,0x36596114, |
| 0x51965865,0xd9643653,0x365a6590,0x51964364, |
| 0x43081505,0x920b2032,0x2c718b28,0xd7242249, |
| 0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2, |
| 0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c, |
| 0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7, |
| 0x73975c36,0x10308138,0xc2245105,0x41451031, |
| 0x14e24208,0xc35c3387,0x51453851,0x1c51c514, |
| 0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092, |
| 0x4513d41,0x6533944d,0x1350e658,0xe1545055, |
| 0x64365a50,0x5519383,0x51030815,0x28920718, |
| 0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387, |
| 0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440, |
| 0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65, |
| 0x8e154387,0x364b5ca3,0x38739738, |
| ]); |
| Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([ |
| 0x10000000,0xc00000,0x60061,0x400, |
| 0x0,0x80010008,0x249248a4,0x8229048, |
| 0x2092,0x6c3603,0xb61b6c30,0x6db6036d, |
| 0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b, |
| 0x6db6236,0x1008200,0x12480012,0x24924906, |
| 0x48200049,0x80410002,0x24000900,0x4924a489, |
| 0x10822492,0x20800125,0x48360,0x9241b692, |
| 0x6da4924,0x40009268,0x241b010,0x291b4900, |
| 0x6d249249,0x49493423,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x2492, |
| ]); |
| |
| Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([ |
| 0x801c0144,0x1453803,0x14700038,0xc0005145, |
| 0x1401,0x14,0x140000,0x0, |
| 0x510000,0x4e00e007,0xe0051,0x3451451c, |
| 0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4, |
| 0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07, |
| 0x2830c286,0x830c3083,0xc30030,0x33430c, |
| 0x30c3003,0x70051030,0x16301f00,0x8301f00d, |
| 0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51, |
| 0x14314315,0x4f143145,0x34c05401,0x4c30944c, |
| 0x55150c3,0x30830055,0x1430c014,0xc00050c3, |
| 0xc30850,0xc314300,0x150053c5,0x25130d30, |
| 0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c, |
| 0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540, |
| 0x34c30944,0x82182214,0x851050c2,0x50851430, |
| 0x1400c50c,0x30c5085,0x50c51450,0x150053c, |
| 0xc25130d3,0x8850d30,0x1430a086,0x450c2144, |
| 0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1, |
| 0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c, |
| 0xc31c3140,0x31430c30,0x14,0x30c3005, |
| 0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3, |
| 0xc500d01,0x5965965a,0x30d46546,0x6435030c, |
| 0x8034c659,0xdb439032,0x2c390034,0xcaaecb24, |
| 0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033, |
| 0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e, |
| 0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce, |
| 0x95c53831,0x28034c5d,0x9b705583,0xa1455830, |
| 0xc76cd6c,0x40143085,0x71451c51,0x871430c, |
| 0x450000c3,0xd3071451,0x1560ca00,0x560c26dc, |
| 0xb35b2851,0xc914369,0x1a14500d,0x46593945, |
| 0xcb2c939,0x94507503,0x328034c3,0x9b70558, |
| 0xe41c5583,0x72caaeca,0x1c308510,0xc7147287, |
| 0x50871c32,0x1470030c,0xd307147,0xc1560ca0, |
| 0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c, |
| 0x8e657394,0x314b1c93,0x39438738,0x43083081, |
| 0x31c22432,0x820c510,0x830d7082,0x50c35c33, |
| 0xc30c338,0xc31c30c3,0x50c30c30,0xc204514, |
| 0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3, |
| 0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a, |
| 0x48308308,0xc3682483,0x14516453,0x4d965845, |
| 0xd4659619,0x36590d94,0xd969964,0x546590d9, |
| 0x20c20541,0x920d20c,0x5914f0da,0x96114514, |
| 0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e, |
| 0x81821827,0xb2032430,0x18b28920,0x422492c7, |
| 0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972, |
| 0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c, |
| 0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28, |
| 0x59a8a269,0x8175e7a,0xb203243,0x718b2892, |
| 0x4114105c,0x17597658,0x74ce5d96,0x5c36572d, |
| 0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8, |
| 0x4171c62,0x5d961045,0x976585d6,0x79669533, |
| 0x964965a2,0x659689e6,0x308175e7,0x24510510, |
| 0x451031c2,0xe2420841,0x5c338714,0x453851c3, |
| 0x51c51451,0xc30c31c,0x451450c7,0x41440c20, |
| 0xc708914,0x82105144,0xf1c58c90,0x1470ea0d, |
| 0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861, |
| 0x24853c51,0x5053c368,0x1341144f,0x96194ce5, |
| 0x1544d439,0x94385514,0xe0d90d96,0x5415464, |
| 0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0, |
| 0x350e6586,0x86082181,0xe89e981d,0x18277689, |
| 0x10308182,0x89207185,0x41c718b2,0x14e24224, |
| 0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb, |
| 0x204e1c75,0x1c61440c,0xc62ca248,0x90891071, |
| 0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475, |
| 0x5e7a57a8,0x51030817,0x28920718,0xf38718b, |
| 0xe5134114,0x39961758,0xe1ce4ce,0x728e3855, |
| 0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24, |
| 0xd04503ce,0x85d63944,0x75338e65,0x5d86075e, |
| 0x89e69647,0x75e76576, |
| ]); |
| Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([ |
| 0x10000000,0xc00000,0x60061,0x400, |
| 0x0,0x60000008,0x6b003080,0xdb6ab6db, |
| 0x2db6,0x800400,0x49245240,0x11482412, |
| 0x104904,0x40020000,0x92292000,0xa4b25924, |
| 0x9649658,0xd80c000,0xdb0c001b,0x80db6d86, |
| 0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d, |
| 0xddadb6ed,0x300001b6,0x6c360,0xe37236e4, |
| 0x46db6236,0xdb6c,0x361b018,0xb91b7200, |
| 0x6dbb1b71,0x6db763,0x20100820,0x61248001, |
| 0x92492490,0x24820004,0x8041000,0x92400090, |
| 0x24924830,0x555b6a49,0x2080012,0x20004804, |
| 0x49252449,0x84112492,0x4000928,0x240201, |
| 0x92922490,0x58924924,0x49456,0x120d8082, |
| 0x6da4800,0x69249249,0x249a01b,0x6c04100, |
| 0x6d240009,0x92492483,0x24d5adb4,0x60208001, |
| 0x92000483,0x24925236,0x6846da49,0x10400092, |
| 0x241b0,0x49291b49,0x636d2492,0x92494935, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924,0x49249249, |
| 0x92492492,0x24924924,0x49249249,0x92492492, |
| 0x24924924,0x49249249,0x92492492,0x24924924, |
| 0x49249249,0x92492492,0x24924924, |
| ]); |
| |
| class Lev1TParametricDescription extends ParametricDescription { |
| /** |
| * @param {number} absState |
| * @param {number} position |
| * @param {number} vector |
| * @returns {number} |
| */ |
| transition(absState, position, vector) { |
| let state = Math.floor(absState / (this.w + 1)); |
| let offset = absState % (this.w + 1); |
| |
| if (position === this.w) { |
| if (state < 2) { |
| const loc = Math.imul(vector, 2) + state; |
| offset += this.unpack(this.offsetIncrs0, loc, 1); |
| state = this.unpack(this.toStates0, loc, 2) - 1; |
| } |
| } else if (position === this.w - 1) { |
| if (state < 3) { |
| const loc = Math.imul(vector, 3) + state; |
| offset += this.unpack(this.offsetIncrs1, loc, 1); |
| state = this.unpack(this.toStates1, loc, 2) - 1; |
| } |
| } else if (position === this.w - 2) { |
| if (state < 6) { |
| const loc = Math.imul(vector, 6) + state; |
| offset += this.unpack(this.offsetIncrs2, loc, 2); |
| state = this.unpack(this.toStates2, loc, 3) - 1; |
| } |
| } else { |
| // eslint-disable-next-line no-lonely-if |
| if (state < 6) { |
| const loc = Math.imul(vector, 6) + state; |
| offset += this.unpack(this.offsetIncrs3, loc, 2); |
| state = this.unpack(this.toStates3, loc, 3) - 1; |
| } |
| } |
| |
| if (state === -1) { |
| // null state |
| return -1; |
| } else { |
| // translate back to abs |
| return Math.imul(state, this.w + 1) + offset; |
| } |
| } |
| |
| // state map |
| // 0 -> [(0, 0)] |
| // 1 -> [(0, 1)] |
| // 2 -> [(0, 1), (1, 1)] |
| // 3 -> [(0, 1), (1, 1), (2, 1)] |
| // 4 -> [(0, 1), (2, 1)] |
| // 5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)] |
| |
| |
| /** @param {number} w - length of word being checked */ |
| constructor(w) { |
| super(w, 1, new Int32Array([0,1,0,-1,-1,-1])); |
| } |
| } |
| |
| Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([ |
| 0x2, |
| ]); |
| Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([ |
| 0x0, |
| ]); |
| |
| Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([ |
| 0xa43, |
| ]); |
| Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([ |
| 0x38, |
| ]); |
| |
| Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([ |
| 0x12180003,0xb45a4914,0x69, |
| ]); |
| Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([ |
| 0x558a0000,0x5555, |
| ]); |
| |
| Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([ |
| 0x900c0003,0xa1904864,0x45a49169,0x5a6d196a, |
| 0x9634, |
| ]); |
| Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([ |
| 0xa0fc0000,0x5555ba08,0x55555555, |
| ]); |