CoolFace
Apppublic

AK-21/Graphite-Industrial-Intelligence

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
splice-buffer.js251 linesDownload Raw Back to lib
1/**2 * Some of the internal operations of micromark do lots of editing3 * operations on very large arrays. This runs into problems with two4 * properties of most circa-2020 JavaScript interpreters:5 *6 *  - Array-length modifications at the high end of an array (push/pop) are7 *    expected to be common and are implemented in (amortized) time8 *    proportional to the number of elements added or removed, whereas9 *    other operations (shift/unshift and splice) are much less efficient.10 *  - Function arguments are passed on the stack, so adding tens of thousands11 *    of elements to an array with `arr.push(...newElements)` will frequently12 *    cause stack overflows. (see <https://stackoverflow.com/questions/22123769/rangeerror-maximum-call-stack-size-exceeded-why>)13 *14 * SpliceBuffers are an implementation of gap buffers, which are a15 * generalization of the "queue made of two stacks" idea. The splice buffer16 * maintains a cursor, and moving the cursor has cost proportional to the17 * distance the cursor moves, but inserting, deleting, or splicing in18 * new information at the cursor is as efficient as the push/pop operation.19 * This allows for an efficient sequence of splices (or pushes, pops, shifts,20 * or unshifts) as long such edits happen at the same part of the array or21 * generally sweep through the array from the beginning to the end.22 *23 * The interface for splice buffers also supports large numbers of inputs by24 * passing a single array argument rather passing multiple arguments on the25 * function call stack.26 *27 * @template T28 *   Item type.29 */30export class SpliceBuffer {31  /**32   * @param {ReadonlyArray<T> | null | undefined} [initial]33   *   Initial items (optional).34   * @returns35   *   Splice buffer.36   */37  constructor(initial) {38    /** @type {Array<T>} */39    this.left = initial ? [...initial] : [];40    /** @type {Array<T>} */41    this.right = [];42  }43 44  /**45   * Array access;46   * does not move the cursor.47   *48   * @param {number} index49   *   Index.50   * @return {T}51   *   Item.52   */53  get(index) {54    if (index < 0 || index >= this.left.length + this.right.length) {55      throw new RangeError('Cannot access index `' + index + '` in a splice buffer of size `' + (this.left.length + this.right.length) + '`');56    }57    if (index < this.left.length) return this.left[index];58    return this.right[this.right.length - index + this.left.length - 1];59  }60 61  /**62   * The length of the splice buffer, one greater than the largest index in the63   * array.64   */65  get length() {66    return this.left.length + this.right.length;67  }68 69  /**70   * Remove and return `list[0]`;71   * moves the cursor to `0`.72   *73   * @returns {T | undefined}74   *   Item, optional.75   */76  shift() {77    this.setCursor(0);78    return this.right.pop();79  }80 81  /**82   * Slice the buffer to get an array;83   * does not move the cursor.84   *85   * @param {number} start86   *   Start.87   * @param {number | null | undefined} [end]88   *   End (optional).89   * @returns {Array<T>}90   *   Array of items.91   */92  slice(start, end) {93    /** @type {number} */94    const stop = end === null || end === undefined ? Number.POSITIVE_INFINITY : end;95    if (stop < this.left.length) {96      return this.left.slice(start, stop);97    }98    if (start > this.left.length) {99      return this.right.slice(this.right.length - stop + this.left.length, this.right.length - start + this.left.length).reverse();100    }101    return this.left.slice(start).concat(this.right.slice(this.right.length - stop + this.left.length).reverse());102  }103 104  /**105   * Mimics the behavior of Array.prototype.splice() except for the change of106   * interface necessary to avoid segfaults when patching in very large arrays.107   *108   * This operation moves cursor is moved to `start` and results in the cursor109   * placed after any inserted items.110   *111   * @param {number} start112   *   Start;113   *   zero-based index at which to start changing the array;114   *   negative numbers count backwards from the end of the array and values115   *   that are out-of bounds are clamped to the appropriate end of the array.116   * @param {number | null | undefined} [deleteCount=0]117   *   Delete count (default: `0`);118   *   maximum number of elements to delete, starting from start.119   * @param {Array<T> | null | undefined} [items=[]]120   *   Items to include in place of the deleted items (default: `[]`).121   * @return {Array<T>}122   *   Any removed items.123   */124  splice(start, deleteCount, items) {125    /** @type {number} */126    const count = deleteCount || 0;127    this.setCursor(Math.trunc(start));128    const removed = this.right.splice(this.right.length - count, Number.POSITIVE_INFINITY);129    if (items) chunkedPush(this.left, items);130    return removed.reverse();131  }132 133  /**134   * Remove and return the highest-numbered item in the array, so135   * `list[list.length - 1]`;136   * Moves the cursor to `length`.137   *138   * @returns {T | undefined}139   *   Item, optional.140   */141  pop() {142    this.setCursor(Number.POSITIVE_INFINITY);143    return this.left.pop();144  }145 146  /**147   * Inserts a single item to the high-numbered side of the array;148   * moves the cursor to `length`.149   *150   * @param {T} item151   *   Item.152   * @returns {undefined}153   *   Nothing.154   */155  push(item) {156    this.setCursor(Number.POSITIVE_INFINITY);157    this.left.push(item);158  }159 160  /**161   * Inserts many items to the high-numbered side of the array.162   * Moves the cursor to `length`.163   *164   * @param {Array<T>} items165   *   Items.166   * @returns {undefined}167   *   Nothing.168   */169  pushMany(items) {170    this.setCursor(Number.POSITIVE_INFINITY);171    chunkedPush(this.left, items);172  }173 174  /**175   * Inserts a single item to the low-numbered side of the array;176   * Moves the cursor to `0`.177   *178   * @param {T} item179   *   Item.180   * @returns {undefined}181   *   Nothing.182   */183  unshift(item) {184    this.setCursor(0);185    this.right.push(item);186  }187 188  /**189   * Inserts many items to the low-numbered side of the array;190   * moves the cursor to `0`.191   *192   * @param {Array<T>} items193   *   Items.194   * @returns {undefined}195   *   Nothing.196   */197  unshiftMany(items) {198    this.setCursor(0);199    chunkedPush(this.right, items.reverse());200  }201 202  /**203   * Move the cursor to a specific position in the array. Requires204   * time proportional to the distance moved.205   *206   * If `n < 0`, the cursor will end up at the beginning.207   * If `n > length`, the cursor will end up at the end.208   *209   * @param {number} n210   *   Position.211   * @return {undefined}212   *   Nothing.213   */214  setCursor(n) {215    if (n === this.left.length || n > this.left.length && this.right.length === 0 || n < 0 && this.left.length === 0) return;216    if (n < this.left.length) {217      // Move cursor to the this.left218      const removed = this.left.splice(n, Number.POSITIVE_INFINITY);219      chunkedPush(this.right, removed.reverse());220    } else {221      // Move cursor to the this.right222      const removed = this.right.splice(this.left.length + this.right.length - n, Number.POSITIVE_INFINITY);223      chunkedPush(this.left, removed.reverse());224    }225  }226}227 228/**229 * Avoid stack overflow by pushing items onto the stack in segments230 *231 * @template T232 *   Item type.233 * @param {Array<T>} list234 *   List to inject into.235 * @param {ReadonlyArray<T>} right236 *   Items to inject.237 * @return {undefined}238 *   Nothing.239 */240function chunkedPush(list, right) {241  /** @type {number} */242  let chunkStart = 0;243  if (right.length < 10000) {244    list.push(...right);245  } else {246    while (chunkStart < right.length) {247      list.push(...right.slice(chunkStart, chunkStart + 10000));248      chunkStart += 10000;249    }250  }251}