diff --git a/packages/stdlib/src/bits/flags/index.test.ts b/packages/stdlib/src/bits/flags/index.test.ts new file mode 100644 index 0000000..5c3f48d --- /dev/null +++ b/packages/stdlib/src/bits/flags/index.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect } from 'vitest'; +import { flagsGenerator, and, or, not, has, is, unset, toggle } from './index'; + +describe('flagsGenerator', () => { + it('generate unique flags', () => { + const generateFlag = flagsGenerator(); + + const flag1 = generateFlag(); + const flag2 = generateFlag(); + const flag3 = generateFlag(); + + expect(flag1).toBe(1); + expect(flag2).toBe(2); + expect(flag3).toBe(4); + }); + + it('throw an error if more than 31 flags are created', () => { + const generateFlag = flagsGenerator(); + + for (let i = 0; i < 31; i++) { + generateFlag(); + } + + expect(() => generateFlag()).toThrow(RangeError); + }); +}); + +describe('flagsAnd', () => { + it('no effect on zero flags', () => { + const result = and(); + + expect(result).toBe(-1); + }); + + it('source flag is returned if no flags are provided', () => { + const result = and(0b1010); + + expect(result).toBe(0b1010); + }); + + it('perform bitwise AND operation on flags', () => { + const result = and(0b1111, 0b1010, 0b1100); + + expect(result).toBe(0b1000); + }); +}); + +describe('flagsOr', () => { + it('no effect on zero flags', () => { + const result = or(); + + expect(result).toBe(0); + }); + + it('source flag is returned if no flags are provided', () => { + const result = or(0b1010); + + expect(result).toBe(0b1010); + }); + + it('perform bitwise OR operation on flags', () => { + const result = or(0b1111, 0b1010, 0b1100); + + expect(result).toBe(0b1111); + }); +}); + +describe('flagsNot', () => { + it('perform bitwise NOT operation on a flag', () => { + const result = not(0b101); + + expect(result).toBe(-0b110); + }); +}); + +describe('flagsHas', () => { + it('check if a flag has a specific bit set', () => { + const result = has(0b1010, 0b1000); + + expect(result).toBe(true); + }); + + it('check if a flag has a specific bit unset', () => { + const result = has(0b1010, 0b0100); + + expect(result).toBe(false); + }); +}); + +describe('flagsIs', () => { + it('check if a flag is set', () => { + const result = is(0b1010); + + expect(result).toBe(true); + }); + + it('check if a flag is unset', () => { + const result = is(0); + + expect(result).toBe(false); + }); +}); + +describe('flagsUnset', () => { + it('unset a flag', () => { + const result = unset(0b1010, 0b1000); + + expect(result).toBe(0b0010); + }); +}); + +describe('flagsToggle', () => { + it('toggle a flag', () => { + const result = toggle(0b1010, 0b1000); + + expect(result).toBe(0b0010); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/bits/flags/index.ts b/packages/stdlib/src/bits/flags/index.ts new file mode 100644 index 0000000..772f4cd --- /dev/null +++ b/packages/stdlib/src/bits/flags/index.ts @@ -0,0 +1,90 @@ +/** + * Create a function that generates unique flags + * + * @returns {Function} A function that generates unique flags + * @throws {RangeError} If more than 31 flags are created + */ +export function flagsGenerator() { + let lastFlag = 0; + + return () => { + // 31 flags is the maximum number of flags that can be created + // (without zero) because of the 32-bit integer limit in bitwise operations + if (lastFlag & 0x40000000) + throw new RangeError('Cannot create more than 31 flags'); + + return (lastFlag = lastFlag === 0 ? 1 : lastFlag << 1); + }; +} + +/** + * Function to combine multiple flags using the AND operator + * + * @param {number[]} flags - The flags to combine + * @returns {number} The combined flags + */ +export function and(...flags: number[]) { + return flags.reduce((acc, flag) => acc & flag, -1); +} + +/** + * Function to combine multiple flags using the OR operator + * + * @param {number[]} flags - The flags to combine + * @returns {number} The combined flags + */ +export function or(...flags: number[]) { + return flags.reduce((acc, flag) => acc | flag, 0); +} + +/** + * Function to apply the NOT operator to a flag + * + * @param {number} flag - The flag to apply the NOT operator to + * @returns {number} The result of the NOT operator + */ +export function not(flag: number) { + return ~flag; +} + +/** + * Function to make sure a flag has a specific bit set + * + * @param {number} flag - The flag to check + * @returns {boolean} Whether the flag has the bit set + */ +export function has(flag: number, other: number) { + return (flag & other) === other; +} + +/** + * Function to check if a flag is set + * + * @param {number} flag - The flag to check + * @returns {boolean} Whether the flag is set + */ +export function is(flag: number) { + return flag !== 0; +} + +/** + * Function to unset a flag + * + * @param {number} flag - Source flag + * @param {number} other - Flag to unset + * @returns {number} The new flag + */ +export function unset(flag: number, other: number) { + return flag & ~other; +} + +/** + * Function to toggle (xor) a flag + * + * @param {number} flag - Source flag + * @param {number} other - Flag to toggle + * @returns {number} The new flag + */ +export function toggle(flag: number, other: number) { + return flag ^ other; +}