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
text/typescript
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;
}