Created
May 10, 2023 19:44
-
-
Save GregWWalters/ecf4cd74f049519f576ec748d821cbda to your computer and use it in GitHub Desktop.
BitFlag generator class in JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-bitwise,no-plusplus,no-underscore-dangle */ | |
/* eslint-disable no-func-assign,no-return-assign */ | |
const util = require('../authorization/util'); | |
// TODO: move utils | |
const MAX_BIT = Math.log2(Number.MAX_SAFE_INTEGER); | |
/** | |
* Get the value of the next bit in the series and increment the next bit | |
* value. If the maximum safe bit has already been claimed, it will throw | |
* a RangeError. | |
* @memberOf BitFlag | |
* @returns {Integer} bit | |
*/ | |
const ZERO_BIT = 0 >>> 0; | |
/** | |
* Creates and returns a new BitFlag constructor | |
* @returns {Constructor:BitFlag} constructor | |
*/ | |
function newBitFlagSeries() { | |
// force _bit to be an unsigned integer | |
let _bit = ZERO_BIT; | |
const _generated = []; | |
function claimBit() { | |
// claimBit will return 1 the first time, then increment every time | |
// thereafter. | |
// eslint-disable-next-line no-shadow | |
claimBit = function claimBit() { | |
if (_bit >= MAX_BIT) { | |
throw new RangeError( | |
'maximum number of BitFlags exceeded for this generator' | |
); | |
} | |
return (_bit <<= 1); | |
}; | |
return (_bit = 1 >>> 0); | |
} | |
/** | |
* If called as a constructor with the `new` operator, create a BitFlag for | |
* the next bit in the BitFlag series. If called as a regular function, | |
* convert an integer to a BitFlag instance. | |
* @constructor | |
* @property {String} label | |
* @property {Integer} value | |
* @function | |
* @param {...unknown} args | |
* @param {?String} args[0] - label | |
* @param {?Integer} args[0] - value | |
*/ | |
function BitFlag(...args) { | |
// if called as a converter | |
if (!new.target) return BitFlag.from(...args); | |
// if called as constructor with `new` | |
const [label = '', value = undefined] = args; | |
if (!label || !util.isStringLike(label)) | |
throw new SyntaxError('must provide a label to BitFlag constructor'); | |
if (util.isSet(value)) | |
throw new SyntaxError( | |
'cannot specify BitFlag value when calling with new.' | |
); | |
Object.defineProperties(this, { | |
constructor: { value: BitFlag }, | |
bit: { value: claimBit() }, | |
label: { value: String(label) } | |
}); | |
_generated.push(this); | |
return this; | |
} | |
Object.defineProperties(BitFlag, { | |
/** | |
* Defines the maximum allowed value for a flag | |
* @memberOf BitFlag | |
*/ | |
MAX_BIT: { value: MAX_BIT, enumerable: true }, | |
/** | |
* The number of BitFlags generated | |
* @memberOf BitFlag | |
* @type {Integer} count | |
*/ | |
length: { | |
get() { | |
return _generated.length; | |
}, | |
enumerable: true | |
}, | |
/** | |
* Returns the greatest bit value assigned | |
* @memberOf BitFlag | |
* @type {Integer} bit | |
*/ | |
greatestBit: { | |
get() { | |
return _bit; | |
}, | |
enumerable: true | |
}, | |
/** | |
* the array of generated flags | |
* @memberOf BitFlag | |
* @type {BitFlag[]} flags | |
*/ | |
flags: { | |
get() { | |
return [..._generated]; | |
}, | |
enumerable: true | |
}, | |
parseFromString: { | |
value: function parseFromString(str) { | |
if (str?.constructor !== String) { | |
throw new TypeError('argument must be a String'); | |
} | |
// TODO | |
}, | |
enumerable: false | |
}, | |
/** | |
* Creates a new BitFlag with the specified properties, skipping the | |
* generator and automatically incremented value. You probably shouldn't | |
* be using it. | |
* @memberOf BitFlag | |
* @function | |
* @param {(String|Number|Object)} object - value to convert from | |
* @param {?String} object.label - optional for object literals | |
* @param {?Number} object.value - required for object literals | |
*/ | |
from: { | |
value: function from(...args) { | |
let object; | |
if (args.length > 1) { | |
const [label, bit] = args; | |
object = { label, bit }; | |
} else if (args.length > 0) { | |
[object] = args; | |
} | |
const { constructor: ctor, label, bit } = object ?? {}; | |
switch (true) { | |
case ctor === BitFlag: | |
return object; | |
case ctor === String || object instanceof String: | |
return new BitFlag(object); | |
case ctor === Number || object instanceof Number: | |
if (!Number.isSafeInteger(object)) | |
throw new TypeError( | |
`BitFlag value must be an integer less than ${MAX_BIT} (${MAX_BIT.toString( | |
2 | |
)})` | |
); | |
return Object.create(BitFlag.prototype, { | |
constructor: { value: BitFlag }, | |
label: { value: '', enumerable: true }, | |
bit: { value: object >>> 0, enumerable: true } | |
}); | |
case ctor instanceof Object: | |
if (!Number.isSafeInteger(bit)) | |
throw new TypeError( | |
'must provide a safe integer value when converting to BitFlag' | |
); | |
if (util.isSet(label) && !util.isStringLike(label)) | |
throw new TypeError('label must be a String if provided'); | |
return Object.create(BitFlag.prototype, { | |
constructor: { value: BitFlag }, | |
label: { | |
value: label ? String(label) : '', | |
enumerable: true | |
}, | |
bit: { value: Number(bit) >>> 0 } | |
}); | |
default: | |
throw new TypeError( | |
`can't create a BitFlag from ${util.classOf(object)}` | |
); | |
} | |
}, | |
enumerable: true | |
}, | |
/** | |
* returns whether all the required flags are set on the test BitFlag | |
* @memberof bitflag | |
* @function | |
* @param {(BitFlag|Integer)} require - flags to require | |
* @param {BitFlag} test - BiFlag value to test | |
* @returns {boolean} | |
*/ | |
all: { | |
value: function all(require, test) { | |
return (require & test) === require; | |
}, | |
enumerable: true | |
}, | |
/** | |
* returns whether any of the required flags are set on the test BitFlag | |
* @memberof bitflag | |
* @function | |
* @param {(BitFlag|Integer)} require - flags to require | |
* @param {BitFlag} test - BiFlag value to test | |
* @returns {boolean} | |
*/ | |
any: { | |
value: function any(require, test) { | |
return (test & require) > 0; | |
}, | |
enumerable: true | |
}, | |
/** | |
* Combines the BitFlags into a single value (a logical and) that can be used | |
* for simpler testing | |
* @memberof bitflag | |
* @function | |
* @param {BitFlag[]} flags - the BitFlags to combine | |
* @returns {BitFlag} - the combined value as a BitFlag | |
*/ | |
combine: { | |
value: function combine(...flags) { | |
return BitFlag( | |
flags.reduce( | |
(combined, current) => ({ | |
label: | |
combined.label && current.label | |
? `${combined.label}+${current.label}` | |
: combined.label || current.label || '', | |
bit: combined.bit | current.bit | |
}), | |
{ bit: 0 >>> 0 } | |
) | |
); | |
}, | |
enumerable: true | |
} | |
}); | |
Object.defineProperties(BitFlag.prototype, { | |
constructor: { value: BitFlag }, | |
/** | |
* @memberOf BitFlag# | |
* @type {Integer} bit | |
*/ | |
bit: { | |
value: 0, | |
enumerable: true | |
}, | |
/** | |
* @memberOf BitFlag# | |
* @type {String} label | |
*/ | |
label: { | |
value: '<label>', | |
enumerable: true | |
}, | |
/** | |
* @memberOf BitFlag# | |
* @type {Integer} bit | |
* @function | |
*/ | |
valueOf: { | |
value: function valueOf() { | |
return this.bit; | |
} | |
}, | |
/** | |
* @memberOf BitFlag# | |
* @type {String} | |
* @function | |
*/ | |
toString: { | |
value: function toString() { | |
return `${this.label || '[BitFlag]'}(${this.bit.toString(2)})`; | |
} | |
}, | |
/** | |
* return the logical "and" of this BitFlag and all BitFlags passed as | |
* arguments | |
* @memberOf BitFlag# | |
* @function | |
* @param {...BitFlag} bitFlag | |
* @returns {BitFlag} combined | |
*/ | |
and: { | |
value: function and(...bitFlag) { | |
return BitFlag.from({ | |
label: [this.label, ...bitFlag].join('+'), | |
value: bitFlag.reduce( | |
(previous, current) => previous | current.bit, | |
this.bit | |
) | |
}); | |
} | |
}, | |
/** | |
* returns whether this BitFlag has all the bits set of the BitFlags | |
* passed as arguments | |
* @memberOf BitFlag# | |
* @function | |
* @param {...BitFlag} bitFlag | |
* @returns {Boolean} | |
*/ | |
hasAll: { | |
value: function hasAll(...bitFlag) { | |
const require = bitFlag.reduce( | |
(previous, current) => previous | current.bit, | |
0 >>> 0 | |
); | |
return BitFlag.all(require, this); | |
}, | |
enumerable: true | |
}, | |
/** | |
* returns whether this BitFlag has any of the bits set of the BitFlags | |
* passed as arguments | |
* @memberOf BitFlag# | |
* @function | |
* @param {...BitFlag} bitFlag | |
* @returns {Boolean} | |
*/ | |
hasAny: { | |
value: function hasAll(...bitFlag) { | |
const require = bitFlag.reduce( | |
(previous, current) => previous | current.bit, | |
0 >>> 0 | |
); | |
return BitFlag.any(require, this); | |
}, | |
enumerable: true | |
} | |
}); | |
return BitFlag; | |
} | |
module.exports = newBitFlagSeries; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment