summary refs log tree commit diff
path: root/src/util/util/BitField.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/util/BitField.ts')
-rw-r--r--src/util/util/BitField.ts150
1 files changed, 150 insertions, 0 deletions
diff --git a/src/util/util/BitField.ts b/src/util/util/BitField.ts
new file mode 100644
index 00000000..9bdbf6d7
--- /dev/null
+++ b/src/util/util/BitField.ts
@@ -0,0 +1,150 @@
+"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)) !== BigInt(0);
+	}
+
+	/**
+	 * 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 = BigInt(0);
+		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 = BigInt(0);
+		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 = BigInt(0)): bigint {
+		// @ts-ignore
+		const FLAGS = this.FLAGS || this.constructor?.FLAGS;
+		if ((typeof bit === "number" || typeof bit === "bigint") && bit >= BigInt(0)) 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), BigInt(0));
+		}
+		if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit];
+		if (bit === "0") return BigInt(0); //special case: 0
+		if (typeof bit === "string") return BigInt(bit); //last ditch effort...
+		if(/--debug|--inspect/.test(process.execArgv.join(' '))) debugger; //if you're here, we have an invalid bitfield... if bit is 0, thats fine, I guess...
+		throw new RangeError("BITFIELD_INVALID: " + bit);
+	}
+}
+
+export function BitFlag(x: bigint | number) {
+	return BigInt(1) << BigInt(x);
+}