From 53c969370a0f511ac4cd6617f3e8f3feb0c86a74 Mon Sep 17 00:00:00 2001 From: robonen Date: Fri, 25 Oct 2024 06:20:43 +0700 Subject: [PATCH 1/5] refactor(packages/stdlib): separate flags and helpers in bit tools --- packages/stdlib/src/bits/flags/index.test.ts | 94 +--------------- packages/stdlib/src/bits/flags/index.ts | 101 ------------------ .../stdlib/src/bits/helpers/index.test.ts | 95 ++++++++++++++++ packages/stdlib/src/bits/helpers/index.ts | 100 +++++++++++++++++ 4 files changed, 196 insertions(+), 194 deletions(-) create mode 100644 packages/stdlib/src/bits/helpers/index.test.ts create mode 100644 packages/stdlib/src/bits/helpers/index.ts diff --git a/packages/stdlib/src/bits/flags/index.test.ts b/packages/stdlib/src/bits/flags/index.test.ts index 2d8de66..b9dffb3 100644 --- a/packages/stdlib/src/bits/flags/index.test.ts +++ b/packages/stdlib/src/bits/flags/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { flagsGenerator, and, or, not, has, is, unset, toggle } from './index'; +import { flagsGenerator } from '.'; describe('flagsGenerator', () => { it('generate unique flags', () => { @@ -24,95 +24,3 @@ describe('flagsGenerator', () => { expect(() => generateFlag()).toThrow(new RangeError('Cannot create more than 31 flags')); }); }); - -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 index 36f5e86..1be8ed3 100644 --- a/packages/stdlib/src/bits/flags/index.ts +++ b/packages/stdlib/src/bits/flags/index.ts @@ -20,104 +20,3 @@ export function flagsGenerator() { return (lastFlag = lastFlag === 0 ? 1 : lastFlag << 1); }; } - -/** - * @name and - * @category Bits - * @description Function to combine multiple flags using the AND operator - * - * @param {number[]} flags - The flags to combine - * @returns {number} The combined flags - * - * @since 0.0.2 - */ -export function and(...flags: number[]) { - return flags.reduce((acc, flag) => acc & flag, -1); -} - -/** - * @name or - * @category Bits - * @description Function to combine multiple flags using the OR operator - * - * @param {number[]} flags - The flags to combine - * @returns {number} The combined flags - * - * @since 0.0.2 - */ -export function or(...flags: number[]) { - return flags.reduce((acc, flag) => acc | flag, 0); -} - -/** - * @name not - * @category Bits - * @description Function to combine multiple flags using the XOR operator - * - * @param {number} flag - The flag to apply the NOT operator to - * @returns {number} The result of the NOT operator - * - * @since 0.0.2 - */ -export function not(flag: number) { - return ~flag; -} - -/** - * @name has - * @category Bits - * @description Function to make sure a flag has a specific bit set - * - * @param {number} flag - The flag to check - * @param {number} other - Flag to check - * @returns {boolean} Whether the flag has the bit set - * - * @since 0.0.2 - */ -export function has(flag: number, other: number) { - return (flag & other) === other; -} - -/** - * @name is - * @category Bits - * @description Function to check if a flag is set - * - * @param {number} flag - The flag to check - * @returns {boolean} Whether the flag is set - * - * @since 0.0.2 - */ -export function is(flag: number) { - return flag !== 0; -} - -/** - * @name unset - * @category Bits - * @description Function to unset a flag - * - * @param {number} flag - Source flag - * @param {number} other - Flag to unset - * @returns {number} The new flag - * - * @since 0.0.2 - */ -export function unset(flag: number, other: number) { - return flag & ~other; -} - -/** - * @name toggle - * @category Bits - * @description Function to toggle (xor) a flag - * - * @param {number} flag - Source flag - * @param {number} other - Flag to toggle - * @returns {number} The new flag - * - * @since 0.0.2 - */ -export function toggle(flag: number, other: number) { - return flag ^ other; -} diff --git a/packages/stdlib/src/bits/helpers/index.test.ts b/packages/stdlib/src/bits/helpers/index.test.ts new file mode 100644 index 0000000..502e322 --- /dev/null +++ b/packages/stdlib/src/bits/helpers/index.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from 'vitest'; +import { and, or, not, has, is, unset, toggle } from '.'; + + +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/helpers/index.ts b/packages/stdlib/src/bits/helpers/index.ts new file mode 100644 index 0000000..ff4b692 --- /dev/null +++ b/packages/stdlib/src/bits/helpers/index.ts @@ -0,0 +1,100 @@ +/** + * @name and + * @category Bits + * @description Function to combine multiple flags using the AND operator + * + * @param {number[]} flags - The flags to combine + * @returns {number} The combined flags + * + * @since 0.0.2 + */ +export function and(...flags: number[]) { + return flags.reduce((acc, flag) => acc & flag, -1); +} + +/** +* @name or +* @category Bits +* @description Function to combine multiple flags using the OR operator +* +* @param {number[]} flags - The flags to combine +* @returns {number} The combined flags +* +* @since 0.0.2 +*/ +export function or(...flags: number[]) { + return flags.reduce((acc, flag) => acc | flag, 0); +} + +/** +* @name not +* @category Bits +* @description Function to combine multiple flags using the XOR operator +* +* @param {number} flag - The flag to apply the NOT operator to +* @returns {number} The result of the NOT operator +* +* @since 0.0.2 +*/ +export function not(flag: number) { + return ~flag; +} + +/** +* @name has +* @category Bits +* @description Function to make sure a flag has a specific bit set +* +* @param {number} flag - The flag to check +* @param {number} other - Flag to check +* @returns {boolean} Whether the flag has the bit set +* +* @since 0.0.2 +*/ +export function has(flag: number, other: number) { + return (flag & other) === other; +} + +/** +* @name is +* @category Bits +* @description Function to check if a flag is set +* +* @param {number} flag - The flag to check +* @returns {boolean} Whether the flag is set +* +* @since 0.0.2 +*/ +export function is(flag: number) { + return flag !== 0; +} + +/** +* @name unset +* @category Bits +* @description Function to unset a flag +* +* @param {number} flag - Source flag +* @param {number} other - Flag to unset +* @returns {number} The new flag +* +* @since 0.0.2 +*/ +export function unset(flag: number, other: number) { + return flag & ~other; +} + +/** +* @name toggle +* @category Bits +* @description Function to toggle (xor) a flag +* +* @param {number} flag - Source flag +* @param {number} other - Flag to toggle +* @returns {number} The new flag +* +* @since 0.0.2 +*/ +export function toggle(flag: number, other: number) { + return flag ^ other; +} From 8080e7eafe95f0705bd83a098b5cc78743c4adb6 Mon Sep 17 00:00:00 2001 From: robonen Date: Fri, 25 Oct 2024 06:22:42 +0700 Subject: [PATCH 2/5] feat(packages/stdlib): add bit vector tool --- packages/stdlib/src/bits/vector/index.test.ts | 57 +++++++++++++++++ packages/stdlib/src/bits/vector/index.ts | 61 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 packages/stdlib/src/bits/vector/index.test.ts create mode 100644 packages/stdlib/src/bits/vector/index.ts diff --git a/packages/stdlib/src/bits/vector/index.test.ts b/packages/stdlib/src/bits/vector/index.test.ts new file mode 100644 index 0000000..7940e71 --- /dev/null +++ b/packages/stdlib/src/bits/vector/index.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest'; +import { BitVector } from '.'; + +describe('BitVector', () => { + it('should initialize with the correct size', () => { + const size = 16; + const expectedSize = Math.ceil(size / 8); + const bitVector = new BitVector(size); + + expect(bitVector.length).toBe(expectedSize); + }); + + it('should set and get bits correctly', () => { + const bitVector = new BitVector(16); + bitVector.setBit(5); + + expect(bitVector.getBit(5)).toBe(true); + expect(bitVector.getBit(4)).toBe(false); + }); + + it('should clear bits correctly', () => { + const bitVector = new BitVector(16); + bitVector.setBit(5); + + expect(bitVector.getBit(5)).toBe(true); + + bitVector.clearBit(5); + + expect(bitVector.getBit(5)).toBe(false); + }); + + it('should find the previous bit correctly', () => { + const bitVector = new BitVector(100); + const indices = [99, 88, 66, 65, 64, 63, 15, 14, 1, 0]; + const result = []; + indices.forEach(index => bitVector.setBit(index)); + + for (let i = bitVector.previousBit(100); i !== -1; i = bitVector.previousBit(i)) { + result.push(i); + } + + expect(result).toEqual(indices); + }); + + it('should return -1 when no previous bit is found', () => { + const bitVector = new BitVector(16); + + expect(bitVector.previousBit(0)).toBe(-1); + }); + + it('should throw RangeError when previousBit is called with an unreachable value', () => { + const bitVector = new BitVector(16); + bitVector.setBit(5); + + expect(() => bitVector.previousBit(24)).toThrow(new RangeError('Unreachable value')); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/bits/vector/index.ts b/packages/stdlib/src/bits/vector/index.ts new file mode 100644 index 0000000..aa1c745 --- /dev/null +++ b/packages/stdlib/src/bits/vector/index.ts @@ -0,0 +1,61 @@ +export interface BitVector { + getBit(index: number): boolean; + setBit(index: number): void; + clearBit(index: number): void; + previousBit(index: number): number; +} + +/** + * @name BitVector + * @category Bits + * @description A bit vector is a vector of bits that can be used to store a collection of bits + * + * @since 0.0.3 + */ +export class BitVector extends Uint8Array implements BitVector { + constructor(size: number) { + super(Math.ceil(size / 8)); + } + + getBit(index: number) { + const value = this[index >> 3]! & (1 << (index & 7)); + return value !== 0; + } + + setBit(index: number) { + this[index >> 3]! |= 1 << (index & 7); + } + + clearBit(index: number): void { + this[index >> 3]! &= ~(1 << (index & 7)); + } + + previousBit(index: number): number { + while (index !== ((index >> 3) << 3)) { + --index; + + if (this.getBit(index)) { + return index; + } + } + + let byteIndex = (index >> 3) - 1; + + while (byteIndex >= 0 && this[byteIndex] === 0) + --byteIndex; + + if (byteIndex < 0) + return -1; + + index = (byteIndex << 3) + 7; + + while (index >= (byteIndex << 3)) { + if (this.getBit(index)) + return index; + + --index; + } + + throw new RangeError('Unreachable value'); + } +} \ No newline at end of file From c96213137ef8bd48ba3b06c609fca6d005fe8c6b Mon Sep 17 00:00:00 2001 From: robonen Date: Sat, 26 Oct 2024 06:37:27 +0700 Subject: [PATCH 3/5] refactor(packages/stdlib): stack return undefined on peek if it empty --- packages/stdlib/src/structs/stack/index.test.ts | 6 ++++-- packages/stdlib/src/structs/stack/index.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/stdlib/src/structs/stack/index.test.ts b/packages/stdlib/src/structs/stack/index.test.ts index e67522a..7d3920a 100644 --- a/packages/stdlib/src/structs/stack/index.test.ts +++ b/packages/stdlib/src/structs/stack/index.test.ts @@ -79,9 +79,11 @@ describe('stack', () => { expect(stack.length).toBe(3); }); - it('throw an error if the stack is empty', () => { + it('return undefined if the stack is empty', () => { const stack = new Stack(); - expect(() => stack.peek()).toThrow(new RangeError('Stack is empty')); + const topElement = stack.peek(); + + expect(topElement).toBeUndefined(); }); }); diff --git a/packages/stdlib/src/structs/stack/index.ts b/packages/stdlib/src/structs/stack/index.ts index db48251..6a6d1af 100644 --- a/packages/stdlib/src/structs/stack/index.ts +++ b/packages/stdlib/src/structs/stack/index.ts @@ -96,7 +96,7 @@ export class Stack implements Iterable, AsyncIterable { */ public peek() { if (this.isEmpty) - throw new RangeError('Stack is empty'); + return undefined; return last(this.stack); } From fc774bc1af6b1b12a5d1f715a52a0a8d33f5209a Mon Sep 17 00:00:00 2001 From: robonen Date: Sat, 26 Oct 2024 06:45:53 +0700 Subject: [PATCH 4/5] refactor(packages/stdlib): add small margin to the expected value --- packages/stdlib/src/async/sleep/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stdlib/src/async/sleep/index.test.ts b/packages/stdlib/src/async/sleep/index.test.ts index c86384d..263755c 100644 --- a/packages/stdlib/src/async/sleep/index.test.ts +++ b/packages/stdlib/src/async/sleep/index.test.ts @@ -14,6 +14,6 @@ describe('sleep', () => { const end = performance.now(); - expect(end - start).toBeGreaterThanOrEqual(delay); + expect(end - start).toBeGreaterThan(delay - 5); }); }); \ No newline at end of file From 3cc500f22b17ced785933ed094ea89ebcce85c35 Mon Sep 17 00:00:00 2001 From: robonen Date: Sat, 26 Oct 2024 06:47:59 +0700 Subject: [PATCH 5/5] chore(packages/stdlib): remove unused import --- packages/stdlib/src/async/sleep/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stdlib/src/async/sleep/index.test.ts b/packages/stdlib/src/async/sleep/index.test.ts index 263755c..c018887 100644 --- a/packages/stdlib/src/async/sleep/index.test.ts +++ b/packages/stdlib/src/async/sleep/index.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; import { sleep } from '.'; describe('sleep', () => {