finished implementation of BigBitFlagSets

This commit is contained in:
2026-03-13 22:41:05 +01:00
parent dda19cdd14
commit 06e7932ac5
8 changed files with 406 additions and 52 deletions

View File

@@ -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<bigint, bigint, BigBitFlagSet>,
RequireParentsOrDefineWithValueOrOrdinal<bigint, bigint, BigBitFlagSet>
{
private readonly _underlying: FlagSetBuilder<bigint>
public constructor() {
this._underlying = new FlagSetBuilder()
}
public define(): WithValueOrOrdinal<bigint, bigint, BigBitFlagSet>
public define(alias: string): WithValueOrOrdinalOrCompose<bigint, bigint, BigBitFlagSet>
public define(alias?: string): WithValueOrOrdinalOrCompose<bigint, bigint, BigBitFlagSet> {
this._underlying.define(alias)
return this
}
public compose(...flags: string[]): DefineWithValueOrOrdinal<bigint, bigint, BigBitFlagSet> {
this._underlying.compose(flags)
return this
}
public withValue(
value: bigint,
): RequireParentsOrDefineWithValueOrOrdinal<bigint, bigint, BigBitFlagSet> {
this._underlying.withValue(value)
return this
}
public withOrdinal(
ordinal: number,
): RequireParentsOrDefineWithValueOrOrdinal<bigint, bigint, BigBitFlagSet> {
throw new Error('Method not implemented.')
}
public requires(...flags: string[]): DefineWithValueOrOrdinal<bigint, bigint, BigBitFlagSet> {
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<bigint>[]): BigBitFlagSet
export function createBigBitFlagSet<D extends string>(
declarations: Record<D, NamedFlagWithValueOrOrdinal<bigint>>,
): BigBitFlagSet & Record<D, FlagDefinition<bigint>>
export function createBigBitFlagSet(
declarations: ListOfFlagsWithValueOrOrdinal<bigint>,
): BigBitFlagSet {
const builder = new BigBitFlagSetBuilder()
applyDeclarations(declarations, builder)
return builder.getResult()
}

View File

@@ -1,3 +1,4 @@
export { ArrayFlagSetBuilder, createArrayFlagSet } from './array' export { ArrayFlagSetBuilder, createArrayFlagSet } from './array'
export { BigBitFlagSetBuilder, createBigBitFlagSet } from './bigint'
export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection' export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection'
export { BitFlagSetBuilder, createBitFlagSet } from './number' export { BitFlagSetBuilder, createBitFlagSet } from './number'

View File

@@ -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<bigint> {
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<bigint, bigint> {
public makeDefinitions(
sortedPartialDefinitions: PartialFlagDefinition<bigint>[],
results: Map<PartialFlagDefinition<bigint>, FlagDefinition<bigint>>,
): void {
const precomputedValues = new Map<PartialFlagDefinition<bigint>, 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))
}
}
}

View File

@@ -1,9 +1,10 @@
export { FlagsDictionary, FlagDefinition, printFlagValue, valueToString } from './dictionary' export { BigBitFlagDefinition, BigBitFlagDefinitionFactory } from './bigint'
export { FlagDefinition, FlagsDictionary, printFlagValue, valueToString } from './dictionary'
export { export {
FlagsGraph,
PartialFlagInit,
PartialFlagDefinition,
refByAlias,
FlagDefinitionFactory, FlagDefinitionFactory,
FlagsGraph,
PartialFlagDefinition,
PartialFlagInit,
refByAlias,
} from './graph' } from './graph'
export { BitFlagDefinition, BitFlagDefinitionFactory } from './number' export { BitFlagDefinition, BitFlagDefinitionFactory } from './number'

View File

