diff --git a/node/src/builders/array.ts b/node/src/builders/array.ts new file mode 100644 index 0000000..70b88bf --- /dev/null +++ b/node/src/builders/array.ts @@ -0,0 +1,73 @@ +import { FlagDefinition } from '~/definitions' +import { ArrayFlagDefinitionFactory } from '~/definitions/array' +import { ArrayFlagSet } from '~/flagsets' + +import { + applyDeclarations, + FlagWithValue, + ListOfFlagsWithValue, + NamedFlagWithValue, +} from './declarative' +import { FlagSetBuilder } from './generic' +import { + DefineWithValueOrOrdinal, + RequireParentsOrDefineWithValueOrOrdinal, + WithValueOrOrdinal, + WithValueOrOrdinalOrCompose, +} from './syntax' + +export class ArrayFlagSetBuilder + implements + WithValueOrOrdinalOrCompose>, + RequireParentsOrDefineWithValueOrOrdinal> +{ + private readonly _underlying: FlagSetBuilder + + public constructor() { + this._underlying = new FlagSetBuilder() + } + + public define(): WithValueOrOrdinal> + public define(alias: string): WithValueOrOrdinalOrCompose> + public define(alias?: string): WithValueOrOrdinalOrCompose> { + this._underlying.define(alias) + return this + } + + public compose(...flags: string[]): DefineWithValueOrOrdinal> { + this._underlying.compose(flags) + return this + } + + public withValue(value: T): RequireParentsOrDefineWithValueOrOrdinal> { + this._underlying.withValue(value) + return this + } + + public withOrdinal( + ordinal: number, + ): RequireParentsOrDefineWithValueOrOrdinal> { + throw new Error('Method not implemented.') + } + + public requires(...flags: string[]): DefineWithValueOrOrdinal> { + this._underlying.requires(flags) + return this + } + + public getResult(): ArrayFlagSet { + const graph = this._underlying.finish() + const factory = new ArrayFlagDefinitionFactory() + return new ArrayFlagSet(graph.intoDictionary(factory)) + } +} + +export function createArrayFlagSet(declarations: FlagWithValue[]): ArrayFlagSet +export function createArrayFlagSet( + declarations: Record>, +): ArrayFlagSet & Record> +export function createArrayFlagSet(declarations: ListOfFlagsWithValue): ArrayFlagSet { + const builder = new ArrayFlagSetBuilder() + applyDeclarations(declarations, builder) + return builder.getResult() +} diff --git a/node/src/builders/collection.ts b/node/src/builders/collection.ts index c160383..d40562f 100644 --- a/node/src/builders/collection.ts +++ b/node/src/builders/collection.ts @@ -1,22 +1,20 @@ +import { FlagDefinition } from '~/definitions' +import { CollectionFlagDefinitionFactory } from '~/definitions/collection' import { CollectionFlagSet } from '~/flagsets' + import { applyDeclarations, FlagWithValue, - FlagWithValueOrOrdinal, ListOfFlagsWithValue, - ListOfFlagsWithValueOrOrdinal, NamedFlagWithValue, - NamedFlagWithValueOrOrdinal, } from './declarative' -import { FlagDefinition } from '~/definitions' +import { FlagSetBuilder } from './generic' import { DefineWithValueOrOrdinal, RequireParentsOrDefineWithValueOrOrdinal, WithValueOrOrdinal, WithValueOrOrdinalOrCompose, } from './syntax' -import { FlagSetBuilder } from './generic' -import { CollectionFlagDefinitionFactory } from '~/definitions/collection' export class CollectionFlagSetBuilder implements diff --git a/node/src/builders/index.ts b/node/src/builders/index.ts index d56c462..65990aa 100644 --- a/node/src/builders/index.ts +++ b/node/src/builders/index.ts @@ -1,2 +1,3 @@ +export { ArrayFlagSetBuilder, createArrayFlagSet } from './array' +export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection' export { BitFlagSetBuilder, createBitFlagSet } from './number' -export { createCollectionFlagSet, CollectionFlagSetBuilder } from './collection' diff --git a/node/src/definitions/array.ts b/node/src/definitions/array.ts new file mode 100644 index 0000000..428dcc7 --- /dev/null +++ b/node/src/definitions/array.ts @@ -0,0 +1,119 @@ +import { FlagDefinition, FlagDefinitionFactory, PartialFlagDefinition } from '.' + +export class ArrayFlagDefinition implements FlagDefinition { + private readonly _value: T | undefined + private readonly _alias: string | undefined + private readonly _parents: ArrayFlagDefinition[] + private readonly _children: ArrayFlagDefinition[] + + public constructor( + value: T | undefined, + alias: string | undefined, + parents: ArrayFlagDefinition[], + ) { + this._value = value + this._alias = alias + this._parents = parents + this._children = [] + + for (const parent of this._parents) { + parent._children.push(this) + } + } + + public get alias(): string | undefined { + return this._alias + } + + public get values(): T[] { + const values: T[] = [] + if (this._value === undefined) { + for (const parent of this._parents) { + for (const parentValue of parent.values) { + if (!values.includes(parentValue)) { + values.push(parentValue) + } + } + } + } else { + if (!values.includes(this._value)) { + values.push(this._value) + } + } + return values + } + + public isIn(set: T[]): boolean { + let result = this._value === undefined || set.includes(this._value) + for (const parent of this._parents) { + result &&= parent.isIn(set) + } + return result + } + + public addTo(set: T[]): T[] { + const result: T[] = [...set] + if (this._value !== undefined) { + if (!result.includes(this._value)) { + result.push(this._value) + } + } + for (const parent of this._parents) { + parent.addToMutable(result) + } + return result + } + + private addToMutable(set: T[]): void { + if (this._value !== undefined) { + if (!set.includes(this._value)) { + set.push(this._value) + } + } + for (const parent of this._parents) { + parent.addToMutable(set) + } + } + + public removeFrom(set: T[]): T[] { + const result: T[] = [...set] + if (this._value !== undefined) { + const i = result.indexOf(this._value) + if (i >= 0) { + result.splice(i, 1) + } + } + for (const parent of this._parents) { + parent.removeFromMutable(result) + } + return result + } + + public removeFromMutable(set: T[]): void { + if (this._value !== undefined) { + const i = set.indexOf(this._value) + if (i >= 0) { + set.splice(i, 1) + } + } + for (const parent of this._parents) { + parent.removeFromMutable(set) + } + } +} + +export class ArrayFlagDefinitionFactory implements FlagDefinitionFactory { + public makeDefinitions( + sortedPartialDefinitions: PartialFlagDefinition[], + results: Map, FlagDefinition>, + ): void { + for (const pfd of sortedPartialDefinitions) { + const parents: ArrayFlagDefinition[] = [] + for (const parentPfd of pfd.parents) { + parents.push(results.get(parentPfd) as ArrayFlagDefinition) + } + + results.set(pfd, new ArrayFlagDefinition(pfd.value, pfd.alias, parents)) + } + } +} diff --git a/node/src/flagsets/array.ts b/node/src/flagsets/array.ts index 9a369db..093ba77 100644 --- a/node/src/flagsets/array.ts +++ b/node/src/flagsets/array.ts @@ -1,11 +1,38 @@ +import { FlagDefinition, FlagsDictionary } from '~/definitions' +import { EnumerateFlags } from '~/enumeration' + import type { FlagSet } from '.' -import { EnumerateFlags } from '../enumeration' export class ArrayFlagSet implements FlagSet { + private readonly _dictionary: FlagsDictionary + + public constructor(dictionary: FlagsDictionary) { + this._dictionary = dictionary + } + public none(): T[] { return [] } + public of(...values: T[]): T[] { + return values + } + + public named(...aliases: string[]): T[] { + const result: T[] = [] + for (const alias of aliases) { + const definition = this.getFlag(alias) + if (definition !== undefined) { + for (const value of definition.values) { + if (!result.includes(value)) { + result.push(value) + } + } + } + } + return result + } + public union(first: T[], second: T[]): T[] { const unionArray: T[] = [] for (const item of first) { @@ -50,19 +77,57 @@ export class ArrayFlagSet implements FlagSet { return true } + public hasAny(flags: T[], required: T[]): boolean { + let result = false + for (const value of required) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined && definition.isIn(flags)) { + result = true + break + } + } + return result + } + + public hasAll(flags: T[], required: T[]): boolean { + let result = true + for (const value of required) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined && !definition.isIn(flags)) { + result = false + break + } + } + return result + } + public enumerate(flags: T[]): EnumerateFlags { return flags } - maximum(flags: T[]): T[] { - throw new Error('Method not implemented.') + public maximum(flags: T[]): T[] { + let result: T[] = [] + for (const value of flags) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined) { + result = definition.addTo(result) + } + } + return result } - minimum(flags: T[]): T[] { - throw new Error('Method not implemented.') + public minimum(flags: T[]): T[] { + let result: T[] = [] + for (const value of flags) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined && definition.isIn(flags)) { + result = definition.addTo(result) + } + } + return result } - public getFlag(alias: string): FlagDefinition | undefined { - return this._dictionary.lookUp(alias) + public getFlag(alias: string): FlagDefinition | undefined { + return this._dictionary.findByAlias(alias) } } diff --git a/node/src/index.ts b/node/src/index.ts index 8fdb435..26aabe8 100644 --- a/node/src/index.ts +++ b/node/src/index.ts @@ -1,12 +1,12 @@ -export { createBitFlagSet, createCollectionFlagSet } from './builders' +export { createArrayFlagSet, createBitFlagSet, createCollectionFlagSet } from './builders' export { FlagDefinition } from './definitions' export { InvalidBitFlagValueError } from './errors' export { ArrayFlagSet, Base64BitFlagSet, BigBitFlagSet, - BitFlagSet, BitFlags, + BitFlagSet, CollectionFlagSet, FlagSet, } from './flagsets' diff --git a/node/tests/flagsets/array.test.ts b/node/tests/flagsets/array.test.ts index 08ecbf3..3ffc5ec 100644 --- a/node/tests/flagsets/array.test.ts +++ b/node/tests/flagsets/array.test.ts @@ -1,15 +1,37 @@ -import { ArrayFlagSet } from '~' import { describe, expect, test } from 'vitest' +import { ArrayFlagSet, createArrayFlagSet } from '~' + describe(ArrayFlagSet, () => { test('none', () => { - const flags = new ArrayFlagSet() + const flags = createArrayFlagSet([]) expect(flags.none()).toEqual([]) }) + test('of', () => { + const flags = createArrayFlagSet([]) + + expect(flags.of()).toEqual([]) + expect(flags.of('a')).toEqual(['a']) + expect(flags.of('x', 'y', 'z')).toEqual(['x', 'y', 'z']) + }) + + test('named', () => { + const flags = createArrayFlagSet([ + { value: 12, as: 'a' }, + { value: 45, as: 'b' }, + { value: 78, as: 'c' }, + { compose: ['a', 'b'], as: 'ab' }, + ]) + + expect(flags.named()).toEqual([]) + expect(flags.named('a')).toEqual([12]) + expect(flags.named('ab', 'c')).toEqual([12, 45, 78]) + }) + test('union', () => { - const flags = new ArrayFlagSet() + const flags = createArrayFlagSet([]) expect(flags.union([], [])).toEqual([]) expect(flags.union(['A'], [])).toEqual(['A']) @@ -19,7 +41,7 @@ describe(ArrayFlagSet, () => { }) test('difference', () => { - const flags = new ArrayFlagSet() + const flags = createArrayFlagSet([]) expect(flags.difference([], [])).toEqual([]) expect(flags.difference(['A'], [])).toEqual(['A']) @@ -29,7 +51,7 @@ describe(ArrayFlagSet, () => { }) test('intersection', () => { - const flags = new ArrayFlagSet() + const flags = createArrayFlagSet([]) expect(flags.intersection([], [])).toEqual([]) expect(flags.intersection(['A'], [])).toEqual([]) @@ -40,7 +62,7 @@ describe(ArrayFlagSet, () => { }) test('isSuperset', () => { - const flags = new ArrayFlagSet() + const flags = createArrayFlagSet([]) expect(flags.isSuperset([], [])).toBe(true) expect(flags.isSuperset(['A', 'B'], [])).toBe(true) @@ -50,11 +72,69 @@ describe(ArrayFlagSet, () => { expect(flags.isSuperset(['C', 'D'], ['B'])).toBe(false) }) + test('hasAny', () => { + const flags = createArrayFlagSet([ + { value: 12, as: 'a' }, + { value: 45, as: 'b', requires: ['a'] }, + { value: 78, as: 'c' }, + ]) + + expect(flags.hasAny([], [])).toBe(false) + expect(flags.hasAny([12, 45, 78], [])).toBe(false) + expect(flags.hasAny([12, 45, 78], [12])).toBe(true) + expect(flags.hasAny([12], [12, 78])).toBe(true) + expect(flags.hasAny([45, 78], [45])).toBe(false) + }) + + test('hasAll', () => { + const flags = createArrayFlagSet([ + { value: 12, as: 'a' }, + { value: 45, as: 'b', requires: ['a'] }, + { value: 78, as: 'c' }, + ]) + + expect(flags.hasAll([], [])).toBe(true) + expect(flags.hasAll([12, 45, 78], [])).toBe(true) + expect(flags.hasAll([12, 45, 78], [12])).toBe(true) + expect(flags.hasAll([12], [12, 78])).toBe(false) + expect(flags.hasAll([45, 78], [45])).toBe(false) + }) + test('enumerate', () => { - const flags = new ArrayFlagSet() + const flags = createArrayFlagSet([]) expect([...flags.enumerate([])]).toEqual([]) expect([...flags.enumerate(['A'])]).toEqual(['A']) expect([...flags.enumerate(['A', 'B', 'C'])]).toEqual(['A', 'B', 'C']) }) + + test('maximum', () => { + const flags = createArrayFlagSet([ + { value: 12, as: 'a' }, + { value: 45, as: 'b', requires: ['a'] }, + { value: 78, as: 'c', requires: ['b'] }, + ]) + + expect(flags.maximum([])).toEqual([]) + expect(flags.maximum([12])).toEqual([12]) + expect(flags.maximum([45])).toEqual([45, 12]) + expect(flags.maximum([78])).toEqual([78, 45, 12]) + expect(flags.maximum([99])).toEqual([]) + }) + + test('minimum', () => { + const flags = createArrayFlagSet([ + { value: 12, as: 'a' }, + { value: 45, as: 'b', requires: ['a'] }, + { value: 78, as: 'c', requires: ['b'] }, + ]) + + expect(flags.minimum([])).toEqual([]) + expect(flags.minimum([12])).toEqual([12]) + expect(flags.minimum([45])).toEqual([]) + expect(flags.minimum([12, 45])).toEqual([12, 45]) + expect(flags.minimum([12, 78])).toEqual([12]) + expect(flags.minimum([12, 45, 78])).toEqual([12, 45, 78]) + expect(flags.minimum([99])).toEqual([]) + }) })