From 06e7932ac566c27fbdfe5649e4283a13faeb06cb Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Fri, 13 Mar 2026 22:41:05 +0100 Subject: [PATCH] finished implementation of BigBitFlagSets --- node/src/builders/bigint.ts | 76 +++++++++++++++ node/src/builders/index.ts | 1 + node/src/definitions/bigint.ts | 88 +++++++++++++++++ node/src/definitions/index.ts | 11 ++- node/src/flagsets/bigint.ts | 72 +++++++++----- node/src/flagsets/number.ts | 54 +++++++---- node/src/index.ts | 7 +- node/tests/flagsets/bigint.test.ts | 149 +++++++++++++++++++++++++++-- 8 files changed, 406 insertions(+), 52 deletions(-) create mode 100644 node/src/builders/bigint.ts create mode 100644 node/src/definitions/bigint.ts diff --git a/node/src/builders/bigint.ts b/node/src/builders/bigint.ts new file mode 100644 index 0000000..78ac52d --- /dev/null +++ b/node/src/builders/bigint.ts @@ -0,0 +1,76 @@ +import { BigBitFlagDefinitionFactory, FlagDefinition } from '~/definitions' +import { BigBitFlagSet } from '~/flagsets' + +import { + applyDeclarations, + FlagWithValueOrOrdinal, + ListOfFlagsWithValueOrOrdinal, + NamedFlagWithValueOrOrdinal, +} from './declarative' +import { FlagSetBuilder } from './generic' +import { + DefineWithValueOrOrdinal, + RequireParentsOrDefineWithValueOrOrdinal, + WithValueOrOrdinal, + WithValueOrOrdinalOrCompose, +} from './syntax' + +export class BigBitFlagSetBuilder + implements + WithValueOrOrdinalOrCompose, + RequireParentsOrDefineWithValueOrOrdinal +{ + private readonly _underlying: FlagSetBuilder + + public constructor() { + this._underlying = new FlagSetBuilder() + } + + public define(): WithValueOrOrdinal + public define(alias: string): WithValueOrOrdinalOrCompose + public define(alias?: string): WithValueOrOrdinalOrCompose { + this._underlying.define(alias) + return this + } + + public compose(...flags: string[]): DefineWithValueOrOrdinal { + this._underlying.compose(flags) + return this + } + + public withValue( + value: bigint, + ): RequireParentsOrDefineWithValueOrOrdinal { + this._underlying.withValue(value) + return this + } + + public withOrdinal( + ordinal: number, + ): RequireParentsOrDefineWithValueOrOrdinal { + throw new Error('Method not implemented.') + } + + public requires(...flags: string[]): DefineWithValueOrOrdinal { + this._underlying.requires(flags) + return this + } + + public getResult(): BigBitFlagSet { + const graph = this._underlying.finish() + const factory = new BigBitFlagDefinitionFactory() + return new BigBitFlagSet(graph.intoDictionary(factory)) + } +} + +export function createBigBitFlagSet(declarations: FlagWithValueOrOrdinal[]): BigBitFlagSet +export function createBigBitFlagSet( + declarations: Record>, +): BigBitFlagSet & Record> +export function createBigBitFlagSet( + declarations: ListOfFlagsWithValueOrOrdinal, +): BigBitFlagSet { + const builder = new BigBitFlagSetBuilder() + applyDeclarations(declarations, builder) + return builder.getResult() +} diff --git a/node/src/builders/index.ts b/node/src/builders/index.ts index 65990aa..79bf65b 100644 --- a/node/src/builders/index.ts +++ b/node/src/builders/index.ts @@ -1,3 +1,4 @@ export { ArrayFlagSetBuilder, createArrayFlagSet } from './array' +export { BigBitFlagSetBuilder, createBigBitFlagSet } from './bigint' export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection' export { BitFlagSetBuilder, createBitFlagSet } from './number' diff --git a/node/src/definitions/bigint.ts b/node/src/definitions/bigint.ts new file mode 100644 index 0000000..d6d529a --- /dev/null +++ b/node/src/definitions/bigint.ts @@ -0,0 +1,88 @@ +import { ENV_BI } from '~/env' + +import { FlagDefinition, FlagDefinitionFactory, PartialFlagDefinition } from '.' + +interface PrecomputedValues { + base: bigint + additive: bigint + subtractive: bigint +} + +export class BigBitFlagDefinition implements FlagDefinition { + private readonly _baseValue: bigint + private readonly _additiveValue: bigint + private readonly _subtractiveValue: bigint + private readonly _alias: string | undefined + + public constructor(precomputedValues: PrecomputedValues, alias: string | undefined) { + this._baseValue = precomputedValues.base + this._additiveValue = precomputedValues.additive + this._subtractiveValue = precomputedValues.subtractive + this._alias = alias + } + + public get alias(): string | undefined { + return this._alias + } + + public get values(): bigint { + return this._baseValue + } + + public isIn(set: bigint): boolean { + return (set & this._additiveValue) === this._additiveValue + } + + public addTo(set: bigint): bigint { + return set | this._additiveValue + } + + public removeFrom(set: bigint): bigint { + return set | this._additiveValue + } +} + +export class BigBitFlagDefinitionFactory implements FlagDefinitionFactory { + public makeDefinitions( + sortedPartialDefinitions: PartialFlagDefinition[], + results: Map, FlagDefinition>, + ): void { + const precomputedValues = new Map, PrecomputedValues>() + + for (let i = 0; i < sortedPartialDefinitions.length; i++) { + const pfd = sortedPartialDefinitions[i] + + const values: PrecomputedValues = { + base: pfd.value ?? ENV_BI.ZERO, + additive: pfd.value ?? ENV_BI.ZERO, + subtractive: pfd.value ?? ENV_BI.ZERO, + } + + for (const parentPfd of pfd.parents) { + const parentValues = precomputedValues.get(parentPfd) + if (parentValues !== undefined) { + values.additive = values.additive | parentValues.additive + if (pfd.value === undefined) { + values.base = values.base | parentValues.base + } + } + } + + precomputedValues.set(pfd, values) + } + + for (let i = sortedPartialDefinitions.length - 1; i >= 0; i--) { + const pfd = sortedPartialDefinitions[i] + const values = precomputedValues.get(pfd)! + + for (const childPfd of pfd.children) { + const childValues = precomputedValues.get(childPfd) + if (childValues !== undefined) { + values.subtractive = values.subtractive | childValues.subtractive + } + } + + results.set(pfd, new BigBitFlagDefinition(values, pfd.alias)) + } + } +} diff --git a/node/src/definitions/index.ts b/node/src/definitions/index.ts index 8021a7e..28e9494 100644 --- a/node/src/definitions/index.ts +++ b/node/src/definitions/index.ts @@ -1,9 +1,10 @@ -export { FlagsDictionary, FlagDefinition, printFlagValue, valueToString } from './dictionary' +export { BigBitFlagDefinition, BigBitFlagDefinitionFactory } from './bigint' +export { FlagDefinition, FlagsDictionary, printFlagValue, valueToString } from './dictionary' export { - FlagsGraph, - PartialFlagInit, - PartialFlagDefinition, - refByAlias, FlagDefinitionFactory, + FlagsGraph, + PartialFlagDefinition, + PartialFlagInit, + refByAlias, } from './graph' export { BitFlagDefinition, BitFlagDefinitionFactory } from './number' diff --git a/node/src/flagsets/bigint.ts b/node/src/flagsets/bigint.ts index a7cc821..f78a1b5 100644 --- a/node/src/flagsets/bigint.ts +++ b/node/src/flagsets/bigint.ts @@ -1,33 +1,31 @@ +import { FlagDefinition, FlagsDictionary } from '~/definitions' +import { BigBitFlagsIterator, EnumerateFlags, useIterator } from '~/enumeration' +import { ENV_BI } from '~/env' + import type { FlagSet } from '.' -import { UnavailableFeatureError } from '../errors' -import { ENV_BI } from '../env' -import { BigBitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration' export class BigBitFlagSet implements FlagSet { - /** - * Creates a new empty flag set. - * - * @throws UnavailableFeatureError When this constructor is called in an - * environment that does not natively support {@link BigInt}s. - */ - public constructor() { - if (!ENV_BI.AVAILABLE) { - throw new UnavailableFeatureError('BigInts') - } - } + private readonly _dictionary: FlagsDictionary - minimum(flags: bigint): bigint { - throw new Error('Method not implemented.') - } - - maximum(flags: bigint): bigint { - throw new Error('Method not implemented.') + public constructor(dictionary: FlagsDictionary) { + this._dictionary = dictionary } public none(): bigint { return ENV_BI.ZERO } + public of(...values: bigint[]): bigint { + return values.reduce((set, value) => set | value, ENV_BI.ZERO) + } + + public named(...aliases: string[]): bigint { + return aliases.reduce( + (set, alias) => set | (this.getFlag(alias)?.values ?? ENV_BI.ZERO), + ENV_BI.ZERO, + ) + } + public union(first: bigint, second: bigint): bigint { return first | second } @@ -44,11 +42,41 @@ export class BigBitFlagSet implements FlagSet { return (first & second) == second } + public hasAny(flags: bigint, required: bigint): boolean { + return this.minimum(this.intersection(flags, required)) !== ENV_BI.ZERO + } + + public hasAll(flags: bigint, required: bigint): boolean { + return this.isSuperset(flags, this.maximum(required)) + } + public enumerate(flags: bigint): EnumerateFlags { return useIterator(flags, BigBitFlagsIterator) } - public getFlag(alias: string): FlagDefinition | undefined { - return this._dictionary.lookUp(alias) + public maximum(flags: bigint): bigint { + let result = ENV_BI.ZERO + for (const value of this.enumerate(flags)) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined) { + result = definition.addTo(result) + } + } + return result + } + + public minimum(flags: bigint): bigint { + let result = ENV_BI.ZERO + for (const value of this.enumerate(flags)) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined && definition.isIn(flags)) { + result = definition.addTo(result) + } + } + return result + } + + public getFlag(alias: string): FlagDefinition | undefined { + return this._dictionary.findByAlias(alias) } } diff --git a/node/src/flagsets/number.ts b/node/src/flagsets/number.ts index c8e4e77..3dc0a29 100644 --- a/node/src/flagsets/number.ts +++ b/node/src/flagsets/number.ts @@ -1,46 +1,66 @@ -import type { FlagSet } from '.' -import { BitFlagsIterator, EnumerateFlags, useIterator } from '~/enumeration' import { FlagDefinition, FlagsDictionary } from '~/definitions' +import { BigBitFlagsIterator, BitFlagsIterator, EnumerateFlags, useIterator } from '~/enumeration' + +import type { FlagSet } from '.' export const BitFlags = { + union(first: T, second: T): T { + return (first | second) as T + }, + + intersection(first: T, second: T): T { + return (first & second) as T + }, + + difference(first: T, second: T): T { + return (first & ~second) as T + }, + + isSuperset(first: T, second: T): boolean { + return (first & second) === second + }, + + enumerate(flags: T): EnumerateFlags { + if (typeof flags === 'number') { + return useIterator(flags, BitFlagsIterator) as EnumerateFlags + } else { + return useIterator(flags, BigBitFlagsIterator) as EnumerateFlags + } + }, +} as { /** * Computes the union of two sets of bitflags. * Any bits that are set in either of the inputs will be set in the result. */ - union(first: number, second: number): number { - return first | second - }, + union(first: number, second: number): number + union(first: bigint, second: bigint): bigint /** * Computes the intersection of two sets of bitflags. * Only bits that are set in both of the inputs will be set in the result. */ - intersection(first: number, second: number): number { - return first & second - }, + intersection(first: number, second: number): number + intersection(first: bigint, second: bigint): bigint /** * Computes the difference between two sets of bitflags. * Only bits that are set in the first input and not set in the second will be set in the result. */ - difference(first: number, second: number): number { - return first & ~second - }, + difference(first: number, second: number): number + difference(first: bigint, second: bigint): bigint /** * Tests if a set of bitflags is a superset of the other. * Return `true` if every bit that is set in the second input is also set in the first. */ - isSuperset(first: number, second: number): boolean { - return (first & second) === second - }, + isSuperset(first: number, second: number): boolean + isSuperset(first: bigint, second: bigint): boolean /** * Returns an iterable over the individual bits that are set. */ - enumerate(flags: number): EnumerateFlags { - return useIterator(flags, BitFlagsIterator) - }, + enumerate(flags: number): EnumerateFlags + enumerate(flags: bigint): EnumerateFlags } export class BitFlagSet implements FlagSet { diff --git a/node/src/index.ts b/node/src/index.ts index 26aabe8..fac517c 100644 --- a/node/src/index.ts +++ b/node/src/index.ts @@ -1,4 +1,9 @@ -export { createArrayFlagSet, createBitFlagSet, createCollectionFlagSet } from './builders' +export { + createArrayFlagSet, + createBigBitFlagSet, + createBitFlagSet, + createCollectionFlagSet, +} from './builders' export { FlagDefinition } from './definitions' export { InvalidBitFlagValueError } from './errors' export { diff --git a/node/tests/flagsets/bigint.test.ts b/node/tests/flagsets/bigint.test.ts index 0696e27..3331a70 100644 --- a/node/tests/flagsets/bigint.test.ts +++ b/node/tests/flagsets/bigint.test.ts @@ -1,15 +1,41 @@ -import { BigBitFlagSet } from '~' import { describe, expect, test } from 'vitest' +import { BigBitFlagSet, BitFlags, createBigBitFlagSet } from '~' + describe(BigBitFlagSet, () => { test('none', () => { - const flags = new BigBitFlagSet() + const flags = createBigBitFlagSet([]) expect(flags.none()).toEqual(0n) }) + test('of', () => { + const flags = createBigBitFlagSet([]) + + expect(flags.of()).toEqual(0n) + expect(flags.of(1n)).toEqual(1n) + expect(flags.of(3n, 8n)).toEqual(11n) + expect(flags.of(3n, 5n, 2n)).toEqual(7n) + }) + + test('named', () => { + const flags = createBigBitFlagSet([ + { value: 1n, as: 'A' }, + { value: 2n, as: 'B' }, + { value: 4n, as: 'C' }, + { value: 8n, as: 'D' }, + { compose: ['A', 'B'], as: 'AB' }, + { compose: ['A', 'C'], as: 'AC' }, + ]) + + expect(flags.named()).toEqual(0n) + expect(flags.named('A')).toEqual(1n) + expect(flags.named('AB', 'D')).toEqual(11n) + expect(flags.named('AB', 'AC', 'B')).toEqual(7n) + }) + test('union', () => { - const flags = new BigBitFlagSet() + const flags = createBigBitFlagSet([]) expect(flags.union(0n, 0n)).toEqual(0n) expect(flags.union(1n, 0n)).toEqual(1n) @@ -19,7 +45,7 @@ describe(BigBitFlagSet, () => { }) test('difference', () => { - const flags = new BigBitFlagSet() + const flags = createBigBitFlagSet([]) expect(flags.difference(0n, 0n)).toEqual(0n) expect(flags.difference(1n, 0n)).toEqual(1n) @@ -29,7 +55,7 @@ describe(BigBitFlagSet, () => { }) test('intersection', () => { - const flags = new BigBitFlagSet() + const flags = createBigBitFlagSet([]) expect(flags.intersection(0n, 0n)).toEqual(0n) expect(flags.intersection(1n, 0n)).toEqual(0n) @@ -40,7 +66,7 @@ describe(BigBitFlagSet, () => { }) test('isSuperset', () => { - const flags = new BigBitFlagSet() + const flags = createBigBitFlagSet([]) expect(flags.isSuperset(0n, 0n)).toBe(true) expect(flags.isSuperset(3n, 0n)).toBe(true) @@ -50,8 +76,40 @@ describe(BigBitFlagSet, () => { expect(flags.isSuperset(8n, 4n)).toBe(false) }) + test('hasAny', () => { + const flags = createBigBitFlagSet([ + { value: 1n, as: 'A' }, + { value: 2n, as: 'B', requires: ['A'] }, + { value: 4n, as: 'C', requires: ['A'] }, + { value: 8n, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.hasAny(15n, 0n)).toBe(false) + expect(flags.hasAny(0n, 0n)).toBe(false) + expect(flags.hasAny(7n, 1n)).toBe(true) + expect(flags.hasAny(1n, 7n)).toBe(true) + expect(flags.hasAny(5n, 12n)).toBe(false) + expect(flags.hasAny(2n, 2n)).toBe(false) + }) + + test('hasAll', () => { + const flags = createBigBitFlagSet([ + { value: 1n, as: 'A' }, + { value: 2n, as: 'B', requires: ['A'] }, + { value: 4n, as: 'C', requires: ['A'] }, + { value: 8n, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.hasAll(15n, 0n)).toBe(true) + expect(flags.hasAll(0n, 0n)).toBe(true) + expect(flags.hasAll(7n, 2n)).toBe(true) + expect(flags.hasAll(6n, 2n)).toBe(false) + expect(flags.hasAll(1n, 7n)).toBe(false) + expect(flags.hasAll(5n, 12n)).toBe(false) + }) + test('enumerate', () => { - const flags = new BigBitFlagSet() + const flags = createBigBitFlagSet([]) expect([...flags.enumerate(0n)]).toEqual([]) expect([...flags.enumerate(1n)]).toEqual([1n]) @@ -60,4 +118,81 @@ describe(BigBitFlagSet, () => { expect([...flags.enumerate(11n)]).toEqual([1n, 2n, 8n]) expect([...flags.enumerate(100n)]).toEqual([4n, 32n, 64n]) }) + + test('maximum', () => { + const flags = createBigBitFlagSet([ + { value: 1n, as: 'A' }, + { value: 2n, as: 'B', requires: ['A'] }, + { value: 4n, as: 'C', requires: ['A'] }, + { value: 8n, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.maximum(0n)).toEqual(0n) + expect(flags.maximum(1n)).toEqual(1n) + expect(flags.maximum(2n)).toEqual(3n) + expect(flags.maximum(3n)).toEqual(3n) + expect(flags.maximum(4n)).toEqual(5n) + expect(flags.maximum(8n)).toEqual(15n) + }) + + test('minimum', () => { + const flags = createBigBitFlagSet([ + { value: 1n, as: 'A' }, + { value: 2n, as: 'B', requires: ['A'] }, + { value: 4n, as: 'C', requires: ['A'] }, + { value: 8n, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.minimum(0n)).toEqual(0n) + expect(flags.minimum(1n)).toEqual(1n) + expect(flags.minimum(2n)).toEqual(0n) + expect(flags.minimum(3n)).toEqual(3n) + expect(flags.minimum(4n)).toEqual(0n) + expect(flags.minimum(13n)).toEqual(5n) + }) +}) + +describe('BitFlags', () => { + test('union', () => { + expect(BitFlags.union(0n, 0n)).toEqual(0n) + expect(BitFlags.union(1n, 0n)).toEqual(1n) + expect(BitFlags.union(0n, 2n)).toEqual(2n) + expect(BitFlags.union(1n, 2n)).toEqual(3n) + expect(BitFlags.union(3n, 6n)).toEqual(7n) + }) + + test('difference', () => { + expect(BitFlags.difference(0n, 0n)).toEqual(0n) + expect(BitFlags.difference(1n, 0n)).toEqual(1n) + expect(BitFlags.difference(3n, 6n)).toEqual(1n) + expect(BitFlags.difference(6n, 3n)).toEqual(4n) + expect(BitFlags.difference(8n, 17n)).toEqual(8n) + }) + + test('intersection', () => { + expect(BitFlags.intersection(0n, 0n)).toEqual(0n) + expect(BitFlags.intersection(1n, 0n)).toEqual(0n) + expect(BitFlags.intersection(1n, 2n)).toEqual(0n) + expect(BitFlags.intersection(1n, 3n)).toEqual(1n) + expect(BitFlags.intersection(11n, 5n)).toEqual(1n) + expect(BitFlags.intersection(11n, 7n)).toEqual(3n) + }) + + test('isSuperset', () => { + expect(BitFlags.isSuperset(0n, 0n)).toBe(true) + expect(BitFlags.isSuperset(3n, 0n)).toBe(true) + expect(BitFlags.isSuperset(3n, 1n)).toBe(true) + expect(BitFlags.isSuperset(3n, 3n)).toBe(true) + expect(BitFlags.isSuperset(0n, 3n)).toBe(false) + expect(BitFlags.isSuperset(8n, 4n)).toBe(false) + }) + + test('enumerate', () => { + expect([...BitFlags.enumerate(0n)]).toEqual([]) + expect([...BitFlags.enumerate(1n)]).toEqual([1n]) + expect([...BitFlags.enumerate(2n)]).toEqual([2n]) + expect([...BitFlags.enumerate(3n)]).toEqual([1n, 2n]) + expect([...BitFlags.enumerate(11n)]).toEqual([1n, 2n, 8n]) + expect([...BitFlags.enumerate(100n)]).toEqual([4n, 32n, 64n]) + }) })