Newer
Older
percord / src / util / util / BitField.ts
"use strict";

// https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
// @fc-license-skip

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 {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        const FLAGS = this.FLAGS || this.constructor?.FLAGS;

        if (typeof bit === "string") {
            if (typeof FLAGS[bit] !== "undefined") return FLAGS[bit];
            else bit = BigInt(bit);
        }

        if ((typeof bit === "number" || typeof bit === "bigint") && bit >= BigInt(0)) return BigInt(bit);

        if (bit instanceof BitField) return bit.bitfield;

        if (Array.isArray(bit)) {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @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));
        }

        throw new RangeError("BITFIELD_INVALID: " + bit);
    }
}

export function BitFlag(x: bigint | number) {
    return BigInt(1) << BigInt(x);
}