summary refs log tree commit diff
path: root/src/util/BitField.ts
blob: 5cccd3528c6b58041ebe25cf4020cb43e5a57219 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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(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);
	}
}