postcss-calc 11.0.3 → 11.1.1
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 +70 -8
- package/package.json +10 -6
- package/src/index.js +37 -163
- package/src/lib/node.js +5 -5
- package/src/lib/parser.js +40 -33
- package/src/lib/serialize.js +73 -33
- package/src/lib/simplify/bucket.js +1 -1
- package/src/lib/simplify/call.js +30 -2
- package/src/lib/simplify/clamp.js +25 -3
- package/src/lib/simplify/fold.js +14 -1
- package/src/lib/simplify/hypot.js +3 -3
- package/src/lib/simplify/min-max.js +3 -3
- package/src/lib/simplify/mod-rem.js +3 -3
- package/src/lib/simplify/round.js +4 -4
- package/src/lib/tokenizer.js +42 -30
- package/src/reduce.js +176 -0
- package/types/index.d.ts +0 -15
- package/types/lib/parser.d.ts +16 -0
- package/types/lib/serialize.d.ts +4 -0
- package/types/lib/simplify/call.d.ts +11 -1
- package/types/lib/simplify/fold.d.ts +14 -1
- package/types/lib/tokenizer.d.ts +3 -1
- package/types/reduce.d.ts +41 -0
- package/types/lib/type.d.ts +0 -23
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ statement is left as is, to fallback to the [W3C calc() implementation].
|
|
|
13
13
|
npm install postcss-calc
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## PostCSS usage
|
|
17
17
|
|
|
18
18
|
```js
|
|
19
19
|
// dependencies
|
|
@@ -52,7 +52,63 @@ h1 {
|
|
|
52
52
|
|
|
53
53
|
Checkout [tests] for more examples.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
## Use the reducer without PostCSS
|
|
56
|
+
|
|
57
|
+
For a single CSS component-value string, import the dedicated reducer entry
|
|
58
|
+
point. It reduces `calc()` and the supported CSS math functions it finds while
|
|
59
|
+
leaving all other text untouched.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import reduceCalc from 'postcss-calc/reduce';
|
|
63
|
+
|
|
64
|
+
reduceCalc('calc(1in + 10px)');
|
|
65
|
+
// => '1.10417in'
|
|
66
|
+
|
|
67
|
+
reduceCalc('min(50px, calc(2 * 40px))');
|
|
68
|
+
// => '50px'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It accepts `precision`, `unwrapSingleNegativeNumber`, `warnWhenCannotResolve`, `onParseError`,
|
|
72
|
+
and `onWarn`:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const result = reduceCalc('calc(100% + var(--gap))', {
|
|
76
|
+
precision: false,
|
|
77
|
+
warnWhenCannotResolve: true,
|
|
78
|
+
onWarn: console.warn,
|
|
79
|
+
onParseError(error, input) {
|
|
80
|
+
console.error(`Invalid calculation: ${input}`, error);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Unlike the PostCSS plugin, the standalone reducer does not show warnings
|
|
86
|
+
by default; provide `onParseError` and/or `onWarn` if you want diagnostics.
|
|
87
|
+
|
|
88
|
+
### Standalone reducer options
|
|
89
|
+
|
|
90
|
+
#### `unwrapSingleNegativeNumber` (default: `false`)
|
|
91
|
+
|
|
92
|
+
Controls whether a finite negative result is serialized as a bare value or
|
|
93
|
+
wrapped in `calc()`. Keep the default when reducing declaration values; set it
|
|
94
|
+
to `true` when the surrounding CSS context requires a bare negative value, such
|
|
95
|
+
as a selector:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
reduceCalc('calc(5px - 10px)');
|
|
99
|
+
// => 'calc(-5px)'
|
|
100
|
+
|
|
101
|
+
reduceCalc('calc(5px - 10px)', { unwrapNegativeNumbers: true });
|
|
102
|
+
// => '-5px'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### PostCSS plugin options
|
|
106
|
+
|
|
107
|
+
These options apply when using the PostCSS plugin:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
postcss().use(calc({ precision: 10 }));
|
|
111
|
+
```
|
|
56
112
|
|
|
57
113
|
#### `precision` (default: `5`)
|
|
58
114
|
|
|
@@ -107,7 +163,11 @@ With `mediaQueries: true`, this becomes:
|
|
|
107
163
|
|
|
108
164
|
#### `selectors` (default: `false`)
|
|
109
165
|
|
|
110
|
-
|
|
166
|
+
Reduces `calc()` functions found in selectors. Selectors do not accept
|
|
167
|
+
`calc()` functions, so the plugin replaces them with their reduced values.
|
|
168
|
+
Finite negative results are serialized as bare values because a selector cannot
|
|
169
|
+
contain a `calc()` function; the plugin enables `unwrapSingleNegativeNumber` automatically
|
|
170
|
+
for selectors.
|
|
111
171
|
|
|
112
172
|
```js
|
|
113
173
|
var out = postcss()
|
|
@@ -131,11 +191,13 @@ Callback invoked when a `calc()` body fails to parse or simplify. Matches
|
|
|
131
191
|
[`@csstools/css-calc`][csstools-css-calc]'s shape:
|
|
132
192
|
|
|
133
193
|
```js
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
194
|
+
postcss().use(
|
|
195
|
+
calc({
|
|
196
|
+
onParseError: (err, input) => {
|
|
197
|
+
throw err; // or log, route to a different channel, etc.
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
);
|
|
139
201
|
```
|
|
140
202
|
|
|
141
203
|
When omitted, errors are reported via PostCSS `result.warn()` so the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
".": {
|
|
20
20
|
"types": "./types/index.d.ts",
|
|
21
21
|
"default": "./src/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./reduce": {
|
|
24
|
+
"types": "./types/reduce.d.ts",
|
|
25
|
+
"default": "./src/reduce.js"
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
"files": [
|
|
@@ -40,18 +44,18 @@
|
|
|
40
44
|
"devDependencies": {
|
|
41
45
|
"@csstools/css-calc": "^3.3.0",
|
|
42
46
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
43
|
-
"@types/node": "^26.4.
|
|
47
|
+
"@types/node": "^26.4.1",
|
|
44
48
|
"fast-check": "^4.9.0",
|
|
45
|
-
"oxfmt": "^0.
|
|
46
|
-
"oxlint": "^1.
|
|
47
|
-
"postcss": "^8.5.
|
|
49
|
+
"oxfmt": "^0.66.0",
|
|
50
|
+
"oxlint": "^1.81.0",
|
|
51
|
+
"postcss": "^8.5.28",
|
|
48
52
|
"typescript": "~7.0.2"
|
|
49
53
|
},
|
|
50
54
|
"dependencies": {
|
|
51
55
|
"@csstools/css-tokenizer": "^4.0.0"
|
|
52
56
|
},
|
|
53
57
|
"peerDependencies": {
|
|
54
|
-
"postcss": "^8.5.
|
|
58
|
+
"postcss": "^8.5.28"
|
|
55
59
|
},
|
|
56
60
|
"scripts": {
|
|
57
61
|
"lint": "oxlint . && tsc && oxfmt --check",
|
package/src/index.js
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
// PostCSS adapter
|
|
2
|
-
|
|
3
|
-
// → serialize, and writes the result back.
|
|
4
|
-
import {
|
|
5
|
-
tokenize as cssTokenize,
|
|
6
|
-
TokenType as CssType,
|
|
7
|
-
} from '@csstools/css-tokenizer';
|
|
8
|
-
import { tokenizeTokens } from './lib/tokenizer.js';
|
|
9
|
-
import { parse } from './lib/parser.js';
|
|
10
|
-
import { simplify } from './lib/simplify.js';
|
|
11
|
-
import { isSupportedMathFunction } from './lib/simplify/call.js';
|
|
12
|
-
import { serialize } from './lib/serialize.js';
|
|
13
|
-
|
|
14
|
-
const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
15
|
-
|
|
16
|
-
const BLOCK_CLOSE = new Map([
|
|
17
|
-
[CssType.OpenParen, CssType.CloseParen],
|
|
18
|
-
[CssType.OpenSquare, CssType.CloseSquare],
|
|
19
|
-
[CssType.OpenCurly, CssType.CloseCurly],
|
|
20
|
-
]);
|
|
1
|
+
// PostCSS adapter over the standalone component-value reducer.
|
|
2
|
+
import reduceCalc, { hasPotentialMathFunction } from './reduce.js';
|
|
21
3
|
|
|
22
4
|
/**
|
|
23
5
|
* @typedef {object} PostCssCalcOptions
|
|
@@ -31,144 +13,7 @@ const BLOCK_CLOSE = new Map([
|
|
|
31
13
|
/** @typedef {Required<Omit<PostCssCalcOptions, 'onParseError'>> & Pick<PostCssCalcOptions, 'onParseError'>} ResolvedOptions */
|
|
32
14
|
|
|
33
15
|
/**
|
|
34
|
-
*
|
|
35
|
-
* `value` is the original full property text, used only for the
|
|
36
|
-
* warnWhenCannotResolve message.
|
|
37
|
-
*
|
|
38
|
-
* @typedef {object} TransformContext
|
|
39
|
-
* @property {ResolvedOptions} options
|
|
40
|
-
* @property {import('postcss').Result} result
|
|
41
|
-
* @property {import('postcss').ChildNode} item
|
|
42
|
-
* @property {string} value
|
|
43
|
-
* @property {import('@csstools/css-tokenizer').CSSToken[]} tokens
|
|
44
|
-
* @property {Replacement[]} replacements
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @typedef {object} Replacement
|
|
49
|
-
* @property {number} start
|
|
50
|
-
* @property {number} end
|
|
51
|
-
* @property {import('./lib/node.js').Node} node
|
|
52
|
-
* @property {string} calcName
|
|
53
|
-
* @property {string} matchedName
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Walk one component-value level. Unsupported functions and simple blocks are
|
|
58
|
-
* traversed, while a supported function is treated as one opaque calculation
|
|
59
|
-
* even when parsing it fails. A missing closer consumes through EOF, matching
|
|
60
|
-
* CSS component-value parsing's error recovery.
|
|
61
|
-
*
|
|
62
|
-
* @param {number} start
|
|
63
|
-
* @param {import('@csstools/css-tokenizer').TokenType | undefined} expectedClose
|
|
64
|
-
* @param {TransformContext} ctx
|
|
65
|
-
* @param {boolean} transform
|
|
66
|
-
* @return {number} Index of the matching closer, or the EOF token.
|
|
67
|
-
*/
|
|
68
|
-
function walkTokens(start, expectedClose, ctx, transform) {
|
|
69
|
-
for (let i = start; i < ctx.tokens.length; i++) {
|
|
70
|
-
const token = ctx.tokens[i];
|
|
71
|
-
if (token[0] === CssType.EOF || token[0] === expectedClose) {
|
|
72
|
-
return i;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const blockClose = BLOCK_CLOSE.get(token[0]);
|
|
76
|
-
if (blockClose) {
|
|
77
|
-
i = walkTokens(i + 1, blockClose, ctx, transform);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (token[0] !== CssType.Function) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const name = token[4].value;
|
|
86
|
-
const isCalc = MATCH_CALC.test(name);
|
|
87
|
-
const isMath = !isCalc && isSupportedMathFunction(name);
|
|
88
|
-
if (!transform || (!isCalc && !isMath)) {
|
|
89
|
-
i = walkTokens(i + 1, CssType.CloseParen, ctx, transform);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Locate the complete outer function without transforming its children.
|
|
94
|
-
const close = walkTokens(i + 1, CssType.CloseParen, ctx, false);
|
|
95
|
-
const closed = ctx.tokens[close][0] === CssType.CloseParen;
|
|
96
|
-
const end = closed ? ctx.tokens[close][3] + 1 : ctx.value.length;
|
|
97
|
-
const sliceStart = isCalc ? i + 1 : i;
|
|
98
|
-
const sliceEnd = closed ? close + (isCalc ? 0 : 1) : close;
|
|
99
|
-
const inputStart = isCalc ? token[3] + 1 : token[2];
|
|
100
|
-
const inputEnd = closed && isCalc ? ctx.tokens[close][2] : end;
|
|
101
|
-
const contents = ctx.value.slice(inputStart, inputEnd);
|
|
102
|
-
try {
|
|
103
|
-
const node = simplify(
|
|
104
|
-
parse(tokenizeTokens(ctx.tokens.slice(sliceStart, sliceEnd), end))
|
|
105
|
-
);
|
|
106
|
-
ctx.replacements.push({
|
|
107
|
-
start: token[2],
|
|
108
|
-
end,
|
|
109
|
-
node,
|
|
110
|
-
calcName: isCalc ? name : 'calc',
|
|
111
|
-
matchedName: name,
|
|
112
|
-
});
|
|
113
|
-
} catch (error) {
|
|
114
|
-
const err = error instanceof Error ? error : new Error('Error');
|
|
115
|
-
if (ctx.options.onParseError) {
|
|
116
|
-
ctx.options.onParseError(err, contents);
|
|
117
|
-
} else {
|
|
118
|
-
ctx.result.warn(err.message, { node: ctx.item });
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
i = close;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return ctx.tokens.length - 1;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @param {string} value
|
|
129
|
-
* @param {ResolvedOptions} options
|
|
130
|
-
* @param {import('postcss').Result} result
|
|
131
|
-
* @param {import('postcss').ChildNode} item
|
|
132
|
-
* @return {string}
|
|
133
|
-
*/
|
|
134
|
-
function transformValue(value, options, result, item) {
|
|
135
|
-
const tokens = cssTokenize({ css: value });
|
|
136
|
-
/** @type {Replacement[]} */
|
|
137
|
-
const replacements = [];
|
|
138
|
-
const ctx = { options, result, item, value, tokens, replacements };
|
|
139
|
-
walkTokens(0, undefined, ctx, true);
|
|
140
|
-
|
|
141
|
-
/** @type {(Replacement & {text: string})[]} */
|
|
142
|
-
const serialized = replacements.map((replacement) => {
|
|
143
|
-
const text = serialize(replacement.node, {
|
|
144
|
-
precision: options.precision,
|
|
145
|
-
calcName: replacement.calcName,
|
|
146
|
-
});
|
|
147
|
-
if (
|
|
148
|
-
options.warnWhenCannotResolve &&
|
|
149
|
-
text.startsWith(`${replacement.matchedName}(`)
|
|
150
|
-
) {
|
|
151
|
-
result.warn('Could not reduce expression: ' + value, {
|
|
152
|
-
plugin: 'postcss-calc',
|
|
153
|
-
node: item,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
return { ...replacement, text };
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
let output = value;
|
|
160
|
-
for (let i = serialized.length - 1; i >= 0; i--) {
|
|
161
|
-
const replacement = serialized[i];
|
|
162
|
-
output =
|
|
163
|
-
output.slice(0, replacement.start) +
|
|
164
|
-
replacement.text +
|
|
165
|
-
output.slice(replacement.end);
|
|
166
|
-
}
|
|
167
|
-
return output;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Runs `transformValue` over one text property of a decl/atrule/rule node
|
|
16
|
+
* Runs `reduceCalc` over one text property of a decl/atrule/rule node
|
|
172
17
|
* and updates it in place.
|
|
173
18
|
* `setProp` closes over the property name and the concrete node type at
|
|
174
19
|
* each call site, since `Declaration`/`AtRule`/`Rule` don't share a typed
|
|
@@ -179,10 +24,36 @@ function transformValue(value, options, result, item) {
|
|
|
179
24
|
* @param {(target: import('postcss').ChildNode, value: string) => void} setProp
|
|
180
25
|
* @param {ResolvedOptions} options
|
|
181
26
|
* @param {import('postcss').Result} result
|
|
27
|
+
* @param {boolean} unwrapSingleNegativeNumber
|
|
182
28
|
* @return {void}
|
|
183
29
|
*/
|
|
184
|
-
function applyTransform(
|
|
185
|
-
|
|
30
|
+
function applyTransform(
|
|
31
|
+
node,
|
|
32
|
+
current,
|
|
33
|
+
setProp,
|
|
34
|
+
options,
|
|
35
|
+
result,
|
|
36
|
+
unwrapSingleNegativeNumber
|
|
37
|
+
) {
|
|
38
|
+
if (!hasPotentialMathFunction(current)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const transformed = reduceCalc(current, {
|
|
42
|
+
precision: options.precision,
|
|
43
|
+
warnWhenCannotResolve: options.warnWhenCannotResolve,
|
|
44
|
+
onParseError:
|
|
45
|
+
options.onParseError ??
|
|
46
|
+
((error) => {
|
|
47
|
+
result.warn(error.message, { node });
|
|
48
|
+
}),
|
|
49
|
+
onWarn: (message) => {
|
|
50
|
+
result.warn(message, { plugin: 'postcss-calc', node });
|
|
51
|
+
},
|
|
52
|
+
unwrapSingleNegativeNumber,
|
|
53
|
+
});
|
|
54
|
+
if (transformed !== current) {
|
|
55
|
+
setProp(node, transformed);
|
|
56
|
+
}
|
|
186
57
|
}
|
|
187
58
|
|
|
188
59
|
/**
|
|
@@ -215,7 +86,8 @@ function pluginCreator(opts) {
|
|
|
215
86
|
/** @type {import('postcss').Declaration} */ (n).value = v;
|
|
216
87
|
},
|
|
217
88
|
options,
|
|
218
|
-
result
|
|
89
|
+
result,
|
|
90
|
+
false
|
|
219
91
|
);
|
|
220
92
|
}
|
|
221
93
|
if (node.type === 'atrule' && options.mediaQueries) {
|
|
@@ -226,7 +98,8 @@ function pluginCreator(opts) {
|
|
|
226
98
|
/** @type {import('postcss').AtRule} */ (n).params = v;
|
|
227
99
|
},
|
|
228
100
|
options,
|
|
229
|
-
result
|
|
101
|
+
result,
|
|
102
|
+
false
|
|
230
103
|
);
|
|
231
104
|
}
|
|
232
105
|
if (node.type === 'rule' && options.selectors) {
|
|
@@ -239,7 +112,8 @@ function pluginCreator(opts) {
|
|
|
239
112
|
/** @type {import('postcss').Rule} */ (n).selector = v;
|
|
240
113
|
},
|
|
241
114
|
options,
|
|
242
|
-
result
|
|
115
|
+
result,
|
|
116
|
+
true
|
|
243
117
|
);
|
|
244
118
|
}
|
|
245
119
|
});
|
package/src/lib/node.js
CHANGED
|
@@ -71,7 +71,7 @@ function mkSum(rawTerms) {
|
|
|
71
71
|
pushSumTerm(flat, t);
|
|
72
72
|
}
|
|
73
73
|
if (flat.length === 0) {
|
|
74
|
-
return
|
|
74
|
+
return num(0);
|
|
75
75
|
}
|
|
76
76
|
if (flat.length === 1 && flat[0].sign === 1) {
|
|
77
77
|
return flat[0].node;
|
|
@@ -101,10 +101,10 @@ function pushSumTerm(out, term) {
|
|
|
101
101
|
// canonical-form rule downstream code relies on.
|
|
102
102
|
if (sign === -1) {
|
|
103
103
|
if (node.type === 'Num') {
|
|
104
|
-
node =
|
|
104
|
+
node = num(-node.value);
|
|
105
105
|
sign = 1;
|
|
106
106
|
} else if (node.type === 'Dim') {
|
|
107
|
-
node =
|
|
107
|
+
node = dim(-node.value, node.unit);
|
|
108
108
|
sign = 1;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -128,7 +128,7 @@ function mkProduct(rawFactors) {
|
|
|
128
128
|
pushProductFactor(flat, f);
|
|
129
129
|
}
|
|
130
130
|
if (flat.length === 0) {
|
|
131
|
-
return
|
|
131
|
+
return num(1);
|
|
132
132
|
}
|
|
133
133
|
if (flat.length === 1 && flat[0].exponent === 1) {
|
|
134
134
|
return flat[0].node;
|
|
@@ -184,7 +184,7 @@ function negate(node) {
|
|
|
184
184
|
}
|
|
185
185
|
// Opaque (Ident, Call, Product): wrap as a single negative-sign term —
|
|
186
186
|
// the only case where sign=-1 remains on a SumTerm.
|
|
187
|
-
return
|
|
187
|
+
return mkSum([{ sign: -1, node }]);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
export { num, dim, ident, call, mkSum, mkProduct, negate };
|
package/src/lib/parser.js
CHANGED
|
@@ -10,15 +10,6 @@ import { mkSum, mkProduct, negate, num, dim, ident, call } from './node.js';
|
|
|
10
10
|
* @typedef {(p: Parser, token: Token) => Node} PrefixParselet
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* @param {Token} t
|
|
15
|
-
* @param {string} value
|
|
16
|
-
* @return {boolean}
|
|
17
|
-
*/
|
|
18
|
-
function isPunct(t, value) {
|
|
19
|
-
return t.type === 'punct' && t.value === value;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
/**
|
|
23
14
|
* §10.9 — case-insensitive except for NaN.
|
|
24
15
|
* @param {string} name
|
|
@@ -81,6 +72,39 @@ class Parser {
|
|
|
81
72
|
return t;
|
|
82
73
|
}
|
|
83
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} value
|
|
77
|
+
* @param {string} [value2]
|
|
78
|
+
* @return {boolean}
|
|
79
|
+
*/
|
|
80
|
+
isPunct(value, value2) {
|
|
81
|
+
const t = this.peek();
|
|
82
|
+
return (
|
|
83
|
+
t.type === 'punct' &&
|
|
84
|
+
(t.value === value || (value2 !== undefined && t.value === value2))
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} value
|
|
90
|
+
* @return {boolean}
|
|
91
|
+
*/
|
|
92
|
+
matchPunct(value) {
|
|
93
|
+
if (this.isPunct(value)) {
|
|
94
|
+
this.next();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} value
|
|
102
|
+
* @return {Token}
|
|
103
|
+
*/
|
|
104
|
+
expectPunct(value) {
|
|
105
|
+
return this.expect('punct', value);
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
/**
|
|
85
109
|
* @param {number} [minBp]
|
|
86
110
|
* @return {Node}
|
|
@@ -111,14 +135,7 @@ class Parser {
|
|
|
111
135
|
sign: /** @type {1 | -1} */ (token.value === '+' ? 1 : -1),
|
|
112
136
|
node: this.parseExpr(ADD_BP + 1),
|
|
113
137
|
});
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
next.type !== 'punct' ||
|
|
117
|
-
(next.value !== '+' && next.value !== '-')
|
|
118
|
-
) {
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
} while (ADD_BP >= minBp);
|
|
138
|
+
} while (this.isPunct('+', '-'));
|
|
122
139
|
left = mkSum(terms);
|
|
123
140
|
continue;
|
|
124
141
|
}
|
|
@@ -132,14 +149,7 @@ class Parser {
|
|
|
132
149
|
exponent: /** @type {1 | -1} */ (token.value === '*' ? 1 : -1),
|
|
133
150
|
node: this.parseExpr(MUL_BP + 1),
|
|
134
151
|
});
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
next.type !== 'punct' ||
|
|
138
|
-
(next.value !== '*' && next.value !== '/')
|
|
139
|
-
) {
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
} while (MUL_BP >= minBp);
|
|
152
|
+
} while (this.isPunct('*', '/'));
|
|
143
153
|
left = mkProduct(factors);
|
|
144
154
|
continue;
|
|
145
155
|
}
|
|
@@ -255,22 +265,19 @@ const PREFIX = {
|
|
|
255
265
|
),
|
|
256
266
|
|
|
257
267
|
ident: (p, t) => {
|
|
258
|
-
|
|
259
|
-
if (nxt.type === 'punct' && nxt.value === '(') {
|
|
260
|
-
p.next();
|
|
268
|
+
if (p.matchPunct('(')) {
|
|
261
269
|
if (OPAQUE_ARG_FUNCTIONS.has(t.value.toLowerCase())) {
|
|
262
270
|
return parseOpaqueCall(p, t.value);
|
|
263
271
|
}
|
|
264
272
|
/** @type {Node[]} */
|
|
265
273
|
const args = [];
|
|
266
|
-
if (!
|
|
274
|
+
if (!p.isPunct(')')) {
|
|
267
275
|
args.push(p.parseExpr(0));
|
|
268
|
-
while (
|
|
269
|
-
p.next();
|
|
276
|
+
while (p.matchPunct(',')) {
|
|
270
277
|
args.push(p.parseExpr(0));
|
|
271
278
|
}
|
|
272
279
|
}
|
|
273
|
-
p.
|
|
280
|
+
p.expectPunct(')');
|
|
274
281
|
return call(t.value, args);
|
|
275
282
|
}
|
|
276
283
|
const kw = foldCalcKeyword(t.value);
|
|
@@ -282,7 +289,7 @@ const PREFIX = {
|
|
|
282
289
|
|
|
283
290
|
'(': (p) => {
|
|
284
291
|
const e = p.parseExpr(0);
|
|
285
|
-
p.
|
|
292
|
+
p.expectPunct(')');
|
|
286
293
|
return e.type === 'Sum' ? { ...e, grouped: true } : e;
|
|
287
294
|
},
|
|
288
295
|
|