@cssints/css-grammar 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,789 @@
1
+ import { InvalidValueError, ParseError, UnknownPropertyError } from "./errors.mjs";
2
+ import { properties, syntaxes } from "./mdn.mjs";
3
+ //#region src/twin-core.ts
4
+ const props = properties;
5
+ const syns = syntaxes;
6
+ const trim = (s) => s.replace(/^[ \n\t]+/, "");
7
+ function next(s) {
8
+ s = trim(s);
9
+ if (s === "") return [{ t: "eof" }, ""];
10
+ let m;
11
+ if (m = /^<'([^']*)'>/.exec(s)) return [{
12
+ t: "prop",
13
+ v: m[1]
14
+ }, s.slice(m[0].length)];
15
+ if (m = /^<([^>]*)>/.exec(s)) {
16
+ const [v, range] = m[1].split(" [");
17
+ const [lo, hi] = range ? range.slice(0, -1).split(",") : [];
18
+ return [{
19
+ t: "type",
20
+ v,
21
+ lo,
22
+ hi
23
+ }, s.slice(m[0].length)];
24
+ }
25
+ if (m = /^'([^']*)'/.exec(s)) return [{
26
+ t: "lit",
27
+ v: m[1]
28
+ }, s.slice(m[0].length)];
29
+ if (m = /^(\|\||&&)/.exec(s)) return [{
30
+ t: "p",
31
+ v: m[1]
32
+ }, s.slice(2)];
33
+ if (m = /^\{([^}]*)\}/.exec(s)) {
34
+ const [lo, hi = lo] = m[1].split(",");
35
+ return [{
36
+ t: "range",
37
+ lo,
38
+ hi: hi || "∞"
39
+ }, s.slice(m[0].length)];
40
+ }
41
+ if (m = /^[[\]()|?*+#!,/;]/.exec(s)) return [{
42
+ t: "p",
43
+ v: m[0]
44
+ }, s.slice(1)];
45
+ if (m = /^[^()[\]|&?*+#!,/{}<>' \n\t]+(?=[ \n\t]|$)/.exec(s)) return [{
46
+ t: "kw",
47
+ v: m[0]
48
+ }, s.slice(m[0].length)];
49
+ const w = /^[A-Za-z0-9_-]*/.exec(s)[0];
50
+ const r = s.slice(w.length);
51
+ if (w === "") return [{ t: "eof" }, ""];
52
+ return r.startsWith("(") ? [{
53
+ t: "fn",
54
+ v: w
55
+ }, r.slice(1)] : [{
56
+ t: "kw",
57
+ v: w
58
+ }, r];
59
+ }
60
+ const parsed = /* @__PURE__ */ new Map();
61
+ let whole = "";
62
+ const bad = (message, rest, expected) => new ParseError({
63
+ message,
64
+ grammar: whole,
65
+ position: whole.length - rest.length,
66
+ expected
67
+ });
68
+ function parseGrammar(s) {
69
+ let n = parsed.get(s);
70
+ if (!n) {
71
+ whole = s;
72
+ parsed.set(s, n = parseAlt(s)[0]);
73
+ }
74
+ return n;
75
+ }
76
+ const infix = (tag, op, sub) => (s) => {
77
+ const xs = [];
78
+ for (;;) {
79
+ const [n, r] = sub(s);
80
+ xs.push(n);
81
+ const [t, r2] = next(r);
82
+ if (t.t === "p" && t.v === op) s = r2;
83
+ else return [xs.length === 1 ? xs[0] : [tag, xs], r];
84
+ }
85
+ };
86
+ const parseAlt = infix("alt", "|", infix("or", "||", infix("and", "&&", parseSeq)));
87
+ function parseSeq(s) {
88
+ const xs = [];
89
+ for (;;) {
90
+ const [t] = next(s);
91
+ if (t.t === "eof" || t.t === "p" && [
92
+ "|",
93
+ "||",
94
+ "&&",
95
+ "]",
96
+ ")"
97
+ ].includes(t.v)) return [xs.length === 1 ? xs[0] : ["seq", xs], s];
98
+ const [n, r] = parseTerm(s);
99
+ xs.push(n);
100
+ s = r;
101
+ }
102
+ }
103
+ function parseTerm(s) {
104
+ const [t, r] = next(s);
105
+ if (t.t === "p" && t.v === "[") {
106
+ const [n, r2] = parseAlt(r);
107
+ const [close, r3] = next(r2);
108
+ if (close.t !== "p" || close.v !== "]") throw bad(`grammar: expected "]" at "${r2}"`, r2, ["\"]\""]);
109
+ return mult(n, r3);
110
+ }
111
+ if (t.t === "fn") return fnArgs(t.v, r);
112
+ if (t.t === "p" && t.v === "(") return fnArgs("", r);
113
+ if (t.t === "type") return mult(t.lo === void 0 ? ["type", t.v] : [
114
+ "type",
115
+ t.v,
116
+ t.lo,
117
+ t.hi
118
+ ], r);
119
+ if (t.t === "prop" || t.t === "kw") return mult([t.t, t.v], r);
120
+ if (t.t === "p" && (t.v === "," || t.v === "/" || t.v === ";")) return mult(["lit", t.v], r);
121
+ if (t.t === "lit") return mult(["lit", t.v], r);
122
+ throw bad(`grammar: unexpected token at "${s}"`, s, ["a term"]);
123
+ }
124
+ function fnArgs(v, s) {
125
+ const [n, r] = parseAlt(s);
126
+ const [close, r2] = next(r);
127
+ if (close.t !== "p" || close.v !== ")") throw bad(`grammar: expected ")" at "${r}"`, r, ["\")\""]);
128
+ return mult([
129
+ "fn",
130
+ v,
131
+ n
132
+ ], r2);
133
+ }
134
+ const dec = (b) => b === "∞" ? b : String(Number(b) - 1);
135
+ const MULT = {
136
+ "?": "opt",
137
+ "*": "star",
138
+ "+": "plus",
139
+ "#": "list"
140
+ };
141
+ function mult(n, s) {
142
+ for (;;) {
143
+ const [t, r] = next(s);
144
+ if (t.t === "p" && MULT[t.v]) n = [MULT[t.v], n];
145
+ else if (t.t === "range") n = n[0] === "list" ? ["seq", [n[1], [
146
+ "rep",
147
+ ["seq", [["lit", ","], n[1]]],
148
+ dec(t.lo),
149
+ dec(t.hi)
150
+ ]]] : [
151
+ "rep",
152
+ n,
153
+ t.lo,
154
+ t.hi
155
+ ];
156
+ else if (t.t === "p" && t.v === "!") n = ["bang", n];
157
+ else return [n, s];
158
+ s = r;
159
+ }
160
+ }
161
+ function tok1(s) {
162
+ const t = /^[^ ,)]*/.exec(s)[0];
163
+ return [t, s.slice(t.length)];
164
+ }
165
+ const NUM = String.raw`[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?`;
166
+ const LENGTH_UNIT = "px|em|rem|vw|vh|vmin|vmax|ch|ex|lh|rlh|cm|mm|in|pt|pc|q|svw|svh|lvw|lvh|dvw|dvh|cqw|cqh|cqi|cqb|cqmin|cqmax|ic|cap|rex|rch|ric|rcap";
167
+ const re = (s) => new RegExp(`^(?:${s})$`);
168
+ const IDENT = String.raw`(?:--|-?[A-Za-z_\u0080-\uffff])[\w\u0080-\uffff-]*`;
169
+ const PRIMITIVE = {
170
+ length: re(`${NUM}(?:${LENGTH_UNIT})|0`),
171
+ percentage: re(`${NUM}%`),
172
+ "length-percentage": re(`${NUM}(?:${LENGTH_UNIT})|0|${NUM}%`),
173
+ number: re(NUM),
174
+ integer: re(String.raw`[+-]?\d+`),
175
+ "hex-color": /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,
176
+ "custom-ident": new RegExp(`^(?!(?:initial|inherit|unset|revert|revert-layer|default)$)${IDENT}$`, "i"),
177
+ "dashed-ident": /^--.+$/,
178
+ angle: re(`${NUM}(?:deg|rad|grad|turn)`),
179
+ time: re(`${NUM}(?:s|ms)`),
180
+ dimension: re(`${NUM}(?:${LENGTH_UNIT}|deg|rad|grad|turn|s|ms|hz|khz|dpi|dpcm|dppx|x|fr)`),
181
+ resolution: re(`${NUM}(?:dpi|dpcm|dppx|x)`),
182
+ flex: re(`${NUM}fr`),
183
+ zero: re(String.raw`[+-]?(?:0+(?:\.0+)?|\.0+)(?:e[+-]?\d+)?`),
184
+ ident: re(IDENT),
185
+ "ident-token": re(IDENT),
186
+ "hash-token": /^#[\w\u0080-\uffff-]+$/,
187
+ "intrinsic-size-keyword": /^(?:min-content|max-content|fit-content|stretch)$/,
188
+ x: re(NUM),
189
+ y: re(NUM)
190
+ };
191
+ const STRING = /^(?:"[^"]*"|'[^']*')/;
192
+ const URL_RE = /^url\(\s*(?:"[^"]*"|'[^']*'|[^\s"'()\\]*)\s*\)/;
193
+ function declarationValue(s) {
194
+ let depth = 0;
195
+ for (let i = 0; i < s.length; i++) {
196
+ const c = s[i];
197
+ if (c === "\"" || c === "'") {
198
+ i = s.indexOf(c, i + 1);
199
+ if (i < 0) return -1;
200
+ } else if ("([{".includes(c)) depth++;
201
+ else if (")]}".includes(c)) {
202
+ if (depth) depth--;
203
+ else return c === ")" && i > 0 ? i : -1;
204
+ } else if (!depth && (c === ";" || c === "!")) return -1;
205
+ }
206
+ return depth || !s ? -1 : s.length;
207
+ }
208
+ const spaced = (q) => q.replace(/[:/]/g, " $& ");
209
+ function args(s, sep) {
210
+ const at = [];
211
+ let depth = 0;
212
+ for (let i = 0; i < s.length; i++) {
213
+ const c = s[i];
214
+ if (c === "\"" || c === "'") {
215
+ i = s.indexOf(c, i + 1);
216
+ if (i < 0) break;
217
+ } else if ("([{".includes(c)) depth++;
218
+ else if (")]}".includes(c)) {
219
+ if (!depth) return {
220
+ end: c === ")" ? i : -1,
221
+ at
222
+ };
223
+ depth--;
224
+ } else if (c === sep && !depth) at.push(i);
225
+ }
226
+ return {
227
+ end: -1,
228
+ at
229
+ };
230
+ }
231
+ function ifCall(s) {
232
+ if (!s.startsWith("if(")) return -1;
233
+ const body = s.slice(3);
234
+ const { end, at } = args(body, ";");
235
+ if (end < 0) return -1;
236
+ const cuts = [
237
+ -1,
238
+ ...at,
239
+ end
240
+ ];
241
+ const branches = cuts.slice(1).map((e, i) => body.slice(cuts[i] + 1, e).trim());
242
+ if (branches.at(-1) === "") branches.pop();
243
+ const branch = (b) => {
244
+ const [i] = args(`${b})`, ":").at;
245
+ if (i === void 0) return false;
246
+ const cond = trim(spaced(b.slice(0, i)));
247
+ const value = b.slice(i + 1).trim();
248
+ const ctx = {
249
+ len: cond.length,
250
+ far: -1,
251
+ expected: /* @__PURE__ */ new Set()
252
+ };
253
+ return consume(parseGrammar(syns["if-condition"]), cond, ctx).has("") && (value === "" || declarationValue(value) === value.length);
254
+ };
255
+ return branches.length && branches.every(branch) ? end + 4 : -1;
256
+ }
257
+ function calcSum(s, ctx) {
258
+ const [t, end] = mathScan(s, void 0, true);
259
+ if (t && fits(t, KIND["length-percentage"])) return end;
260
+ ctx.why = [ctx.len - s.length, t ? `${s.slice(0, end)} is ${typeName(t)}, not <length-percentage>` : why];
261
+ return -1;
262
+ }
263
+ const SCAN = {
264
+ string: (s) => STRING.exec(s)?.[0].length ?? -1,
265
+ url: (s) => URL_RE.exec(s)?.[0].length ?? -1,
266
+ "declaration-value": declarationValue,
267
+ "if()": ifCall,
268
+ "calc-sum": calcSum
269
+ };
270
+ const isNative = (name) => !!(PRIMITIVE[name] || SCAN[name]);
271
+ const RGB = [
272
+ "r",
273
+ "g",
274
+ "b",
275
+ "alpha"
276
+ ];
277
+ const HSL = [
278
+ "h",
279
+ "s",
280
+ "l",
281
+ "alpha"
282
+ ];
283
+ const LAB = [
284
+ "l",
285
+ "a",
286
+ "b",
287
+ "alpha"
288
+ ];
289
+ const LCH = [
290
+ "l",
291
+ "c",
292
+ "h",
293
+ "alpha"
294
+ ];
295
+ const CHANNELS = /* @__PURE__ */ new Map([
296
+ ["rgb", RGB],
297
+ ["rgba", RGB],
298
+ ["hsl", HSL],
299
+ ["hsla", HSL],
300
+ ["hwb", [
301
+ "h",
302
+ "w",
303
+ "b",
304
+ "alpha"
305
+ ]],
306
+ ["lab", LAB],
307
+ ["oklab", LAB],
308
+ ["lch", LCH],
309
+ ["oklch", LCH],
310
+ ["color", ["alpha"]]
311
+ ]);
312
+ const SPACES = new Map([...syns["predefined-rgb"].split(" | ").map((k) => [k, [
313
+ "r",
314
+ "g",
315
+ "b"
316
+ ]]), ...syns["xyz-space"].split(" | ").map((k) => [k, [
317
+ "x",
318
+ "y",
319
+ "z"
320
+ ]])]);
321
+ function spaceChannels(body) {
322
+ let b = body.slice(0, args(body, "").end);
323
+ for (let p = ""; p !== b;) [p, b] = [b, b.replace(/\([^()]*\)/g, "")];
324
+ return b.split(/\s+/).map((k) => SPACES.get(k)).find(Boolean) ?? [];
325
+ }
326
+ const PENDING = /(?:var|env|if)\(/;
327
+ const ENV = /^env\([ \n\t]*[A-Za-z_-][\w-]*/;
328
+ function pendingEnd(s) {
329
+ if (!/^(?:var|env|if)\(/.test(s)) return -1;
330
+ if (s[0] === "i") return ifCall(s);
331
+ const { end } = args(s.slice(4), "");
332
+ if (end < 0) return -1;
333
+ const call = s.slice(0, end + 5);
334
+ return (s[0] === "v" ? matches(syns["var()"], call) : ENV.test(call)) ? end + 5 : -1;
335
+ }
336
+ function skipPending(s) {
337
+ for (let e; (e = pendingEnd(s)) >= 0;) s = trim(s.slice(e));
338
+ return s;
339
+ }
340
+ function pend(n, s, ctx, label) {
341
+ const e = pendingEnd(s);
342
+ if (e < 0) return NONE;
343
+ const rest = trim(s.slice(e));
344
+ return /* @__PURE__ */ new Set([
345
+ s,
346
+ rest,
347
+ ...consume(n, rest, ctx, label)
348
+ ]);
349
+ }
350
+ const complete = (rs, ctx) => ctx.pend ? [...rs].some((r) => skipPending(r) === "") : rs.has("");
351
+ function wrongCase(ctx, s, t, name, call = "") {
352
+ if (t.length === name.length && t !== name && t.toLowerCase() === name.toLowerCase()) ctx.fix = [
353
+ ctx.len - s.length,
354
+ t + call,
355
+ name + call
356
+ ];
357
+ }
358
+ function fail(ctx, s, what) {
359
+ const at = ctx.len - s.length;
360
+ if (at > ctx.far) {
361
+ ctx.far = at;
362
+ ctx.expected = /* @__PURE__ */ new Set();
363
+ }
364
+ if (at === ctx.far) ctx.expected.add(what);
365
+ }
366
+ const NONE = /* @__PURE__ */ new Set();
367
+ const union = (sets) => {
368
+ const out = /* @__PURE__ */ new Set();
369
+ for (const s of sets) for (const r of s) out.add(r);
370
+ return out;
371
+ };
372
+ const flatMap = (rs, f) => union([...rs].map(f));
373
+ function consume(n, s, ctx, label) {
374
+ const leaf = (ok, rest, what) => ok ? /* @__PURE__ */ new Set([trim(rest)]) : (fail(ctx, s, label ?? what), ctx.pend ? pend(n, s, ctx, label) : NONE);
375
+ const sub = (x, r) => consume(x, r, ctx, label);
376
+ switch (n[0]) {
377
+ case "kw": {
378
+ const [t, r] = tok1(s);
379
+ if (t !== n[1]) wrongCase(ctx, s, t, n[1]);
380
+ return leaf(t === n[1], r, n[1]);
381
+ }
382
+ case "lit": return leaf(s.startsWith(n[1]), s.slice(n[1].length), `'${n[1]}'`);
383
+ case "type": return consumeType(n, s, ctx, label);
384
+ case "prop": {
385
+ const g = props[n[1]];
386
+ return g ? consume(parseGrammar(g), s, ctx, label ?? `<'${n[1]}'>`) : leaf(false, s, `<'${n[1]}'>`);
387
+ }
388
+ case "fn": {
389
+ if (!s.startsWith(`${n[1]}(`)) {
390
+ if (n[1] && s[n[1].length] === "(") wrongCase(ctx, s, s.slice(0, n[1].length), n[1], "()");
391
+ return leaf(false, s, `${n[1]}()`);
392
+ }
393
+ const body = trim(s.slice(n[1].length + 1));
394
+ const outer = ctx.ch;
395
+ if (n[1]) {
396
+ const ch = /^from[ \n\t]/.test(body) ? CHANNELS.get(n[1]) : void 0;
397
+ ctx.ch = ch && n[1] === "color" ? [...ch, ...spaceChannels(body)] : ch;
398
+ }
399
+ const rs = flatMap(sub(n[2], body), (r0) => {
400
+ const r = ctx.pend ? skipPending(r0) : r0;
401
+ return r.startsWith(")") ? /* @__PURE__ */ new Set([trim(r.slice(1))]) : (fail(ctx, r0, label ?? "')'"), NONE);
402
+ });
403
+ ctx.ch = outer;
404
+ return rs;
405
+ }
406
+ case "alt": return union(n[1].map((x) => sub(x, s)));
407
+ case "seq": return seq(n[1], s, sub);
408
+ case "opt": return /* @__PURE__ */ new Set([s, ...sub(n[1], s)]);
409
+ case "star": return closure(/* @__PURE__ */ new Set([s]), (r) => sub(n[1], r));
410
+ case "plus": return closure(sub(n[1], s), (r) => sub(n[1], r));
411
+ case "list": return closure(sub(n[1], s), (r) => {
412
+ if (r.startsWith(",")) return sub(n[1], trim(r.slice(1)));
413
+ const e = ctx.pend ? pendingEnd(r) : -1;
414
+ return e < 0 ? NONE : union([sub(n[1], r), sub(n[1], trim(r.slice(e)))]);
415
+ });
416
+ case "rep": return rep(s, Number(n[2]), n[3] === "∞" ? Infinity : Number(n[3]), (r) => sub(n[1], r));
417
+ case "bang": {
418
+ const rs = new Set([...sub(n[1], s)].filter((r) => r !== s));
419
+ if (!rs.size) fail(ctx, s, label ?? "a non-empty group");
420
+ return rs;
421
+ }
422
+ case "or": return anyOrder(n[1], s, false, sub, ctx.pend);
423
+ case "and": return anyOrder(n[1], s, true, sub, ctx.pend);
424
+ }
425
+ }
426
+ const SCALE = {
427
+ grad: .9,
428
+ rad: 180 / Math.PI,
429
+ turn: 360,
430
+ ms: .001
431
+ };
432
+ const NUM_HEAD = new RegExp(`^${NUM}`, "i");
433
+ const magnitude = (t) => t === "∞" ? Infinity : t === "-∞" ? -Infinity : parseFloat(t) * (SCALE[t.replace(NUM_HEAD, "").trim().toLowerCase()] ?? 1);
434
+ const NUMBER = { e: {} };
435
+ const ANY = {
436
+ e: {},
437
+ any: true
438
+ };
439
+ const dim = (k) => ({ e: { [k]: 1 } });
440
+ const bases = (t) => Object.keys(t.e);
441
+ const plain = (t) => bases(t).length === 1 && t.e[bases(t)[0]] === 1;
442
+ const same = (a, b) => bases(a).length === bases(b).length && bases(a).every((k) => a.e[k] === b.e[k]);
443
+ const isNumber = (t) => t.any || !t.mix && !bases(t).length;
444
+ const typeName = (t) => t.any ? "anything" : `${bases(t).map((k) => `${k === "percent" ? "percentage" : k}${t.e[k] === 1 ? "" : `^${t.e[k]}`}`).join(" * ")}${t.mix ? "-percentage" : ""}` || "number";
445
+ function add(a, b) {
446
+ if (a.any || b.any) return ANY;
447
+ if (same(a, b)) return {
448
+ e: a.e,
449
+ mix: a.mix || b.mix
450
+ };
451
+ const pct = (t) => same(t, dim("percent")) && !t.mix;
452
+ if (pct(a) && plain(b)) return {
453
+ e: b.e,
454
+ mix: true
455
+ };
456
+ if (pct(b) && plain(a)) return {
457
+ e: a.e,
458
+ mix: true
459
+ };
460
+ }
461
+ function mul(a, b, sign) {
462
+ if (a.any || b.any) return ANY;
463
+ const e = { ...a.e };
464
+ for (const k in b.e) if (!(e[k] = (e[k] ?? 0) + sign * b.e[k])) delete e[k];
465
+ return {
466
+ e,
467
+ mix: a.mix || b.mix
468
+ };
469
+ }
470
+ const KIND = {
471
+ number: ["", false],
472
+ integer: ["", false],
473
+ x: ["", false],
474
+ y: ["", false],
475
+ length: ["length", false],
476
+ percentage: ["percent", false],
477
+ "length-percentage": ["length", true],
478
+ angle: ["angle", false],
479
+ "angle-percentage": ["angle", true],
480
+ time: ["time", false],
481
+ resolution: ["resolution", false]
482
+ };
483
+ const fits = (t, [k, pct]) => t.any || (t.mix ? pct && plain(t) && t.e[k] === 1 : k === "" ? !bases(t).length : plain(t) && (t.e[k] === 1 || pct && t.e.percent === 1));
484
+ const MATH = new Set("calc min max clamp round mod rem sin cos tan asin acos atan atan2 pow sqrt hypot log exp abs sign".split(" "));
485
+ const MATH_START = new RegExp(`^(?:${[...MATH].join("|")})\\(`);
486
+ const UNITS = new Map([
487
+ ...LENGTH_UNIT.split("|").map((u) => [u, "length"]),
488
+ ...[
489
+ "deg",
490
+ "rad",
491
+ "grad",
492
+ "turn"
493
+ ].map((u) => [u, "angle"]),
494
+ ...["s", "ms"].map((u) => [u, "time"]),
495
+ ...[
496
+ "dpi",
497
+ "dpcm",
498
+ "dppx",
499
+ "x"
500
+ ].map((u) => [u, "resolution"])
501
+ ]);
502
+ const sticky = (r) => new RegExp(r, "iy");
503
+ const NUM_TOKEN = sticky(NUM);
504
+ const UNIT_TOKEN = /%|[A-Za-z]+/y;
505
+ const WORD = /-?[A-Za-z][\w-]*/y;
506
+ const WS = /[ \n\t]*/y;
507
+ const ROUND_MODE = /(?:nearest|up|down|to-zero)(?=[ \n\t]*,)/y;
508
+ const NONE_WORD = /none(?![\w-])/y;
509
+ let why = "";
510
+ function mathScan(s, ch, sumOnly) {
511
+ let i = 0;
512
+ const tok = (r) => {
513
+ r.lastIndex = i;
514
+ const m = r.exec(s);
515
+ if (m) i = r.lastIndex;
516
+ return m?.[0];
517
+ };
518
+ const ws = () => void tok(WS);
519
+ const no = (m) => (why ||= m, void 0);
520
+ function sum() {
521
+ let a = product();
522
+ for (;;) {
523
+ const at = i;
524
+ ws();
525
+ const op = s[i];
526
+ if (!a || op !== "+" && op !== "-" || i === at || !/[ \n\t]/.test(s[i + 1] ?? "")) {
527
+ i = at;
528
+ return a;
529
+ }
530
+ i++;
531
+ ws();
532
+ const b = product();
533
+ if (!b) return void 0;
534
+ a = add(a, b) ?? no(`cannot ${op === "+" ? "add" : "subtract"} ${typeName(a)} and ${typeName(b)}`);
535
+ }
536
+ }
537
+ function product() {
538
+ let a = value();
539
+ for (;;) {
540
+ const at = i;
541
+ ws();
542
+ const op = s[i];
543
+ if (!a || op !== "*" && op !== "/") {
544
+ i = at;
545
+ return a;
546
+ }
547
+ i++;
548
+ ws();
549
+ const b = value();
550
+ if (!b) return void 0;
551
+ a = mul(a, b, op === "*" ? 1 : -1);
552
+ }
553
+ }
554
+ function value() {
555
+ if (s[i] === "(") {
556
+ i++;
557
+ ws();
558
+ const t = sum();
559
+ ws();
560
+ if (!t) return void 0;
561
+ if (s[i] !== ")") return no("expected \")\"");
562
+ i++;
563
+ return t;
564
+ }
565
+ if (tok(NUM_TOKEN) !== void 0) {
566
+ const u = tok(UNIT_TOKEN);
567
+ if (u === void 0) return NUMBER;
568
+ const k = u === "%" ? "percent" : UNITS.get(u);
569
+ return k ? dim(k) : no(`unknown unit "${u}"`);
570
+ }
571
+ const w = tok(WORD);
572
+ if (w === void 0) return no("expected a number, a dimension or a function");
573
+ if (s[i] === "(") return call(w, i++);
574
+ return /^(?:-?infinity|e|pi|nan)$/i.test(w) || ch?.includes(w.toLowerCase()) ? NUMBER : no(`unknown "${w}"`);
575
+ }
576
+ function call(name, open) {
577
+ if (name === "var" || name === "env") {
578
+ const { end } = args(s.slice(i), "");
579
+ if (end < 0) return no(`${name}() is not closed`);
580
+ i += end + 1;
581
+ return name === "env" || matches(syns["var()"], s.slice(open - 3, i)) ? ANY : no("not a var()");
582
+ }
583
+ if (!MATH.has(name)) return no(`${name}() is not a math function`);
584
+ const ts = [];
585
+ for (let mode = name === "round";;) {
586
+ ws();
587
+ if (mode && tok(ROUND_MODE) !== void 0) mode = false;
588
+ else {
589
+ mode = false;
590
+ const t = name === "clamp" && tok(NONE_WORD) !== void 0 ? "none" : sum();
591
+ if (!t) return void 0;
592
+ ts.push(t);
593
+ }
594
+ ws();
595
+ if (s[i] === ",") i++;
596
+ else if (s[i] === ")") break;
597
+ else return no(i >= s.length ? `${name}() is not closed` : "expected \",\" or \")\"");
598
+ }
599
+ i++;
600
+ const n = ts.length;
601
+ const cs = ts.filter((t) => t !== "none");
602
+ const [a, b] = cs;
603
+ const arity = (lo, hi = lo) => n >= lo && n <= hi ? true : (no(`${name}() takes ${lo === hi ? lo : `${lo} to ${hi}`} arguments`), false);
604
+ const fold = () => cs.reduce((x, y) => x && (add(x, y) ?? no(`${name}() cannot take ${typeName(x)} and ${typeName(y)}`)), a);
605
+ const numbers = (r) => cs.every(isNumber) ? r : no(`${name}() takes numbers, not ${typeName(cs.find((t) => !isNumber(t)))}`);
606
+ switch (name) {
607
+ case "calc":
608
+ case "abs": return arity(1) ? a : void 0;
609
+ case "min":
610
+ case "max":
611
+ case "hypot": return arity(1, Infinity) ? fold() : void 0;
612
+ case "clamp": return arity(3) ? ts[1] === "none" ? no("clamp() needs a value in the middle") : fold() : void 0;
613
+ case "round": return n === 1 && isNumber(a) ? a : arity(2) ? fold() : void 0;
614
+ case "mod":
615
+ case "rem": return arity(2) ? fold() : void 0;
616
+ case "sign": return arity(1) ? NUMBER : void 0;
617
+ case "sin":
618
+ case "cos":
619
+ case "tan": return arity(1) ? isNumber(a) || same(a, dim("angle")) ? NUMBER : no(`${name}() takes a number or an angle, not ${typeName(a)}`) : void 0;
620
+ case "asin":
621
+ case "acos":
622
+ case "atan": return arity(1) ? numbers(dim("angle")) : void 0;
623
+ case "sqrt":
624
+ case "exp": return arity(1) ? numbers(NUMBER) : void 0;
625
+ case "pow": return arity(2) ? numbers(NUMBER) : void 0;
626
+ case "log": return arity(1, 2) ? numbers(NUMBER) : void 0;
627
+ default: return arity(2) ? a.any || b.any || same(a, b) && !a.mix && !b.mix ? dim("angle") : no(`atan2() cannot take ${typeName(a)} and ${typeName(b)}`) : void 0;
628
+ }
629
+ }
630
+ why = "";
631
+ return [sumOnly ? sum() : value(), i];
632
+ }
633
+ function mathValue(v, s, t, r, ctx) {
634
+ const kind = KIND[v];
635
+ if (!kind) return NONE;
636
+ if (ctx.ch && (v === "number" || v === "percentage" || v === "angle")) {
637
+ if (v === "number" && ctx.ch.includes(t.toLowerCase())) return /* @__PURE__ */ new Set([trim(r)]);
638
+ if (s.startsWith("var(")) return consumeType(["type", "var()"], s, ctx);
639
+ }
640
+ if (!t.includes("(") || !MATH_START.test(t)) return NONE;
641
+ const [ty, end] = mathScan(s, ctx.ch, false);
642
+ if (ty && fits(ty, kind)) return /* @__PURE__ */ new Set([trim(s.slice(end))]);
643
+ ctx.why = [ctx.len - s.length, ty ? `${s.slice(0, end)} is ${typeName(ty)}, not <${v}>` : why];
644
+ return NONE;
645
+ }
646
+ function consumeType([, v, lo, hi], s, ctx, label) {
647
+ let rs;
648
+ if (PRIMITIVE[v]) {
649
+ const [t, r] = tok1(s);
650
+ rs = PRIMITIVE[v].test(t) ? /* @__PURE__ */ new Set([trim(r)]) : mathValue(v, s, t, r, ctx);
651
+ } else if (SCAN[v]) {
652
+ const n = SCAN[v](s, ctx);
653
+ rs = n < 0 ? NONE : /* @__PURE__ */ new Set([trim(s.slice(n))]);
654
+ } else if (syns[v]) {
655
+ const f = v.endsWith("()") ? v.slice(0, -2) : void 0;
656
+ if (f !== void 0 && !s.startsWith(`${f}(`)) {
657
+ if (s[f.length] === "(") wrongCase(ctx, s, s.slice(0, f.length), f, "()");
658
+ rs = NONE;
659
+ } else rs = consume(parseGrammar(syns[v]), s, ctx, label ?? `<${v}>`);
660
+ if (v === "angle-percentage") {
661
+ const m = mathValue(v, s, ...tok1(s), ctx);
662
+ if (m !== NONE) rs = union([rs, m]);
663
+ }
664
+ } else rs = NONE;
665
+ if (rs === NONE) {
666
+ fail(ctx, s, label ?? `<${v}>`);
667
+ if (ctx.pend && (PRIMITIVE[v] || SCAN[v] || v.endsWith("()"))) return pend([
668
+ "type",
669
+ v,
670
+ lo,
671
+ hi
672
+ ], s, ctx, label);
673
+ }
674
+ if (lo === void 0 || hi === void 0) return rs;
675
+ const out = /* @__PURE__ */ new Set();
676
+ for (const r of rs) {
677
+ const x = magnitude(s.slice(0, s.length - r.length));
678
+ if (Number.isNaN(x) || x >= magnitude(lo) && x <= magnitude(hi)) out.add(r);
679
+ else fail(ctx, s, `<${v} [${lo},${hi}]>`);
680
+ }
681
+ return out;
682
+ }
683
+ const nullable = (n) => n[0] === "opt" || n[0] === "star" || n[0] === "rep" && n[2] === "0";
684
+ function seq(xs, s, sub) {
685
+ let rs = /* @__PURE__ */ new Set([s]), start = NONE, moved = NONE;
686
+ const done = /* @__PURE__ */ new Set();
687
+ xs.forEach((x, i) => {
688
+ if (x[0] !== "lit" || x[1] !== ",") {
689
+ if (start === NONE) start = rs;
690
+ rs = flatMap(rs, (r) => sub(x, r));
691
+ return;
692
+ }
693
+ const go = new Set([...rs].filter((r) => !start.has(r)));
694
+ const after = flatMap(go, (r) => sub(x, r));
695
+ const rest = xs.slice(i + 1);
696
+ if (rest.length && rest.every(nullable)) {
697
+ go.forEach((r) => done.add(r));
698
+ moved = after;
699
+ }
700
+ rs = start = new Set([...rs].filter((r) => start.has(r)).concat(...after));
701
+ });
702
+ return new Set([...rs].filter((r) => !moved.has(r)).concat(...done));
703
+ }
704
+ function closure(rs, step) {
705
+ const all = new Set(rs);
706
+ for (let todo = [...rs]; todo.length;) {
707
+ const fresh = [];
708
+ for (const r of todo) for (const q of step(r)) if (!all.has(q)) {
709
+ all.add(q);
710
+ fresh.push(q);
711
+ }
712
+ todo = fresh;
713
+ }
714
+ return all;
715
+ }
716
+ function rep(s, lo, hi, step) {
717
+ const out = /* @__PURE__ */ new Set();
718
+ let cur = /* @__PURE__ */ new Set([s]);
719
+ for (let i = 0;; i++) {
720
+ if (i >= lo) {
721
+ const before = out.size;
722
+ for (const r of cur) out.add(r);
723
+ if (i > lo && out.size === before) return out;
724
+ }
725
+ if (i === hi || cur.size === 0) return out;
726
+ cur = flatMap(cur, step);
727
+ }
728
+ }
729
+ function anyOrder(xs, s, all, sub, pend = false) {
730
+ if (!xs.length) return /* @__PURE__ */ new Set([s]);
731
+ const e = pend ? pendingEnd(s) : -1;
732
+ const q = e < 0 ? s : trim(s.slice(e));
733
+ const out = e < 0 ? /* @__PURE__ */ new Set() : union([/* @__PURE__ */ new Set([s, q]), anyOrder(xs, q, false, sub, pend)]);
734
+ xs.forEach((x, i) => {
735
+ for (const r1 of sub(x, s)) {
736
+ if (e >= 0 && (r1 === s || r1 === q)) continue;
737
+ if (!all || e >= 0) out.add(r1);
738
+ for (const r of anyOrder(xs.filter((_, j) => j !== i), r1, all && e < 0, sub, pend)) out.add(r);
739
+ }
740
+ });
741
+ return out;
742
+ }
743
+ function matches(syntax, s, pending = false) {
744
+ const v = trim(s);
745
+ const ctx = {
746
+ len: v.length,
747
+ far: -1,
748
+ expected: /* @__PURE__ */ new Set(),
749
+ pend: pending && PENDING.test(v)
750
+ };
751
+ return complete(consume(parseGrammar(syntax), v, ctx), ctx);
752
+ }
753
+ const CSS_WIDE_VALUES = /* @__PURE__ */ new Set([
754
+ "inherit",
755
+ "initial",
756
+ "unset",
757
+ "revert",
758
+ "revert-layer"
759
+ ]);
760
+ function valid(p, s, syntax = props[p]) {
761
+ if (!syntax) throw new UnknownPropertyError({
762
+ message: `${p}: no grammar for this property`,
763
+ property: p
764
+ });
765
+ const v = trim(s);
766
+ if (CSS_WIDE_VALUES.has(v)) return s;
767
+ const ctx = {
768
+ len: v.length,
769
+ far: -1,
770
+ expected: /* @__PURE__ */ new Set(),
771
+ pend: PENDING.test(v)
772
+ };
773
+ const rs = consume(parseGrammar(syntax), v, ctx);
774
+ if (complete(rs, ctx)) return s;
775
+ for (const r of rs) fail(ctx, r, "end of value");
776
+ const [token] = tok1(v.slice(ctx.far));
777
+ const what = token ? `unexpected "${token}"` : "unexpected end";
778
+ const expected = [...ctx.expected];
779
+ const fix = ctx.fix?.[0] === ctx.far ? ` ("${ctx.fix[1]}" is ${ctx.fix[2]} in CSS; write "${ctx.fix[2]}")` : "";
780
+ throw new InvalidValueError({
781
+ message: `${p}: ${what} in "${s}"${fix}, expected ${expected.join(" | ")}${ctx.why?.[0] === ctx.far ? ` (${ctx.why[1]})` : ""}`,
782
+ property: p,
783
+ value: s,
784
+ position: ctx.far + (s.length - v.length),
785
+ expected
786
+ });
787
+ }
788
+ //#endregion
789
+ export { consume, isNative, magnitude, matches, next, nullable, parseGrammar, spaced, valid };