implemented of() and named() for numbers
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
package fr.louisdevie.tatsuki.definition;
|
||||||
|
|
||||||
|
interface FlagDefinition<S> {
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package fr.louisdevie.tatsuki.definitions;
|
package fr.louisdevie.tatsuki.definition;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class FlagDefinitionMap<F, S> {
|
public class FlagDefinitionMap<F, S> {
|
||||||
private Map<F, FlagDefinition<S>> _container;
|
private Map<F, FlagDefinition<S>> _container;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package fr.louisdevie.tatsuki.definitions;
|
|
||||||
|
|
||||||
interface FlagDefinition<S> {
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"trailingComma": "es5",
|
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
}
|
}
|
||||||
|
|||||||
4425
node/package-lock.json
generated
4425
node/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,27 +6,21 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
"postbuild": "cp package.json README.md ../LICENSE dist",
|
"postbuild": "cp package.json README.md ../LICENSE dist",
|
||||||
"test": "jest",
|
"test": "vitest --ui",
|
||||||
|
"coverage": "vitest run --coverage",
|
||||||
"type-check": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"format": "prettier --write src/ tests/"
|
"format": "prettier --write src/ tests/"
|
||||||
},
|
},
|
||||||
"tsup": {
|
"tsup": {
|
||||||
"entry": ["src/index.ts"],
|
"entry": [
|
||||||
|
"src/index.ts"
|
||||||
|
],
|
||||||
"splitting": false,
|
"splitting": false,
|
||||||
"dts": true,
|
"dts": true,
|
||||||
"minify": true,
|
"minify": true,
|
||||||
"sourcemap": true,
|
"sourcemap": true,
|
||||||
"clean": true
|
"clean": true
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"preset": "ts-jest",
|
|
||||||
"testEnvironment": "node",
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"~": "<rootDir>/src/index.ts"
|
|
||||||
},
|
|
||||||
"collectCoverageFrom": ["src/**/*.ts"],
|
|
||||||
"coverageReporters": [ ["cobertura", {"file": "coverage.xml"}] ]
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/louisdevie/multiflag.git"
|
"url": "git+https://github.com/louisdevie/multiflag.git"
|
||||||
@@ -42,12 +36,12 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/louisdevie/multiflag#readme",
|
"homepage": "https://github.com/louisdevie/multiflag#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.14",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"jest": "^29.7.0",
|
"@vitest/ui": "^4.0.18",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.8.1",
|
||||||
"ts-jest": "^29.3.2",
|
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsup": "^8.3.5",
|
"tsup": "^8.5.1",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.9.3",
|
||||||
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
130
node/src/builders/declarative.ts
Normal file
130
node/src/builders/declarative.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import type {
|
||||||
|
DefineWithOrdinal,
|
||||||
|
DefineWithValue,
|
||||||
|
DefineWithValueOrOrdinal,
|
||||||
|
} from './syntax'
|
||||||
|
|
||||||
|
// Generic helper functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the record keys into the 'as' property of the values and return an
|
||||||
|
* array containing those values.
|
||||||
|
*/
|
||||||
|
function toDefinitionArray<D>(
|
||||||
|
record: Record<string, D>,
|
||||||
|
): (D & { as: string })[] {
|
||||||
|
return Object.keys(record).map((key) => ({ ...record[key], as: key }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declarations for builders that supports only definitions by value
|
||||||
|
|
||||||
|
export type FlagWithValue<F> =
|
||||||
|
| { value: F; as?: string; requires?: string[] }
|
||||||
|
| { compose: string[]; as: string }
|
||||||
|
|
||||||
|
export type NamedFlagWithValue<F> =
|
||||||
|
| { value: F; requires?: string[] }
|
||||||
|
| { compose: string[] }
|
||||||
|
|
||||||
|
export type ListOfFlagsWithValue<F> =
|
||||||
|
| FlagWithValue<F>[]
|
||||||
|
| Record<string, NamedFlagWithValue<F>>
|
||||||
|
|
||||||
|
export function applyDeclarationsWithValue<F>(
|
||||||
|
declarations: ListOfFlagsWithValue<F>,
|
||||||
|
builder: DefineWithValue<F, unknown, unknown>,
|
||||||
|
) {
|
||||||
|
const declarationsArray = Array.isArray(declarations)
|
||||||
|
? declarations
|
||||||
|
: toDefinitionArray(declarations)
|
||||||
|
|
||||||
|
for (const declaration of declarationsArray) {
|
||||||
|
if ('compose' in declaration) {
|
||||||
|
builder.define(declaration.as).compose(...declaration.compose)
|
||||||
|
} else {
|
||||||
|
// it doesn't matter if "as" is undefined,
|
||||||
|
// we can always call withValue() on the returned value
|
||||||
|
builder
|
||||||
|
.define(declaration.as!)
|
||||||
|
.withValue(declaration.value)
|
||||||
|
.requires(...(declaration.requires ?? []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declarations for builders that supports only definitions by ordinal
|
||||||
|
|
||||||
|
export type FlagWithOrdinal<F> =
|
||||||
|
| { ordinal: number; as?: string; requires?: string[] }
|
||||||
|
| { compose: string[]; as: string }
|
||||||
|
|
||||||
|
export type NamedFlagWithOrdinal<F> =
|
||||||
|
| { ordinal: number; requires?: string[] }
|
||||||
|
| { compose: string[] }
|
||||||
|
|
||||||
|
export type ListOfFlagsWithOrdinal<F> =
|
||||||
|
| FlagWithOrdinal<F>[]
|
||||||
|
| Record<string, NamedFlagWithOrdinal<F>>
|
||||||
|
|
||||||
|
export function applyDeclarationsWithOrdinal<F>(
|
||||||
|
declarations: ListOfFlagsWithOrdinal<F>,
|
||||||
|
builder: DefineWithOrdinal<F, unknown, unknown>,
|
||||||
|
) {
|
||||||
|
const declarationsArray = Array.isArray(declarations)
|
||||||
|
? declarations
|
||||||
|
: toDefinitionArray(declarations)
|
||||||
|
|
||||||
|
for (const declaration of declarationsArray) {
|
||||||
|
if ('compose' in declaration) {
|
||||||
|
builder.define(declaration.as).compose(...declaration.compose)
|
||||||
|
} else {
|
||||||
|
// it doesn't matter if "as" is undefined,
|
||||||
|
// we can always call withOrdinal() on the returned value
|
||||||
|
builder
|
||||||
|
.define(declaration.as!)
|
||||||
|
.withOrdinal(declaration.ordinal)
|
||||||
|
.requires(...(declaration.requires ?? []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declarations for builders that supports definitions by value and ordinal
|
||||||
|
|
||||||
|
export type FlagWithValueOrOrdinal<F> =
|
||||||
|
| { value: F; as?: string; requires?: string[] }
|
||||||
|
| { ordinal: number; as?: string; requires?: string[] }
|
||||||
|
| { compose: string[]; as: string }
|
||||||
|
|
||||||
|
export type NamedFlagWithValueOrOrdinal<F> =
|
||||||
|
| { value: F; requires?: string[] }
|
||||||
|
| { ordinal: number; requires?: string[] }
|
||||||
|
| { compose: string[] }
|
||||||
|
|
||||||
|
export type ListOfFlagsWithValueOrOrdinal<F> =
|
||||||
|
| FlagWithValueOrOrdinal<F>[]
|
||||||
|
| Record<string, NamedFlagWithValueOrOrdinal<F>>
|
||||||
|
|
||||||
|
export function applyDeclarationsWithValueOrOrdinal<F>(
|
||||||
|
declarations: ListOfFlagsWithValueOrOrdinal<F>,
|
||||||
|
builder: DefineWithValueOrOrdinal<F, unknown, unknown>,
|
||||||
|
) {
|
||||||
|
const declarationsArray = Array.isArray(declarations)
|
||||||
|
? declarations
|
||||||
|
: toDefinitionArray(declarations)
|
||||||
|
|
||||||
|
for (const declaration of declarationsArray) {
|
||||||
|
if ('compose' in declaration) {
|
||||||
|
builder.define(declaration.as).compose(...declaration.compose)
|
||||||
|
} else if ('ordinal' in declaration) {
|
||||||
|
builder
|
||||||
|
.define(declaration.as!) // see note above
|
||||||
|
.withOrdinal(declaration.ordinal)
|
||||||
|
.requires(...(declaration.requires ?? []))
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
.define(declaration.as!) // see note above
|
||||||
|
.withValue(declaration.value)
|
||||||
|
.requires(...(declaration.requires ?? []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
171
node/src/builders/generic.ts
Normal file
171
node/src/builders/generic.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { InvalidOperationError, InvalidReferenceError } from '../errors'
|
||||||
|
import { FlagDefinition, FlagsDictionary } from '~'
|
||||||
|
import { printFlagValue } from '../definitions'
|
||||||
|
import { GenericFlagsDictionary } from '../definitions/dictionary'
|
||||||
|
|
||||||
|
export interface FlagDefinitionFactory<F, S> {
|
||||||
|
readonly supportsDefinitionsByValue: boolean
|
||||||
|
readonly supportsDefinitionsByOrdinal: boolean
|
||||||
|
|
||||||
|
precomputeTopDown(partialDefinition: PartialDefinition<F, S>): void
|
||||||
|
|
||||||
|
precomputeBottomUp(partialDefinition: PartialDefinition<F, S>): void
|
||||||
|
|
||||||
|
createFlagDefinition(partialDefinition: PartialDefinition<F, S>): FlagDefinition<F, S>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartialDefinition<F, S> {
|
||||||
|
alias?: string
|
||||||
|
value?: F
|
||||||
|
parentRefs?: Set<string>
|
||||||
|
parents?: Set<PartialDefinition<F, S>>
|
||||||
|
children?: Set<PartialDefinition<F, S>>
|
||||||
|
baseValues?: S
|
||||||
|
additiveValues?: S
|
||||||
|
subtractiveValues?: S
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRelationship<F, S>(
|
||||||
|
parent: PartialDefinition<F, S>,
|
||||||
|
child: PartialDefinition<F, S>,
|
||||||
|
): void {
|
||||||
|
if (parent.children === undefined) {
|
||||||
|
parent.children = new Set([child])
|
||||||
|
} else {
|
||||||
|
parent.children.add(child)
|
||||||
|
}
|
||||||
|
if (child.parents === undefined) {
|
||||||
|
child.parents = new Set([parent])
|
||||||
|
} else {
|
||||||
|
child.parents.add(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenericFlagSetBuilder<F, S> {
|
||||||
|
private readonly _factory: FlagDefinitionFactory<F, S>
|
||||||
|
private readonly _definitions: Set<PartialDefinition<F, S>>
|
||||||
|
private _currentDefinition: PartialDefinition<F, S> | undefined
|
||||||
|
|
||||||
|
public constructor(factory: FlagDefinitionFactory<F, S>) {
|
||||||
|
this._factory = factory
|
||||||
|
this._definitions = new Set()
|
||||||
|
this._currentDefinition = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
private get canMoveOnToNextDefinition(): boolean {
|
||||||
|
return (
|
||||||
|
this._currentDefinition === undefined ||
|
||||||
|
this._currentDefinition.value !== undefined ||
|
||||||
|
this._currentDefinition.parentRefs !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private createNewDefinition(alias: string | undefined): void {
|
||||||
|
const def = { alias }
|
||||||
|
this._definitions.add(def)
|
||||||
|
this._currentDefinition = def
|
||||||
|
}
|
||||||
|
|
||||||
|
public define(alias: string | undefined): void {
|
||||||
|
if (!this.canMoveOnToNextDefinition) {
|
||||||
|
throw new InvalidOperationError('define')
|
||||||
|
}
|
||||||
|
this.createNewDefinition(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
public compose(flags: string[]): void {
|
||||||
|
if (
|
||||||
|
this._currentDefinition === undefined ||
|
||||||
|
this._currentDefinition.alias === undefined ||
|
||||||
|
this._currentDefinition.parentRefs !== undefined
|
||||||
|
) {
|
||||||
|
throw new InvalidOperationError('compose')
|
||||||
|
}
|
||||||
|
this._currentDefinition.parentRefs = new Set(flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
public withValue(value: F): void {
|
||||||
|
if (
|
||||||
|
this._currentDefinition === undefined ||
|
||||||
|
this._currentDefinition.value !== undefined ||
|
||||||
|
this._currentDefinition.parentRefs !== undefined
|
||||||
|
) {
|
||||||
|
throw new InvalidOperationError('withValue')
|
||||||
|
}
|
||||||
|
this._currentDefinition.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public requires(flags: string[]): void {
|
||||||
|
if (
|
||||||
|
this._currentDefinition === undefined ||
|
||||||
|
this._currentDefinition.value === undefined ||
|
||||||
|
this._currentDefinition.parentRefs !== undefined
|
||||||
|
) {
|
||||||
|
throw new InvalidOperationError('requires')
|
||||||
|
}
|
||||||
|
this._currentDefinition.parentRefs = new Set(flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildDictionary(): FlagsDictionary<F, S> {
|
||||||
|
// this array will contain the nodes of the graph, in topological order
|
||||||
|
const sorted: PartialDefinition<F, S>[] = []
|
||||||
|
|
||||||
|
const startNodes: PartialDefinition<F, S>[] = []
|
||||||
|
for (const n of this._definitions) {
|
||||||
|
if (n.parentRefs === undefined || n.parentRefs.size === 0) {
|
||||||
|
startNodes.push(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kahn's algorithm and resolution of flag parentRef at the same time
|
||||||
|
while (startNodes.length > 0) {
|
||||||
|
const n = startNodes.pop()!
|
||||||
|
sorted.push(n)
|
||||||
|
for (const m of this._definitions) {
|
||||||
|
// if m is a child of n
|
||||||
|
if (
|
||||||
|
m.parentRefs !== undefined &&
|
||||||
|
n.alias !== undefined &&
|
||||||
|
m.parentRefs.has(n.alias)
|
||||||
|
) {
|
||||||
|
// remove this edge from the parentRefs and create the relationship
|
||||||
|
m.parentRefs.delete(n.alias)
|
||||||
|
addRelationship(n, m)
|
||||||
|
// if m has no other parents, add it to the start nodes
|
||||||
|
if (m.parentRefs.size === 0) {
|
||||||
|
startNodes.push(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for remaining parent refs
|
||||||
|
for (const n of this._definitions) {
|
||||||
|
if (n.parentRefs !== undefined && n.parentRefs.size > 0) {
|
||||||
|
throw new InvalidReferenceError(
|
||||||
|
[...n.parentRefs.entries()][0][0],
|
||||||
|
this.displayPartialDefinition(n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dict = new GenericFlagsDictionary<F, S>()
|
||||||
|
for (let i = 0; i < sorted.length; i++) {
|
||||||
|
this._factory.precomputeTopDown(sorted[i])
|
||||||
|
}
|
||||||
|
for (let i = sorted.length - 1; i >= 0; i--) {
|
||||||
|
this._factory.precomputeBottomUp(sorted[i])
|
||||||
|
dict.add(this._factory.createFlagDefinition(sorted[i]))
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
private displayPartialDefinition(definition: PartialDefinition<F, S>): string {
|
||||||
|
if (definition.alias) {
|
||||||
|
return '"' + definition.alias + '"'
|
||||||
|
} else {
|
||||||
|
// generate the definition and try to print its base value
|
||||||
|
return printFlagValue(this._factory.createFlagDefinition(definition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
export { FlagSetBuilder } from './root'
|
export { BitFlagSetBuilder, createBitFlagSet } from './number'
|
||||||
|
|||||||
@@ -1,32 +1,123 @@
|
|||||||
import { BitFlagSet } from '../flagsets'
|
import { BitFlagSet, FlagDefinition, FlagsDictionary } from '~'
|
||||||
import {
|
import {
|
||||||
DefineFlag,
|
DefineWithValueOrOrdinal,
|
||||||
RequireParentsOrDefineFlag,
|
RequireParentsOrDefineWithValueOrOrdinal,
|
||||||
SetValueOrCompose,
|
WithValueOrOrdinal,
|
||||||
|
WithValueOrOrdinalOrCompose,
|
||||||
} from './syntax'
|
} from './syntax'
|
||||||
|
import {
|
||||||
|
applyDeclarationsWithValueOrOrdinal,
|
||||||
|
FlagWithValueOrOrdinal,
|
||||||
|
ListOfFlagsWithValueOrOrdinal,
|
||||||
|
NamedFlagWithValueOrOrdinal,
|
||||||
|
} from './declarative'
|
||||||
|
import { BitFlagDefinition } from '../definitions'
|
||||||
|
import { FlagDefinitionFactory, GenericFlagSetBuilder, PartialDefinition } from './generic'
|
||||||
|
|
||||||
export class BitFlagSetBuilder
|
export class BitFlagSetBuilder
|
||||||
implements
|
implements
|
||||||
RequireParentsOrDefineFlag<BitFlagSet>,
|
WithValueOrOrdinalOrCompose<number, number, BitFlagSet>,
|
||||||
SetValueOrCompose<BitFlagSet>
|
RequireParentsOrDefineWithValueOrOrdinal<number, number, BitFlagSet>
|
||||||
{
|
{
|
||||||
public define(name: string): SetValueOrCompose<BitFlagSet> {
|
private readonly _underlying: GenericFlagSetBuilder<number, number>
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this._underlying = new GenericFlagSetBuilder(new BitFlagDefinitionFactory())
|
||||||
|
}
|
||||||
|
|
||||||
|
public define(): WithValueOrOrdinal<number, number, BitFlagSet>
|
||||||
|
public define(alias: string): WithValueOrOrdinalOrCompose<number, number, BitFlagSet>
|
||||||
|
public define(alias?: string): WithValueOrOrdinalOrCompose<number, number, BitFlagSet> {
|
||||||
|
this._underlying.define(alias)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public requires(...flags: string[]): DefineFlag<BitFlagSet> {
|
public compose(...flags: string[]): DefineWithValueOrOrdinal<number, number, BitFlagSet> {
|
||||||
|
this._underlying.compose(flags)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public withValue(value: number): RequireParentsOrDefineFlag<BitFlagSet> {
|
public withValue(
|
||||||
|
value: number,
|
||||||
|
): RequireParentsOrDefineWithValueOrOrdinal<number, number, BitFlagSet> {
|
||||||
|
this._underlying.withValue(value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public compose(...flags: string[]): DefineFlag<BitFlagSet> {
|
public withOrdinal(
|
||||||
|
ordinal: number,
|
||||||
|
): RequireParentsOrDefineWithValueOrOrdinal<number, number, BitFlagSet> {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
public requires(...flags: string[]): DefineWithValueOrOrdinal<number, number, BitFlagSet> {
|
||||||
|
this._underlying.requires(flags)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public build(): BitFlagSet {
|
public getDictionary(): FlagsDictionary<number, number> {
|
||||||
return new BitFlagSet()
|
return this._underlying.buildDictionary() as any
|
||||||
|
}
|
||||||
|
|
||||||
|
public getResult(): BitFlagSet {
|
||||||
|
return new BitFlagSet(this.getDictionary())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BitFlagDefinitionFactory implements FlagDefinitionFactory<number, number> {
|
||||||
|
public readonly supportsDefinitionsByOrdinal = true
|
||||||
|
public readonly supportsDefinitionsByValue = true
|
||||||
|
|
||||||
|
public precomputeTopDown(partialDefinition: PartialDefinition<number, number>): void {
|
||||||
|
partialDefinition.additiveValues = 0
|
||||||
|
partialDefinition.baseValues = 0
|
||||||
|
if (partialDefinition.value !== undefined) {
|
||||||
|
partialDefinition.additiveValues = partialDefinition.value
|
||||||
|
partialDefinition.baseValues = partialDefinition.value
|
||||||
|
}
|
||||||
|
if (partialDefinition.parents !== undefined) {
|
||||||
|
for (const parent of partialDefinition.parents.values()) {
|
||||||
|
partialDefinition.additiveValues =
|
||||||
|
partialDefinition.additiveValues | (parent.additiveValues ?? 0)
|
||||||
|
if (partialDefinition.value === undefined) {
|
||||||
|
partialDefinition.baseValues =
|
||||||
|
partialDefinition.baseValues | (parent.baseValues ?? 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public precomputeBottomUp(partialDefinition: PartialDefinition<number, number>): void {
|
||||||
|
partialDefinition.subtractiveValues = 0
|
||||||
|
if (partialDefinition.value) {
|
||||||
|
partialDefinition.subtractiveValues = partialDefinition.value
|
||||||
|
}
|
||||||
|
if (partialDefinition.children) {
|
||||||
|
for (const child of partialDefinition.children.values()) {
|
||||||
|
partialDefinition.subtractiveValues =
|
||||||
|
partialDefinition.subtractiveValues | (child.subtractiveValues ?? 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public createFlagDefinition(
|
||||||
|
partialDefinition: PartialDefinition<number, number>,
|
||||||
|
): FlagDefinition<number, number> {
|
||||||
|
return new BitFlagDefinition(
|
||||||
|
partialDefinition.baseValues ?? 0,
|
||||||
|
partialDefinition.additiveValues ?? 0,
|
||||||
|
partialDefinition.subtractiveValues ?? 0,
|
||||||
|
partialDefinition.alias,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBitFlagSet(declarations: FlagWithValueOrOrdinal<number>[]): BitFlagSet
|
||||||
|
export function createBitFlagSet<D extends string>(
|
||||||
|
declarations: Record<D, NamedFlagWithValueOrOrdinal<number>>,
|
||||||
|
): BitFlagSet & Record<D, FlagDefinition<number, number>>
|
||||||
|
export function createBitFlagSet(declarations: ListOfFlagsWithValueOrOrdinal<number>): BitFlagSet {
|
||||||
|
const builder = new BitFlagSetBuilder()
|
||||||
|
applyDeclarationsWithValueOrOrdinal(declarations, builder)
|
||||||
|
return builder.getResult()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import type { SelectFlagSetType, DefineFlag } from './syntax'
|
|
||||||
import { BitFlagSetBuilder } from './number'
|
|
||||||
import { BitFlagSet } from '../flagsets'
|
|
||||||
|
|
||||||
export class FlagSetBuilder implements SelectFlagSetType {
|
|
||||||
public useBitFlags(): DefineFlag<BitFlagSet> {
|
|
||||||
return new BitFlagSetBuilder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,147 @@
|
|||||||
import type { BitFlagSet } from '../flagsets'
|
import type { FlagsDictionary, FlagSet } from '~'
|
||||||
|
|
||||||
export interface SelectFlagSetType {
|
export interface Root<F, S, R = FlagSet<F, S>> {
|
||||||
useBitFlags(): DefineFlag<BitFlagSet>
|
/**
|
||||||
|
* Build a dictionary with the flags previously defined.
|
||||||
|
*/
|
||||||
|
getDictionary(): FlagsDictionary<F, S>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a flag set with the flags previously defined.
|
||||||
|
*/
|
||||||
|
getResult(): R
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DefineFlag<T> {
|
// Syntax for builders that supports only definitions by value
|
||||||
define(name: string): SetValueOrCompose<T>
|
|
||||||
|
|
||||||
build(): T
|
export interface DefineWithValue<F, S, R = FlagSet<F, S>>
|
||||||
|
extends Root<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Define an anonymous flag.
|
||||||
|
*/
|
||||||
|
define(): WithValue<F, S, R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a named flag.
|
||||||
|
* @param alias The name of the flag.
|
||||||
|
*/
|
||||||
|
define(alias: string): WithValueOrCompose<F, S, R>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequireParentsOrDefineFlag<T> extends DefineFlag<T> {
|
export interface WithValue<F, S, R> {
|
||||||
requires(...flags: string[]): DefineFlag<T>
|
/**
|
||||||
|
* Set the value of this flag.
|
||||||
|
* @param value The value for the flag.
|
||||||
|
*/
|
||||||
|
withValue(value: F): RequireParentsOrDefineWithValue<F, S, R>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetValueOrCompose<T> {
|
export interface WithValueOrCompose<F, S, R> extends WithValue<F, S, R> {
|
||||||
withValue(value: number): RequireParentsOrDefineFlag<T>
|
/**
|
||||||
|
* Define this flag as a composed flag.
|
||||||
compose(...flags: string[]): DefineFlag<T>
|
* @param flags The name of the flags in the group.
|
||||||
|
*/
|
||||||
|
compose(...flags: string[]): DefineWithValue<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequireParentsOrDefineWithValue<F, S, R>
|
||||||
|
extends DefineWithValue<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Set the parents of this flag.
|
||||||
|
* @param flags The names of the parent flags.
|
||||||
|
*/
|
||||||
|
requires(...flags: string[]): DefineWithValue<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax for builders that supports only definitions by ordinal
|
||||||
|
|
||||||
|
export interface DefineWithOrdinal<F, S, R = FlagSet<F, S>>
|
||||||
|
extends Root<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Define an anonymous flag.
|
||||||
|
*/
|
||||||
|
define(): WithOrdinal<F, S, R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a named flag.
|
||||||
|
* @param alias The name of the flag.
|
||||||
|
*/
|
||||||
|
define(alias: string): WithOrdinalOrCompose<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithOrdinal<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Set the value of this flag.
|
||||||
|
* @param ordinal The number of the flag (starting at 1). A unique value
|
||||||
|
* will be assigned based on this number.
|
||||||
|
*/
|
||||||
|
withOrdinal(ordinal: number): RequireParentsOrDefineWithOrdinal<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithOrdinalOrCompose<F, S, R> extends WithOrdinal<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Define this flag as a composed flag.
|
||||||
|
* @param flags The name of the flags in the group.
|
||||||
|
*/
|
||||||
|
compose(...flags: string[]): DefineWithOrdinal<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequireParentsOrDefineWithOrdinal<F, S, R>
|
||||||
|
extends DefineWithOrdinal<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Set the parents of this flag.
|
||||||
|
* @param flags The names of the parent flags.
|
||||||
|
*/
|
||||||
|
requires(...flags: string[]): DefineWithOrdinal<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax for builders that supports definitions by value and ordinal
|
||||||
|
|
||||||
|
export interface DefineWithValueOrOrdinal<F, S, R = FlagSet<F, S>>
|
||||||
|
extends Root<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Define an anonymous flag.
|
||||||
|
*/
|
||||||
|
define(): WithValueOrOrdinal<F, S, R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a named flag.
|
||||||
|
* @param alias The name of the flag.
|
||||||
|
*/
|
||||||
|
define(alias: string): WithValueOrOrdinalOrCompose<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithValueOrOrdinal<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Set the value of this flag.
|
||||||
|
* @param value The value for the flag.
|
||||||
|
*/
|
||||||
|
withValue(value: F): RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of this flag.
|
||||||
|
* @param ordinal The number of the flag (starting at 1). A unique value
|
||||||
|
* will be assigned based on this number.
|
||||||
|
*/
|
||||||
|
withOrdinal(
|
||||||
|
ordinal: number,
|
||||||
|
): RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithValueOrOrdinalOrCompose<F, S, R>
|
||||||
|
extends WithValueOrOrdinal<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Define this flag as a composed flag.
|
||||||
|
* @param flags The name of the flags in the group.
|
||||||
|
*/
|
||||||
|
compose(...flags: string[]): DefineWithValueOrOrdinal<F, S, R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
|
||||||
|
extends DefineWithValueOrOrdinal<F, S, R> {
|
||||||
|
/**
|
||||||
|
* Set the parents of this flag.
|
||||||
|
* @param flags The names of the parent flags.
|
||||||
|
*/
|
||||||
|
requires(...flags: string[]): DefineWithValueOrOrdinal<F, S, R>
|
||||||
}
|
}
|
||||||
|
|||||||
67
node/src/definitions/dictionary.ts
Normal file
67
node/src/definitions/dictionary.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import type { FlagDefinition } from '.'
|
||||||
|
import { ReusedFlagAliasError, ReusedFlagValueError } from '../errors'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of {@link FlagDefinition}s.
|
||||||
|
*/
|
||||||
|
export interface FlagsDictionary<F, S> {
|
||||||
|
/**
|
||||||
|
* Search for a flag in the collection.
|
||||||
|
* @param alias The alias of the flag.
|
||||||
|
* @returns The corresponding definition, or `undefined` if there is no flag
|
||||||
|
* with this alias.
|
||||||
|
*/
|
||||||
|
findByAlias(alias: string): FlagDefinition<F, S> | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Built-in dictionary implementation.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export class GenericFlagsDictionary<F, S> implements FlagsDictionary<F, S> {
|
||||||
|
private readonly _named: Map<string, FlagDefinition<F, S>>
|
||||||
|
private readonly _anonymous: FlagDefinition<F, S>[]
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this._named = new Map()
|
||||||
|
this._anonymous = []
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(definition: FlagDefinition<F, S>): void {
|
||||||
|
if (definition.alias === undefined) {
|
||||||
|
this._anonymous.push(definition)
|
||||||
|
} else {
|
||||||
|
this._named.set(definition.alias, definition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a flag in the collection.
|
||||||
|
* @param alias The alias of the flag.
|
||||||
|
* @returns The corresponding definition, or `undefined` if there is no flag
|
||||||
|
* with this alias.
|
||||||
|
*/
|
||||||
|
public findByAlias(alias: string): FlagDefinition<F, S> | undefined {
|
||||||
|
return this._named.get(alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public define(definition: FlagDefinition<F, S>) {
|
||||||
|
for (const other of this._named.values()) {
|
||||||
|
if (
|
||||||
|
definition.alias !== undefined &&
|
||||||
|
definition.alias === other.alias
|
||||||
|
) {
|
||||||
|
throw new ReusedFlagAliasError(definition.alias)
|
||||||
|
}
|
||||||
|
if (definition.hasSameValue(other)) {
|
||||||
|
throw new ReusedFlagValueError(definition, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const other of this._anonymous) {
|
||||||
|
if (definition.hasSameValue(other)) {
|
||||||
|
throw new ReusedFlagValueError(definition, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
23
node/src/definitions/index.ts
Normal file
23
node/src/definitions/index.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export { FlagsDictionary } from './dictionary'
|
||||||
|
export { BitFlagDefinition } from './number'
|
||||||
|
|
||||||
|
export interface FlagDefinition<F, S> {
|
||||||
|
/**
|
||||||
|
* The alias of the flag.
|
||||||
|
*/
|
||||||
|
readonly alias: string | undefined
|
||||||
|
|
||||||
|
readonly values: S
|
||||||
|
|
||||||
|
hasSameValue(other: FlagDefinition<F, S>): boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const valueToString = Symbol()
|
||||||
|
|
||||||
|
export function printFlagValue(flag: FlagDefinition<unknown, unknown>): string {
|
||||||
|
if (valueToString in flag) {
|
||||||
|
return (flag[valueToString] as Function)()
|
||||||
|
} else {
|
||||||
|
return String(flag.values)
|
||||||
|
}
|
||||||
|
}
|
||||||
32
node/src/definitions/number.ts
Normal file
32
node/src/definitions/number.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { FlagDefinition, valueToString } from '.'
|
||||||
|
|
||||||
|
export class BitFlagDefinition implements FlagDefinition<number, number> {
|
||||||
|
private readonly _baseValues: number
|
||||||
|
private readonly _additiveValues: number
|
||||||
|
private readonly _subtractiveValues: number
|
||||||
|
private readonly _alias: string | undefined
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
baseValues: number,
|
||||||
|
additiveValues: number,
|
||||||
|
subtractiveValues: number,
|
||||||
|
alias: string | undefined,
|
||||||
|
) {
|
||||||
|
this._baseValues = baseValues
|
||||||
|
this._additiveValues = additiveValues
|
||||||
|
this._subtractiveValues = subtractiveValues
|
||||||
|
this._alias = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
public get alias(): string | undefined {
|
||||||
|
return this._alias
|
||||||
|
}
|
||||||
|
|
||||||
|
public get values(): number {
|
||||||
|
return this._baseValues
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasSameValue(other: FlagDefinition<number, number>): boolean {
|
||||||
|
return other instanceof BitFlagDefinition && this._baseValues === other._baseValues
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,10 +9,6 @@ export class BigBitFlagsIterator implements Iterator<bigint> {
|
|||||||
this._current = ENV_BI.ONE
|
this._current = ENV_BI.ONE
|
||||||
}
|
}
|
||||||
|
|
||||||
public [Symbol.iterator](): IterableIterator<bigint> {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public next(): IteratorResult<bigint, undefined> {
|
public next(): IteratorResult<bigint, undefined> {
|
||||||
if (this._value == ENV_BI.ZERO) {
|
if (this._value == ENV_BI.ZERO) {
|
||||||
return { done: true, value: undefined }
|
return { done: true, value: undefined }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { EnumerateFlags } from '.'
|
|||||||
|
|
||||||
export function useIterator<S, F>(
|
export function useIterator<S, F>(
|
||||||
value: S,
|
value: S,
|
||||||
IterConstructor: { new (value: S): Iterator<F> }
|
IterConstructor: { new (value: S): Iterator<F> },
|
||||||
): EnumerateFlags<F> {
|
): EnumerateFlags<F> {
|
||||||
const enumerate = {
|
const enumerate = {
|
||||||
_value: value,
|
_value: value,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const ENV_BI = Object.freeze(
|
export const ENV_BI = Object.freeze(
|
||||||
typeof BigInt === 'function'
|
typeof BigInt === 'function'
|
||||||
? { AVAILABLE: true, ZERO: BigInt(0), ONE: BigInt(1) }
|
? { AVAILABLE: true, ZERO: BigInt(0), ONE: BigInt(1) }
|
||||||
: { AVAILABLE: false }
|
: { AVAILABLE: false },
|
||||||
) as {
|
) as {
|
||||||
readonly AVAILABLE: boolean
|
readonly AVAILABLE: boolean
|
||||||
readonly ZERO: bigint
|
readonly ZERO: bigint
|
||||||
@@ -20,7 +20,7 @@ export const ENV_SET = Object.freeze(
|
|||||||
difference: polyfillDifference(Set.prototype),
|
difference: polyfillDifference(Set.prototype),
|
||||||
isSupersetOf: polyfillIsSupersetOf(Set.prototype),
|
isSupersetOf: polyfillIsSupersetOf(Set.prototype),
|
||||||
}
|
}
|
||||||
: { AVAILABLE: false }
|
: { AVAILABLE: false },
|
||||||
) as {
|
) as {
|
||||||
readonly AVAILABLE: boolean
|
readonly AVAILABLE: boolean
|
||||||
readonly union: SetBinaryOperation
|
readonly union: SetBinaryOperation
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { FlagDefinition, printFlagValue } from './definitions'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error thrown when a feature is not available in the current environment.
|
* Error thrown when a feature is not available in the current environment.
|
||||||
*/
|
*/
|
||||||
@@ -9,8 +11,8 @@ export class UnavailableFeatureError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error thrown by `FlagSet`s that store the flags using a binary format when a
|
* Error thrown when trying to define a flag stored in a binary with a value
|
||||||
* flag value isn't a power of two.
|
* that isn't a power of two.
|
||||||
*/
|
*/
|
||||||
export class InvalidBitFlagValueError extends Error {
|
export class InvalidBitFlagValueError extends Error {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@@ -20,12 +22,50 @@ export class InvalidBitFlagValueError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error thrown if the {@link FlagSet.flag} method is called with a value that
|
* Error thrown when trying to define a flag with an alias that was already used
|
||||||
* was already used for another flag in the same `FlagSet`.
|
* for another flag in the same set.
|
||||||
|
*/
|
||||||
|
export class ReusedFlagAliasError extends Error {
|
||||||
|
/** @internal */
|
||||||
|
public constructor(alias: string) {
|
||||||
|
super(`The flag "${alias}" has already been defined.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when trying to define a flag with a value that was already used
|
||||||
|
* for another flag in the same set.
|
||||||
*/
|
*/
|
||||||
export class ReusedFlagValueError extends Error {
|
export class ReusedFlagValueError extends Error {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public constructor(value: any) {
|
public constructor(
|
||||||
super(`The flag value ${value} is already being used for another flag.`)
|
a: FlagDefinition<unknown, unknown>,
|
||||||
|
b: FlagDefinition<unknown, unknown>,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
`The value ${printFlagValue(a)} is already being used ${b.alias ? 'for the flag "' + b.alias + '"' : 'by another flag'}.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when calling builder methods in a state that is not allowed.
|
||||||
|
*/
|
||||||
|
export class InvalidOperationError extends Error {
|
||||||
|
/** @internal */
|
||||||
|
public constructor(methodName: string, explanation?: string) {
|
||||||
|
super(
|
||||||
|
`.${methodName}() is not allowed here${explanation ? ': ' + explanation : ''}.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown by builders when referencing a flag that is not defined.
|
||||||
|
*/
|
||||||
|
export class InvalidReferenceError extends Error {
|
||||||
|
/** @internal */
|
||||||
|
public constructor(alias: string, requiredBy: string) {
|
||||||
|
super(`Undefined flag "${alias}" required by ${requiredBy}.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,4 +61,8 @@ export class ArrayFlagSet<T> implements FlagSet<T, T[]> {
|
|||||||
minimum(flags: T[]): T[] {
|
minimum(flags: T[]): T[] {
|
||||||
throw new Error('Method not implemented.')
|
throw new Error('Method not implemented.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFlag(alias: string): FlagDefinition<number, number> | undefined {
|
||||||
|
return this._dictionary.lookUp(alias)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,4 +127,8 @@ export class Base64BitFlagSet implements FlagSet<number, string> {
|
|||||||
minimum(flags: string): string {
|
minimum(flags: string): string {
|
||||||
throw new Error('not implemented')
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFlag(alias: string): FlagDefinition<number, number> | undefined {
|
||||||
|
return this._dictionary.lookUp(alias)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,8 @@ export class BigBitFlagSet implements FlagSet<bigint, bigint> {
|
|||||||
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 {
|
||||||
|
return this._dictionary.lookUp(alias)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,4 +46,8 @@ export class CollectionFlagSet<T> implements FlagSet<T, Set<T>> {
|
|||||||
maximum(flags: Set<T>): Set<T> {
|
maximum(flags: Set<T>): Set<T> {
|
||||||
throw new Error('Method not implemented.')
|
throw new Error('Method not implemented.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFlag(alias: string): FlagDefinition<number, number> | undefined {
|
||||||
|
return this._dictionary.lookUp(alias)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { EnumerateFlags } from '../enumeration'
|
import { EnumerateFlags } from '../enumeration'
|
||||||
|
import { FlagDefinition } from '../definitions'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a group of flags of type `F` and the relationships between
|
* Represents a group of flags of type `F` and the relationships between
|
||||||
@@ -13,6 +14,16 @@ export interface FlagSet<F, S> {
|
|||||||
*/
|
*/
|
||||||
none(): S
|
none(): S
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a set of flags from a list of values.
|
||||||
|
*/
|
||||||
|
of(...values: F[]): S
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a set of flags from a list of aliases.
|
||||||
|
*/
|
||||||
|
named(...aliases: string[]): S
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the union of two sets of flags.
|
* Computes the union of two sets of flags.
|
||||||
*
|
*
|
||||||
@@ -54,6 +65,30 @@ export interface FlagSet<F, S> {
|
|||||||
*/
|
*/
|
||||||
isSuperset(first: S, second: S): boolean
|
isSuperset(first: S, second: S): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the first set of flags includes at least one of the flags
|
||||||
|
* from the second set.
|
||||||
|
*
|
||||||
|
* A flag is considered to be part of the set only if all of its parents are
|
||||||
|
* present too.
|
||||||
|
*
|
||||||
|
* @param flags - A set of flags.
|
||||||
|
* @param required - The flags to search for in the first set.
|
||||||
|
*/
|
||||||
|
hasAny(flags: S, required: S): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the first set of flags includes all the flags from the
|
||||||
|
* second set.
|
||||||
|
*
|
||||||
|
* A flag is considered to be part of the set only if all of its parents are
|
||||||
|
* present too.
|
||||||
|
*
|
||||||
|
* @param flags - A set of flags.
|
||||||
|
* @param required - The flags to search for in the first set.
|
||||||
|
*/
|
||||||
|
hasAll(flags: S, required: S): boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an iterable over the individual flags in a set.
|
* Returns an iterable over the individual flags in a set.
|
||||||
*
|
*
|
||||||
@@ -86,6 +121,14 @@ export interface FlagSet<F, S> {
|
|||||||
* @see minimum
|
* @see minimum
|
||||||
*/
|
*/
|
||||||
maximum(flags: S): S
|
maximum(flags: S): S
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a flag definition from its alias.
|
||||||
|
* @param alias The alias of the flag.
|
||||||
|
* @returns The corresponding definition, or `undefined` if there is no flag
|
||||||
|
* with this alias.
|
||||||
|
*/
|
||||||
|
getFlag(alias: string): FlagDefinition<F, S> | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ArrayFlagSet } from './array'
|
export { ArrayFlagSet } from './array'
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
import type { FlagSet } from '.'
|
import type { FlagSet } from '.'
|
||||||
import { BitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration'
|
import { BitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration'
|
||||||
|
import { FlagDefinition, FlagsDictionary } from '../definitions'
|
||||||
|
|
||||||
export class BitFlagSet implements FlagSet<number, number> {
|
export class BitFlagSet implements FlagSet<number, number> {
|
||||||
|
private readonly _dictionary: FlagsDictionary<number, number>
|
||||||
|
|
||||||
|
public constructor(dictionary: FlagsDictionary<number, number>) {
|
||||||
|
this._dictionary = dictionary
|
||||||
|
}
|
||||||
|
|
||||||
public none(): number {
|
public none(): number {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public of(...values: number[]): number {
|
||||||
|
return values.reduce((set, value) => set | value, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public named(...aliases: string[]): number {
|
||||||
|
return aliases.reduce(
|
||||||
|
(set, alias) => set | (this.getFlag(alias)?.values ?? 0),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public union(first: number, second: number): number {
|
public union(first: number, second: number): number {
|
||||||
return first | second
|
return first | second
|
||||||
}
|
}
|
||||||
@@ -22,6 +40,14 @@ export class BitFlagSet implements FlagSet<number, number> {
|
|||||||
return (first & second) == second
|
return (first & second) == second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasAny(flags: number, required: number): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasAll(flags: number, required: number): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
public enumerate(flags: number): EnumerateFlags<number> {
|
public enumerate(flags: number): EnumerateFlags<number> {
|
||||||
return useIterator(flags, BitFlagsIterator)
|
return useIterator(flags, BitFlagsIterator)
|
||||||
}
|
}
|
||||||
@@ -33,4 +59,8 @@ export class BitFlagSet implements FlagSet<number, number> {
|
|||||||
public minimum(flags: number): number {
|
public minimum(flags: number): number {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFlag(alias: string): FlagDefinition<number, number> | undefined {
|
||||||
|
return this._dictionary.findByAlias(alias)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
export { FlagSetBuilder } from './builders'
|
export { FlagSetBuilder, createBitFlagSet } from './builders'
|
||||||
|
export { FlagDefinition, FlagsDictionary } from './definitions'
|
||||||
|
export { InvalidBitFlagValueError } from './errors'
|
||||||
export {
|
export {
|
||||||
ArrayFlagSet,
|
ArrayFlagSet,
|
||||||
Base64BitFlagSet,
|
Base64BitFlagSet,
|
||||||
BigBitFlagSet,
|
BigBitFlagSet,
|
||||||
BitFlagSet,
|
BitFlagSet,
|
||||||
CollectionFlagSet,
|
CollectionFlagSet,
|
||||||
|
FlagSet,
|
||||||
} from './flagsets'
|
} from './flagsets'
|
||||||
export { InvalidBitFlagValueError } from './errors'
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import { ArrayFlagSet } from '@module'
|
|
||||||
|
|
||||||
|
|
||||||
test('Normalise to minimum', () => {
|
|
||||||
const flags = new ArrayFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B', flagA)
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
const flagD = flags.flag('D', flagC)
|
|
||||||
|
|
||||||
expect(flags.minimum([])).toEqual([])
|
|
||||||
expect(flags.minimum(['A'])).toEqual(['A'])
|
|
||||||
expect(flags.minimum(['B'])).toEqual([])
|
|
||||||
expect(flags.minimum(['A', 'B'])).toEqual(['A', 'B'])
|
|
||||||
expect(flags.minimum(['A', 'B', 'D'])).toEqual(['A', 'B'])
|
|
||||||
expect(flags.minimum(['A', 'C', 'D'])).toEqual(['A', 'C', 'D'])
|
|
||||||
expect(flags.minimum(['A', 'E'])).toEqual(['A'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to maximum', () => {
|
|
||||||
const flags = new ArrayFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B', flagA)
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
const flagD = flags.flag('D', flagC)
|
|
||||||
|
|
||||||
expect(flags.maximum([])).toEqual([])
|
|
||||||
expect(flags.maximum(['A'])).toEqual(['A'])
|
|
||||||
expect(flags.maximum(['B'])).toEqual(['B', 'A'])
|
|
||||||
expect(flags.maximum(['A', 'B'])).toEqual(['A', 'B'])
|
|
||||||
expect(flags.maximum(['A', 'B', 'D'])).toEqual(['A', 'B', 'D', 'C'])
|
|
||||||
expect(flags.maximum(['A', 'C', 'D'])).toEqual(['A', 'C', 'D'])
|
|
||||||
expect(flags.maximum(['A', 'E'])).toEqual(['A'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Add to array', () => {
|
|
||||||
const flags = new ArrayFlagSet<string>()
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagC = flags.flag('C')
|
|
||||||
const flagsBAndC = flags.flag(flagB, flagC)
|
|
||||||
|
|
||||||
expect(flagB.addTo(['A'])).toEqual(['A', 'B'])
|
|
||||||
expect(flagC.addTo(['A'])).toEqual(['A', 'C'])
|
|
||||||
expect(flagsBAndC.addTo(['A'])).toEqual(['A', 'B', 'C'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Remove from array', () => {
|
|
||||||
const flags = new ArrayFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
|
|
||||||
expect(flagA.removeFrom(['A', 'B', 'C'])).toEqual(['B'])
|
|
||||||
expect(flagB.removeFrom(['A', 'B', 'C'])).toEqual(['A', 'C'])
|
|
||||||
expect(flagC.removeFrom(['A', 'B', 'C'])).toEqual(['A', 'B'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is in array', () => {
|
|
||||||
const flags = new ArrayFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
|
|
||||||
expect(flagA.isIn(['A'])).toBe(true)
|
|
||||||
expect(flagB.isIn(['A', 'B'])).toBe(true)
|
|
||||||
expect(flagC.isIn(['C'])).toBe(false)
|
|
||||||
expect(flagC.isIn(['A', 'C'])).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is abstract', () => {
|
|
||||||
const flags = new ArrayFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagsAAndB = flags.flag(flagA, flagB)
|
|
||||||
const flagC = flags.flag('C', flagsAAndB)
|
|
||||||
|
|
||||||
expect(flagA.isAbstract).toBe(false)
|
|
||||||
expect(flagB.isAbstract).toBe(false)
|
|
||||||
expect(flagsAAndB.isAbstract).toBe(true)
|
|
||||||
expect(flagC.isAbstract).toBe(false)
|
|
||||||
})
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
import { Base64BitflagSet } from '@module'
|
|
||||||
import { decodeByte, encodeByte } from '../src/base64'
|
|
||||||
|
|
||||||
test('Create from an index', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
|
|
||||||
const flag2 = flags.flag(2)
|
|
||||||
expect(flag2.addTo('')).toEqual('C')
|
|
||||||
|
|
||||||
expect(() => flags.flag(0)).toThrow(RangeError)
|
|
||||||
expect(() => flags.flag(-2)).toThrow(RangeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Union of two base-64 strings', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
|
|
||||||
expect(flags.union('', '')).toEqual('A')
|
|
||||||
expect(flags.union('A', 'A')).toEqual('A')
|
|
||||||
expect(flags.union('B', 'A')).toEqual('B')
|
|
||||||
expect(flags.union('A', 'C')).toEqual('C')
|
|
||||||
expect(flags.union('B', 'C')).toEqual('D')
|
|
||||||
expect(flags.union('D', 'G')).toEqual('H')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Difference of two base-64 strings', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
|
|
||||||
expect(flags.difference('', '')).toEqual('A')
|
|
||||||
expect(flags.difference('A', 'A')).toEqual('A')
|
|
||||||
expect(flags.difference('B', 'A')).toEqual('B')
|
|
||||||
expect(flags.difference('D', 'G')).toEqual('B')
|
|
||||||
expect(flags.difference('G', 'D')).toEqual('E')
|
|
||||||
expect(flags.difference('IB', 'R')).toEqual('IB')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Intersection of two base-64 strings', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
|
|
||||||
expect(flags.intersection('', '')).toEqual('A')
|
|
||||||
expect(flags.intersection('A', 'A')).toEqual('A')
|
|
||||||
expect(flags.intersection('B', 'A')).toEqual('A')
|
|
||||||
expect(flags.intersection('B', 'C')).toEqual('A')
|
|
||||||
expect(flags.intersection('B', 'D')).toEqual('B')
|
|
||||||
expect(flags.intersection('L', 'F')).toEqual('B')
|
|
||||||
expect(flags.intersection('L', 'H')).toEqual('D')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Iterate over a base-64 string', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
|
|
||||||
expect([...flags.iterate('A')]).toEqual([])
|
|
||||||
expect([...flags.iterate('B')]).toEqual([1])
|
|
||||||
expect([...flags.iterate('C')]).toEqual([2])
|
|
||||||
expect([...flags.iterate('D')]).toEqual([1, 2])
|
|
||||||
expect([...flags.iterate('L')]).toEqual([1, 2, 4])
|
|
||||||
expect([...flags.iterate('kB')]).toEqual([3, 6, 7])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to minimum', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2, flag1)
|
|
||||||
const flag3 = flags.flag(3, flag1)
|
|
||||||
const flag4 = flags.flag(4, flag3)
|
|
||||||
|
|
||||||
expect(flags.minimum('A')).toEqual('A')
|
|
||||||
expect(flags.minimum('B')).toEqual('B')
|
|
||||||
expect(flags.minimum('C')).toEqual('A')
|
|
||||||
expect(flags.minimum('D')).toEqual('D')
|
|
||||||
expect(flags.minimum('L')).toEqual('D')
|
|
||||||
expect(flags.minimum('N')).toEqual('N')
|
|
||||||
expect(flags.minimum('R')).toEqual('B')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to maximum', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2, flag1)
|
|
||||||
const flag3 = flags.flag(3, flag1)
|
|
||||||
const flag4 = flags.flag(4, flag3)
|
|
||||||
|
|
||||||
expect(flags.maximum('A')).toEqual('A')
|
|
||||||
expect(flags.maximum('B')).toEqual('B')
|
|
||||||
expect(flags.maximum('C')).toEqual('D')
|
|
||||||
expect(flags.maximum('D')).toEqual('D')
|
|
||||||
expect(flags.maximum('L')).toEqual('P')
|
|
||||||
expect(flags.maximum('N')).toEqual('N')
|
|
||||||
expect(flags.maximum('R')).toEqual('B')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Add to base64 string', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
const flag2 = flags.flag(2)
|
|
||||||
const flag3 = flags.flag(3)
|
|
||||||
const flags2And3 = flags.flag(flag2, flag3)
|
|
||||||
|
|
||||||
expect(flag2.addTo('B')).toEqual('D')
|
|
||||||
expect(flag3.addTo('B')).toEqual('F')
|
|
||||||
expect(flags2And3.addTo('B')).toEqual('H')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Remove from base64 string', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2)
|
|
||||||
const flag3 = flags.flag(3, flag1)
|
|
||||||
|
|
||||||
expect(flag1.removeFrom('H')).toEqual('C')
|
|
||||||
expect(flag2.removeFrom('H')).toEqual('F')
|
|
||||||
expect(flag3.removeFrom('H')).toEqual('D')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is in base64 string', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2)
|
|
||||||
const flag3 = flags.flag(3, flag1)
|
|
||||||
|
|
||||||
expect(flag1.isIn('B')).toBe(true)
|
|
||||||
expect(flag2.isIn('D')).toBe(true)
|
|
||||||
expect(flag3.isIn('E')).toBe(false)
|
|
||||||
expect(flag3.isIn('F')).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is abstract', () => {
|
|
||||||
const flags = new Base64BitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2)
|
|
||||||
const flags1And2 = flags.flag(flag1, flag2)
|
|
||||||
const flag3 = flags.flag(3, flags1And2)
|
|
||||||
|
|
||||||
expect(flag1.isAbstract).toBe(false)
|
|
||||||
expect(flag2.isAbstract).toBe(false)
|
|
||||||
expect(flags1And2.isAbstract).toBe(true)
|
|
||||||
expect(flag3.isAbstract).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('encode a base-64 byte', () => {
|
|
||||||
expect(encodeByte(0)).toEqual('A')
|
|
||||||
expect(encodeByte(1)).toEqual('B')
|
|
||||||
expect(encodeByte(2)).toEqual('C')
|
|
||||||
expect(encodeByte(3)).toEqual('D')
|
|
||||||
expect(encodeByte(4)).toEqual('E')
|
|
||||||
expect(encodeByte(5)).toEqual('F')
|
|
||||||
expect(encodeByte(6)).toEqual('G')
|
|
||||||
expect(encodeByte(7)).toEqual('H')
|
|
||||||
expect(encodeByte(8)).toEqual('I')
|
|
||||||
expect(encodeByte(9)).toEqual('J')
|
|
||||||
expect(encodeByte(10)).toEqual('K')
|
|
||||||
expect(encodeByte(11)).toEqual('L')
|
|
||||||
expect(encodeByte(12)).toEqual('M')
|
|
||||||
expect(encodeByte(13)).toEqual('N')
|
|
||||||
expect(encodeByte(14)).toEqual('O')
|
|
||||||
expect(encodeByte(15)).toEqual('P')
|
|
||||||
expect(encodeByte(16)).toEqual('Q')
|
|
||||||
expect(encodeByte(17)).toEqual('R')
|
|
||||||
expect(encodeByte(18)).toEqual('S')
|
|
||||||
expect(encodeByte(19)).toEqual('T')
|
|
||||||
expect(encodeByte(20)).toEqual('U')
|
|
||||||
expect(encodeByte(21)).toEqual('V')
|
|
||||||
expect(encodeByte(22)).toEqual('W')
|
|
||||||
expect(encodeByte(23)).toEqual('X')
|
|
||||||
expect(encodeByte(24)).toEqual('Y')
|
|
||||||
expect(encodeByte(25)).toEqual('Z')
|
|
||||||
expect(encodeByte(26)).toEqual('a')
|
|
||||||
expect(encodeByte(27)).toEqual('b')
|
|
||||||
expect(encodeByte(28)).toEqual('c')
|
|
||||||
expect(encodeByte(29)).toEqual('d')
|
|
||||||
expect(encodeByte(30)).toEqual('e')
|
|
||||||
expect(encodeByte(31)).toEqual('f')
|
|
||||||
expect(encodeByte(32)).toEqual('g')
|
|
||||||
expect(encodeByte(33)).toEqual('h')
|
|
||||||
expect(encodeByte(34)).toEqual('i')
|
|
||||||
expect(encodeByte(35)).toEqual('j')
|
|
||||||
expect(encodeByte(36)).toEqual('k')
|
|
||||||
expect(encodeByte(37)).toEqual('l')
|
|
||||||
expect(encodeByte(38)).toEqual('m')
|
|
||||||
expect(encodeByte(39)).toEqual('n')
|
|
||||||
expect(encodeByte(40)).toEqual('o')
|
|
||||||
expect(encodeByte(41)).toEqual('p')
|
|
||||||
expect(encodeByte(42)).toEqual('q')
|
|
||||||
expect(encodeByte(43)).toEqual('r')
|
|
||||||
expect(encodeByte(44)).toEqual('s')
|
|
||||||
expect(encodeByte(45)).toEqual('t')
|
|
||||||
expect(encodeByte(46)).toEqual('u')
|
|
||||||
expect(encodeByte(47)).toEqual('v')
|
|
||||||
expect(encodeByte(48)).toEqual('w')
|
|
||||||
expect(encodeByte(49)).toEqual('x')
|
|
||||||
expect(encodeByte(50)).toEqual('y')
|
|
||||||
expect(encodeByte(51)).toEqual('z')
|
|
||||||
expect(encodeByte(52)).toEqual('0')
|
|
||||||
expect(encodeByte(53)).toEqual('1')
|
|
||||||
expect(encodeByte(54)).toEqual('2')
|
|
||||||
expect(encodeByte(55)).toEqual('3')
|
|
||||||
expect(encodeByte(56)).toEqual('4')
|
|
||||||
expect(encodeByte(57)).toEqual('5')
|
|
||||||
expect(encodeByte(58)).toEqual('6')
|
|
||||||
expect(encodeByte(59)).toEqual('7')
|
|
||||||
expect(encodeByte(60)).toEqual('8')
|
|
||||||
expect(encodeByte(61)).toEqual('9')
|
|
||||||
expect(encodeByte(62)).toEqual('-')
|
|
||||||
expect(encodeByte(63)).toEqual('_')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('encode a base-64 byte', () => {
|
|
||||||
expect(decodeByte('A')).toEqual(0)
|
|
||||||
expect(decodeByte('B')).toEqual(1)
|
|
||||||
expect(decodeByte('C')).toEqual(2)
|
|
||||||
expect(decodeByte('D')).toEqual(3)
|
|
||||||
expect(decodeByte('E')).toEqual(4)
|
|
||||||
expect(decodeByte('F')).toEqual(5)
|
|
||||||
expect(decodeByte('G')).toEqual(6)
|
|
||||||
expect(decodeByte('H')).toEqual(7)
|
|
||||||
expect(decodeByte('I')).toEqual(8)
|
|
||||||
expect(decodeByte('J')).toEqual(9)
|
|
||||||
expect(decodeByte('K')).toEqual(10)
|
|
||||||
expect(decodeByte('L')).toEqual(11)
|
|
||||||
expect(decodeByte('M')).toEqual(12)
|
|
||||||
expect(decodeByte('N')).toEqual(13)
|
|
||||||
expect(decodeByte('O')).toEqual(14)
|
|
||||||
expect(decodeByte('P')).toEqual(15)
|
|
||||||
expect(decodeByte('Q')).toEqual(16)
|
|
||||||
expect(decodeByte('R')).toEqual(17)
|
|
||||||
expect(decodeByte('S')).toEqual(18)
|
|
||||||
expect(decodeByte('T')).toEqual(19)
|
|
||||||
expect(decodeByte('U')).toEqual(20)
|
|
||||||
expect(decodeByte('V')).toEqual(21)
|
|
||||||
expect(decodeByte('W')).toEqual(22)
|
|
||||||
expect(decodeByte('X')).toEqual(23)
|
|
||||||
expect(decodeByte('Y')).toEqual(24)
|
|
||||||
expect(decodeByte('Z')).toEqual(25)
|
|
||||||
expect(decodeByte('a')).toEqual(26)
|
|
||||||
expect(decodeByte('b')).toEqual(27)
|
|
||||||
expect(decodeByte('c')).toEqual(28)
|
|
||||||
expect(decodeByte('d')).toEqual(29)
|
|
||||||
expect(decodeByte('e')).toEqual(30)
|
|
||||||
expect(decodeByte('f')).toEqual(31)
|
|
||||||
expect(decodeByte('g')).toEqual(32)
|
|
||||||
expect(decodeByte('h')).toEqual(33)
|
|
||||||
expect(decodeByte('i')).toEqual(34)
|
|
||||||
expect(decodeByte('j')).toEqual(35)
|
|
||||||
expect(decodeByte('k')).toEqual(36)
|
|
||||||
expect(decodeByte('l')).toEqual(37)
|
|
||||||
expect(decodeByte('m')).toEqual(38)
|
|
||||||
expect(decodeByte('n')).toEqual(39)
|
|
||||||
expect(decodeByte('o')).toEqual(40)
|
|
||||||
expect(decodeByte('p')).toEqual(41)
|
|
||||||
expect(decodeByte('q')).toEqual(42)
|
|
||||||
expect(decodeByte('r')).toEqual(43)
|
|
||||||
expect(decodeByte('s')).toEqual(44)
|
|
||||||
expect(decodeByte('t')).toEqual(45)
|
|
||||||
expect(decodeByte('u')).toEqual(46)
|
|
||||||
expect(decodeByte('v')).toEqual(47)
|
|
||||||
expect(decodeByte('w')).toEqual(48)
|
|
||||||
expect(decodeByte('x')).toEqual(49)
|
|
||||||
expect(decodeByte('y')).toEqual(50)
|
|
||||||
expect(decodeByte('z')).toEqual(51)
|
|
||||||
expect(decodeByte('0')).toEqual(52)
|
|
||||||
expect(decodeByte('1')).toEqual(53)
|
|
||||||
expect(decodeByte('2')).toEqual(54)
|
|
||||||
expect(decodeByte('3')).toEqual(55)
|
|
||||||
expect(decodeByte('4')).toEqual(56)
|
|
||||||
expect(decodeByte('5')).toEqual(57)
|
|
||||||
expect(decodeByte('6')).toEqual(58)
|
|
||||||
expect(decodeByte('7')).toEqual(59)
|
|
||||||
expect(decodeByte('8')).toEqual(60)
|
|
||||||
expect(decodeByte('9')).toEqual(61)
|
|
||||||
expect(decodeByte('-')).toEqual(62)
|
|
||||||
expect(decodeByte('_')).toEqual(63)
|
|
||||||
})
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { BigBitFlagSet } from '~'
|
|
||||||
|
|
||||||
const bigPowerOfTwo = 2n ** 100n
|
|
||||||
|
|
||||||
|
|
||||||
test('Iterate over a number', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
|
|
||||||
expect([...flags.iterate(0n)]).toEqual([])
|
|
||||||
expect([...flags.iterate(1n)]).toEqual([1n])
|
|
||||||
expect([...flags.iterate(2n)]).toEqual([2n])
|
|
||||||
expect([...flags.iterate(3n)]).toEqual([1n, 2n])
|
|
||||||
expect([...flags.iterate(11n)]).toEqual([1n, 2n, 8n])
|
|
||||||
expect([...flags.iterate(100n)]).toEqual([4n, 32n, 64n])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to minimum', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
const flag1 = flags.flag(1n)
|
|
||||||
const flag2 = flags.flag(2n, flag1)
|
|
||||||
const flag4 = flags.flag(4n, flag1)
|
|
||||||
const flag8 = flags.flag(8n, flag4)
|
|
||||||
|
|
||||||
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(11n)).toEqual(3n)
|
|
||||||
expect(flags.minimum(13n)).toEqual(13n)
|
|
||||||
expect(flags.minimum(17n)).toEqual(1n)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to maximum', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
const flag1 = flags.flag(1n)
|
|
||||||
const flag2 = flags.flag(2n, flag1)
|
|
||||||
const flag4 = flags.flag(4n, flag1)
|
|
||||||
const flag8 = flags.flag(8n, flag4)
|
|
||||||
|
|
||||||
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(11n)).toEqual(15n)
|
|
||||||
expect(flags.maximum(13n)).toEqual(13n)
|
|
||||||
expect(flags.maximum(17n)).toEqual(1n)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Add to bigint', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
const flag2 = flags.flag(2n)
|
|
||||||
const flag4 = flags.flag(4n)
|
|
||||||
const flags2And4 = flags.flag(flag2, flag4)
|
|
||||||
const flag100 = flags.flag(bigPowerOfTwo)
|
|
||||||
|
|
||||||
expect(flag2.addTo(1n)).toEqual(3n)
|
|
||||||
expect(flag4.addTo(1n)).toEqual(5n)
|
|
||||||
expect(flags2And4.addTo(1n)).toEqual(7n)
|
|
||||||
expect(flag100.addTo(1n)).toEqual(bigPowerOfTwo + 1n)
|
|
||||||
expect(flag2.addTo(bigPowerOfTwo)).toEqual(bigPowerOfTwo + 2n)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Remove from bigint', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
const flag1 = flags.flag(1n)
|
|
||||||
const flag2 = flags.flag(2n)
|
|
||||||
const flag4 = flags.flag(4n, flag1)
|
|
||||||
const flag100 = flags.flag(bigPowerOfTwo)
|
|
||||||
|
|
||||||
expect(flag1.removeFrom(7n)).toEqual(2n)
|
|
||||||
expect(flag2.removeFrom(7n)).toEqual(5n)
|
|
||||||
expect(flag4.removeFrom(7n)).toEqual(3n)
|
|
||||||
expect(flag100.removeFrom(bigPowerOfTwo + 2n)).toEqual(2n)
|
|
||||||
expect(flag100.removeFrom(2n)).toEqual(2n)
|
|
||||||
expect(flag1.removeFrom(bigPowerOfTwo - 1n)).toEqual(bigPowerOfTwo - 6n)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is in bigint', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
const flag1 = flags.flag(1n)
|
|
||||||
const flag2 = flags.flag(2n)
|
|
||||||
const flag4 = flags.flag(4n, flag1)
|
|
||||||
|
|
||||||
expect(flag1.isIn(1n)).toBe(true)
|
|
||||||
expect(flag2.isIn(3n)).toBe(true)
|
|
||||||
expect(flag4.isIn(4n)).toBe(false)
|
|
||||||
expect(flag4.isIn(5n)).toBe(true)
|
|
||||||
expect(flag1.isIn(bigPowerOfTwo + 1n)).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is abstract', () => {
|
|
||||||
const flags = new BigBitFlagSet()
|
|
||||||
const flag1 = flags.flag(1n)
|
|
||||||
const flag2 = flags.flag(2n)
|
|
||||||
const flags1And2 = flags.flag(flag1, flag2)
|
|
||||||
const flag4 = flags.flag(4n, flags1And2)
|
|
||||||
|
|
||||||
expect(flag1.isAbstract).toBe(false)
|
|
||||||
expect(flag2.isAbstract).toBe(false)
|
|
||||||
expect(flags1And2.isAbstract).toBe(true)
|
|
||||||
expect(flag4.isAbstract).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Environment without bigint', async () => {
|
|
||||||
const originalBigInt = globalThis.BigInt
|
|
||||||
// @ts-ignore
|
|
||||||
delete globalThis.BigInt
|
|
||||||
let module: { BigBitFlagSet: typeof BigBitFlagSet }
|
|
||||||
await jest.isolateModulesAsync(async () => {
|
|
||||||
module = await import('@module')
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(() => new module.BigBitFlagSet()).toThrow()
|
|
||||||
|
|
||||||
globalThis.BigInt = originalBigInt
|
|
||||||
})
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
import { CollectionFlagSet } from '@module'
|
|
||||||
import { ok } from 'node:assert'
|
|
||||||
|
|
||||||
function set<T>(...values: T[]): Set<T> {
|
|
||||||
return new Set<T>(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
test('Union of two sets', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
|
|
||||||
expect(flags.union(set(), set())).toEqual(set())
|
|
||||||
expect(flags.union(set('A'), set())).toEqual(set('A'))
|
|
||||||
expect(flags.union(set(), set('B'))).toEqual(set('B'))
|
|
||||||
expect(flags.union(set('A'), set('B'))).toEqual(set('A', 'B'))
|
|
||||||
expect(flags.union(set('A', 'B'), set('B', 'C'))).toEqual(
|
|
||||||
set('A', 'B', 'C')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Difference of two sets', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
|
|
||||||
expect(flags.difference(set(), set())).toEqual(set())
|
|
||||||
expect(flags.difference(set('A'), set())).toEqual(set('A'))
|
|
||||||
expect(flags.difference(set('A', 'B'), set('B', 'C'))).toEqual(set('A'))
|
|
||||||
expect(flags.difference(set('B', 'C'), set('A', 'B'))).toEqual(set('C'))
|
|
||||||
expect(flags.difference(set('D'), set('A', 'E'))).toEqual(set('D'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Intersection of two sets', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
|
|
||||||
expect(flags.intersection(set(), set())).toEqual(set())
|
|
||||||
expect(flags.intersection(set('A'), set())).toEqual(set())
|
|
||||||
expect(flags.intersection(set('A'), set('B'))).toEqual(set())
|
|
||||||
expect(flags.intersection(set('A'), set('A', 'B'))).toEqual(set('A'))
|
|
||||||
expect(flags.intersection(set('A', 'B', 'D'), set('A', 'C'))).toEqual(
|
|
||||||
set('A')
|
|
||||||
)
|
|
||||||
expect(flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C'))).toEqual(
|
|
||||||
set('A', 'B')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Iterate over a set', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
|
|
||||||
expect([...flags.iterate(set())]).toEqual([])
|
|
||||||
expect([...flags.iterate(set('A'))]).toEqual(['A'])
|
|
||||||
expect([...flags.iterate(set('A', 'B', 'C'))]).toEqual(['A', 'B', 'C'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to minimum', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B', flagA)
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
const flagD = flags.flag('D', flagC)
|
|
||||||
|
|
||||||
expect(flags.minimum(set())).toEqual(set())
|
|
||||||
expect(flags.minimum(set('A'))).toEqual(set('A'))
|
|
||||||
expect(flags.minimum(set('B'))).toEqual(set())
|
|
||||||
expect(flags.minimum(set('A', 'B'))).toEqual(set('A', 'B'))
|
|
||||||
expect(flags.minimum(set('A', 'B', 'D'))).toEqual(set('A', 'B'))
|
|
||||||
expect(flags.minimum(set('A', 'C', 'D'))).toEqual(set('A', 'C', 'D'))
|
|
||||||
expect(flags.minimum(set('A', 'E'))).toEqual(set('A'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to maximum', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B', flagA)
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
const flagD = flags.flag('D', flagC)
|
|
||||||
|
|
||||||
expect(flags.maximum(set())).toEqual(set())
|
|
||||||
expect(flags.maximum(set('A'))).toEqual(set('A'))
|
|
||||||
expect(flags.maximum(set('B'))).toEqual(set('B', 'A'))
|
|
||||||
expect(flags.maximum(set('A', 'B'))).toEqual(set('A', 'B'))
|
|
||||||
expect(flags.maximum(set('A', 'B', 'D'))).toEqual(set('A', 'B', 'D', 'C'))
|
|
||||||
expect(flags.maximum(set('A', 'C', 'D'))).toEqual(set('A', 'C', 'D'))
|
|
||||||
expect(flags.maximum(set('A', 'E'))).toEqual(set('A'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Add to set', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagC = flags.flag('C')
|
|
||||||
const flagsBAndC = flags.flag(flagB, flagC)
|
|
||||||
|
|
||||||
expect(flagB.addTo(set('A'))).toEqual(set('A', 'B'))
|
|
||||||
expect(flagC.addTo(set('A'))).toEqual(set('A', 'C'))
|
|
||||||
expect(flagsBAndC.addTo(set('A'))).toEqual(set('A', 'B', 'C'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Remove from set', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
|
|
||||||
expect(flagA.removeFrom(set('A', 'B', 'C'))).toEqual(set('B'))
|
|
||||||
expect(flagB.removeFrom(set('A', 'B', 'C'))).toEqual(set('A', 'C'))
|
|
||||||
expect(flagC.removeFrom(set('A', 'B', 'C'))).toEqual(set('A', 'B'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is in set', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagC = flags.flag('C', flagA)
|
|
||||||
|
|
||||||
expect(flagA.isIn(set('A'))).toBe(true)
|
|
||||||
expect(flagB.isIn(set('A', 'B'))).toBe(true)
|
|
||||||
expect(flagC.isIn(set('C'))).toBe(false)
|
|
||||||
expect(flagC.isIn(set('A', 'C'))).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Is abstract', () => {
|
|
||||||
const flags = new CollectionFlagSet<string>()
|
|
||||||
const flagA = flags.flag('A')
|
|
||||||
const flagB = flags.flag('B')
|
|
||||||
const flagsAAndB = flags.flag(flagA, flagB)
|
|
||||||
const flagC = flags.flag('C', flagsAAndB)
|
|
||||||
|
|
||||||
expect(flagA.isAbstract).toBe(false)
|
|
||||||
expect(flagB.isAbstract).toBe(false)
|
|
||||||
expect(flagsAAndB.isAbstract).toBe(true)
|
|
||||||
expect(flagC.isAbstract).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Environment without Set', async () => {
|
|
||||||
const originalSet = globalThis.Set
|
|
||||||
// @ts-ignore
|
|
||||||
delete globalThis.Set
|
|
||||||
let module: { CollectionFlagSet: typeof CollectionFlagSet }
|
|
||||||
await jest.isolateModulesAsync(async () => {
|
|
||||||
module = await import('@module')
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(() => new module.CollectionFlagSet()).toThrow()
|
|
||||||
|
|
||||||
globalThis.Set = originalSet
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Environment with Set.prototype.union', async () => {
|
|
||||||
ok(!('union' in globalThis.Set.prototype))
|
|
||||||
|
|
||||||
const mockUnion = jest.fn((_) => new Set())
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.Set.prototype.union = mockUnion
|
|
||||||
let module: { CollectionFlagSet: typeof CollectionFlagSet } | undefined
|
|
||||||
await jest.isolateModulesAsync(async () => {
|
|
||||||
module = await import('@module')
|
|
||||||
})
|
|
||||||
ok(module !== undefined)
|
|
||||||
|
|
||||||
const set1 = new Set()
|
|
||||||
const set2 = new Set()
|
|
||||||
const flags = new module.CollectionFlagSet()
|
|
||||||
flags.union(set1, set2)
|
|
||||||
expect(mockUnion).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockUnion.mock.contexts[0]).toBe(set1)
|
|
||||||
expect(mockUnion.mock.calls[0][0]).toBe(set2)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
delete globalThis.Set.prototype.union
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Environment with Set.prototype.difference', async () => {
|
|
||||||
ok(!('difference' in globalThis.Set.prototype))
|
|
||||||
|
|
||||||
const mockDifference = jest.fn((_) => new Set())
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.Set.prototype.difference = mockDifference
|
|
||||||
let module: { CollectionFlagSet: typeof CollectionFlagSet } | undefined
|
|
||||||
await jest.isolateModulesAsync(async () => {
|
|
||||||
module = await import('@module')
|
|
||||||
})
|
|
||||||
ok(module !== undefined)
|
|
||||||
|
|
||||||
const set1 = new Set()
|
|
||||||
const set2 = new Set()
|
|
||||||
const flags = new module.CollectionFlagSet()
|
|
||||||
flags.difference(set1, set2)
|
|
||||||
expect(mockDifference).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockDifference.mock.contexts[0]).toBe(set1)
|
|
||||||
expect(mockDifference.mock.calls[0][0]).toBe(set2)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
delete globalThis.Set.prototype.difference
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Environment with Set.prototype.intersection', async () => {
|
|
||||||
ok(!('intersection' in globalThis.Set.prototype))
|
|
||||||
|
|
||||||
const mockIntersection = jest.fn((_) => new Set())
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.Set.prototype.intersection = mockIntersection
|
|
||||||
let module: { CollectionFlagSet: typeof CollectionFlagSet } | undefined
|
|
||||||
await jest.isolateModulesAsync(async () => {
|
|
||||||
module = await import('@module')
|
|
||||||
})
|
|
||||||
ok(module !== undefined)
|
|
||||||
|
|
||||||
const set1 = new Set()
|
|
||||||
const set2 = new Set()
|
|
||||||
const flags = new module.CollectionFlagSet()
|
|
||||||
flags.intersection(set1, set2)
|
|
||||||
expect(mockIntersection).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockIntersection.mock.contexts[0]).toBe(set1)
|
|
||||||
expect(mockIntersection.mock.calls[0][0]).toBe(set2)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
delete globalThis.Set.prototype.intersection
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Environment with Set.prototype.isSupersetOf', async () => {
|
|
||||||
ok(!('isSupersetOf' in globalThis.Set.prototype))
|
|
||||||
|
|
||||||
const mockIsSupersetOf = jest.fn((_) => new Set())
|
|
||||||
// @ts-ignore
|
|
||||||
globalThis.Set.prototype.isSupersetOf = mockIsSupersetOf
|
|
||||||
let module: { CollectionFlagSet: typeof CollectionFlagSet } | undefined
|
|
||||||
await jest.isolateModulesAsync(async () => {
|
|
||||||
module = await import('@module')
|
|
||||||
})
|
|
||||||
ok(module !== undefined)
|
|
||||||
|
|
||||||
const set1 = new Set()
|
|
||||||
const set2 = new Set()
|
|
||||||
const flags = new module.CollectionFlagSet()
|
|
||||||
flags.isSupersetOf(set1, set2)
|
|
||||||
expect(mockIsSupersetOf).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockIsSupersetOf.mock.contexts[0]).toBe(set1)
|
|
||||||
expect(mockIsSupersetOf.mock.calls[0][0]).toBe(set2)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
delete globalThis.Set.prototype.isSupersetOf
|
|
||||||
})
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { NumberBitflagSet, ForeignFlagError } from '@module'
|
|
||||||
|
|
||||||
test('cannot create a flag with a foreign parent', () => {
|
|
||||||
const flags = new NumberBitflagSet()
|
|
||||||
const flagA = flags.flag(1)
|
|
||||||
|
|
||||||
const otherFlags = new NumberBitflagSet()
|
|
||||||
const flagX = otherFlags.flag(1)
|
|
||||||
|
|
||||||
flags.flag(2, flagA) // OK
|
|
||||||
expect(() => flags.flag(4, flagX)).toThrow(ForeignFlagError)
|
|
||||||
})
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { NumberBitflagSet, ReusedFlagValueError } from '@module'
|
|
||||||
|
|
||||||
test('cannot create an abstract flag with less than two parents', () => {
|
|
||||||
const flags = new NumberBitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
|
|
||||||
expect(() => flags.flag()).toThrow(TypeError)
|
|
||||||
expect(() => flags.flag(flag1)).toThrow(TypeError)
|
|
||||||
expect(() => flags.flag(flag1, flag1)).toThrow(TypeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('calls to flag() with arguments in the wrong order throw a TypeError', () => {
|
|
||||||
const flags = new NumberBitflagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
expect(() => flags.flag(flag1, 2, flag2)).toThrow(TypeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Use same value twice', () => {
|
|
||||||
const flags = new NumberBitflagSet()
|
|
||||||
const flag = flags.flag(1)
|
|
||||||
expect(() => flags.flag(1)).toThrow(ReusedFlagValueError)
|
|
||||||
})
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { NumberBitflagSet, InvalidBitflagValueError } from '@module'
|
|
||||||
|
|
||||||
test('Not powers of two', () => {
|
|
||||||
const flags = new BitFlagSet()
|
|
||||||
expect(() => flags.flag(0)).toThrow(InvalidBitFlagValueError)
|
|
||||||
expect(() => flags.flag(11)).toThrow(InvalidBitFlagValueError)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to minimum', () => {
|
|
||||||
const flags = new BitFlagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2, flag1)
|
|
||||||
const flag4 = flags.flag(4, flag1)
|
|
||||||
const flag8 = flags.flag(8, flag4)
|
|
||||||
|
|
||||||
expect(flags.minimum(0)).toEqual(0)
|
|
||||||
expect(flags.minimum(1)).toEqual(1)
|
|
||||||
expect(flags.minimum(2)).toEqual(0)
|
|
||||||
expect(flags.minimum(3)).toEqual(3)
|
|
||||||
expect(flags.minimum(11)).toEqual(3)
|
|
||||||
expect(flags.minimum(13)).toEqual(13)
|
|
||||||
expect(flags.minimum(17)).toEqual(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Normalise to maximum', () => {
|
|
||||||
const flags = new BitFlagSet()
|
|
||||||
const flag1 = flags.flag(1)
|
|
||||||
const flag2 = flags.flag(2, flag1)
|
|
||||||
const flag4 = flags.flag(4, flag1)
|
|
||||||
const flag8 = flags.flag(8, flag4)
|
|
||||||
|
|
||||||
expect(flags.maximum(0)).toEqual(0)
|
|
||||||
expect(flags.maximum(1)).toEqual(1)
|
|
||||||
expect(flags.maximum(2)).toEqual(3)
|
|
||||||
expect(flags.maximum(3)).toEqual(3)
|
|
||||||
expect(flags.maximum(11)).toEqual(15)
|
|
||||||
expect(flags.maximum(13)).toEqual(13)
|
|
||||||
expect(flags.maximum(17)).toEqual(1)
|
|
||||||
})
|
|
||||||
49
node/tests/definitions/dictionary.test.ts
Normal file
49
node/tests/definitions/dictionary.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { FlagDefinition, FlagsDictionary } from '~'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
import { ReusedFlagAliasError, ReusedFlagValueError } from '../../src/errors'
|
||||||
|
|
||||||
|
class TestDefinition implements FlagDefinition<unknown, unknown> {
|
||||||
|
private readonly _value: number
|
||||||
|
|
||||||
|
public constructor(value: number) {
|
||||||
|
this._value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public get values(): unknown {
|
||||||
|
return this._value
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasSameValue(other: FlagDefinition<unknown, unknown>): boolean {
|
||||||
|
return other instanceof TestDefinition && this._value === other._value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe(FlagsDictionary, () => {
|
||||||
|
test('define then look up', () => {
|
||||||
|
const dict = new FlagsDictionary()
|
||||||
|
const def = new TestDefinition(1)
|
||||||
|
|
||||||
|
dict.define('test', def)
|
||||||
|
|
||||||
|
expect(dict.findByAlias('test')).toBe(def)
|
||||||
|
expect(dict.findByAlias('undefined')).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("can't use the same alias twice", () => {
|
||||||
|
const dict = new FlagsDictionary()
|
||||||
|
|
||||||
|
dict.define('test', new TestDefinition(1))
|
||||||
|
expect(() => dict.define('test', new TestDefinition(2))).toThrow(
|
||||||
|
ReusedFlagAliasError,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("can't use the same value twice", () => {
|
||||||
|
const dict = new FlagsDictionary()
|
||||||
|
|
||||||
|
dict.define('test A', new TestDefinition(1))
|
||||||
|
expect(() => dict.define('test B', new TestDefinition(1))).toThrow(
|
||||||
|
ReusedFlagValueError,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ArrayFlagSet } from '~'
|
import { ArrayFlagSet } from '~'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
describe(ArrayFlagSet, () => {
|
describe(ArrayFlagSet, () => {
|
||||||
test('none', () => {
|
test('none', () => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Base64BitFlagSet } from '~'
|
import { Base64BitFlagSet } from '~'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
describe(Base64BitFlagSet, () => {
|
describe(Base64BitFlagSet, () => {
|
||||||
test('none', () => {
|
test('none', () => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BigBitFlagSet } from '~'
|
import { BigBitFlagSet } from '~'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
describe(BigBitFlagSet, () => {
|
describe(BigBitFlagSet, () => {
|
||||||
test('none', () => {
|
test('none', () => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CollectionFlagSet } from '~'
|
import { CollectionFlagSet } from '~'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
function set<T>(...values: T[]): Set<T> {
|
function set<T>(...values: T[]): Set<T> {
|
||||||
return new Set<T>(values)
|
return new Set<T>(values)
|
||||||
@@ -19,7 +20,7 @@ describe(CollectionFlagSet, () => {
|
|||||||
expect(flags.union(set(), set('B'))).toEqual(set('B'))
|
expect(flags.union(set(), set('B'))).toEqual(set('B'))
|
||||||
expect(flags.union(set('A'), set('B'))).toEqual(set('A', 'B'))
|
expect(flags.union(set('A'), set('B'))).toEqual(set('A', 'B'))
|
||||||
expect(flags.union(set('A', 'B'), set('B', 'C'))).toEqual(
|
expect(flags.union(set('A', 'B'), set('B', 'C'))).toEqual(
|
||||||
set('A', 'B', 'C')
|
set('A', 'B', 'C'),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -41,10 +42,10 @@ describe(CollectionFlagSet, () => {
|
|||||||
expect(flags.intersection(set('A'), set('B'))).toEqual(set())
|
expect(flags.intersection(set('A'), set('B'))).toEqual(set())
|
||||||
expect(flags.intersection(set('A'), set('A', 'B'))).toEqual(set('A'))
|
expect(flags.intersection(set('A'), set('A', 'B'))).toEqual(set('A'))
|
||||||
expect(flags.intersection(set('A', 'B', 'D'), set('A', 'C'))).toEqual(
|
expect(flags.intersection(set('A', 'B', 'D'), set('A', 'C'))).toEqual(
|
||||||
set('A')
|
set('A'),
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C'))
|
flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C')),
|
||||||
).toEqual(set('A', 'B'))
|
).toEqual(set('A', 'B'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,40 @@
|
|||||||
import { BitFlagSet } from '~'
|
import { BitFlagSet, createBitFlagSet } from '~'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
describe(BitFlagSet, () => {
|
describe(BitFlagSet, () => {
|
||||||
test('none', () => {
|
test('none', () => {
|
||||||
const flags = new BitFlagSet()
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
expect(flags.none()).toEqual(0)
|
expect(flags.none()).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('of', () => {
|
||||||
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
|
expect(flags.of()).toEqual(0)
|
||||||
|
expect(flags.of(1)).toEqual(1)
|
||||||
|
expect(flags.of(3, 8)).toEqual(11)
|
||||||
|
expect(flags.of(3, 5, 2)).toEqual(7)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('named', () => {
|
||||||
|
const flags = createBitFlagSet([
|
||||||
|
{ value: 1, as: 'A' },
|
||||||
|
{ value: 2, as: 'B' },
|
||||||
|
{ value: 4, as: 'C' },
|
||||||
|
{ value: 8, as: 'D' },
|
||||||
|
{ compose: ['A', 'B'], as: 'AB' },
|
||||||
|
{ compose: ['A', 'C'], as: 'AC' },
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(flags.named()).toEqual(0)
|
||||||
|
expect(flags.named('A')).toEqual(1)
|
||||||
|
expect(flags.named('AB', 'D')).toEqual(11)
|
||||||
|
expect(flags.named('AB', 'AC', 'B')).toEqual(7)
|
||||||
|
})
|
||||||
|
|
||||||
test('union', () => {
|
test('union', () => {
|
||||||
const flags = new BitFlagSet()
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
expect(flags.union(0, 0)).toEqual(0)
|
expect(flags.union(0, 0)).toEqual(0)
|
||||||
expect(flags.union(1, 0)).toEqual(1)
|
expect(flags.union(1, 0)).toEqual(1)
|
||||||
@@ -18,7 +44,7 @@ describe(BitFlagSet, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('difference', () => {
|
test('difference', () => {
|
||||||
const flags = new BitFlagSet()
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
expect(flags.difference(0, 0)).toEqual(0)
|
expect(flags.difference(0, 0)).toEqual(0)
|
||||||
expect(flags.difference(1, 0)).toEqual(1)
|
expect(flags.difference(1, 0)).toEqual(1)
|
||||||
@@ -28,7 +54,7 @@ describe(BitFlagSet, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('intersection', () => {
|
test('intersection', () => {
|
||||||
const flags = new BitFlagSet()
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
expect(flags.intersection(0, 0)).toEqual(0)
|
expect(flags.intersection(0, 0)).toEqual(0)
|
||||||
expect(flags.intersection(1, 0)).toEqual(0)
|
expect(flags.intersection(1, 0)).toEqual(0)
|
||||||
@@ -39,7 +65,7 @@ describe(BitFlagSet, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('isSuperset', () => {
|
test('isSuperset', () => {
|
||||||
const flags = new BitFlagSet()
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
expect(flags.isSuperset(0, 0)).toBe(true)
|
expect(flags.isSuperset(0, 0)).toBe(true)
|
||||||
expect(flags.isSuperset(3, 0)).toBe(true)
|
expect(flags.isSuperset(3, 0)).toBe(true)
|
||||||
@@ -50,7 +76,7 @@ describe(BitFlagSet, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('enumerate', () => {
|
test('enumerate', () => {
|
||||||
const flags = new BitFlagSet()
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
expect([...flags.enumerate(0)]).toEqual([])
|
expect([...flags.enumerate(0)]).toEqual([])
|
||||||
expect([...flags.enumerate(1)]).toEqual([1])
|
expect([...flags.enumerate(1)]).toEqual([1])
|
||||||
|
|||||||
9
node/vitest.config.ts
Normal file
9
node/vitest.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
alias: {
|
||||||
|
'~': 'src/index.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user