You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
4.0 KiB
163 lines
4.0 KiB
"use strict"; |
|
|
|
/** |
|
* @typedef {[number, boolean]} RangeValue |
|
*/ |
|
|
|
/** |
|
* @callback RangeValueCallback |
|
* @param {RangeValue} rangeValue |
|
* @returns {boolean} |
|
*/ |
|
class Range { |
|
/** |
|
* @param {"left" | "right"} side |
|
* @param {boolean} exclusive |
|
* @returns {">" | ">=" | "<" | "<="} |
|
*/ |
|
static getOperator(side, exclusive) { |
|
if (side === "left") { |
|
return exclusive ? ">" : ">="; |
|
} |
|
|
|
return exclusive ? "<" : "<="; |
|
} |
|
/** |
|
* @param {number} value |
|
* @param {boolean} logic is not logic applied |
|
* @param {boolean} exclusive is range exclusive |
|
* @returns {string} |
|
*/ |
|
|
|
|
|
static formatRight(value, logic, exclusive) { |
|
if (logic === false) { |
|
return Range.formatLeft(value, !logic, !exclusive); |
|
} |
|
|
|
return `should be ${Range.getOperator("right", exclusive)} ${value}`; |
|
} |
|
/** |
|
* @param {number} value |
|
* @param {boolean} logic is not logic applied |
|
* @param {boolean} exclusive is range exclusive |
|
* @returns {string} |
|
*/ |
|
|
|
|
|
static formatLeft(value, logic, exclusive) { |
|
if (logic === false) { |
|
return Range.formatRight(value, !logic, !exclusive); |
|
} |
|
|
|
return `should be ${Range.getOperator("left", exclusive)} ${value}`; |
|
} |
|
/** |
|
* @param {number} start left side value |
|
* @param {number} end right side value |
|
* @param {boolean} startExclusive is range exclusive from left side |
|
* @param {boolean} endExclusive is range exclusive from right side |
|
* @param {boolean} logic is not logic applied |
|
* @returns {string} |
|
*/ |
|
|
|
|
|
static formatRange(start, end, startExclusive, endExclusive, logic) { |
|
let result = "should be"; |
|
result += ` ${Range.getOperator(logic ? "left" : "right", logic ? startExclusive : !startExclusive)} ${start} `; |
|
result += logic ? "and" : "or"; |
|
result += ` ${Range.getOperator(logic ? "right" : "left", logic ? endExclusive : !endExclusive)} ${end}`; |
|
return result; |
|
} |
|
/** |
|
* @param {Array<RangeValue>} values |
|
* @param {boolean} logic is not logic applied |
|
* @return {RangeValue} computed value and it's exclusive flag |
|
*/ |
|
|
|
|
|
static getRangeValue(values, logic) { |
|
let minMax = logic ? Infinity : -Infinity; |
|
let j = -1; |
|
const predicate = logic ? |
|
/** @type {RangeValueCallback} */ |
|
([value]) => value <= minMax : |
|
/** @type {RangeValueCallback} */ |
|
([value]) => value >= minMax; |
|
|
|
for (let i = 0; i < values.length; i++) { |
|
if (predicate(values[i])) { |
|
[minMax] = values[i]; |
|
j = i; |
|
} |
|
} |
|
|
|
if (j > -1) { |
|
return values[j]; |
|
} |
|
|
|
return [Infinity, true]; |
|
} |
|
|
|
constructor() { |
|
/** @type {Array<RangeValue>} */ |
|
this._left = []; |
|
/** @type {Array<RangeValue>} */ |
|
|
|
this._right = []; |
|
} |
|
/** |
|
* @param {number} value |
|
* @param {boolean=} exclusive |
|
*/ |
|
|
|
|
|
left(value, exclusive = false) { |
|
this._left.push([value, exclusive]); |
|
} |
|
/** |
|
* @param {number} value |
|
* @param {boolean=} exclusive |
|
*/ |
|
|
|
|
|
right(value, exclusive = false) { |
|
this._right.push([value, exclusive]); |
|
} |
|
/** |
|
* @param {boolean} logic is not logic applied |
|
* @return {string} "smart" range string representation |
|
*/ |
|
|
|
|
|
format(logic = true) { |
|
const [start, leftExclusive] = Range.getRangeValue(this._left, logic); |
|
const [end, rightExclusive] = Range.getRangeValue(this._right, !logic); |
|
|
|
if (!Number.isFinite(start) && !Number.isFinite(end)) { |
|
return ""; |
|
} |
|
|
|
const realStart = leftExclusive ? start + 1 : start; |
|
const realEnd = rightExclusive ? end - 1 : end; // e.g. 5 < x < 7, 5 < x <= 6, 6 <= x <= 6 |
|
|
|
if (realStart === realEnd) { |
|
return `should be ${logic ? "" : "!"}= ${realStart}`; |
|
} // e.g. 4 < x < ∞ |
|
|
|
|
|
if (Number.isFinite(start) && !Number.isFinite(end)) { |
|
return Range.formatLeft(start, logic, leftExclusive); |
|
} // e.g. ∞ < x < 4 |
|
|
|
|
|
if (!Number.isFinite(start) && Number.isFinite(end)) { |
|
return Range.formatRight(end, logic, rightExclusive); |
|
} |
|
|
|
return Range.formatRange(start, end, leftExclusive, rightExclusive, logic); |
|
} |
|
|
|
} |
|
|
|
module.exports = Range; |