UNPKG

rc-js-util

Version:

A collection of TS and C++ utilities to help writing performant and correct applications, achieved through strict typing and (removable) invariant checking.

142 lines (122 loc) 3.59 kB
import { CircularBuffer } from "./circular-buffer.js"; import { _Production } from "../production/_production.js"; import { IFIFOStack } from "./i-fifo-stack.js"; /** * @public * Sets the behavior of {@link CircularFIFOStack} when a value is pushed which won't fit. * * @remarks * Does not affect underflow, which is always considered exceptional. */ export enum ECircularStackOverflowMode { /** * Do nothing. */ NoOp = 1, /** * Throw an error if the buffer overflows. */ Exception, /** * Overwrite the first value. */ Overwrite, /** * Doubles the stack size and copies in place, running in O(size). */ Grow, } /** * @public * Circular first in first out stack. * * @remarks * See {@link ECircularStackOverflowMode} for details of overflow behavior. */ export class CircularFIFOStack<TValue> implements IFIFOStack<TValue> { public constructor ( capacity: number, mode: ECircularStackOverflowMode = ECircularStackOverflowMode.Grow, ) { this.capacity = capacity; this.mode = mode; this.buffer = CircularBuffer.createEmpty(capacity); } public getCapacity(): number { return this.capacity; } /** * Pushes a value to the top of the stack (depending on `mode`). */ public push(value: TValue): void { if (this.start + this.buffer.size == this.end) { switch (this.mode) { case ECircularStackOverflowMode.NoOp: return; case ECircularStackOverflowMode.Exception: throw _Production.createError("Attempted to push to full stack."); break; case ECircularStackOverflowMode.Overwrite: this.pop(); break; case ECircularStackOverflowMode.Grow: this.growStack(); break; default: _Production.assertValueIsNever(this.mode); } } this.buffer.setValue(this.end++, value); } /** * Remove the bottom element in the stack and return it. * * @remarks * Attempting to pop an empty stack is considered exceptional regardless of `mode`. You can * call `getIsEmpty` or `getRemainingCapacity` to determine if pop is safe to call. */ public pop(): TValue { if (this.getIsEmpty()) { throw _Production.createError("Attempted to pop empty stack."); } // null out the value to avoid memory leaks return this.buffer.getSetValue(this.start++, null) as TValue; } public getIsEmpty(): boolean { return this.start == this.end; } public getRemainingCapacity(): number { return this.start + this.capacity - this.end; } private growStack(): void { const largerCircularStack = new CircularFIFOStack<TValue>(this.capacity * 2, this.mode); let size = this.capacity; while (size--) { const valueToCopy = this.pop(); largerCircularStack.push(valueToCopy); } this.buffer = largerCircularStack.buffer; this.capacity *= 2; this.start = largerCircularStack.start; this.end = largerCircularStack.end; } private buffer: CircularBuffer<TValue | null>; private readonly mode: ECircularStackOverflowMode; private capacity: number; private start = 0; private end = 0; }