summary refs log tree commit diff
path: root/util/util/BitField.ts
diff options
context:
space:
mode:
Diffstat (limited to 'util/util/BitField.ts')
-rw-r--r--util/util/BitField.ts143
1 files changed, 143 insertions, 0 deletions
diff --git a/util/util/BitField.ts b/util/util/BitField.ts
new file mode 100644

index 00000000..728dc632 --- /dev/null +++ b/util/util/BitField.ts
@@ -0,0 +1,143 @@ +"use strict"; + +// https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +export type BitFieldResolvable = number | BigInt | BitField | string | BitFieldResolvable[]; + +/** + * Data structure that makes it easy to interact with a bitfield. + */ +export class BitField { + public bitfield: bigint = BigInt(0); + + public static FLAGS: Record<string, bigint> = {}; + + constructor(bits: BitFieldResolvable = 0) { + this.bitfield = BitField.resolve.call(this, bits); + } + + /** + * Checks whether the bitfield has a bit, or any of multiple bits. + */ + any(bit: BitFieldResolvable): boolean { + return (this.bitfield & BitField.resolve.call(this, bit)) !== 0n; + } + + /** + * Checks if this bitfield equals another + */ + equals(bit: BitFieldResolvable): boolean { + return this.bitfield === BitField.resolve.call(this, bit); + } + + /** + * Checks whether the bitfield has a bit, or multiple bits. + */ + has(bit: BitFieldResolvable): boolean { + if (Array.isArray(bit)) return bit.every((p) => this.has(p)); + const BIT = BitField.resolve.call(this, bit); + return (this.bitfield & BIT) === BIT; + } + + /** + * Gets all given bits that are missing from the bitfield. + */ + missing(bits: BitFieldResolvable) { + if (!Array.isArray(bits)) bits = new BitField(bits).toArray(); + return bits.filter((p) => !this.has(p)); + } + + /** + * Freezes these bits, making them immutable. + */ + freeze(): Readonly<BitField> { + return Object.freeze(this); + } + + /** + * Adds bits to these ones. + * @param {...BitFieldResolvable} [bits] Bits to add + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + add(...bits: BitFieldResolvable[]): BitField { + let total = 0n; + for (const bit of bits) { + total |= BitField.resolve.call(this, bit); + } + if (Object.isFrozen(this)) return new BitField(this.bitfield | total); + this.bitfield |= total; + return this; + } + + /** + * Removes bits from these. + * @param {...BitFieldResolvable} [bits] Bits to remove + */ + remove(...bits: BitFieldResolvable[]) { + let total = 0n; + for (const bit of bits) { + total |= BitField.resolve.call(this, bit); + } + if (Object.isFrozen(this)) return new BitField(this.bitfield & ~total); + this.bitfield &= ~total; + return this; + } + + /** + * Gets an object mapping field names to a {@link boolean} indicating whether the + * bit is available. + * @param {...*} hasParams Additional parameters for the has method, if any + */ + serialize() { + const serialized: Record<string, boolean> = {}; + for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit); + return serialized; + } + + /** + * Gets an {@link Array} of bitfield names based on the bits available. + */ + toArray(): string[] { + return Object.keys(BitField.FLAGS).filter((bit) => this.has(bit)); + } + + toJSON() { + return this.bitfield; + } + + valueOf() { + return this.bitfield; + } + + *[Symbol.iterator]() { + yield* this.toArray(); + } + + /** + * Data that can be resolved to give a bitfield. This can be: + * * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS}) + * * An instance of BitField + * * An Array of BitFieldResolvable + * @typedef {number|BitField|BitFieldResolvable[]} BitFieldResolvable + */ + + /** + * Resolves bitfields to their numeric form. + * @param {BitFieldResolvable} [bit=0] - bit(s) to resolve + * @returns {number} + */ + static resolve(bit: BitFieldResolvable = 0n): bigint { + // @ts-ignore + const FLAGS = this.FLAGS || this.constructor?.FLAGS; + if ((typeof bit === "number" || typeof bit === "bigint") && bit >= 0n) return BigInt(bit); + if (bit instanceof BitField) return bit.bitfield; + if (Array.isArray(bit)) { + // @ts-ignore + const resolve = this.constructor?.resolve || this.resolve; + return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), 0n); + } + if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit]; + throw new RangeError("BITFIELD_INVALID: " + bit); + } +}