postcss-calc 11.2.1 → 11.2.3
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/LICENSE +0 -0
- package/README.md +47 -35
- package/package.json +15 -15
- package/src/index.js +2 -1
- package/src/lib/analyze.js +0 -6
- package/src/lib/compile.js +1 -1
- package/src/lib/function-analyzers.js +119 -0
- package/src/lib/functions.js +25 -175
- package/src/lib/node.js +61 -4
- package/src/lib/parser/opaque.js +152 -0
- package/src/lib/parser/tokens.js +242 -0
- package/src/lib/parser.js +32 -377
- package/src/lib/serialize/expression.js +329 -0
- package/src/lib/serialize/precedence.js +47 -0
- package/src/lib/serialize/precision.js +244 -0
- package/src/lib/serialize.js +24 -561
- package/src/lib/simplify/bucket.js +22 -5
- package/src/lib/simplify/cancel.js +35 -11
- package/src/lib/simplify/clamp.js +2 -2
- package/src/lib/simplify/exact.js +18 -0
- package/src/lib/simplify/fold.js +15 -1
- package/src/lib/simplify/min-max.js +2 -2
- package/src/lib/simplify/product-barrier.js +55 -0
- package/src/lib/simplify/product-quotient.js +141 -0
- package/src/lib/simplify/product.js +92 -48
- package/src/lib/simplify/sum.js +6 -2
- package/src/lib/simplify.js +22 -5
- package/src/lib/types.js +94 -0
- package/src/reduce.js +2 -1
- package/types/lib/function-analyzers.d.ts +27 -0
- package/types/lib/functions.d.ts +3 -28
- package/types/lib/node.d.ts +17 -2
- package/types/lib/parser/opaque.d.ts +12 -0
- package/types/lib/parser/tokens.d.ts +83 -0
- package/types/lib/parser.d.ts +4 -44
- package/types/lib/serialize/expression.d.ts +55 -0
- package/types/lib/serialize/precedence.d.ts +18 -0
- package/types/lib/serialize/precision.d.ts +102 -0
- package/types/lib/simplify/bucket.d.ts +4 -1
- package/types/lib/simplify/cancel.d.ts +11 -7
- package/types/lib/simplify/exact.d.ts +9 -0
- package/types/lib/simplify/fold.d.ts +14 -1
- package/types/lib/simplify/product-barrier.d.ts +26 -0
- package/types/lib/simplify/product-quotient.d.ts +48 -0
- package/types/lib/simplify/product.d.ts +3 -7
- package/types/lib/simplify/sum.d.ts +2 -1
- package/types/lib/simplify.d.ts +3 -8
- package/types/lib/types.d.ts +48 -0
- package/types/lib/calculation-type.d.ts +0 -18
package/src/lib/node.js
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
// - No ungrouped Sum directly contains another Sum (flattened on
|
|
9
9
|
// construction). A grouped Sum is retained so a later negative sign
|
|
10
10
|
// cannot be distributed across opaque terms.
|
|
11
|
-
// - No Product directly contains another Product (flattened).
|
|
11
|
+
// - No Product directly contains another ungrouped Product (flattened). A
|
|
12
|
+
// grouped Product is retained: it is a parenthesized group around a
|
|
13
|
+
// substitution function (`var()`, `env()`, ...), whose tokens must not
|
|
14
|
+
// merge with the surrounding factors. A grouped Product may hold a single
|
|
15
|
+
// factor.
|
|
12
16
|
// - A Sum/Product with one positive element collapses to that element.
|
|
13
17
|
// - A Sum/Product with no elements collapses to Num(0) / Num(1).
|
|
14
18
|
// - Positive zero-valued Nums are dropped from all-number sums. They are
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
* @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
|
|
29
33
|
* @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
|
|
30
34
|
* @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
|
|
31
|
-
* @typedef {{type: 'Product', factors: ProductFactor[]}} Product
|
|
35
|
+
* @typedef {{type: 'Product', factors: ProductFactor[], grouped?: boolean}} Product
|
|
32
36
|
* @typedef {Num | Dim | Ident | Call | OpaqueCall | Sum | Product} Node
|
|
33
37
|
*/
|
|
34
38
|
|
|
@@ -185,7 +189,7 @@ function mkProduct(rawFactors) {
|
|
|
185
189
|
*/
|
|
186
190
|
function pushProductFactor(out, f) {
|
|
187
191
|
const n = f.node;
|
|
188
|
-
if (n.type === 'Product') {
|
|
192
|
+
if (n.type === 'Product' && !n.grouped) {
|
|
189
193
|
for (const inner of n.factors) {
|
|
190
194
|
out.push({
|
|
191
195
|
exponent: /** @type {1 | -1} */ (f.exponent * inner.exponent),
|
|
@@ -201,6 +205,48 @@ function pushProductFactor(out, f) {
|
|
|
201
205
|
out.push(f);
|
|
202
206
|
}
|
|
203
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Mark a node as a source parenthesized group. A Sum or Product keeps its
|
|
210
|
+
* structure; any other node becomes a single-factor grouped Product.
|
|
211
|
+
* @param {Node} node
|
|
212
|
+
* @return {Node}
|
|
213
|
+
*/
|
|
214
|
+
function mkGroup(node) {
|
|
215
|
+
if (node.type === 'Sum' || node.type === 'Product') {
|
|
216
|
+
return { ...node, grouped: true };
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
type: 'Product',
|
|
220
|
+
factors: [{ exponent: 1, node }],
|
|
221
|
+
grouped: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Whether a node is a substitution function call (`var()`, `env()`, ...), or
|
|
227
|
+
* an ungrouped Product that directly contains one. Nested groups are not
|
|
228
|
+
* inspected: they already bound their own tokens.
|
|
229
|
+
* @param {Node} node
|
|
230
|
+
* @return {boolean}
|
|
231
|
+
*/
|
|
232
|
+
function isSubstitutionNode(node) {
|
|
233
|
+
if (node.type === 'OpaqueCall') return true;
|
|
234
|
+
if (node.type === 'Product' && !node.grouped) {
|
|
235
|
+
return node.factors.some((factor) => isSubstitutionNode(factor.node));
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Group a parenthesized expression when its tokens must stay bound: a sum, or
|
|
242
|
+
* a product around a substitution function.
|
|
243
|
+
* @param {Node} node
|
|
244
|
+
* @return {Node}
|
|
245
|
+
*/
|
|
246
|
+
function groupSubstitution(node) {
|
|
247
|
+
return node.type === 'Sum' || isSubstitutionNode(node) ? mkGroup(node) : node;
|
|
248
|
+
}
|
|
249
|
+
|
|
204
250
|
/**
|
|
205
251
|
* Negate any node, preserving canonical form.
|
|
206
252
|
* @param {Node} node
|
|
@@ -233,4 +279,15 @@ function negate(node) {
|
|
|
233
279
|
return mkSum([{ sign: -1, node }]);
|
|
234
280
|
}
|
|
235
281
|
|
|
236
|
-
export {
|
|
282
|
+
export {
|
|
283
|
+
num,
|
|
284
|
+
dim,
|
|
285
|
+
ident,
|
|
286
|
+
call,
|
|
287
|
+
opaqueCall,
|
|
288
|
+
mkSum,
|
|
289
|
+
mkProduct,
|
|
290
|
+
mkGroup,
|
|
291
|
+
groupSubstitution,
|
|
292
|
+
negate,
|
|
293
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Opaque function parsing: `var()`, unknown functions and their component trees.
|
|
2
|
+
import { TokenType as CssType } from '@csstools/css-tokenizer';
|
|
3
|
+
import { ident, opaqueCall } from '../node.js';
|
|
4
|
+
import {
|
|
5
|
+
isCalculationFunction,
|
|
6
|
+
isSupportedMathFunction,
|
|
7
|
+
} from '../functions.js';
|
|
8
|
+
import { assertDepth } from '../limits.js';
|
|
9
|
+
import { eofPositionAt, sourceSpelling } from './tokens.js';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('@csstools/css-tokenizer').CSSToken} CSSToken */
|
|
12
|
+
/** @typedef {import('../node.js').Node} Node */
|
|
13
|
+
/** @typedef {import('../node.js').OpaqueComponent} OpaqueComponent */
|
|
14
|
+
/** @typedef {import('./tokens.js').ParseInput} ParseInput */
|
|
15
|
+
/** @typedef {import('./tokens.js').Cursor} Cursor */
|
|
16
|
+
/** @typedef {import('./tokens.js').FunctionToken} FunctionToken */
|
|
17
|
+
/** @typedef {(input: ParseInput, start: number, end: number) => Node} ParseRange */
|
|
18
|
+
|
|
19
|
+
/** @param {OpaqueComponent[]} target @param {OpaqueComponent} part */
|
|
20
|
+
function pushComponent(target, part) {
|
|
21
|
+
if (typeof part === 'string' && typeof target.at(-1) === 'string')
|
|
22
|
+
target[target.length - 1] += part;
|
|
23
|
+
else target.push(part);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @return {number} */
|
|
27
|
+
function closeOfCall(input, cursor, token, name) {
|
|
28
|
+
const close = input.index.closeOf(token.index, input.end);
|
|
29
|
+
if (close === -1)
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Unclosed ${name}( at position ${eofPositionAt(input, cursor.index)}`
|
|
32
|
+
);
|
|
33
|
+
return close;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @param {string} rawName @param {ParseRange} parseRange @return {Node} */
|
|
37
|
+
function parseOpaqueCall(input, cursor, token, name, rawName, parseRange) {
|
|
38
|
+
const close = closeOfCall(input, cursor, token, name);
|
|
39
|
+
cursor.skipTo(close + 1);
|
|
40
|
+
return opaqueCall(
|
|
41
|
+
name,
|
|
42
|
+
componentTree(input, token.index + 1, close, parseRange, []),
|
|
43
|
+
sourceSpelling(rawName, name)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @param {CSSToken[]} tokens @param {number} start @param {number} end */
|
|
48
|
+
function rawTokens(tokens, start, end) {
|
|
49
|
+
let raw = '';
|
|
50
|
+
for (let i = start; i < end; i++) raw += tokens[i][1];
|
|
51
|
+
return raw;
|
|
52
|
+
}
|
|
53
|
+
/** @param {CSSToken[]} tokens @param {number} start @param {number} end */
|
|
54
|
+
function customProperty(tokens, start, end) {
|
|
55
|
+
/** @type {CSSToken | null} */
|
|
56
|
+
let found = null;
|
|
57
|
+
let foundIndex = -1;
|
|
58
|
+
for (let i = start; i < end; i++) {
|
|
59
|
+
const type = tokens[i][0];
|
|
60
|
+
if (type === CssType.Whitespace || type === CssType.Comment) continue;
|
|
61
|
+
if (found !== null) return null;
|
|
62
|
+
found = tokens[i];
|
|
63
|
+
foundIndex = i;
|
|
64
|
+
}
|
|
65
|
+
if (!found || found[0] !== CssType.Ident) return null;
|
|
66
|
+
const decoded = found[4].value;
|
|
67
|
+
return decoded.startsWith('--') && decoded !== '--'
|
|
68
|
+
? { decoded, raw: found[1], index: foundIndex }
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Append the component tree of `[start, end)` to `root` and return it.
|
|
73
|
+
* Callers seed `root` so leading components need no copy or spread.
|
|
74
|
+
* @param {ParseInput} input @param {number} start @param {number} rangeEnd @param {ParseRange} parseRange @param {OpaqueComponent[]} root @return {OpaqueComponent[]}
|
|
75
|
+
*/
|
|
76
|
+
function componentTree(input, start, rangeEnd, parseRange, root) {
|
|
77
|
+
/** @type {OpaqueComponent[]} */ let tree = root;
|
|
78
|
+
let end = rangeEnd;
|
|
79
|
+
/** @type {{parent: OpaqueComponent[], tree: OpaqueComponent[], close: number, end: number}[]} */
|
|
80
|
+
const frames = [];
|
|
81
|
+
const { tokens } = input;
|
|
82
|
+
let i = start;
|
|
83
|
+
while (true) {
|
|
84
|
+
if (i >= end) {
|
|
85
|
+
if (frames.length === 0) break;
|
|
86
|
+
const frame =
|
|
87
|
+
/** @type {{parent: OpaqueComponent[], tree: OpaqueComponent[], close: number, end: number}} */ (
|
|
88
|
+
frames.pop()
|
|
89
|
+
);
|
|
90
|
+
tree = frame.parent;
|
|
91
|
+
pushComponent(tree, frame.tree);
|
|
92
|
+
pushComponent(tree, tokens[frame.close][1]);
|
|
93
|
+
end = frame.end;
|
|
94
|
+
i = frame.close + 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const token = tokens[i];
|
|
99
|
+
const close = input.index.closeOf(i, end);
|
|
100
|
+
if (close === -1) {
|
|
101
|
+
pushComponent(tree, token[1]);
|
|
102
|
+
i++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isMathFunction =
|
|
107
|
+
token[0] === CssType.Function &&
|
|
108
|
+
(isCalculationFunction(token[4].value) ||
|
|
109
|
+
isSupportedMathFunction(token[4].value));
|
|
110
|
+
if (isMathFunction) {
|
|
111
|
+
try {
|
|
112
|
+
pushComponent(tree, parseRange(input, i, close + 1));
|
|
113
|
+
} catch {
|
|
114
|
+
pushComponent(tree, rawTokens(tokens, i, close + 1));
|
|
115
|
+
}
|
|
116
|
+
i = close + 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
pushComponent(tree, token[1]);
|
|
121
|
+
assertDepth(frames.length + 1);
|
|
122
|
+
/** @type {OpaqueComponent[]} */
|
|
123
|
+
const child = [];
|
|
124
|
+
frames.push({ parent: tree, tree: child, close, end });
|
|
125
|
+
tree = child;
|
|
126
|
+
end = close;
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
return root;
|
|
130
|
+
}
|
|
131
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @param {string} rawName @param {ParseRange} parseRange @return {Node} */
|
|
132
|
+
function parseVar(input, cursor, token, name, rawName, parseRange) {
|
|
133
|
+
const { tokens } = input;
|
|
134
|
+
const start = token.index + 1;
|
|
135
|
+
const close = closeOfCall(input, cursor, token, name);
|
|
136
|
+
const comma = input.index.firstTopLevelComma(start, close);
|
|
137
|
+
const property = customProperty(tokens, start, comma === -1 ? close : comma);
|
|
138
|
+
if (!property)
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Invalid custom property in ${name}() at position ${tokens[start]?.[2] ?? eofPositionAt(input, cursor.index)}`
|
|
141
|
+
);
|
|
142
|
+
cursor.skipTo(close + 1);
|
|
143
|
+
/** @type {OpaqueComponent[]} */
|
|
144
|
+
const components = [
|
|
145
|
+
ident(property.decoded, sourceSpelling(property.raw, property.decoded)),
|
|
146
|
+
];
|
|
147
|
+
if (comma !== -1)
|
|
148
|
+
componentTree(input, property.index + 1, close, parseRange, components);
|
|
149
|
+
return opaqueCall(name, components, sourceSpelling(rawName, name));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export { parseOpaqueCall, parseVar };
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Token scanning and cursor navigation for the Pratt parser.
|
|
2
|
+
import { TokenType as CssType } from '@csstools/css-tokenizer';
|
|
3
|
+
import { CSS_NUMBER_PREFIX } from '../regex.js';
|
|
4
|
+
|
|
5
|
+
/** @typedef {import('@csstools/css-tokenizer').CSSToken} CSSToken */
|
|
6
|
+
/** @typedef {ReturnType<typeof import('../block-index.js').indexBlocks>} BlockIndex */
|
|
7
|
+
/** @typedef {{raw: string, pos: number, ws: boolean, index: number}} TokenBase */
|
|
8
|
+
/** @typedef {TokenBase & {type: 'number', value: number, signCharacter?: '+' | '-'}} NumberToken */
|
|
9
|
+
/** @typedef {TokenBase & {type: 'dimension', value: number, unit: string, rawUnit: string, signCharacter?: '+' | '-'}} DimensionToken */
|
|
10
|
+
/** @typedef {TokenBase & {type: 'ident', value: string}} IdentToken */
|
|
11
|
+
/** @typedef {TokenBase & {type: 'function', value: string}} FunctionToken */
|
|
12
|
+
/** @typedef {'(' | ')' | ',' | '+' | '-' | '*' | '/'} Punctuator */
|
|
13
|
+
/** @typedef {TokenBase & {type: 'punct', value: Punctuator}} PunctToken */
|
|
14
|
+
/** @typedef {TokenBase & {type: 'eof', value: '', raw: ''}} EofToken */
|
|
15
|
+
/** @typedef {NumberToken | DimensionToken | IdentToken | FunctionToken | PunctToken | EofToken} Token */
|
|
16
|
+
/**
|
|
17
|
+
* Immutable bounds and shared block index for one parse range.
|
|
18
|
+
* @typedef {Readonly<{tokens: CSSToken[], end: number, index: BlockIndex}>} ParseInput
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** @param {string} value @return {value is '+' | '-' | '*' | '/'} */
|
|
22
|
+
function isOperator(value) {
|
|
23
|
+
return value === '+' || value === '-' || value === '*' || value === '/';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {string} raw @param {string} decoded */
|
|
27
|
+
function sourceSpelling(raw, decoded) {
|
|
28
|
+
return raw === decoded ? undefined : raw;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mutable navigation state only. `index` is always the next native token
|
|
33
|
+
* position; trivia is intentionally left visible to `scanToken`.
|
|
34
|
+
*/
|
|
35
|
+
class Cursor {
|
|
36
|
+
/** @param {number} start */
|
|
37
|
+
constructor(start) {
|
|
38
|
+
/** @type {number} */
|
|
39
|
+
this.index = start;
|
|
40
|
+
/** @type {boolean} */
|
|
41
|
+
this.firstToken = true;
|
|
42
|
+
/** @type {Token | null} */
|
|
43
|
+
this.lookahead = null;
|
|
44
|
+
/** @type {number} */
|
|
45
|
+
this.lookaheadNextIndex = start;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {number} index @return {void} */
|
|
49
|
+
skipTo(index) {
|
|
50
|
+
this.index = index;
|
|
51
|
+
this.lookaheadNextIndex = index;
|
|
52
|
+
this.firstToken = false;
|
|
53
|
+
this.lookahead = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Scan one token without consuming it. `firstToken` supplies the virtual
|
|
59
|
+
* leading trivia at a bounded parse boundary; all later whitespace state is
|
|
60
|
+
* derived from the native tokens encountered in this scan.
|
|
61
|
+
*
|
|
62
|
+
* @param {ParseInput} input
|
|
63
|
+
* @param {Cursor} cursor
|
|
64
|
+
* @return {Token}
|
|
65
|
+
*/
|
|
66
|
+
function scanToken(input, cursor) {
|
|
67
|
+
let i = cursor.index;
|
|
68
|
+
let ws = cursor.firstToken;
|
|
69
|
+
while (i < input.end) {
|
|
70
|
+
const native = input.tokens[i];
|
|
71
|
+
if (native[0] === CssType.Whitespace || native[0] === CssType.Comment) {
|
|
72
|
+
ws = true;
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (native[0] === CssType.EOF) break;
|
|
77
|
+
const token = normalizeToken(native, i, ws);
|
|
78
|
+
cursor.lookahead = token;
|
|
79
|
+
cursor.lookaheadNextIndex = i + 1;
|
|
80
|
+
return token;
|
|
81
|
+
}
|
|
82
|
+
/** @type {EofToken} */
|
|
83
|
+
const token = {
|
|
84
|
+
type: 'eof',
|
|
85
|
+
value: '',
|
|
86
|
+
raw: '',
|
|
87
|
+
pos: eofPositionAt(input, i),
|
|
88
|
+
ws,
|
|
89
|
+
index: i,
|
|
90
|
+
};
|
|
91
|
+
cursor.lookahead = token;
|
|
92
|
+
// Native EOF is a real token and is consumed past its array index. When
|
|
93
|
+
// the bounded range ends before native EOF, this is a virtual EOF and must
|
|
94
|
+
// remain at the range boundary.
|
|
95
|
+
cursor.lookaheadNextIndex =
|
|
96
|
+
i < input.end && input.tokens[i][0] === CssType.EOF ? i + 1 : input.end;
|
|
97
|
+
return token;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @param {CSSToken} t @param {number} index @param {boolean} ws @return {Token} */
|
|
101
|
+
function normalizeToken(t, index, ws) {
|
|
102
|
+
const [type, raw, pos, , detail] = t;
|
|
103
|
+
switch (type) {
|
|
104
|
+
case CssType.Number:
|
|
105
|
+
return {
|
|
106
|
+
type: 'number',
|
|
107
|
+
value: detail.value,
|
|
108
|
+
raw,
|
|
109
|
+
pos,
|
|
110
|
+
ws,
|
|
111
|
+
index,
|
|
112
|
+
signCharacter: detail.signCharacter,
|
|
113
|
+
};
|
|
114
|
+
case CssType.Dimension: {
|
|
115
|
+
const match = CSS_NUMBER_PREFIX.exec(raw);
|
|
116
|
+
return {
|
|
117
|
+
type: 'dimension',
|
|
118
|
+
value: detail.value,
|
|
119
|
+
raw,
|
|
120
|
+
pos,
|
|
121
|
+
ws,
|
|
122
|
+
index,
|
|
123
|
+
unit: detail.unit,
|
|
124
|
+
rawUnit: match ? raw.slice(match[0].length) : detail.unit,
|
|
125
|
+
signCharacter: detail.signCharacter,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case CssType.Percentage:
|
|
129
|
+
return {
|
|
130
|
+
type: 'dimension',
|
|
131
|
+
value: detail.value,
|
|
132
|
+
raw,
|
|
133
|
+
pos,
|
|
134
|
+
ws,
|
|
135
|
+
index,
|
|
136
|
+
unit: '%',
|
|
137
|
+
rawUnit: '%',
|
|
138
|
+
signCharacter: detail.signCharacter,
|
|
139
|
+
};
|
|
140
|
+
case CssType.Ident:
|
|
141
|
+
case CssType.Function:
|
|
142
|
+
return {
|
|
143
|
+
type: type === CssType.Ident ? 'ident' : 'function',
|
|
144
|
+
value: detail.value,
|
|
145
|
+
raw,
|
|
146
|
+
pos,
|
|
147
|
+
ws,
|
|
148
|
+
index,
|
|
149
|
+
};
|
|
150
|
+
case CssType.OpenParen:
|
|
151
|
+
return { type: 'punct', value: '(', raw, pos, ws, index };
|
|
152
|
+
case CssType.CloseParen:
|
|
153
|
+
return { type: 'punct', value: ')', raw, pos, ws, index };
|
|
154
|
+
case CssType.Comma:
|
|
155
|
+
return { type: 'punct', value: ',', raw, pos, ws, index };
|
|
156
|
+
case CssType.Delim:
|
|
157
|
+
if (isOperator(detail.value))
|
|
158
|
+
return {
|
|
159
|
+
type: 'punct',
|
|
160
|
+
value: detail.value,
|
|
161
|
+
raw,
|
|
162
|
+
pos,
|
|
163
|
+
ws,
|
|
164
|
+
index,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Unexpected character "${raw[0] ?? ''}" at position ${pos}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @param {ParseInput} input @param {number} index @return {number} */
|
|
171
|
+
function eofPositionAt(input, index) {
|
|
172
|
+
if (index < input.end && input.tokens[index][0] !== CssType.EOF) {
|
|
173
|
+
return input.tokens[index][2];
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
input.end < input.tokens.length &&
|
|
177
|
+
input.tokens[input.end][0] !== CssType.EOF
|
|
178
|
+
) {
|
|
179
|
+
return input.tokens[input.end][2];
|
|
180
|
+
}
|
|
181
|
+
for (let i = Math.min(input.end, input.tokens.length) - 1; i >= 0; i--) {
|
|
182
|
+
const token = input.tokens[i];
|
|
183
|
+
if (token[0] !== CssType.EOF) return token[3] + 1;
|
|
184
|
+
}
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** @param {ParseInput} input @param {Cursor} cursor @return {Token} */
|
|
189
|
+
function peekToken(input, cursor) {
|
|
190
|
+
return cursor.lookahead ?? scanToken(input, cursor);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Consume the cached token and advance to its native next index. This is one
|
|
195
|
+
* of the only two operations allowed to advance `cursor.index`.
|
|
196
|
+
* @param {ParseInput} input
|
|
197
|
+
* @param {Cursor} cursor
|
|
198
|
+
* @return {Token}
|
|
199
|
+
*/
|
|
200
|
+
function takeToken(input, cursor) {
|
|
201
|
+
const token = peekToken(input, cursor);
|
|
202
|
+
cursor.index = cursor.lookaheadNextIndex;
|
|
203
|
+
cursor.firstToken = false;
|
|
204
|
+
cursor.lookahead = null;
|
|
205
|
+
return token;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {Punctuator} value @param {Punctuator} [value2] @return {boolean} */
|
|
209
|
+
function isPunct(input, cursor, value, value2) {
|
|
210
|
+
const t = peekToken(input, cursor);
|
|
211
|
+
return (
|
|
212
|
+
t.type === 'punct' &&
|
|
213
|
+
(t.value === value || (value2 !== undefined && t.value === value2))
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {Punctuator} value @return {boolean} */
|
|
218
|
+
function matchPunct(input, cursor, value) {
|
|
219
|
+
if (!isPunct(input, cursor, value)) return false;
|
|
220
|
+
takeToken(input, cursor);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {Punctuator} value @return {PunctToken} */
|
|
225
|
+
function expectPunct(input, cursor, value) {
|
|
226
|
+
const t = takeToken(input, cursor);
|
|
227
|
+
if (t.type !== 'punct' || t.value !== value) {
|
|
228
|
+
throw new Error(`Expected ${value} at position ${t.pos}, got "${t.value}"`);
|
|
229
|
+
}
|
|
230
|
+
return t;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
Cursor,
|
|
235
|
+
eofPositionAt,
|
|
236
|
+
expectPunct,
|
|
237
|
+
isPunct,
|
|
238
|
+
matchPunct,
|
|
239
|
+
peekToken,
|
|
240
|
+
sourceSpelling,
|
|
241
|
+
takeToken,
|
|
242
|
+
};
|