@thejaredwilcurt/csslop 0.0.48 → 0.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/package.json +3 -3
- package/src/declarations/border.js +1 -1
- package/src/declarations/css-wide-keywords.js +37 -1
- package/src/declarations/fold.js +13 -3
- package/src/declarations/process.js +60 -7
- package/src/index.js +12 -7
- package/src/parser/source-search.js +6 -0
- package/src/rules/optimize/at-rules.js +113 -0
- package/src/rules/optimize/declaration-writes.js +70 -0
- package/src/rules/optimize/empty-rules.js +51 -0
- package/src/rules/optimize/expand-nesting.js +236 -0
- package/src/rules/optimize/factor-parents.js +343 -0
- package/src/rules/optimize/index.js +71 -0
- package/src/rules/optimize/merge-declarations.js +199 -0
- package/src/rules/optimize/merge-nested-rules.js +117 -0
- package/src/rules/optimize/merge-selectors.js +252 -0
- package/src/rules/optimize/nest-rules.js +202 -0
- package/src/rules/optimize/override-index.js +264 -0
- package/src/rules/optimize/prune-overridden.js +267 -0
- package/src/rules/optimize/selector-normalization.js +60 -0
- package/src/rules/optimize/specificity.js +280 -0
- package/src/{position-try.js → rules/position-try.js} +1 -1
- package/src/rules/selectors.js +9 -0
- package/src/rules/stringify.js +138 -76
- package/src/utilities.js +9 -8
- package/src/value/border.js +85 -0
- package/src/value/{colors.js → color/conversion.js} +3 -2
- package/src/value/color/functions.js +463 -0
- package/src/value/{light-dark.js → color/light-dark.js} +1 -1
- package/src/value/{color-mix.js → color/mix.js} +3 -3
- package/src/value/{relative-colors.js → color/relative.js} +3 -3
- package/src/value/color/shorten.js +93 -0
- package/src/value/gradients.js +1 -1
- package/src/value/hyphenate-limit-chars.js +72 -0
- package/src/value/initial-keywords.js +128 -0
- package/src/value/lengths.js +55 -0
- package/src/value/math/evaluate.js +36 -0
- package/src/value/math/fold.js +315 -0
- package/src/value/math/format.js +230 -0
- package/src/value/math/functions.js +302 -0
- package/src/value/math/index.js +219 -0
- package/src/value/math/parser.js +301 -0
- package/src/value/math/property-ranges.js +174 -0
- package/src/value/math/rewrite.js +222 -0
- package/src/value/math/tokenizer.js +160 -0
- package/src/value/math/unit-totals.js +194 -0
- package/src/value/minify.js +162 -1678
- package/src/value/numbers.js +28 -0
- package/src/value/positions.js +152 -0
- package/src/value/property-optimizations.js +485 -0
- package/src/value/segments.js +74 -0
- package/src/value/{svg-data-uri.js → svg/data-uri.js} +1 -1
- package/src/value/{svg-markup.js → svg/markup.js} +1 -1
- package/src/value/time.js +75 -0
- package/src/value/urls.js +242 -0
- package/src/value/whitespace.js +88 -0
- package/src/rules/optimize.js +0 -2031
- package/src/value/math-expression.js +0 -1156
- package/src/value/math.js +0 -886
- /package/src/value/{named-colors.js → color/named.js} +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Writes the unit totals a calculation resolved to as the shortest CSS value that states them.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatResolvedNumber } from '../shared.js';
|
|
6
|
+
|
|
7
|
+
import { PERCENTAGE } from './unit-totals.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The smallest total a term is written with. A term totalling less than this
|
|
11
|
+
* is the rounding error binary floating point arithmetic leaves behind rather
|
|
12
|
+
* than a quantity the stylesheet states.
|
|
13
|
+
*
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
16
|
+
const NEGLIGIBLE_TOTAL = 1e-12;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The unit each kind of quantity is canonically stated in, and how much of
|
|
20
|
+
* that unit one of the unit written measures. Absolute lengths are absent
|
|
21
|
+
* because every length is already totalled in the pixels it measures.
|
|
22
|
+
*
|
|
23
|
+
* @type {Map<string, {unit: string, perUnit: number}>}
|
|
24
|
+
*/
|
|
25
|
+
const CANONICAL_UNITS = new Map([
|
|
26
|
+
['s', { unit: 's', perUnit: 1 }],
|
|
27
|
+
['ms', { unit: 's', perUnit: 1 / 1000 }],
|
|
28
|
+
['deg', { unit: 'deg', perUnit: 1 }],
|
|
29
|
+
['grad', { unit: 'deg', perUnit: 360 / 400 }],
|
|
30
|
+
['rad', { unit: 'deg', perUnit: 180 / Math.PI }],
|
|
31
|
+
['turn', { unit: 'deg', perUnit: 360 }],
|
|
32
|
+
['hz', { unit: 'hz', perUnit: 1 }],
|
|
33
|
+
['khz', { unit: 'hz', perUnit: 1000 }],
|
|
34
|
+
['dppx', { unit: 'dppx', perUnit: 1 }],
|
|
35
|
+
['x', { unit: 'dppx', perUnit: 1 }],
|
|
36
|
+
['dpi', { unit: 'dppx', perUnit: 1 / 96 }],
|
|
37
|
+
['dpcm', { unit: 'dppx', perUnit: 2.54 / 96 }]
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Counts how many units of each kind of quantity a calculation totalled, so
|
|
42
|
+
* that the kinds it states in more than one unit can be told from the ones it
|
|
43
|
+
* states in a single unit.
|
|
44
|
+
*
|
|
45
|
+
* @param {Map} totals The unit totals the calculation resolved to.
|
|
46
|
+
* @return {Map} How many units each canonical unit was totalled from.
|
|
47
|
+
*/
|
|
48
|
+
function countUnitsByKind (totals) {
|
|
49
|
+
const counts = new Map();
|
|
50
|
+
for (const unit of totals.keys()) {
|
|
51
|
+
const conversion = CANONICAL_UNITS.get(unit);
|
|
52
|
+
if (!conversion) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
counts.set(conversion.unit, (counts.get(conversion.unit) || 0) + 1);
|
|
56
|
+
}
|
|
57
|
+
return counts;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Restates the totals of every kind of quantity a calculation measured in two
|
|
62
|
+
* units of, so that terms measuring the same thing are totalled together
|
|
63
|
+
* (`calc(1s - 200ms)` comes to `.8s`). A kind measured in a single unit keeps
|
|
64
|
+
* that unit: restating `2ms` as `.002s` measures the same time in more
|
|
65
|
+
* characters than it was written with.
|
|
66
|
+
*
|
|
67
|
+
* @param {Map} totals The unit totals the calculation resolved to.
|
|
68
|
+
* @return {Map} The totals, with each kind of quantity stated in one unit.
|
|
69
|
+
*/
|
|
70
|
+
function mergeUnitsOfSameKind (totals) {
|
|
71
|
+
const unitCounts = countUnitsByKind(totals);
|
|
72
|
+
const measuredInSeveralUnits = [...unitCounts.values()].some((count) => {
|
|
73
|
+
return count > 1;
|
|
74
|
+
});
|
|
75
|
+
if (!measuredInSeveralUnits) {
|
|
76
|
+
return totals;
|
|
77
|
+
}
|
|
78
|
+
const merged = new Map();
|
|
79
|
+
for (const [unit, total] of totals) {
|
|
80
|
+
const conversion = CANONICAL_UNITS.get(unit);
|
|
81
|
+
const restatesUnit = conversion && unitCounts.get(conversion.unit) > 1;
|
|
82
|
+
const mergedUnit = restatesUnit ? conversion.unit : unit;
|
|
83
|
+
const mergedTotal = restatesUnit ? total * conversion.perUnit : total;
|
|
84
|
+
merged.set(mergedUnit, (merged.get(mergedUnit) || 0) + mergedTotal);
|
|
85
|
+
}
|
|
86
|
+
return merged;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Collects the terms a resolved calculation is written from, leaving out the
|
|
91
|
+
* ones that totalled to nothing. A unit is totalled from where it is first
|
|
92
|
+
* written, so the terms keep the order the calculation states them in, which
|
|
93
|
+
* is both the order the author chose and one fewer difference between what
|
|
94
|
+
* was written and what is written back out.
|
|
95
|
+
*
|
|
96
|
+
* @param {Map} totals The unit totals the calculation resolved to.
|
|
97
|
+
* @return {Array} The terms to write, each holding its unit and its total.
|
|
98
|
+
*/
|
|
99
|
+
function collectOutputTerms (totals) {
|
|
100
|
+
const terms = [];
|
|
101
|
+
for (const [unit, total] of totals) {
|
|
102
|
+
if (Math.abs(total) < NEGLIGIBLE_TOTAL) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
terms.push({ total, unit });
|
|
106
|
+
}
|
|
107
|
+
return leadWithAddedTerm(terms);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Moves an added term to the front of a calculation that a subtracted one
|
|
112
|
+
* leads. Every term but the first is written after the operator adding or
|
|
113
|
+
* subtracting it, so a term written first states its own sign, and the minus
|
|
114
|
+
* sign of a subtracted one is a character the sum saves by being led by a
|
|
115
|
+
* term that is added instead.
|
|
116
|
+
*
|
|
117
|
+
* @param {Array} terms The terms to write, in the order the calculation states them.
|
|
118
|
+
* @return {Array} The same terms, led by one that is added whenever any of them is.
|
|
119
|
+
*/
|
|
120
|
+
function leadWithAddedTerm (terms) {
|
|
121
|
+
if (!terms.length || terms[0].total > 0) {
|
|
122
|
+
return terms;
|
|
123
|
+
}
|
|
124
|
+
const leadingIndex = terms.findIndex((term) => {
|
|
125
|
+
return term.total > 0;
|
|
126
|
+
});
|
|
127
|
+
if (leadingIndex === -1) {
|
|
128
|
+
return terms;
|
|
129
|
+
}
|
|
130
|
+
const reordered = [...terms];
|
|
131
|
+
const [addedTerm] = reordered.splice(leadingIndex, 1);
|
|
132
|
+
reordered.unshift(addedTerm);
|
|
133
|
+
return reordered;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reports whether writing only the terms collected would drop a percentage the
|
|
138
|
+
* calculation was written with. A percentage that cancels out still ties the
|
|
139
|
+
* value to the basis the percentage is resolved against, so a calculation
|
|
140
|
+
* keeping other terms cannot be written without it.
|
|
141
|
+
*
|
|
142
|
+
* @param {Map} totals The unit totals the calculation resolved to.
|
|
143
|
+
* @param {Array} outputTerms The terms that would be written.
|
|
144
|
+
* @return {boolean} Whether a percentage the calculation states would be lost.
|
|
145
|
+
*/
|
|
146
|
+
function dropsPercentageBasis (totals, outputTerms) {
|
|
147
|
+
if (!totals.has(PERCENTAGE) || !outputTerms.length) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return !outputTerms.some((term) => {
|
|
151
|
+
return term.unit === PERCENTAGE;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Writes the total of a term as the number a stylesheet states it with. A
|
|
157
|
+
* total that no written number states faithfully — one that is infinite, one
|
|
158
|
+
* beyond the integers a double holds exactly, and one that rounds away to
|
|
159
|
+
* nothing — has no number to be written as.
|
|
160
|
+
*
|
|
161
|
+
* @param {number} total The total the term resolved to.
|
|
162
|
+
* @return {string|null} The number as it is written into the stylesheet, or null when it cannot be written.
|
|
163
|
+
*/
|
|
164
|
+
function writeTermNumber (total) {
|
|
165
|
+
if (!Number.isFinite(total) || Math.abs(total) > Number.MAX_SAFE_INTEGER) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const written = formatResolvedNumber(total);
|
|
169
|
+
const roundsAwayToNothing = total !== 0 && parseFloat(written) === 0;
|
|
170
|
+
if (roundsAwayToNothing) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return written;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Writes the unit totals a calculation resolved to as the shortest value
|
|
178
|
+
* stating them: the lone quantity they came to, or, when units that only
|
|
179
|
+
* resolve against the element are left over, a `calc()` summing what remains.
|
|
180
|
+
*
|
|
181
|
+
* @param {Map} totals The unit totals the calculation resolved to.
|
|
182
|
+
* @return {string|null} The value the calculation resolved to, or null when it cannot be written faithfully.
|
|
183
|
+
*/
|
|
184
|
+
function formatUnitTotals (totals) {
|
|
185
|
+
const writtenQuantity = totals.writtenQuantity;
|
|
186
|
+
if (
|
|
187
|
+
writtenQuantity &&
|
|
188
|
+
totals.size === 1 &&
|
|
189
|
+
totals.has(writtenQuantity.canonicalUnit) &&
|
|
190
|
+
totals.get(writtenQuantity.canonicalUnit) === writtenQuantity.canonicalNumber
|
|
191
|
+
) {
|
|
192
|
+
return formatResolvedNumber(writtenQuantity.number) + writtenQuantity.unit;
|
|
193
|
+
}
|
|
194
|
+
const mergedTotals = mergeUnitsOfSameKind(totals);
|
|
195
|
+
const hasUnrepresentableReciprocalUnit = [...mergedTotals.keys()].some((unit) => {
|
|
196
|
+
return unit.startsWith('reciprocal:');
|
|
197
|
+
});
|
|
198
|
+
if (hasUnrepresentableReciprocalUnit) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const outputTerms = collectOutputTerms(mergedTotals);
|
|
202
|
+
if (dropsPercentageBasis(mergedTotals, outputTerms)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (!outputTerms.length) {
|
|
206
|
+
return '0';
|
|
207
|
+
}
|
|
208
|
+
const [leadingTerm, ...trailingTerms] = outputTerms;
|
|
209
|
+
const leadingNumber = writeTermNumber(leadingTerm.total);
|
|
210
|
+
if (leadingNumber === null) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
let written = leadingNumber + leadingTerm.unit;
|
|
214
|
+
for (const term of trailingTerms) {
|
|
215
|
+
const number = writeTermNumber(Math.abs(term.total));
|
|
216
|
+
if (number === null) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
// Both operators have to be surrounded by whitespace to be read as one
|
|
220
|
+
written = written + (term.total < 0 ? ' - ' : ' + ') + number + term.unit;
|
|
221
|
+
}
|
|
222
|
+
if (trailingTerms.length) {
|
|
223
|
+
return 'calc(' + written + ')';
|
|
224
|
+
}
|
|
225
|
+
return written;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export {
|
|
229
|
+
formatUnitTotals
|
|
230
|
+
};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Resolves the CSS math functions whose arguments are arithmetic, such as `min()`, `clamp()`, and `round()`, once their arguments are known.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createUnitTotals,
|
|
7
|
+
readSingleQuantity,
|
|
8
|
+
UNITLESS
|
|
9
|
+
} from './unit-totals.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How each rounding strategy of `round()` turns the number of intervals it is
|
|
13
|
+
* given into the whole number of them it keeps.
|
|
14
|
+
*
|
|
15
|
+
* @type {Map<string, function(number): number>}
|
|
16
|
+
*/
|
|
17
|
+
const ROUNDING_STRATEGIES = new Map([
|
|
18
|
+
['nearest', Math.round],
|
|
19
|
+
['up', Math.ceil],
|
|
20
|
+
['down', Math.floor],
|
|
21
|
+
['to-zero', Math.trunc]
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Reads the arguments of a function as the numbers of one shared unit, which
|
|
26
|
+
* is what a function comparing or rounding its arguments needs them to be.
|
|
27
|
+
* Arguments carrying different units are only comparable once they are
|
|
28
|
+
* resolved against the element, which is long after minification.
|
|
29
|
+
*
|
|
30
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
31
|
+
* @return {object|null} The shared unit and the numbers the arguments state, or null when they do not share one.
|
|
32
|
+
*/
|
|
33
|
+
function readComparableArguments (argumentList) {
|
|
34
|
+
const numbers = [];
|
|
35
|
+
let sharedUnit = null;
|
|
36
|
+
for (const argument of argumentList) {
|
|
37
|
+
if (!argument.value) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const quantity = readSingleQuantity(argument.value);
|
|
41
|
+
if (!quantity) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (sharedUnit !== null && quantity.unit !== sharedUnit) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
sharedUnit = quantity.unit;
|
|
48
|
+
numbers.push(quantity.number);
|
|
49
|
+
}
|
|
50
|
+
if (!numbers.length) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return { numbers, unit: sharedUnit };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reads the single value argument a function takes, which no keyword may
|
|
58
|
+
* stand in for.
|
|
59
|
+
*
|
|
60
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
61
|
+
* @return {Map|null} The unit totals of the lone argument, or null when the function was called with anything else.
|
|
62
|
+
*/
|
|
63
|
+
function readLoneArgument (argumentList) {
|
|
64
|
+
if (argumentList.length !== 1) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return argumentList[0].value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolves `calc()`, which states the value its one argument works out to.
|
|
72
|
+
*
|
|
73
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
74
|
+
* @return {Map|null} The unit totals the calculation states, or null when it cannot be resolved.
|
|
75
|
+
*/
|
|
76
|
+
function evaluateCalculation (argumentList) {
|
|
77
|
+
return readLoneArgument(argumentList);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves `min()`, which states the smallest of the values it compares.
|
|
82
|
+
*
|
|
83
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
84
|
+
* @return {Map|null} The unit totals of the smallest argument, or null when the arguments are not comparable.
|
|
85
|
+
*/
|
|
86
|
+
function evaluateMinimum (argumentList) {
|
|
87
|
+
const comparable = readComparableArguments(argumentList);
|
|
88
|
+
if (!comparable) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const winningIndex = comparable.numbers.indexOf(Math.min(...comparable.numbers));
|
|
92
|
+
return argumentList[winningIndex].value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves `max()`, which states the largest of the values it compares.
|
|
97
|
+
*
|
|
98
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
99
|
+
* @return {Map|null} The unit totals of the largest argument, or null when the arguments are not comparable.
|
|
100
|
+
*/
|
|
101
|
+
function evaluateMaximum (argumentList) {
|
|
102
|
+
const comparable = readComparableArguments(argumentList);
|
|
103
|
+
if (!comparable) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const winningIndex = comparable.numbers.indexOf(Math.max(...comparable.numbers));
|
|
107
|
+
return argumentList[winningIndex].value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reports whether an end of a `clamp()` range is left open, which the `none`
|
|
112
|
+
* keyword does, leaving that end to bound nothing.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} argument The argument written at that end of the range.
|
|
115
|
+
* @return {boolean} Whether that end of the range is open.
|
|
116
|
+
*/
|
|
117
|
+
function boundsNothing (argument) {
|
|
118
|
+
return argument.keyword === 'none';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolves `clamp()`, which holds its preferred value inside the range its
|
|
123
|
+
* other two arguments bound. The smallest value wins a range written back to
|
|
124
|
+
* front, since the preferred value is held below the maximum before it is
|
|
125
|
+
* held above the minimum.
|
|
126
|
+
*
|
|
127
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
128
|
+
* @return {Map|null} The unit totals of the clamped value, or null when the arguments are not comparable.
|
|
129
|
+
*/
|
|
130
|
+
function evaluateClamp (argumentList) {
|
|
131
|
+
if (argumentList.length !== 3) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const [lowerBound, preferred, upperBound] = argumentList;
|
|
135
|
+
const boundedArguments = argumentList.filter((argument) => {
|
|
136
|
+
return !boundsNothing(argument);
|
|
137
|
+
});
|
|
138
|
+
const comparable = readComparableArguments(boundedArguments);
|
|
139
|
+
if (!comparable || !preferred.value) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
let clamped = readSingleQuantity(preferred.value).number;
|
|
143
|
+
if (!boundsNothing(upperBound)) {
|
|
144
|
+
clamped = Math.min(clamped, readSingleQuantity(upperBound.value).number);
|
|
145
|
+
}
|
|
146
|
+
if (!boundsNothing(lowerBound)) {
|
|
147
|
+
clamped = Math.max(clamped, readSingleQuantity(lowerBound.value).number);
|
|
148
|
+
}
|
|
149
|
+
return createUnitTotals(clamped, comparable.unit);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolves `abs()`, which states its argument's distance from zero.
|
|
154
|
+
*
|
|
155
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
156
|
+
* @return {Map|null} The unit totals of the absolute value, or null when the argument states more than one quantity.
|
|
157
|
+
*/
|
|
158
|
+
function evaluateAbsolute (argumentList) {
|
|
159
|
+
const comparable = readComparableArguments(argumentList);
|
|
160
|
+
if (!comparable || comparable.numbers.length !== 1) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return createUnitTotals(Math.abs(comparable.numbers[0]), comparable.unit);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolves `sign()`, which states which side of zero its argument lies on as
|
|
168
|
+
* a plain number.
|
|
169
|
+
*
|
|
170
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
171
|
+
* @return {Map|null} The unit totals of the sign, or null when the argument states more than one quantity.
|
|
172
|
+
*/
|
|
173
|
+
function evaluateSign (argumentList) {
|
|
174
|
+
const comparable = readComparableArguments(argumentList);
|
|
175
|
+
if (!comparable || comparable.numbers.length !== 1) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return createUnitTotals(Math.sign(comparable.numbers[0]), UNITLESS);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolves `hypot()`, which states the length of the vector its arguments
|
|
183
|
+
* measure the sides of.
|
|
184
|
+
*
|
|
185
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
186
|
+
* @return {Map|null} The unit totals of the hypotenuse, or null when the arguments are not comparable.
|
|
187
|
+
*/
|
|
188
|
+
function evaluateHypotenuse (argumentList) {
|
|
189
|
+
const comparable = readComparableArguments(argumentList);
|
|
190
|
+
if (!comparable) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return createUnitTotals(Math.hypot(...comparable.numbers), comparable.unit);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Reads the two comparable numbers a function divides one of by the other,
|
|
198
|
+
* refusing a divisor of zero, which no division resolves against.
|
|
199
|
+
*
|
|
200
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
201
|
+
* @return {object|null} The shared unit with the dividend and the divisor, or null when the arguments cannot be divided.
|
|
202
|
+
*/
|
|
203
|
+
function readDivisionArguments (argumentList) {
|
|
204
|
+
const comparable = readComparableArguments(argumentList);
|
|
205
|
+
if (!comparable || comparable.numbers.length !== 2 || comparable.numbers[1] === 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
dividend: comparable.numbers[0],
|
|
210
|
+
divisor: comparable.numbers[1],
|
|
211
|
+
unit: comparable.unit
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolves `mod()`, which states what is left of its first argument after
|
|
217
|
+
* taking out whole multiples of its second, carrying the sign of the divisor.
|
|
218
|
+
*
|
|
219
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
220
|
+
* @return {Map|null} The unit totals of the modulus, or null when the arguments cannot be divided.
|
|
221
|
+
*/
|
|
222
|
+
function evaluateModulus (argumentList) {
|
|
223
|
+
const division = readDivisionArguments(argumentList);
|
|
224
|
+
if (!division) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const { dividend, divisor, unit } = division;
|
|
228
|
+
return createUnitTotals(((dividend % divisor) + divisor) % divisor, unit);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Resolves `rem()`, which states what is left of its first argument after
|
|
233
|
+
* taking out whole multiples of its second, carrying the sign of the dividend.
|
|
234
|
+
*
|
|
235
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
236
|
+
* @return {Map|null} The unit totals of the remainder, or null when the arguments cannot be divided.
|
|
237
|
+
*/
|
|
238
|
+
function evaluateRemainder (argumentList) {
|
|
239
|
+
const division = readDivisionArguments(argumentList);
|
|
240
|
+
if (!division) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const { dividend, divisor, unit } = division;
|
|
244
|
+
return createUnitTotals(dividend % divisor, unit);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Resolves `round()`, which states the multiple of its rounding interval that
|
|
249
|
+
* the strategy it is given rounds its value to. A call written without a
|
|
250
|
+
* strategy rounds to the nearest multiple.
|
|
251
|
+
*
|
|
252
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
253
|
+
* @return {Map|null} The unit totals of the rounded value, or null when the arguments cannot be rounded.
|
|
254
|
+
*/
|
|
255
|
+
function evaluateRound (argumentList) {
|
|
256
|
+
let strategyName = 'nearest';
|
|
257
|
+
let valueArguments = argumentList;
|
|
258
|
+
if (argumentList.length === 3) {
|
|
259
|
+
strategyName = argumentList[0].keyword;
|
|
260
|
+
valueArguments = argumentList.slice(1);
|
|
261
|
+
}
|
|
262
|
+
const roundIntervals = ROUNDING_STRATEGIES.get(strategyName);
|
|
263
|
+
const division = readDivisionArguments(valueArguments);
|
|
264
|
+
if (!roundIntervals || !division) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const { dividend, divisor, unit } = division;
|
|
268
|
+
return createUnitTotals(roundIntervals(dividend / divisor) * divisor, unit);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* How each math function resolves the arguments it is called with. The
|
|
273
|
+
* functions listed here are the ones whose arguments are arithmetic, so that
|
|
274
|
+
* `*` and `/` between them are operators rather than characters of some other
|
|
275
|
+
* syntax.
|
|
276
|
+
*
|
|
277
|
+
* @type {Map<string, function(Array): (Map|null)>}
|
|
278
|
+
*/
|
|
279
|
+
const MATH_FUNCTION_EVALUATORS = new Map([
|
|
280
|
+
['abs', evaluateAbsolute],
|
|
281
|
+
['calc', evaluateCalculation],
|
|
282
|
+
['clamp', evaluateClamp],
|
|
283
|
+
['hypot', evaluateHypotenuse],
|
|
284
|
+
['max', evaluateMaximum],
|
|
285
|
+
['min', evaluateMinimum],
|
|
286
|
+
['mod', evaluateModulus],
|
|
287
|
+
['rem', evaluateRemainder],
|
|
288
|
+
['round', evaluateRound],
|
|
289
|
+
['sign', evaluateSign]
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The names of the functions whose arguments are a math expression.
|
|
294
|
+
*
|
|
295
|
+
* @type {Set<string>}
|
|
296
|
+
*/
|
|
297
|
+
const MATH_FUNCTION_NAMES = new Set(MATH_FUNCTION_EVALUATORS.keys());
|
|
298
|
+
|
|
299
|
+
export {
|
|
300
|
+
MATH_FUNCTION_EVALUATORS,
|
|
301
|
+
MATH_FUNCTION_NAMES
|
|
302
|
+
};
|