CoolFace
Apppublic

AK-21/Graphite-Industrial-Intelligence

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
splice-buffer.js289 linesDownload Raw Back to lib
1import {constants} from 'micromark-util-symbol'2 3/**4 * Some of the internal operations of micromark do lots of editing5 * operations on very large arrays. This runs into problems with two6 * properties of most circa-2020 JavaScript interpreters:7 *8 *  - Array-length modifications at the high end of an array (push/pop) are9 *    expected to be common and are implemented in (amortized) time10 *    proportional to the number of elements added or removed, whereas11 *    other operations (shift/unshift and splice) are much less efficient.12 *  - Function arguments are passed on the stack, so adding tens of thousands13 *    of elements to an array with `arr.push(...newElements)` will frequently14 *    cause stack overflows. (see <https://stackoverflow.com/questions/22123769/rangeerror-maximum-call-stack-size-exceeded-why>)15 *16 * SpliceBuffers are an implementation of gap buffers, which are a17 * generalization of the "queue made of two stacks" idea. The splice buffer18 * maintains a cursor, and moving the cursor has cost proportional to the19 * distance the cursor moves, but inserting, deleting, or splicing in20 * new information at the cursor is as efficient as the push/pop operation.21 * This allows for an efficient sequence of splices (or pushes, pops, shifts,22 * or unshifts) as long such edits happen at the same part of the array or23 * generally sweep through the array from the beginning to the end.24 *25 * The interface for splice buffers also supports large numbers of inputs by26 * passing a single array argument rather passing multiple arguments on the27 * function call stack.28 *29 * @template T30 *   Item type.31 */32export class SpliceBuffer {33  /**34   * @param {ReadonlyArray<T> | null | undefined} [initial]35   *   Initial items (optional).36   * @returns37   *   Splice buffer.38   */39  constructor(initial) {40    /** @type {Array<T>} */41    this.left = initial ? [...initial] : []42    /** @type {Array<T>} */43    this.right = []44  }45 46  /**47   * Array access;48   * does not move the cursor.49   *50   * @param {number} index51   *   Index.52   * @return {T}53   *   Item.54   */55  get(index) {56    if (index < 0 || index >= this.left.length + this.right.length) {57      throw new RangeError(58        'Cannot access index `' +59          index +60          '` in a splice buffer of size `' +61          (this.left.length + this.right.length) +62          '`'63      )64    }65 66    if (index < this.left.length) return this.left[index]67    return this.right[this.right.length - index + this.left.length - 1]68  }69 70  /**71   * The length of the splice buffer, one greater than the largest index in the72   * array.73   */74  get length() {75    return this.left.length + this.right.length76  }77 78  /**79   * Remove and return `list[0]`;80   * moves the cursor to `0`.81   *82   * @returns {T | undefined}83   *   Item, optional.84   */85  shift() {86    this.setCursor(0)87    return this.right.pop()88  }89 90  /**91   * Slice the buffer to get an array;92   * does not move the cursor.93   *94   * @param {number} start95   *   Start.96   * @param {number | null | undefined} [end]97   *   End (optional).98   * @returns {Array<T>}99   *   Array of items.100   */101  slice(start, end) {102    /** @type {number} */103    const stop =104      end === null || end === undefined ? Number.POSITIVE_INFINITY : end105 106    if (stop < this.left.length) {107      return this.left.slice(start, stop)108    }109 110    if (start > this.left.length) {111      return this.right112        .slice(113          this.right.length - stop + this.left.length,114          this.right.length - start + this.left.length115        )116        .reverse()117    }118 119    return this.left120      .slice(start)121      .concat(122        this.right.slice(this.right.length - stop + this.left.length).reverse()123      )124  }125 126  /**127   * Mimics the behavior of Array.prototype.splice() except for the change of128   * interface necessary to avoid segfaults when patching in very large arrays.129   *130   * This operation moves cursor is moved to `start` and results in the cursor131   * placed after any inserted items.132   *133   * @param {number} start134   *   Start;135   *   zero-based index at which to start changing the array;136   *   negative numbers count backwards from the end of the array and values137   *   that are out-of bounds are clamped to the appropriate end of the array.138   * @param {number | null | undefined} [deleteCount=0]139   *   Delete count (default: `0`);140   *   maximum number of elements to delete, starting from start.141   * @param {Array<T> | null | undefined} [items=[]]142   *   Items to include in place of the deleted items (default: `[]`).143   * @return {Array<T>}144   *   Any removed items.145   */146  splice(start, deleteCount, items) {147    /** @type {number} */148    const count = deleteCount || 0149 150    this.setCursor(Math.trunc(start))151    const removed = this.right.splice(152      this.right.length - count,153      Number.POSITIVE_INFINITY154    )155    if (items) chunkedPush(this.left, items)156    return removed.reverse()157  }158 159  /**160   * Remove and return the highest-numbered item in the array, so161   * `list[list.length - 1]`;162   * Moves the cursor to `length`.163   *164   * @returns {T | undefined}165   *   Item, optional.166   */167  pop() {168    this.setCursor(Number.POSITIVE_INFINITY)169    return this.left.pop()170  }171 172  /**173   * Inserts a single item to the high-numbered side of the array;174   * moves the cursor to `length`.175   *176   * @param {T} item177   *   Item.178   * @returns {undefined}179   *   Nothing.180   */181  push(item) {182    this.setCursor(Number.POSITIVE_INFINITY)183    this.left.push(item)184  }185 186  /**187   * Inserts many items to the high-numbered side of the array.188   * Moves the cursor to `length`.189   *190   * @param {Array<T>} items191   *   Items.192   * @returns {undefined}193   *   Nothing.194   */195  pushMany(items) {196    this.setCursor(Number.POSITIVE_INFINITY)197    chunkedPush(this.left, items)198  }199 200  /**201   * Inserts a single item to the low-numbered side of the array;202   * Moves the cursor to `0`.203   *204   * @param {T} item205   *   Item.206   * @returns {undefined}207   *   Nothing.208   */209  unshift(item) {210    this.setCursor(0)211    this.right.push(item)212  }213 214  /**215   * Inserts many items to the low-numbered side of the array;216   * moves the cursor to `0`.217   *218   * @param {Array<T>} items219   *   Items.220   * @returns {undefined}221   *   Nothing.222   */223  unshiftMany(items) {224    this.setCursor(0)225    chunkedPush(this.right, items.reverse())226  }227 228  /**229   * Move the cursor to a specific position in the array. Requires230   * time proportional to the distance moved.231   *232   * If `n < 0`, the cursor will end up at the beginning.233   * If `n > length`, the cursor will end up at the end.234   *235   * @param {number} n236   *   Position.237   * @return {undefined}238   *   Nothing.239   */240  setCursor(n) {241    if (242      n === this.left.length ||243      (n > this.left.length && this.right.length === 0) ||244      (n < 0 && this.left.length === 0)245    )246      return247    if (n < this.left.length) {248      // Move cursor to the this.left249      const removed = this.left.splice(n, Number.POSITIVE_INFINITY)250      chunkedPush(this.right, removed.reverse())251    } else {252      // Move cursor to the this.right253      const removed = this.right.splice(254        this.left.length + this.right.length - n,255        Number.POSITIVE_INFINITY256      )257      chunkedPush(this.left, removed.reverse())258    }259  }260}261 262/**263 * Avoid stack overflow by pushing items onto the stack in segments264 *265 * @template T266 *   Item type.267 * @param {Array<T>} list268 *   List to inject into.269 * @param {ReadonlyArray<T>} right270 *   Items to inject.271 * @return {undefined}272 *   Nothing.273 */274function chunkedPush(list, right) {275  /** @type {number} */276  let chunkStart = 0277 278  if (right.length < constants.v8MaxSafeChunkSize) {279    list.push(...right)280  } else {281    while (chunkStart < right.length) {282      list.push(283        ...right.slice(chunkStart, chunkStart + constants.v8MaxSafeChunkSize)284      )285      chunkStart += constants.v8MaxSafeChunkSize286    }287  }288}289