@@ -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 type { FlagSet } from '.'
import { UnavailableFeatureError } from '../errors'
import { ENV_BI } from '../env'
import { BigBitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration'
export class BigBitFlagSet implements FlagSet<bigint, bigint> { export class BigBitFlagSet implements FlagSet<bigint, bigint> {
/** private readonly _dictionary: FlagsDictionary<bigint, bigint>
* 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')
}
}
minimum(flags: bigint): bigint { public constructor(dictionary: FlagsDictionary<bigint, bigint>) {
throw new Error('Method not implemented.') this._dictionary = dictionary
}
maximum(flags: bigint): bigint {
throw new Error('Method not implemented.')
} }
public none(): bigint { public none(): bigint {
return ENV_BI.ZERO 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 { public union(first: bigint, second: bigint): bigint {
return first | second return first | second
} }
@@ -44,11 +42,41 @@ export class BigBitFlagSet implements FlagSet<bigint, bigint> {
return (first & second) == second 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<bigint> { public enumerate(flags: bigint): EnumerateFlags<bigint> {
return useIterator(flags, BigBitFlagsIterator) return useIterator(flags, BigBitFlagsIterator)
} }
public getFlag(alias: string): FlagDefinition<number, number> | undefined { public maximum(flags: bigint): bigint {
return this._dictionary.lookUp(alias) 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<bigint> | undefined {
return this._dictionary.findByAlias(alias)
} }
} }

View File

@@ -1,46 +1,66 @@
import type { FlagSet } from '.'
import { BitFlagsIterator, EnumerateFlags, useIterator } from '~/enumeration'
import { FlagDefinition, FlagsDictionary } from '~/definitions' import { FlagDefinition, FlagsDictionary } from '~/definitions'
import { BigBitFlagsIterator, BitFlagsIterator, EnumerateFlags, useIterator } from '~/enumeration'
import type { FlagSet } from '.'
export const BitFlags = { export const BitFlags = {
union<T extends number | bigint>(first: T, second: T): T {
return (first | second) as T
},
intersection<T extends number | bigint>(first: T, second: T): T {
return (first & second) as T
},
difference<T extends number | bigint>(first: T, second: T): T {
return (first & ~second) as T
},
isSuperset<T extends number | bigint>(first: T, second: T): boolean {
return (first & second) === second
},
enumerate<T extends number | bigint>(flags: T): EnumerateFlags<T> {
if (typeof flags === 'number') {
return useIterator(flags, BitFlagsIterator) as EnumerateFlags<T>
} else {
return useIterator(flags, BigBitFlagsIterator) as EnumerateFlags<T>
}
},
} as {
/** /**
* Computes the union of two sets of bitflags. * Computes the union of two sets of bitflags.
* Any bits that are set in either of the inputs will be set in the result. * Any bits that are set in either of the inputs will be set in the result.
*/ */
union(first: number, second: number): number { union(first: number, second: number): number
return first | second union(first: bigint, second: bigint): bigint
},
/** /**
* Computes the intersection of two sets of bitflags. * Computes the intersection of two sets of bitflags.
* Only bits that are set in both of the inputs will be set in the result. * Only bits that are set in both of the inputs will be set in the result.
*/ */
intersection(first: number, second: number): number { intersection(first: number, second: number): number
return first & second intersection(first: bigint, second: bigint): bigint
},
/** /**
* Computes the difference between two sets of bitflags. * 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. * 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 { difference(first: number, second: number): number
return first & ~second difference(first: bigint, second: bigint): bigint
},
/** /**
* Tests if a set of bitflags is a superset of the other. * 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. * Return `true` if every bit that is set in the second input is also set in the first.
*/ */
isSuperset(first: number, second: number): boolean { isSuperset(first: number, second: number): boolean
return (first & second) === second isSuperset(first: bigint, second: bigint): boolean
},
/** /**
* Returns an iterable over the individual bits that are set. * Returns an iterable over the individual bits that are set.
*/ */
enumerate(flags: number): EnumerateFlags<number> { enumerate(flags: number): EnumerateFlags<number>
return useIterator(flags, BitFlagsIterator) enumerate(flags: bigint): EnumerateFlags<bigint>
},
} }
export class BitFlagSet implements FlagSet<number, number> { export class BitFlagSet implements FlagSet<number, number> {

View File

@@ -1,4 +1,9 @@
export { createArrayFlagSet, createBitFlagSet, createCollectionFlagSet } from './builders' export {
createArrayFlagSet,
createBigBitFlagSet,
createBitFlagSet,
createCollectionFlagSet,
} from './builders'
export { FlagDefinition } from './definitions' export { FlagDefinition } from './definitions'
export { InvalidBitFlagValueError } from './errors' export { InvalidBitFlagValueError } from './errors'
export { export {

View File

@@ -1,15 +1,41 @@
import { BigBitFlagSet } from '~'
import { describe, expect, test } from 'vitest' import { describe, expect, test } from 'vitest'
import { BigBitFlagSet, BitFlags, createBigBitFlagSet } from '~'
describe(BigBitFlagSet, () => { describe(BigBitFlagSet, () => {
test('none', () => { test('none', () => {
const flags = new BigBitFlagSet() const flags = createBigBitFlagSet([])
expect(flags.none()).toEqual(0n) 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', () => { test('union', () => {
const flags = new BigBitFlagSet() const flags = createBigBitFlagSet([])
expect(flags.union(0n, 0n)).toEqual(0n) expect(flags.union(0n, 0n)).toEqual(0n)
expect(flags.union(1n, 0n)).toEqual(1n) expect(flags.union(1n, 0n)).toEqual(1n)
@@ -19,7 +45,7 @@ describe(BigBitFlagSet, () => {
}) })
test('difference', () => { test('difference', () => {
const flags = new BigBitFlagSet() const flags = createBigBitFlagSet([])
expect(flags.difference(0n, 0n)).toEqual(0n) expect(flags.difference(0n, 0n)).toEqual(0n)
expect(flags.difference(1n, 0n)).toEqual(1n) expect(flags.difference(1n, 0n)).toEqual(1n)
@@ -29,7 +55,7 @@ describe(BigBitFlagSet, () => {
}) })
test('intersection', () => { test('intersection', () => {
const flags = new BigBitFlagSet() const flags = createBigBitFlagSet([])
expect(flags.intersection(0n, 0n)).toEqual(0n) expect(flags.intersection(0n, 0n)).toEqual(0n)
expect(flags.intersection(1n, 0n)).toEqual(0n) expect(flags.intersection(1n, 0n)).toEqual(0n)
@@ -40,7 +66,7 @@ describe(BigBitFlagSet, () => {
}) })
test('isSuperset', () => { test('isSuperset', () => {
const flags = new BigBitFlagSet() const flags = createBigBitFlagSet([])
expect(flags.isSuperset(0n, 0n)).toBe(true) expect(flags.isSuperset(0n, 0n)).toBe(true)
expect(flags.isSuperset(3n, 0n)).toBe(true) expect(flags.isSuperset(3n, 0n)).toBe(true)
@@ -50,8 +76,40 @@ describe(BigBitFlagSet, () => {
expect(flags.isSuperset(8n, 4n)).toBe(false) 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', () => { test('enumerate', () => {
const flags = new BigBitFlagSet() const flags = createBigBitFlagSet([])
expect([...flags.enumerate(0n)]).toEqual([]) expect([...flags.enumerate(0n)]).toEqual([])
expect([...flags.enumerate(1n)]).toEqual([1n]) expect([...flags.enumerate(1n)]).toEqual([1n])
@@ -60,4 +118,81 @@ describe(BigBitFlagSet, () => {
expect([...flags.enumerate(11n)]).toEqual([1n, 2n, 8n]) expect([...flags.enumerate(11n)]).toEqual([1n, 2n, 8n])
expect([...flags.enumerate(100n)]).toEqual([4n, 32n, 64n]) 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])
})
}) })