implementation of minimum, maximum, hasAny and hasAll for bitflags
This commit is contained in:
@@ -3,6 +3,20 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"description": "flag/bitflag helper",
|
"description": "flag/bitflag helper",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/louisdevie/multiflag.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"flag",
|
||||||
|
"bitflag"
|
||||||
|
],
|
||||||
|
"author": "Louis DEVIE",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/louisdevie/tatsuki/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/louisdevie/multiflag#readme",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
"postbuild": "cp package.json README.md ../LICENSE dist",
|
"postbuild": "cp package.json README.md ../LICENSE dist",
|
||||||
@@ -21,20 +35,6 @@
|
|||||||
"sourcemap": true,
|
"sourcemap": true,
|
||||||
"clean": true
|
"clean": true
|
||||||
},
|
},
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/louisdevie/multiflag.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"flag",
|
|
||||||
"bitflag"
|
|
||||||
],
|
|
||||||
"author": "Louis DEVIE",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/louisdevie/multiflag/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/louisdevie/multiflag#readme",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"@vitest/ui": "^4.0.18",
|
"@vitest/ui": "^4.0.18",
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type {
|
import type { DefineWithOrdinal, DefineWithValue, DefineWithValueOrOrdinal } from './syntax'
|
||||||
DefineWithOrdinal,
|
import { FlagsGraph, refByAlias } from '~/definitions'
|
||||||
DefineWithValue,
|
|
||||||
DefineWithValueOrOrdinal,
|
|
||||||
} from './syntax'
|
|
||||||
|
|
||||||
// Generic helper functions
|
// Generic helper functions
|
||||||
|
|
||||||
@@ -10,9 +7,7 @@ import type {
|
|||||||
* Copies the record keys into the 'as' property of the values and return an
|
* Copies the record keys into the 'as' property of the values and return an
|
||||||
* array containing those values.
|
* array containing those values.
|
||||||
*/
|
*/
|
||||||
function toDefinitionArray<D>(
|
function toDeclarationArray<D>(record: Record<string, D>): (D & { as: string })[] {
|
||||||
record: Record<string, D>,
|
|
||||||
): (D & { as: string })[] {
|
|
||||||
return Object.keys(record).map((key) => ({ ...record[key], as: key }))
|
return Object.keys(record).map((key) => ({ ...record[key], as: key }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,35 +17,9 @@ export type FlagWithValue<F> =
|
|||||||
| { value: F; as?: string; requires?: string[] }
|
| { value: F; as?: string; requires?: string[] }
|
||||||
| { compose: string[]; as: string }
|
| { compose: string[]; as: string }
|
||||||
|
|
||||||
export type NamedFlagWithValue<F> =
|
export type NamedFlagWithValue<F> = { value: F; requires?: string[] } | { compose: string[] }
|
||||||
| { value: F; requires?: string[] }
|
|
||||||
| { compose: string[] }
|
|
||||||
|
|
||||||
export type ListOfFlagsWithValue<F> =
|
export type ListOfFlagsWithValue<F> = FlagWithValue<F>[] | Record<string, NamedFlagWithValue<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
|
// Declarations for builders that supports only definitions by ordinal
|
||||||
|
|
||||||
@@ -66,28 +35,6 @@ export type ListOfFlagsWithOrdinal<F> =
|
|||||||
| FlagWithOrdinal<F>[]
|
| FlagWithOrdinal<F>[]
|
||||||
| Record<string, NamedFlagWithOrdinal<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
|
// Declarations for builders that supports definitions by value and ordinal
|
||||||
|
|
||||||
export type FlagWithValueOrOrdinal<F> =
|
export type FlagWithValueOrOrdinal<F> =
|
||||||
@@ -104,13 +51,13 @@ export type ListOfFlagsWithValueOrOrdinal<F> =
|
|||||||
| FlagWithValueOrOrdinal<F>[]
|
| FlagWithValueOrOrdinal<F>[]
|
||||||
| Record<string, NamedFlagWithValueOrOrdinal<F>>
|
| Record<string, NamedFlagWithValueOrOrdinal<F>>
|
||||||
|
|
||||||
export function applyDeclarationsWithValueOrOrdinal<F>(
|
export function applyDeclarations<F>(
|
||||||
declarations: ListOfFlagsWithValueOrOrdinal<F>,
|
declarations: ListOfFlagsWithValueOrOrdinal<F>,
|
||||||
builder: DefineWithValueOrOrdinal<F, unknown, unknown>,
|
builder: DefineWithValueOrOrdinal<F, unknown, unknown>,
|
||||||
) {
|
) {
|
||||||
const declarationsArray = Array.isArray(declarations)
|
const declarationsArray = Array.isArray(declarations)
|
||||||
? declarations
|
? declarations
|
||||||
: toDefinitionArray(declarations)
|
: toDeclarationArray(declarations)
|
||||||
|
|
||||||
for (const declaration of declarationsArray) {
|
for (const declaration of declarationsArray) {
|
||||||
if ('compose' in declaration) {
|
if ('compose' in declaration) {
|
||||||
|
|||||||
@@ -1,94 +1,43 @@
|
|||||||
import { InvalidOperationError, InvalidReferenceError } from '../errors'
|
import { InvalidOperationError } from '~/errors'
|
||||||
import { FlagDefinition, FlagsDictionary } from '~'
|
import { FlagsGraph, PartialFlagDefinition, PartialFlagInit, refByAlias } from '~/definitions'
|
||||||
import { printFlagValue } from '../definitions'
|
|
||||||
import { GenericFlagsDictionary } from '../definitions/dictionary'
|
|
||||||
|
|
||||||
export interface FlagDefinitionFactory<F, S> {
|
export class FlagSetBuilder<F> {
|
||||||
readonly supportsDefinitionsByValue: boolean
|
private readonly _graph: FlagsGraph<F>
|
||||||
readonly supportsDefinitionsByOrdinal: boolean
|
private _currentDefinition: PartialFlagInit<F> | undefined
|
||||||
|
|
||||||
precomputeTopDown(partialDefinition: PartialDefinition<F, S>): void
|
public constructor() {
|
||||||
|
this._graph = new FlagsGraph()
|
||||||
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
|
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 {
|
public define(alias: string | undefined): void {
|
||||||
if (!this.canMoveOnToNextDefinition) {
|
if (
|
||||||
|
this._currentDefinition !== undefined &&
|
||||||
|
this._currentDefinition.value === undefined &&
|
||||||
|
this._currentDefinition.parents === undefined
|
||||||
|
) {
|
||||||
throw new InvalidOperationError('define')
|
throw new InvalidOperationError('define')
|
||||||
}
|
}
|
||||||
this.createNewDefinition(alias)
|
this.finishCurrentDefinition()
|
||||||
|
this._currentDefinition = { alias }
|
||||||
}
|
}
|
||||||
|
|
||||||
public compose(flags: string[]): void {
|
public compose(flags: string[]): void {
|
||||||
if (
|
if (
|
||||||
this._currentDefinition === undefined ||
|
this._currentDefinition === undefined ||
|
||||||
this._currentDefinition.alias === undefined ||
|
this._currentDefinition.alias === undefined ||
|
||||||
this._currentDefinition.parentRefs !== undefined
|
this._currentDefinition.parents !== undefined
|
||||||
) {
|
) {
|
||||||
throw new InvalidOperationError('compose')
|
throw new InvalidOperationError('compose')
|
||||||
}
|
}
|
||||||
this._currentDefinition.parentRefs = new Set(flags)
|
this._currentDefinition.parents = refByAlias([...new Set(flags)])
|
||||||
}
|
}
|
||||||
|
|
||||||
public withValue(value: F): void {
|
public withValue(value: F): void {
|
||||||
if (
|
if (
|
||||||
this._currentDefinition === undefined ||
|
this._currentDefinition === undefined ||
|
||||||
this._currentDefinition.value !== undefined ||
|
this._currentDefinition.value !== undefined ||
|
||||||
this._currentDefinition.parentRefs !== undefined
|
this._currentDefinition.parents !== undefined
|
||||||
) {
|
) {
|
||||||
throw new InvalidOperationError('withValue')
|
throw new InvalidOperationError('withValue')
|
||||||
}
|
}
|
||||||
@@ -99,14 +48,25 @@ export class GenericFlagSetBuilder<F, S> {
|
|||||||
if (
|
if (
|
||||||
this._currentDefinition === undefined ||
|
this._currentDefinition === undefined ||
|
||||||
this._currentDefinition.value === undefined ||
|
this._currentDefinition.value === undefined ||
|
||||||
this._currentDefinition.parentRefs !== undefined
|
this._currentDefinition.parents !== undefined
|
||||||
) {
|
) {
|
||||||
throw new InvalidOperationError('requires')
|
throw new InvalidOperationError('requires')
|
||||||
}
|
}
|
||||||
this._currentDefinition.parentRefs = new Set(flags)
|
this._currentDefinition.parents = refByAlias([...new Set(flags)])
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildDictionary(): FlagsDictionary<F, S> {
|
private finishCurrentDefinition() {
|
||||||
|
if (this._currentDefinition !== undefined) {
|
||||||
|
this._graph.put(this._currentDefinition)
|
||||||
|
}
|
||||||
|
this._currentDefinition = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
public finish(): FlagsGraph<F> {
|
||||||
|
this.finishCurrentDefinition()
|
||||||
|
return this._graph
|
||||||
|
}
|
||||||
|
/*
|
||||||
// this array will contain the nodes of the graph, in topological order
|
// this array will contain the nodes of the graph, in topological order
|
||||||
const sorted: PartialDefinition<F, S>[] = []
|
const sorted: PartialDefinition<F, S>[] = []
|
||||||
|
|
||||||
@@ -157,15 +117,14 @@ export class GenericFlagSetBuilder<F, S> {
|
|||||||
this._factory.precomputeBottomUp(sorted[i])
|
this._factory.precomputeBottomUp(sorted[i])
|
||||||
dict.add(this._factory.createFlagDefinition(sorted[i]))
|
dict.add(this._factory.createFlagDefinition(sorted[i]))
|
||||||
}
|
}
|
||||||
return dict
|
return dict*/
|
||||||
}
|
|
||||||
|
|
||||||
private displayPartialDefinition(definition: PartialDefinition<F, S>): string {
|
/*private displayPartialDefinition(definition: PartialDefinition<F, S>): string {
|
||||||
if (definition.alias) {
|
if (definition.alias) {
|
||||||
return '"' + definition.alias + '"'
|
return '"' + definition.alias + '"'
|
||||||
} else {
|
} else {
|
||||||
// generate the definition and try to print its base value
|
// generate the definition and try to print its base value
|
||||||
return printFlagValue(this._factory.createFlagDefinition(definition))
|
return printFlagValue(this._factory.createFlagDefinition(definition))
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BitFlagSet, FlagDefinition, FlagsDictionary } from '~'
|
|
||||||
import {
|
import {
|
||||||
DefineWithValueOrOrdinal,
|
DefineWithValueOrOrdinal,
|
||||||
RequireParentsOrDefineWithValueOrOrdinal,
|
RequireParentsOrDefineWithValueOrOrdinal,
|
||||||
@@ -6,23 +5,24 @@ import {
|
|||||||
WithValueOrOrdinalOrCompose,
|
WithValueOrOrdinalOrCompose,
|
||||||
} from './syntax'
|
} from './syntax'
|
||||||
import {
|
import {
|
||||||
applyDeclarationsWithValueOrOrdinal,
|
applyDeclarations,
|
||||||
FlagWithValueOrOrdinal,
|
FlagWithValueOrOrdinal,
|
||||||
ListOfFlagsWithValueOrOrdinal,
|
ListOfFlagsWithValueOrOrdinal,
|
||||||
NamedFlagWithValueOrOrdinal,
|
NamedFlagWithValueOrOrdinal,
|
||||||
} from './declarative'
|
} from './declarative'
|
||||||
import { BitFlagDefinition } from '../definitions'
|
import { BitFlagDefinitionFactory, FlagDefinition } from '~/definitions'
|
||||||
import { FlagDefinitionFactory, GenericFlagSetBuilder, PartialDefinition } from './generic'
|
import { FlagSetBuilder } from './generic'
|
||||||
|
import { BitFlagSet } from '~/flagsets'
|
||||||
|
|
||||||
export class BitFlagSetBuilder
|
export class BitFlagSetBuilder
|
||||||
implements
|
implements
|
||||||
WithValueOrOrdinalOrCompose<number, number, BitFlagSet>,
|
WithValueOrOrdinalOrCompose<number, number, BitFlagSet>,
|
||||||
RequireParentsOrDefineWithValueOrOrdinal<number, number, BitFlagSet>
|
RequireParentsOrDefineWithValueOrOrdinal<number, number, BitFlagSet>
|
||||||
{
|
{
|
||||||
private readonly _underlying: GenericFlagSetBuilder<number, number>
|
private readonly _underlying: FlagSetBuilder<number>
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this._underlying = new GenericFlagSetBuilder(new BitFlagDefinitionFactory())
|
this._underlying = new FlagSetBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
public define(): WithValueOrOrdinal<number, number, BitFlagSet>
|
public define(): WithValueOrOrdinal<number, number, BitFlagSet>
|
||||||
@@ -55,60 +55,11 @@ export class BitFlagSetBuilder
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDictionary(): FlagsDictionary<number, number> {
|
|
||||||
return this._underlying.buildDictionary() as any
|
|
||||||
}
|
|
||||||
|
|
||||||
public getResult(): BitFlagSet {
|
public getResult(): BitFlagSet {
|
||||||
return new BitFlagSet(this.getDictionary())
|
const graph = this._underlying.finish()
|
||||||
}
|
const definitions = graph.sortedDefinitions()
|
||||||
}
|
const factory = new BitFlagDefinitionFactory()
|
||||||
|
return new BitFlagSet(graph.intoDictionary(factory))
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +69,6 @@ export function createBitFlagSet<D extends string>(
|
|||||||
): BitFlagSet & Record<D, FlagDefinition<number, number>>
|
): BitFlagSet & Record<D, FlagDefinition<number, number>>
|
||||||
export function createBitFlagSet(declarations: ListOfFlagsWithValueOrOrdinal<number>): BitFlagSet {
|
export function createBitFlagSet(declarations: ListOfFlagsWithValueOrOrdinal<number>): BitFlagSet {
|
||||||
const builder = new BitFlagSetBuilder()
|
const builder = new BitFlagSetBuilder()
|
||||||
applyDeclarationsWithValueOrOrdinal(declarations, builder)
|
applyDeclarations(declarations, builder)
|
||||||
return builder.getResult()
|
return builder.getResult()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import type { FlagsDictionary, FlagSet } from '~'
|
import type { FlagsDictionary, FlagSet } from '~'
|
||||||
|
|
||||||
export interface Root<F, S, R = FlagSet<F, S>> {
|
export interface Root<F, S, R = FlagSet<F, S>> {
|
||||||
/**
|
|
||||||
* Build a dictionary with the flags previously defined.
|
|
||||||
*/
|
|
||||||
getDictionary(): FlagsDictionary<F, S>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a flag set with the flags previously defined.
|
* Build a flag set with the flags previously defined.
|
||||||
*/
|
*/
|
||||||
@@ -14,8 +9,7 @@ export interface Root<F, S, R = FlagSet<F, S>> {
|
|||||||
|
|
||||||
// Syntax for builders that supports only definitions by value
|
// Syntax for builders that supports only definitions by value
|
||||||
|
|
||||||
export interface DefineWithValue<F, S, R = FlagSet<F, S>>
|
export interface DefineWithValue<F, S, R = FlagSet<F, S>> extends Root<F, S, R> {
|
||||||
extends Root<F, S, R> {
|
|
||||||
/**
|
/**
|
||||||
* Define an anonymous flag.
|
* Define an anonymous flag.
|
||||||
*/
|
*/
|
||||||
@@ -44,8 +38,7 @@ export interface WithValueOrCompose<F, S, R> extends WithValue<F, S, R> {
|
|||||||
compose(...flags: string[]): DefineWithValue<F, S, R>
|
compose(...flags: string[]): DefineWithValue<F, S, R>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequireParentsOrDefineWithValue<F, S, R>
|
export interface RequireParentsOrDefineWithValue<F, S, R> extends DefineWithValue<F, S, R> {
|
||||||
extends DefineWithValue<F, S, R> {
|
|
||||||
/**
|
/**
|
||||||
* Set the parents of this flag.
|
* Set the parents of this flag.
|
||||||
* @param flags The names of the parent flags.
|
* @param flags The names of the parent flags.
|
||||||
@@ -55,8 +48,7 @@ export interface RequireParentsOrDefineWithValue<F, S, R>
|
|||||||
|
|
||||||
// Syntax for builders that supports only definitions by ordinal
|
// Syntax for builders that supports only definitions by ordinal
|
||||||
|
|
||||||
export interface DefineWithOrdinal<F, S, R = FlagSet<F, S>>
|
export interface DefineWithOrdinal<F, S, R = FlagSet<F, S>> extends Root<F, S, R> {
|
||||||
extends Root<F, S, R> {
|
|
||||||
/**
|
/**
|
||||||
* Define an anonymous flag.
|
* Define an anonymous flag.
|
||||||
*/
|
*/
|
||||||
@@ -86,8 +78,7 @@ export interface WithOrdinalOrCompose<F, S, R> extends WithOrdinal<F, S, R> {
|
|||||||
compose(...flags: string[]): DefineWithOrdinal<F, S, R>
|
compose(...flags: string[]): DefineWithOrdinal<F, S, R>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequireParentsOrDefineWithOrdinal<F, S, R>
|
export interface RequireParentsOrDefineWithOrdinal<F, S, R> extends DefineWithOrdinal<F, S, R> {
|
||||||
extends DefineWithOrdinal<F, S, R> {
|
|
||||||
/**
|
/**
|
||||||
* Set the parents of this flag.
|
* Set the parents of this flag.
|
||||||
* @param flags The names of the parent flags.
|
* @param flags The names of the parent flags.
|
||||||
@@ -97,8 +88,7 @@ export interface RequireParentsOrDefineWithOrdinal<F, S, R>
|
|||||||
|
|
||||||
// Syntax for builders that supports definitions by value and ordinal
|
// Syntax for builders that supports definitions by value and ordinal
|
||||||
|
|
||||||
export interface DefineWithValueOrOrdinal<F, S, R = FlagSet<F, S>>
|
export interface DefineWithValueOrOrdinal<F, S, R = FlagSet<F, S>> extends Root<F, S, R> {
|
||||||
extends Root<F, S, R> {
|
|
||||||
/**
|
/**
|
||||||
* Define an anonymous flag.
|
* Define an anonymous flag.
|
||||||
*/
|
*/
|
||||||
@@ -123,13 +113,10 @@ export interface WithValueOrOrdinal<F, S, R> {
|
|||||||
* @param ordinal The number of the flag (starting at 1). A unique value
|
* @param ordinal The number of the flag (starting at 1). A unique value
|
||||||
* will be assigned based on this number.
|
* will be assigned based on this number.
|
||||||
*/
|
*/
|
||||||
withOrdinal(
|
withOrdinal(ordinal: number): RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
|
||||||
ordinal: number,
|
|
||||||
): RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WithValueOrOrdinalOrCompose<F, S, R>
|
export interface WithValueOrOrdinalOrCompose<F, S, R> extends WithValueOrOrdinal<F, S, R> {
|
||||||
extends WithValueOrOrdinal<F, S, R> {
|
|
||||||
/**
|
/**
|
||||||
* Define this flag as a composed flag.
|
* Define this flag as a composed flag.
|
||||||
* @param flags The name of the flags in the group.
|
* @param flags The name of the flags in the group.
|
||||||
@@ -137,8 +124,11 @@ export interface WithValueOrOrdinalOrCompose<F, S, R>
|
|||||||
compose(...flags: string[]): DefineWithValueOrOrdinal<F, S, R>
|
compose(...flags: string[]): DefineWithValueOrOrdinal<F, S, R>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
|
export interface RequireParentsOrDefineWithValueOrOrdinal<F, S, R> extends DefineWithValueOrOrdinal<
|
||||||
extends DefineWithValueOrOrdinal<F, S, R> {
|
F,
|
||||||
|
S,
|
||||||
|
R
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* Set the parents of this flag.
|
* Set the parents of this flag.
|
||||||
* @param flags The names of the parent flags.
|
* @param flags The names of the parent flags.
|
||||||
|
|||||||
@@ -1,67 +1,48 @@
|
|||||||
import type { FlagDefinition } from '.'
|
|
||||||
import { ReusedFlagAliasError, ReusedFlagValueError } from '../errors'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of {@link FlagDefinition}s.
|
|
||||||
*/
|
|
||||||
export interface FlagsDictionary<F, 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
|
findByAlias(alias: string): FlagDefinition<F, S> | undefined
|
||||||
|
|
||||||
|
findByValue(value: F): FlagDefinition<F, S> | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FlagDefinition<F, S> {
|
||||||
|
/**
|
||||||
|
* The alias of the flag.
|
||||||
|
*/
|
||||||
|
readonly alias: string | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Built-in dictionary implementation.
|
* A set containing the value of the flag, or multiple values if it is a composed flag.
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
export class GenericFlagsDictionary<F, S> implements FlagsDictionary<F, S> {
|
readonly values: S
|
||||||
private readonly _named: Map<string, FlagDefinition<F, S>>
|
|
||||||
private readonly _anonymous: FlagDefinition<F, S>[]
|
|
||||||
|
|
||||||
public constructor() {
|
/**
|
||||||
this._named = new Map()
|
* Test if this flag and all its parents are present in the set.
|
||||||
this._anonymous = []
|
* @param set A set of flags.
|
||||||
|
* @returns `true` if the set includes this flags and its parents.
|
||||||
|
*/
|
||||||
|
isIn(set: S): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add this flag and all its parents to the set.
|
||||||
|
* @param set A set of flags.
|
||||||
|
* @returns A new set of flags containing the flags from the `set`, this flag and its parents.
|
||||||
|
*/
|
||||||
|
addTo(set: S): S
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes this flag and all its children from the set.
|
||||||
|
* @param set A set of flags.
|
||||||
|
* @returns A new set of flags containing the flags from the `set` except this flag and its children.
|
||||||
|
*/
|
||||||
|
removeFrom(set: S): S
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(definition: FlagDefinition<F, S>): void {
|
export const valueToString = Symbol()
|
||||||
if (definition.alias === undefined) {
|
|
||||||
this._anonymous.push(definition)
|
export function printFlagValue(flag: FlagDefinition<unknown, unknown>): string {
|
||||||
|
if (valueToString in flag) {
|
||||||
|
return (flag[valueToString] as Function)()
|
||||||
} else {
|
} else {
|
||||||
this._named.set(definition.alias, definition)
|
return String(flag.values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
258
node/src/definitions/graph.ts
Normal file
258
node/src/definitions/graph.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import { FlagDefinition, FlagsDictionary, printFlagValue } from '~/definitions'
|
||||||
|
import { InternalError } from '~/errors'
|
||||||
|
|
||||||
|
export interface PartialFlagInit<F> {
|
||||||
|
alias?: string
|
||||||
|
value?: F
|
||||||
|
parents?: PartialFlagInit<F>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function refByAlias(refs: string[]): PartialFlagInit<never>[] {
|
||||||
|
return refs.map((ref) => ({ alias: ref }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlagDefinitionFactory<F, S> {
|
||||||
|
makeDefinitions(
|
||||||
|
sortedPartialDefinitions: PartialFlagDefinition<F>[],
|
||||||
|
results: Map<PartialFlagDefinition<F>, FlagDefinition<F, S>>,
|
||||||
|
): void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A graph with {@link PartialFlagDefinition} vertices.
|
||||||
|
*/
|
||||||
|
export class FlagsGraph<F> {
|
||||||
|
private readonly _definitions: PartialFlagDefinition<F>[]
|
||||||
|
private readonly _aliasToDefinition: Map<string, PartialFlagDefinition<F>>
|
||||||
|
private readonly _valueToDefinition: Map<F, PartialFlagDefinition<F>>
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this._definitions = []
|
||||||
|
this._aliasToDefinition = new Map()
|
||||||
|
this._valueToDefinition = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
private add(pfd: PartialFlagDefinition<F>): void {
|
||||||
|
this._definitions.push(pfd)
|
||||||
|
if (pfd.alias !== undefined) {
|
||||||
|
this._aliasToDefinition.set(pfd.alias, pfd)
|
||||||
|
}
|
||||||
|
if (pfd.value !== undefined) {
|
||||||
|
this._valueToDefinition.set(pfd.value, pfd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a flag matching the alias or value of `init`. If no flag is found, a new
|
||||||
|
* placeholder flag is created to make a forward reference.
|
||||||
|
*/
|
||||||
|
private findOrAddPlaceholder(init: PartialFlagInit<F>): PartialFlagDefinition<F> {
|
||||||
|
let pfd
|
||||||
|
if (init.alias !== undefined) {
|
||||||
|
pfd = this._aliasToDefinition.get(init.alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pfd === undefined) {
|
||||||
|
pfd = new PartialFlagDefinition(true, init)
|
||||||
|
this.add(pfd)
|
||||||
|
}
|
||||||
|
return pfd
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new flag to the graph, or upgrade the existing definition.
|
||||||
|
*/
|
||||||
|
public put(init: PartialFlagInit<F>): PartialFlagDefinition<F> {
|
||||||
|
let pfd
|
||||||
|
if (init.alias !== undefined) {
|
||||||
|
pfd = this._aliasToDefinition.get(init.alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pfd === undefined) {
|
||||||
|
pfd = new PartialFlagDefinition(false, init)
|
||||||
|
this.add(pfd)
|
||||||
|
} else {
|
||||||
|
pfd.upgrade(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (init.parents !== undefined) {
|
||||||
|
for (const parentInit of init.parents) {
|
||||||
|
const parentPfd = this.findOrAddPlaceholder(parentInit)
|
||||||
|
pfd.addParent(parentPfd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pfd
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all definitions that are not placeholders.
|
||||||
|
*/
|
||||||
|
public get definitions(): Iterable<PartialFlagDefinition<F>> {
|
||||||
|
return this._definitions.filter((d) => !d.isPlaceholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array containing the definitions in topological order.
|
||||||
|
* @throws CircularReferenceError when the graph can not be sorted because it contains a cycle.
|
||||||
|
*/
|
||||||
|
private sortedDefinitions(): PartialFlagDefinition<F>[] {
|
||||||
|
// work on a copy of the graph
|
||||||
|
const remaining = new Map(this._definitions.map((pfd) => [pfd, new Set(pfd.parents)]))
|
||||||
|
|
||||||
|
const sorted: PartialFlagDefinition<F>[] = []
|
||||||
|
const start: PartialFlagDefinition<F>[] = []
|
||||||
|
|
||||||
|
for (const pfd of [...remaining.keys()]) {
|
||||||
|
const parents = remaining.get(pfd)!
|
||||||
|
if (parents.size === 0) {
|
||||||
|
start.push(pfd)
|
||||||
|
remaining.delete(pfd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (start.length > 0) {
|
||||||
|
const current = start.pop()!
|
||||||
|
sorted.push(current)
|
||||||
|
for (const child of current.children) {
|
||||||
|
const parents = remaining.get(child)!
|
||||||
|
parents.delete(current)
|
||||||
|
if (parents.size === 0) {
|
||||||
|
start.push(child)
|
||||||
|
remaining.delete(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
|
||||||
|
public intoDictionary<S>(factory: FlagDefinitionFactory<F, S>): FlagsDictionary<F, S> {
|
||||||
|
const sortedPartialDefinitions = this.sortedDefinitions()
|
||||||
|
|
||||||
|
const definitions = new Map<PartialFlagDefinition<F>, FlagDefinition<F, S>>()
|
||||||
|
factory.makeDefinitions(sortedPartialDefinitions, definitions)
|
||||||
|
|
||||||
|
const aliasToDefinition = new Map<string, FlagDefinition<F, S>>()
|
||||||
|
for (const [alias, pfd] of this._aliasToDefinition.entries()) {
|
||||||
|
const definition = definitions.get(pfd)
|
||||||
|
if (definition === undefined) {
|
||||||
|
throw new InternalError(`factory didn't provide any definition for ${pfd}`)
|
||||||
|
}
|
||||||
|
aliasToDefinition.set(alias, definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueToDefinition = new Map<F, FlagDefinition<F, S>>()
|
||||||
|
for (const [value, pfd] of this._valueToDefinition.entries()) {
|
||||||
|
const definition = definitions.get(pfd)
|
||||||
|
if (definition === undefined) {
|
||||||
|
throw new InternalError(`factory didn't provide any definition for ${pfd}`)
|
||||||
|
}
|
||||||
|
valueToDefinition.set(value, definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BiMapFlagsDictionary(aliasToDefinition, valueToDefinition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BiMapFlagsDictionary<F, S> implements FlagsDictionary<F, S> {
|
||||||
|
private readonly _aliasToDefinition: Map<string, FlagDefinition<F, S>>
|
||||||
|
private readonly _valueToDefinition: Map<F, FlagDefinition<F, S>>
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
aliasToDefinition: Map<string, FlagDefinition<F, S>>,
|
||||||
|
valueToDefinition: Map<F, FlagDefinition<F, S>>,
|
||||||
|
) {
|
||||||
|
this._aliasToDefinition = aliasToDefinition
|
||||||
|
this._valueToDefinition = valueToDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByAlias(alias: string): FlagDefinition<F, S> | undefined {
|
||||||
|
return this._aliasToDefinition.get(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByValue(value: F): FlagDefinition<F, S> | undefined {
|
||||||
|
return this._valueToDefinition.get(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A partial {@link FlagDefinition} used for building a flag graph.
|
||||||
|
*/
|
||||||
|
export class PartialFlagDefinition<F> {
|
||||||
|
private readonly _alias: string | undefined
|
||||||
|
private readonly _parents: Set<PartialFlagDefinition<F>>
|
||||||
|
private readonly _children: Set<PartialFlagDefinition<F>>
|
||||||
|
|
||||||
|
private _isPlaceholder: boolean
|
||||||
|
private _value: F | undefined
|
||||||
|
|
||||||
|
public constructor(isPlaceholder: boolean, init: PartialFlagInit<F>) {
|
||||||
|
this._isPlaceholder = isPlaceholder
|
||||||
|
this._alias = init.alias
|
||||||
|
this._value = init.value
|
||||||
|
this._parents = new Set()
|
||||||
|
this._children = new Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alias of the flag, or `undefined` if no alias has been set yet.
|
||||||
|
*/
|
||||||
|
public get alias(): string | undefined {
|
||||||
|
return this._alias
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the flag, or `undefined` if no value has been set yet.
|
||||||
|
*/
|
||||||
|
public get value(): F | undefined {
|
||||||
|
return this._value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that this flag has not been defined yet, but has been referenced by another flag.
|
||||||
|
*/
|
||||||
|
public get isPlaceholder(): boolean {
|
||||||
|
return this._isPlaceholder
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade a placeholder flag to an actual definition.
|
||||||
|
*/
|
||||||
|
public upgrade(init: PartialFlagInit<F>): void {
|
||||||
|
this._value = init.value
|
||||||
|
this._isPlaceholder = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of all parent flags (including forward references).
|
||||||
|
*/
|
||||||
|
public get parents(): Iterable<PartialFlagDefinition<F>> {
|
||||||
|
return this._parents
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of all children flags.
|
||||||
|
*/
|
||||||
|
public get children(): Iterable<PartialFlagDefinition<F>> {
|
||||||
|
return this._children
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a parent-child relationship between this flag and another.
|
||||||
|
*/
|
||||||
|
public addParent(parentPfd: PartialFlagDefinition<F>): void {
|
||||||
|
this._parents.add(parentPfd)
|
||||||
|
parentPfd._children.add(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
if (this._alias !== undefined) {
|
||||||
|
return '"' + this._alias + '"'
|
||||||
|
} else if (this._value !== undefined) {
|
||||||
|
return JSON.stringify(this._value)
|
||||||
|
} else {
|
||||||
|
return '[?]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,9 @@
|
|||||||
export { FlagsDictionary } from './dictionary'
|
export { FlagsDictionary, FlagDefinition, printFlagValue, valueToString } from './dictionary'
|
||||||
export { BitFlagDefinition } from './number'
|
export {
|
||||||
|
FlagsGraph,
|
||||||
export interface FlagDefinition<F, S> {
|
PartialFlagInit,
|
||||||
/**
|
PartialFlagDefinition,
|
||||||
* The alias of the flag.
|
refByAlias,
|
||||||
*/
|
FlagDefinitionFactory,
|
||||||
readonly alias: string | undefined
|
} from './graph'
|
||||||
|
export { BitFlagDefinition, BitFlagDefinitionFactory } from './number'
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { FlagDefinition, valueToString } from '.'
|
import { FlagDefinition, FlagDefinitionFactory, PartialFlagDefinition } from '.'
|
||||||
|
|
||||||
|
interface PrecomputedValues {
|
||||||
|
base: number
|
||||||
|
additive: number
|
||||||
|
subtractive: number
|
||||||
|
}
|
||||||
|
|
||||||
export class BitFlagDefinition implements FlagDefinition<number, number> {
|
export class BitFlagDefinition implements FlagDefinition<number, number> {
|
||||||
private readonly _baseValues: number
|
private readonly _baseValue: number
|
||||||
private readonly _additiveValues: number
|
private readonly _additiveValue: number
|
||||||
private readonly _subtractiveValues: number
|
private readonly _subtractiveValue: number
|
||||||
private readonly _alias: string | undefined
|
private readonly _alias: string | undefined
|
||||||
|
|
||||||
public constructor(
|
public constructor(precomputedValues: PrecomputedValues, alias: string | undefined) {
|
||||||
baseValues: number,
|
this._baseValue = precomputedValues.base
|
||||||
additiveValues: number,
|
this._additiveValue = precomputedValues.additive
|
||||||
subtractiveValues: number,
|
this._subtractiveValue = precomputedValues.subtractive
|
||||||
alias: string | undefined,
|
|
||||||
) {
|
|
||||||
this._baseValues = baseValues
|
|
||||||
this._additiveValues = additiveValues
|
|
||||||
this._subtractiveValues = subtractiveValues
|
|
||||||
this._alias = alias
|
this._alias = alias
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,10 +24,63 @@ export class BitFlagDefinition implements FlagDefinition<number, number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get values(): number {
|
public get values(): number {
|
||||||
return this._baseValues
|
return this._baseValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasSameValue(other: FlagDefinition<number, number>): boolean {
|
public isIn(set: number): boolean {
|
||||||
return other instanceof BitFlagDefinition && this._baseValues === other._baseValues
|
return (set & this._additiveValue) === this._additiveValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTo(set: number): number {
|
||||||
|
return set | this._additiveValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFrom(set: number): number {
|
||||||
|
return set | this._additiveValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BitFlagDefinitionFactory implements FlagDefinitionFactory<number, number> {
|
||||||
|
public makeDefinitions(
|
||||||
|
sortedPartialDefinitions: PartialFlagDefinition<number>[],
|
||||||
|
results: Map<PartialFlagDefinition<number>, FlagDefinition<number, number>>,
|
||||||
|
): void {
|
||||||
|
const precomputedValues = new Map<PartialFlagDefinition<number>, PrecomputedValues>()
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedPartialDefinitions.length; i++) {
|
||||||
|
const pfd = sortedPartialDefinitions[i]
|
||||||
|
|
||||||
|
const values: PrecomputedValues = {
|
||||||
|
base: pfd.value ?? 0,
|
||||||
|
additive: pfd.value ?? 0,
|
||||||
|
subtractive: pfd.value ?? 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 BitFlagDefinition(values, pfd.alias))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FlagDefinition, printFlagValue } from './definitions'
|
import { FlagDefinition, printFlagValue } from './definitions'
|
||||||
|
import pkg from '../package.json'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error thrown when a feature is not available in the current environment.
|
* Error thrown when a feature is not available in the current environment.
|
||||||
@@ -38,10 +39,7 @@ export class ReusedFlagAliasError extends Error {
|
|||||||
*/
|
*/
|
||||||
export class ReusedFlagValueError extends Error {
|
export class ReusedFlagValueError extends Error {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public constructor(
|
public constructor(a: FlagDefinition<unknown, unknown>, b: FlagDefinition<unknown, unknown>) {
|
||||||
a: FlagDefinition<unknown, unknown>,
|
|
||||||
b: FlagDefinition<unknown, unknown>,
|
|
||||||
) {
|
|
||||||
super(
|
super(
|
||||||
`The value ${printFlagValue(a)} is already being used ${b.alias ? 'for the flag "' + b.alias + '"' : 'by another flag'}.`,
|
`The value ${printFlagValue(a)} is already being used ${b.alias ? 'for the flag "' + b.alias + '"' : 'by another flag'}.`,
|
||||||
)
|
)
|
||||||
@@ -54,18 +52,33 @@ export class ReusedFlagValueError extends Error {
|
|||||||
export class InvalidOperationError extends Error {
|
export class InvalidOperationError extends Error {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public constructor(methodName: string, explanation?: string) {
|
public constructor(methodName: string, explanation?: string) {
|
||||||
super(
|
super(`.${methodName}() is not allowed here${explanation ? ': ' + explanation : ''}.`)
|
||||||
`.${methodName}() is not allowed here${explanation ? ': ' + explanation : ''}.`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error thrown by builders when referencing a flag that is not defined.
|
* Error thrown by builders when a flag references a flag that is not defined.
|
||||||
*/
|
*/
|
||||||
export class InvalidReferenceError extends Error {
|
export class UndefinedReferenceError extends Error {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public constructor(alias: string, requiredBy: string) {
|
public constructor(alias: string, requiredBy: string) {
|
||||||
super(`Undefined flag "${alias}" required by ${requiredBy}.`)
|
super(`Undefined flag "${alias}" required by ${requiredBy}.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown by builders when a circular dependency is created.
|
||||||
|
*/
|
||||||
|
export class CircularReferenceError extends Error {
|
||||||
|
/** @internal */
|
||||||
|
public constructor(chain: string[]) {
|
||||||
|
super(`Circular reference found: ${chain.join(' → ')}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InternalError extends Error {
|
||||||
|
/** @internal */
|
||||||
|
public constructor(message: string) {
|
||||||
|
super(`[Tatsuki internal error] ${message}. Please report it at ${pkg.bugs.url}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import type { FlagSet } from '.'
|
import type { FlagSet } from '.'
|
||||||
import {
|
import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '../base64'
|
||||||
decodeB64Byte,
|
import { Base64BitflagIterator, EnumerateFlags, useIterator } from '../enumeration'
|
||||||
encodeB64Byte,
|
|
||||||
normaliseB64String,
|
|
||||||
ZERO_STRING,
|
|
||||||
} from '../base64'
|
|
||||||
import {
|
|
||||||
Base64BitflagIterator,
|
|
||||||
EnumerateFlags,
|
|
||||||
useIterator,
|
|
||||||
} from '../enumeration'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides flags that are stored in strings using a little-endian base 64
|
* Provides flags that are stored in strings using a little-endian base 64
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import type { FlagSet } from '.'
|
import type { FlagSet } from '.'
|
||||||
import { UnavailableFeatureError } from '../errors'
|
import { UnavailableFeatureError } from '../errors'
|
||||||
import { ENV_BI } from '../env'
|
import { ENV_BI } from '../env'
|
||||||
import {
|
import { BigBitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration'
|
||||||
BigBitFlagsIterator,
|
|
||||||
EnumerateFlags,
|
|
||||||
useIterator,
|
|
||||||
} from '../enumeration'
|
|
||||||
|
|
||||||
export class BigBitFlagSet implements FlagSet<bigint, bigint> {
|
export class BigBitFlagSet implements FlagSet<bigint, bigint> {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export interface FlagSet<F, S> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the first set of flags includes at least one of the flags
|
* Checks whether the first set of flags includes at least one of the flags
|
||||||
* from the second set.
|
* from the second set. If there is no required flags, the result will always be `false`.
|
||||||
*
|
*
|
||||||
* A flag is considered to be part of the set only if all of its parents are
|
* A flag is considered to be part of the set only if all of its parents are
|
||||||
* present too.
|
* present too.
|
||||||
@@ -79,7 +79,7 @@ export interface FlagSet<F, S> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the first set of flags includes all the flags from the
|
* Checks whether the first set of flags includes all the flags from the
|
||||||
* second set.
|
* second set. If there is no required flags, the result will always be `true`.
|
||||||
*
|
*
|
||||||
* A flag is considered to be part of the set only if all of its parents are
|
* A flag is considered to be part of the set only if all of its parents are
|
||||||
* present too.
|
* present too.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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'
|
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>
|
private readonly _dictionary: FlagsDictionary<number, number>
|
||||||
@@ -18,10 +18,7 @@ export class BitFlagSet implements FlagSet<number, number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public named(...aliases: string[]): number {
|
public named(...aliases: string[]): number {
|
||||||
return aliases.reduce(
|
return aliases.reduce((set, alias) => set | (this.getFlag(alias)?.values ?? 0), 0)
|
||||||
(set, alias) => set | (this.getFlag(alias)?.values ?? 0),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public union(first: number, second: number): number {
|
public union(first: number, second: number): number {
|
||||||
@@ -37,15 +34,15 @@ export class BitFlagSet implements FlagSet<number, number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isSuperset(first: number, second: number): boolean {
|
public isSuperset(first: number, second: number): boolean {
|
||||||
return (first & second) == second
|
return (first & second) === second
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasAny(flags: number, required: number): boolean {
|
public hasAny(flags: number, required: number): boolean {
|
||||||
return false
|
return this.minimum(this.intersection(flags, required)) !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasAll(flags: number, required: number): boolean {
|
public hasAll(flags: number, required: number): boolean {
|
||||||
return false
|
return this.isSuperset(flags, this.maximum(required))
|
||||||
}
|
}
|
||||||
|
|
||||||
public enumerate(flags: number): EnumerateFlags<number> {
|
public enumerate(flags: number): EnumerateFlags<number> {
|
||||||
@@ -53,11 +50,25 @@ export class BitFlagSet implements FlagSet<number, number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public maximum(flags: number): number {
|
public maximum(flags: number): number {
|
||||||
return 0
|
let result = this.none()
|
||||||
|
for (const value of this.enumerate(flags)) {
|
||||||
|
const flag = this._dictionary.findByValue(value)
|
||||||
|
if (flag !== undefined) {
|
||||||
|
result = flag.addTo(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public minimum(flags: number): number {
|
public minimum(flags: number): number {
|
||||||
return 0
|
let result = this.none()
|
||||||
|
for (const value of this.enumerate(flags)) {
|
||||||
|
const flag = this._dictionary.findByValue(value)
|
||||||
|
if (flag !== undefined && flag.isIn(flags)) {
|
||||||
|
result = flag.addTo(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFlag(alias: string): FlagDefinition<number, number> | undefined {
|
public getFlag(alias: string): FlagDefinition<number, number> | undefined {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export { FlagSetBuilder, createBitFlagSet } from './builders'
|
export { createBitFlagSet } from './builders'
|
||||||
export { FlagDefinition, FlagsDictionary } from './definitions'
|
export { FlagDefinition } from './definitions'
|
||||||
export { InvalidBitFlagValueError } from './errors'
|
export { InvalidBitFlagValueError } from './errors'
|
||||||
export {
|
export {
|
||||||
ArrayFlagSet,
|
ArrayFlagSet,
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
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,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
52
node/tests/definitions/graph.test.ts
Normal file
52
node/tests/definitions/graph.test.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
import { FlagsGraph } from '~/definitions/graph'
|
||||||
|
|
||||||
|
describe(FlagsGraph, () => {
|
||||||
|
test('put a definition in the graph', () => {
|
||||||
|
const graph = new FlagsGraph<number>()
|
||||||
|
|
||||||
|
const A = graph.put({ alias: 'A', value: 1 })
|
||||||
|
|
||||||
|
expect(graph.definitions).toHaveLength(1)
|
||||||
|
expect(graph.definitions).toContain(A)
|
||||||
|
|
||||||
|
expect(A.isPlaceholder).toBe(false)
|
||||||
|
expect(A.alias).toBe('A')
|
||||||
|
expect(A.value).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('create a definition with a reference to an existing definition', () => {
|
||||||
|
const graph = new FlagsGraph<number>()
|
||||||
|
|
||||||
|
const A = graph.put({ alias: 'A', value: 1 })
|
||||||
|
|
||||||
|
expect(graph.definitions).toHaveLength(1)
|
||||||
|
expect(graph.definitions).toContain(A)
|
||||||
|
|
||||||
|
const B = graph.put({ alias: 'B', value: 2, parents: [{ alias: 'A' }] })
|
||||||
|
|
||||||
|
expect(graph.definitions).toHaveLength(2)
|
||||||
|
expect(graph.definitions).toContain(B)
|
||||||
|
|
||||||
|
expect(B.parents).toContain(A)
|
||||||
|
expect(A.children).toContain(B)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('create a definition with a forward reference', () => {
|
||||||
|
const graph = new FlagsGraph<number>()
|
||||||
|
|
||||||
|
const B = graph.put({ alias: 'B', value: 2, parents: [{ alias: 'A' }] })
|
||||||
|
|
||||||
|
expect(graph.definitions).toHaveLength(1)
|
||||||
|
expect(graph.definitions).toContain(B)
|
||||||
|
|
||||||
|
const A = graph.put({ alias: 'A', value: 1 })
|
||||||
|
|
||||||
|
expect(graph.definitions).toHaveLength(2)
|
||||||
|
expect(graph.definitions).toContain(A)
|
||||||
|
expect(A.value).toBe(1)
|
||||||
|
|
||||||
|
expect(B.parents).toContain(A)
|
||||||
|
expect(A.children).toContain(B)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -36,10 +36,7 @@ describe(ArrayFlagSet, () => {
|
|||||||
expect(flags.intersection(['A'], ['B'])).toEqual([])
|
expect(flags.intersection(['A'], ['B'])).toEqual([])
|
||||||
expect(flags.intersection(['A'], ['A', 'B'])).toEqual(['A'])
|
expect(flags.intersection(['A'], ['A', 'B'])).toEqual(['A'])
|
||||||
expect(flags.intersection(['A', 'B', 'D'], ['A', 'C'])).toEqual(['A'])
|
expect(flags.intersection(['A', 'B', 'D'], ['A', 'C'])).toEqual(['A'])
|
||||||
expect(flags.intersection(['A', 'B', 'D'], ['A', 'B', 'C'])).toEqual([
|
expect(flags.intersection(['A', 'B', 'D'], ['A', 'B', 'C'])).toEqual(['A', 'B'])
|
||||||
'A',
|
|
||||||
'B',
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('isSuperset', () => {
|
test('isSuperset', () => {
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ describe(CollectionFlagSet, () => {
|
|||||||
expect(flags.union(set('A'), set())).toEqual(set('A'))
|
expect(flags.union(set('A'), set())).toEqual(set('A'))
|
||||||
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'),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('difference', () => {
|
test('difference', () => {
|
||||||
@@ -41,12 +39,8 @@ describe(CollectionFlagSet, () => {
|
|||||||
expect(flags.intersection(set('A'), 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('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(flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C'))).toEqual(set('A', 'B'))
|
||||||
)
|
|
||||||
expect(
|
|
||||||
flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C')),
|
|
||||||
).toEqual(set('A', 'B'))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('isSuperset', () => {
|
test('isSuperset', () => {
|
||||||
@@ -65,10 +59,6 @@ describe(CollectionFlagSet, () => {
|
|||||||
|
|
||||||
expect([...flags.enumerate(set())]).toEqual([])
|
expect([...flags.enumerate(set())]).toEqual([])
|
||||||
expect([...flags.enumerate(set('A'))]).toEqual(['A'])
|
expect([...flags.enumerate(set('A'))]).toEqual(['A'])
|
||||||
expect([...flags.enumerate(set('A', 'B', 'C'))]).toEqual([
|
expect([...flags.enumerate(set('A', 'B', 'C'))]).toEqual(['A', 'B', 'C'])
|
||||||
'A',
|
|
||||||
'B',
|
|
||||||
'C',
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,6 +75,38 @@ describe(BitFlagSet, () => {
|
|||||||
expect(flags.isSuperset(8, 4)).toBe(false)
|
expect(flags.isSuperset(8, 4)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('hasAny', () => {
|
||||||
|
const flags = createBitFlagSet([
|
||||||
|
{ value: 1, as: 'A' },
|
||||||
|
{ value: 2, as: 'B', requires: ['A'] },
|
||||||
|
{ value: 4, as: 'C', requires: ['A'] },
|
||||||
|
{ value: 8, as: 'D', requires: ['B', 'C'] },
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(flags.hasAny(15, 0)).toBe(false)
|
||||||
|
expect(flags.hasAny(0, 0)).toBe(false)
|
||||||
|
expect(flags.hasAny(7, 1)).toBe(true)
|
||||||
|
expect(flags.hasAny(1, 7)).toBe(true)
|
||||||
|
expect(flags.hasAny(5, 12)).toBe(false)
|
||||||
|
expect(flags.hasAny(2, 2)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hasAll', () => {
|
||||||
|
const flags = createBitFlagSet([
|
||||||
|
{ value: 1, as: 'A' },
|
||||||
|
{ value: 2, as: 'B', requires: ['A'] },
|
||||||
|
{ value: 4, as: 'C', requires: ['A'] },
|
||||||
|
{ value: 8, as: 'D', requires: ['B', 'C'] },
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(flags.hasAll(15, 0)).toBe(true)
|
||||||
|
expect(flags.hasAll(0, 0)).toBe(true)
|
||||||
|
expect(flags.hasAll(7, 2)).toBe(true)
|
||||||
|
expect(flags.hasAll(6, 2)).toBe(false)
|
||||||
|
expect(flags.hasAll(1, 7)).toBe(false)
|
||||||
|
expect(flags.hasAll(5, 12)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
test('enumerate', () => {
|
test('enumerate', () => {
|
||||||
const flags = createBitFlagSet([])
|
const flags = createBitFlagSet([])
|
||||||
|
|
||||||
@@ -85,4 +117,36 @@ describe(BitFlagSet, () => {
|
|||||||
expect([...flags.enumerate(11)]).toEqual([1, 2, 8])
|
expect([...flags.enumerate(11)]).toEqual([1, 2, 8])
|
||||||
expect([...flags.enumerate(100)]).toEqual([4, 32, 64])
|
expect([...flags.enumerate(100)]).toEqual([4, 32, 64])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('maximum', () => {
|
||||||
|
const flags = createBitFlagSet([
|
||||||
|
{ value: 1, as: 'A' },
|
||||||
|
{ value: 2, as: 'B', requires: ['A'] },
|
||||||
|
{ value: 4, as: 'C', requires: ['A'] },
|
||||||
|
{ value: 8, as: 'D', requires: ['B', 'C'] },
|
||||||
|
])
|
||||||
|
|
||||||
|
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(4)).toEqual(5)
|
||||||
|
expect(flags.maximum(8)).toEqual(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('minimum', () => {
|
||||||
|
const flags = createBitFlagSet([
|
||||||
|
{ value: 1, as: 'A' },
|
||||||
|
{ value: 2, as: 'B', requires: ['A'] },
|
||||||
|
{ value: 4, as: 'C', requires: ['A'] },
|
||||||
|
{ value: 8, as: 'D', requires: ['B', 'C'] },
|
||||||
|
])
|
||||||
|
|
||||||
|
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(4)).toEqual(0)
|
||||||
|
expect(flags.minimum(13)).toEqual(5)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
@@ -25,12 +25,14 @@
|
|||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "commonjs", /* Specify what module code is generated. */
|
"module": "commonjs" /* Specify what module code is generated. */,
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
"paths": {
|
||||||
"~": ["./src/index.ts"]
|
/* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
"~": ["./src/index.ts"],
|
||||||
|
"~/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
@@ -41,7 +43,7 @@
|
|||||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
"resolveJsonModule": true /* Enable importing .json files. */,
|
||||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@
|
|||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
@@ -68,7 +70,7 @@
|
|||||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
"stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
"stripInternal": true /* Disable emitting declarations that have '@internal' in their JSDoc comments. */,
|
||||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
@@ -79,18 +81,18 @@
|
|||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
|
||||||
/* Type Checking */
|
/* Type Checking */
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
"noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
"noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */,
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
@@ -99,7 +101,7 @@
|
|||||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': 'src/index.ts',
|
'~': 'src',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user