[6a3a178] | 1 | "use strict";
|
---|
| 2 |
|
---|
| 3 | /**
|
---|
| 4 | * @typedef {[number, boolean]} RangeValue
|
---|
| 5 | */
|
---|
| 6 |
|
---|
| 7 | /**
|
---|
| 8 | * @callback RangeValueCallback
|
---|
| 9 | * @param {RangeValue} rangeValue
|
---|
| 10 | * @returns {boolean}
|
---|
| 11 | */
|
---|
| 12 | class Range {
|
---|
| 13 | /**
|
---|
| 14 | * @param {"left" | "right"} side
|
---|
| 15 | * @param {boolean} exclusive
|
---|
| 16 | * @returns {">" | ">=" | "<" | "<="}
|
---|
| 17 | */
|
---|
| 18 | static getOperator(side, exclusive) {
|
---|
| 19 | if (side === 'left') {
|
---|
| 20 | return exclusive ? '>' : '>=';
|
---|
| 21 | }
|
---|
| 22 |
|
---|
| 23 | return exclusive ? '<' : '<=';
|
---|
| 24 | }
|
---|
| 25 | /**
|
---|
| 26 | * @param {number} value
|
---|
| 27 | * @param {boolean} logic is not logic applied
|
---|
| 28 | * @param {boolean} exclusive is range exclusive
|
---|
| 29 | * @returns {string}
|
---|
| 30 | */
|
---|
| 31 |
|
---|
| 32 |
|
---|
| 33 | static formatRight(value, logic, exclusive) {
|
---|
| 34 | if (logic === false) {
|
---|
| 35 | return Range.formatLeft(value, !logic, !exclusive);
|
---|
| 36 | }
|
---|
| 37 |
|
---|
| 38 | return `should be ${Range.getOperator('right', exclusive)} ${value}`;
|
---|
| 39 | }
|
---|
| 40 | /**
|
---|
| 41 | * @param {number} value
|
---|
| 42 | * @param {boolean} logic is not logic applied
|
---|
| 43 | * @param {boolean} exclusive is range exclusive
|
---|
| 44 | * @returns {string}
|
---|
| 45 | */
|
---|
| 46 |
|
---|
| 47 |
|
---|
| 48 | static formatLeft(value, logic, exclusive) {
|
---|
| 49 | if (logic === false) {
|
---|
| 50 | return Range.formatRight(value, !logic, !exclusive);
|
---|
| 51 | }
|
---|
| 52 |
|
---|
| 53 | return `should be ${Range.getOperator('left', exclusive)} ${value}`;
|
---|
| 54 | }
|
---|
| 55 | /**
|
---|
| 56 | * @param {number} start left side value
|
---|
| 57 | * @param {number} end right side value
|
---|
| 58 | * @param {boolean} startExclusive is range exclusive from left side
|
---|
| 59 | * @param {boolean} endExclusive is range exclusive from right side
|
---|
| 60 | * @param {boolean} logic is not logic applied
|
---|
| 61 | * @returns {string}
|
---|
| 62 | */
|
---|
| 63 |
|
---|
| 64 |
|
---|
| 65 | static formatRange(start, end, startExclusive, endExclusive, logic) {
|
---|
| 66 | let result = 'should be';
|
---|
| 67 | result += ` ${Range.getOperator(logic ? 'left' : 'right', logic ? startExclusive : !startExclusive)} ${start} `;
|
---|
| 68 | result += logic ? 'and' : 'or';
|
---|
| 69 | result += ` ${Range.getOperator(logic ? 'right' : 'left', logic ? endExclusive : !endExclusive)} ${end}`;
|
---|
| 70 | return result;
|
---|
| 71 | }
|
---|
| 72 | /**
|
---|
| 73 | * @param {Array<RangeValue>} values
|
---|
| 74 | * @param {boolean} logic is not logic applied
|
---|
| 75 | * @return {RangeValue} computed value and it's exclusive flag
|
---|
| 76 | */
|
---|
| 77 |
|
---|
| 78 |
|
---|
| 79 | static getRangeValue(values, logic) {
|
---|
| 80 | let minMax = logic ? Infinity : -Infinity;
|
---|
| 81 | let j = -1;
|
---|
| 82 | const predicate = logic ?
|
---|
| 83 | /** @type {RangeValueCallback} */
|
---|
| 84 | ([value]) => value <= minMax :
|
---|
| 85 | /** @type {RangeValueCallback} */
|
---|
| 86 | ([value]) => value >= minMax;
|
---|
| 87 |
|
---|
| 88 | for (let i = 0; i < values.length; i++) {
|
---|
| 89 | if (predicate(values[i])) {
|
---|
| 90 | [minMax] = values[i];
|
---|
| 91 | j = i;
|
---|
| 92 | }
|
---|
| 93 | }
|
---|
| 94 |
|
---|
| 95 | if (j > -1) {
|
---|
| 96 | return values[j];
|
---|
| 97 | }
|
---|
| 98 |
|
---|
| 99 | return [Infinity, true];
|
---|
| 100 | }
|
---|
| 101 |
|
---|
| 102 | constructor() {
|
---|
| 103 | /** @type {Array<RangeValue>} */
|
---|
| 104 | this._left = [];
|
---|
| 105 | /** @type {Array<RangeValue>} */
|
---|
| 106 |
|
---|
| 107 | this._right = [];
|
---|
| 108 | }
|
---|
| 109 | /**
|
---|
| 110 | * @param {number} value
|
---|
| 111 | * @param {boolean=} exclusive
|
---|
| 112 | */
|
---|
| 113 |
|
---|
| 114 |
|
---|
| 115 | left(value, exclusive = false) {
|
---|
| 116 | this._left.push([value, exclusive]);
|
---|
| 117 | }
|
---|
| 118 | /**
|
---|
| 119 | * @param {number} value
|
---|
| 120 | * @param {boolean=} exclusive
|
---|
| 121 | */
|
---|
| 122 |
|
---|
| 123 |
|
---|
| 124 | right(value, exclusive = false) {
|
---|
| 125 | this._right.push([value, exclusive]);
|
---|
| 126 | }
|
---|
| 127 | /**
|
---|
| 128 | * @param {boolean} logic is not logic applied
|
---|
| 129 | * @return {string} "smart" range string representation
|
---|
| 130 | */
|
---|
| 131 |
|
---|
| 132 |
|
---|
| 133 | format(logic = true) {
|
---|
| 134 | const [start, leftExclusive] = Range.getRangeValue(this._left, logic);
|
---|
| 135 | const [end, rightExclusive] = Range.getRangeValue(this._right, !logic);
|
---|
| 136 |
|
---|
| 137 | if (!Number.isFinite(start) && !Number.isFinite(end)) {
|
---|
| 138 | return '';
|
---|
| 139 | }
|
---|
| 140 |
|
---|
| 141 | const realStart = leftExclusive ? start + 1 : start;
|
---|
| 142 | const realEnd = rightExclusive ? end - 1 : end; // e.g. 5 < x < 7, 5 < x <= 6, 6 <= x <= 6
|
---|
| 143 |
|
---|
| 144 | if (realStart === realEnd) {
|
---|
| 145 | return `should be ${logic ? '' : '!'}= ${realStart}`;
|
---|
| 146 | } // e.g. 4 < x < ∞
|
---|
| 147 |
|
---|
| 148 |
|
---|
| 149 | if (Number.isFinite(start) && !Number.isFinite(end)) {
|
---|
| 150 | return Range.formatLeft(start, logic, leftExclusive);
|
---|
| 151 | } // e.g. ∞ < x < 4
|
---|
| 152 |
|
---|
| 153 |
|
---|
| 154 | if (!Number.isFinite(start) && Number.isFinite(end)) {
|
---|
| 155 | return Range.formatRight(end, logic, rightExclusive);
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | return Range.formatRange(start, end, leftExclusive, rightExclusive, logic);
|
---|
| 159 | }
|
---|
| 160 |
|
---|
| 161 | }
|
---|
| 162 |
|
---|
| 163 | module.exports = Range; |
---|