@depup/webpack 5.109.0-depup.0 → 5.109.2-depup.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.
Files changed (62) hide show
  1. package/README.md +4 -4
  2. package/bin/webpack.js +12 -0
  3. package/changes.json +4 -4
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/CleanPlugin.js +11 -9
  6. package/lib/CompatibilityPlugin.js +31 -0
  7. package/lib/Compilation.js +21 -5
  8. package/lib/Compiler.js +16 -4
  9. package/lib/DefinePlugin.js +47 -20
  10. package/lib/ExportsInfo.js +22 -5
  11. package/lib/ExternalModule.js +10 -7
  12. package/lib/FileSystemInfo.js +126 -26
  13. package/lib/FlagDependencyUsagePlugin.js +33 -12
  14. package/lib/InitFragment.js +20 -32
  15. package/lib/ModuleFilenameHelpers.js +13 -2
  16. package/lib/NormalModule.js +80 -71
  17. package/lib/NormalModuleFactory.js +8 -2
  18. package/lib/RuntimePlugin.js +40 -113
  19. package/lib/Template.js +23 -3
  20. package/lib/cache/PackFileCacheStrategy.js +259 -5
  21. package/lib/config/defaults.js +21 -2
  22. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  23. package/lib/container/ModuleFederationPlugin.js +15 -9
  24. package/lib/css/CssGenerator.js +8 -2
  25. package/lib/css/CssLoadingRuntimeModule.js +25 -12
  26. package/lib/css/CssModulesPlugin.js +29 -18
  27. package/lib/css/CssParser.js +135 -50
  28. package/lib/css/syntax.js +862 -765
  29. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  30. package/lib/dependencies/CommonJsFullRequireDependency.js +33 -4
  31. package/lib/dependencies/CommonJsImportsParserPlugin.js +2 -0
  32. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  33. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  34. package/lib/dependencies/HarmonyImportDependency.js +1 -1
  35. package/lib/dependencies/HarmonyImportGuard.js +2 -1
  36. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  37. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +15 -9
  38. package/lib/hmr/lazyCompilationBackend.js +19 -4
  39. package/lib/html/HtmlGenerator.js +23 -3
  40. package/lib/html/HtmlModulesPlugin.js +30 -12
  41. package/lib/html/syntax.js +2485 -2399
  42. package/lib/ids/IdHelpers.js +24 -10
  43. package/lib/javascript/JavascriptModulesPlugin.js +83 -49
  44. package/lib/javascript/JavascriptParser.js +49 -13
  45. package/lib/javascript/syntax.js +700 -43
  46. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +10 -2
  47. package/lib/node/ReadFileCompileWasmPlugin.js +10 -2
  48. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  49. package/lib/optimize/ConcatenatedModule.js +19 -15
  50. package/lib/optimize/ModuleConcatenationPlugin.js +73 -24
  51. package/lib/optimize/RealContentHashPlugin.js +10 -7
  52. package/lib/optimize/SplitChunksPlugin.js +146 -44
  53. package/lib/prefetch/ResourceHintPlugin.js +96 -72
  54. package/lib/serialization/BinaryMiddleware.js +270 -938
  55. package/lib/serialization/FileMiddleware.js +299 -60
  56. package/lib/stats/DefaultStatsFactoryPlugin.js +204 -198
  57. package/lib/stats/DefaultStatsPrinterPlugin.js +24 -166
  58. package/lib/stats/StatsFactory.js +31 -9
  59. package/lib/util/identifier.js +21 -1
  60. package/package.json +10 -10
  61. package/schemas/WebpackOptions.json +110 -55
  62. package/types.d.ts +832 -247
package/lib/css/syntax.js CHANGED
@@ -683,6 +683,15 @@ function consumeSpace(input, pos, out) {
683
683
  return fill(out, TT_WHITESPACE, start, pos);
684
684
  }
685
685
 
686
+ // Sticky fast-forward classes: a native run-skip over the ordinary characters of
687
+ // a string / url token, so long values (data: URIs, base64) don't cost one JS
688
+ // char read each. The negated classes match exactly the per-char terminators the
689
+ // loops below handle (quotes / backslash / newlines for strings; plus `(`, `)`,
690
+ // whitespace and non-printable code points for urls).
691
+ const _STRING_SAFE = /[^"'\\\n\r\f]+/y;
692
+ // eslint-disable-next-line no-control-regex -- url terminators include the control range and DEL (they make a bad-url)
693
+ const _URL_SAFE = /[^\u0000-\u0020\u007F"'()\\]+/y;
694
+
686
695
  /**
687
696
  * Consume a string token. Caller advanced past the opening quote so
688
697
  * `pos - 1` holds the ending code point and `pos - 1` is the start.
@@ -695,6 +704,8 @@ function consumeAStringToken(input, pos, out) {
695
704
  const start = pos - 1;
696
705
  const endingCodePoint = input.charCodeAt(pos - 1);
697
706
  for (;;) {
707
+ _STRING_SAFE.lastIndex = pos;
708
+ if (_STRING_SAFE.test(input)) pos = _STRING_SAFE.lastIndex;
698
709
  if (pos === input.length) {
699
710
  return fill(out, TT_STRING, start, pos);
700
711
  }
@@ -874,6 +885,8 @@ function consumeAUrlToken(input, pos, fnStart, out) {
874
885
  const contentStart = pos;
875
886
  out.contentStart = contentStart;
876
887
  for (;;) {
888
+ _URL_SAFE.lastIndex = pos;
889
+ if (_URL_SAFE.test(input)) pos = _URL_SAFE.lastIndex;
877
890
  if (pos === input.length) {
878
891
  out.contentEnd = pos;
879
892
  return fill(out, TT_URL, fnStart, pos);
@@ -1289,78 +1302,23 @@ const {
1289
1302
  } = NodeType;
1290
1303
 
1291
1304
  /**
1292
- * Base AST node. All concrete nodes (tokens, simple blocks, functions,
1293
- * declarations) inherit from this and carry the `[start, end)` byte `range`
1294
- * of the source slice they cover. `loc` is computed on demand from a
1295
- * shared `LocConverter` so we don't pay for line/column conversion until
1296
- * a consumer (warning, error, dependency) actually needs it.
1305
+ * Base AST node — the property-accessor view the `parseA*` entry points return
1306
+ * (see `_makeReader`). Every concrete node carries the `[start, end)` byte
1307
+ * `range` of the source slice it covers; `loc` is computed on demand from the
1308
+ * shared `LocConverter`, so line/column conversion is only paid when a consumer
1309
+ * needs it. The concrete node typedefs below extend this via `&`.
1310
+ *
1311
+ * Inside the parser a node ref is an integer id into the columns; the reader
1312
+ * exposes this property shape over a retained snapshot of those columns.
1313
+ * @typedef {object} Node
1314
+ * @property {number} type node-type discriminator
1315
+ * @property {number} start byte offset of the node's first code point
1316
+ * @property {number} end byte offset just past the node's last code point
1317
+ * @property {[number, number]} range the `[start, end)` byte range
1318
+ * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location (1-based line, 0-based column)
1319
+ * @property {() => string} toString source slice for this node
1320
+ * @property {string} unescapedName name with CSS escapes resolved (name-bearing nodes only)
1297
1321
  */
1298
- class Node {
1299
- /**
1300
- * @param {number} type node type discriminator
1301
- * @param {number} start byte offset of the node's first code point
1302
- * @param {number} end byte offset just past the node's last code point
1303
- * @param {LocConverter} locConverter shared loc converter
1304
- */
1305
- constructor(type, start, end, locConverter) {
1306
- /** @type {number} */
1307
- this.type = type;
1308
- // Byte range as two inline fields rather than a `[start, end]` array —
1309
- // one fewer allocation per node and ~56 bytes lighter (×100k+ nodes).
1310
- /** @type {number} */
1311
- this.start = start;
1312
- /** @type {number} */
1313
- this.end = end;
1314
- /** @type {LocConverter} */
1315
- this._locConverter = locConverter;
1316
- }
1317
-
1318
- /**
1319
- * The `[start, end)` byte range as a tuple — compatibility view over
1320
- * `start` / `end` (builds the array lazily; hot code reads the
1321
- * fields directly).
1322
- * @returns {[number, number]} the byte range
1323
- */
1324
- get range() {
1325
- return [this.start, this.end];
1326
- }
1327
-
1328
- get loc() {
1329
- const lc = this._locConverter;
1330
- // `LocConverter#get` mutates and returns the converter itself, so we
1331
- // must snapshot `line`/`column` between the two calls.
1332
- const s = lc.get(this.start);
1333
- const sl = s.line;
1334
- const sc = s.column;
1335
- const e = lc.get(this.end);
1336
- return {
1337
- start: { line: sl, column: sc },
1338
- end: { line: e.line, column: e.column }
1339
- };
1340
- }
1341
-
1342
- /**
1343
- * Serialize back to source — re-slices the original input (zero-alloc for
1344
- * untouched nodes).
1345
- * @returns {string} the source slice for this node
1346
- */
1347
- toString() {
1348
- return this._locConverter._input.slice(this.start, this.end);
1349
- }
1350
-
1351
- /**
1352
- * For name-bearing nodes (function / at-rule): the `name` with CSS escapes
1353
- * resolved, for case-insensitive keyword matching (`\75 rl` → `url`). Computed
1354
- * on read via `unescapeIdentifier`'s no-escape fast path. Callers without a
1355
- * `name` must not read this.
1356
- * @returns {string} the unescaped name
1357
- */
1358
- get unescapedName() {
1359
- return unescapeIdentifier(
1360
- /** @type {{ name: string }} */ (/** @type {unknown} */ (this)).name
1361
- );
1362
- }
1363
- }
1364
1322
 
1365
1323
  /**
1366
1324
  * @param {string} s numeric text
@@ -1379,175 +1337,15 @@ const _typeFlagOf = (s) =>
1379
1337
  s.includes(".") || s.includes("e") || s.includes("E") ? "number" : "integer";
1380
1338
 
1381
1339
  /**
1382
- * Leaf token node — the only `Node` subclass. `value` is the raw source slice
1340
+ * Leaf token node (property-accessor view) — `value` is the raw source slice
1383
1341
  * (identifier text, quoted string including quotes, a dimension's full `123px`,
1384
- * …). Token-specific extras are named by the `HashToken` / `UrlToken` /
1385
- * `NumberToken` / `DimensionToken` shape typedefs below.
1386
- *
1387
- * The numeric accessors (`numericValue` / `typeFlag` / `sign` / `unit`) are
1388
- * getters, not stored fields: a number / dimension / percentage token costs
1389
- * nothing beyond the base node unless a consumer reads them, and every leaf
1390
- * token keeps a single object shape so the walker's `node.type` dispatch stays
1391
- * monomorphic. They are only meaningful on the matching token type.
1342
+ * …; hash / at-keyword drop their `#` / `@` prefix, url uses its content range).
1343
+ * The `NumberToken` / `HashToken` / `UrlToken` / `DimensionToken` typedefs below
1344
+ * narrow the value accessors. `numericValue` / `typeFlag` / `sign` / `unit` are
1345
+ * derived from the source on read and are only meaningful on the matching token
1346
+ * type; `contentStart` / `contentEnd` mark a url token's inner content range.
1347
+ * @typedef {Node & { value: string, unescaped: string, numericValue: number, typeFlag: "integer" | "number" | "id" | "unrestricted", sign: "+" | "-" | "", unit: string, contentStart: number, contentEnd: number }} Token
1392
1348
  */
1393
- class Token extends Node {
1394
- /**
1395
- * @param {number} type node type
1396
- * @param {number} start byte offset of the token's first code point
1397
- * @param {number} end byte offset just past the token's last code point
1398
- * @param {LocConverter} locConverter shared loc converter
1399
- */
1400
- // No own fields: a leaf token is exactly a `Node` plus the value getters
1401
- // below. `value` is derived from the byte range on read instead of cached in
1402
- // a `_value` slot — most tokens (whitespace, punctuation) never read it, and
1403
- // dropping the slot is ~8 bytes saved on every token (the bulk of all nodes).
1404
- // hash / at-keyword strip their `#` / `@` prefix; url uses its content range
1405
- // (`contentStart` / `contentEnd`, the token's only own fields).
1406
-
1407
- /**
1408
- * @returns {string} the token's value (raw source slice unless overridden)
1409
- */
1410
- get value() {
1411
- const input = this._locConverter._input;
1412
- const type = this.type;
1413
- // hash (`#name` → `name`) and at-keyword (`@name` → `name`) drop one char.
1414
- if (type === T_HASH || type === T_AT_KEYWORD) {
1415
- return input.slice(this.start + 1, this.end);
1416
- }
1417
- if (type === T_URL) {
1418
- const u = /** @type {UrlToken} */ (/** @type {unknown} */ (this));
1419
- return input.slice(u.contentStart, u.contentEnd);
1420
- }
1421
- return input.slice(this.start, this.end);
1422
- }
1423
-
1424
- /**
1425
- * The token's value with CSS escapes resolved (`\2d` → `-`, `\75 rl` → `url`),
1426
- * per https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point — the
1427
- * form to match keywords / export as a CSS-Modules name against. For a string
1428
- * token it is the content between the quotes (the spec string value). Computed
1429
- * on read; `unescapeIdentifier` fast-returns the value unchanged when it has no
1430
- * escapes (the common case), so nothing is stored per token.
1431
- * @returns {string} the unescaped value
1432
- */
1433
- get unescaped() {
1434
- const v = this.value;
1435
- // A string token's `value` carries its delimiting quotes; its value is the content between them.
1436
- return this.type === T_STRING
1437
- ? unescapeIdentifier(v.slice(1, -1))
1438
- : unescapeIdentifier(v);
1439
- }
1440
-
1441
- /**
1442
- * Parsed numeric value (number / percentage / dimension tokens). Derived from
1443
- * `value` on access — the `%` is dropped for percentages and the unit for
1444
- * dimensions (split with `_consumeANumber`, recomputed here so nothing is
1445
- * stored per token).
1446
- * @returns {number} the parsed numeric value
1447
- */
1448
- get numericValue() {
1449
- const v = this.value;
1450
- if (this.type === T_DIMENSION) {
1451
- return Number(v.slice(0, _consumeANumber(v, 0)));
1452
- }
1453
- if (this.type === T_PERCENTAGE) return Number(v.slice(0, -1));
1454
- return Number(v);
1455
- }
1456
-
1457
- /**
1458
- * Spec type flag. For number / dimension tokens it's "integer" / "number"
1459
- * (derived from `value`); for hash tokens it's "id" / "unrestricted" (re-derived
1460
- * from the source). The two senses share the name in the spec; both are computed
1461
- * on read so every leaf token keeps the same object shape (no own `typeFlag`).
1462
- * @returns {"integer" | "number" | "id" | "unrestricted"} the spec type flag
1463
- */
1464
- get typeFlag() {
1465
- if (this.type === T_HASH) {
1466
- // Re-derive id-ness from the source (whether the name after `#` starts an
1467
- // ident sequence) rather than storing an `_isId` slot — keeps hash tokens
1468
- // the same shape as every other leaf token. Matches the lexer's
1469
- // `consumeNumberSign`, which sets `isId` from the same check.
1470
- const input = this._locConverter._input;
1471
- const p = this.start + 1;
1472
- return _ifThreeCodePointsWouldStartAnIdentSequence(
1473
- input,
1474
- p,
1475
- input.charCodeAt(p),
1476
- input.charCodeAt(p + 1),
1477
- input.charCodeAt(p + 2)
1478
- )
1479
- ? "id"
1480
- : "unrestricted";
1481
- }
1482
- const v = this.value;
1483
- return _typeFlagOf(
1484
- this.type === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
1485
- );
1486
- }
1487
-
1488
- /**
1489
- * @returns {"+" | "-" | ""} the spec sign (number / percentage / dimension tokens)
1490
- */
1491
- get sign() {
1492
- return _signOf(this.value);
1493
- }
1494
-
1495
- /**
1496
- * @returns {string} the unit, lower-cased per spec (dimension tokens)
1497
- */
1498
- get unit() {
1499
- const v = this.value;
1500
- return v.slice(_consumeANumber(v, 0)).toLowerCase();
1501
- }
1502
- }
1503
-
1504
- /**
1505
- * The non-leaf `Node` subclass: functions, simple blocks, declarations, at-rules
1506
- * and qualified rules. It declares the union of every container field up front so
1507
- * all five share **one** hidden class — the consume algorithms only overwrite the
1508
- * slots relevant to their type, never adding a property, so the shape never
1509
- * transitions. This caps the shape count: the walker's hot `.type` / `.value` /
1510
- * `.prelude` loads otherwise see eight distinct node maps (five container types
1511
- * plus the token / hash / url token maps), tipping V8's inline cache into the
1512
- * slow megamorphic path; folding the five containers into one leaves four maps
1513
- * (token, hash, url, container) — within the polymorphic limit, so those loads
1514
- * stay inline-cached. Unused-for-the-type slots keep their defaults (the field
1515
- * typedefs below document which fields each type uses). The stylesheet node is
1516
- * rare (one per parse) and stays a bare `Node`.
1517
- */
1518
- class Container extends Node {
1519
- /**
1520
- * @param {number} type node type
1521
- * @param {number} start byte offset of the node's first code point
1522
- * @param {number} end byte offset just past the node's last code point
1523
- * @param {LocConverter} locConverter shared loc converter
1524
- */
1525
- constructor(type, start, end, locConverter) {
1526
- super(type, start, end, locConverter);
1527
- /** @type {string} name (function / at-rule / declaration) */
1528
- this.name = "";
1529
- /** @type {number} */
1530
- this.nameStart = start;
1531
- /** @type {number} */
1532
- this.nameEnd = start;
1533
- /** @type {ComponentValue[] | null} component values (function / block / declaration) */
1534
- this.value = null;
1535
- /** @type {ComponentValue[] | null} prelude (at-rule / qualified rule) */
1536
- this.prelude = null;
1537
- /** @type {Declaration[] | null} */
1538
- this.declarations = null;
1539
- /** @type {Rule[] | null} */
1540
- this.childRules = null;
1541
- /** @type {number} `{` start offset, or -1 (at-rule / qualified rule) */
1542
- this.blockStart = -1;
1543
- /** @type {number} `}` end offset, or -1 (at-rule / qualified rule) */
1544
- this.blockEnd = -1;
1545
- /** @type {boolean} stripped `!important` (declaration) */
1546
- this.important = false;
1547
- /** @type {SimpleBlockToken | undefined} opening char (simple block) */
1548
- this.token = undefined;
1549
- }
1550
- }
1551
1349
 
1552
1350
  /**
1553
1351
  * Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`).
@@ -1646,10 +1444,10 @@ class Container extends Node {
1646
1444
  * @typedef {Node & { rules: Rule[] }} Stylesheet
1647
1445
  */
1648
1446
 
1649
- // Lexer-token-type → AST-node-type map. A single \`new Token\` construct site
1650
- // (vs a ~20-case switch with a \`new Token\` in each arm) keeps V8 on the fast
1651
- // monomorphic allocation path — the switch form showed up as generic construct
1652
- // stubs in profiles. URL is the one type with extra own state, handled first.
1447
+ // Lexer-token-type → AST-node-type map. A single `_makeLeaf` call site (vs a
1448
+ // ~20-case switch with an alloc in each arm) keeps V8 on the fast monomorphic
1449
+ // path — the switch form showed up as generic stubs in profiles. URL is the one
1450
+ // type with extra own state, handled first.
1653
1451
  const _ttToNodeType = new Uint8Array(27);
1654
1452
  _ttToNodeType[TT_WHITESPACE] = T_WHITESPACE;
1655
1453
  _ttToNodeType[TT_IDENTIFIER] = T_IDENT;
@@ -1671,21 +1469,14 @@ _ttToNodeType[TT_RIGHT_CURLY_BRACKET] = T_RIGHT_CURLY_BRACKET;
1671
1469
  _ttToNodeType[TT_CDO] = T_CDO;
1672
1470
  _ttToNodeType[TT_CDC] = T_CDC;
1673
1471
 
1674
- // === AST construction backend ===
1675
- // The consume algorithms build nodes through these module-level primitives
1676
- // rather than `new Token` / `new Container` directly, so the node
1677
- // representation can be swapped under the parser. The object backend below
1678
- // builds the retainable `Node` / `Token` / `Container` tree the `parseA*`
1679
- // entry points return; the Struct-of-Arrays backend (added separately) writes
1680
- // the same nodes into reused typed arrays for the streaming `grammar`, where
1681
- // per-node allocation dominates cost. Child lists are plain arrays in both
1682
- // backends (a node ref is an object or an integer index); only node creation /
1683
- // field access differs, so only those ops are swapped.
1684
-
1685
- // Current loc converter for the active parse (object backend reads it).
1686
- let _objLocConverter = /** @type {LocConverter} */ (
1687
- /** @type {unknown} */ (null)
1688
- );
1472
+ // === AST construction ===
1473
+ // Nodes live in one struct-of-arrays node store: a node ref is an integer id
1474
+ // into parallel typed-array columns, so per-node allocation is avoided entirely.
1475
+ // The consume algorithms build nodes through the `_make*` / `_set*` primitives
1476
+ // below, which write those columns directly. The streaming `grammar` walks each
1477
+ // top-level node and recycles the columns; the `parseA*` entry points instead
1478
+ // retain the columns as a snapshot and hand back property-accessor nodes over it
1479
+ // (see `_makeReader`). Child lists are plain arrays of node ids in both modes.
1689
1480
 
1690
1481
  // Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
1691
1482
  // `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
@@ -1694,7 +1485,7 @@ let _objLocConverter = /** @type {LocConverter} */ (
1694
1485
  // / functions kept, so `url()` in a selector or `@import url(…)` still resolves).
1695
1486
  // A skipped node is still tokenized (positions stay correct) but never pushed,
1696
1487
  // so it is never walked or read — the caller must only skip what nothing reads.
1697
- // `useObjectBackend` restores the defaults so `parseA*` build the full tree.
1488
+ // `parseA*` leave these at their no-skip defaults so they build the full tree.
1698
1489
  const _NO_SKIP_TYPES = new Uint8Array(32);
1699
1490
  // Shared frozen empty list for block bodies with no decls / no child rules (the
1700
1491
  // common case — most rules carry only declarations). Every consumer reads these
@@ -1711,227 +1502,71 @@ let _skipActive = false;
1711
1502
  let _skipSelectorPrelude = false;
1712
1503
  let _skipAtRulePrelude = false;
1713
1504
 
1714
- /** @type {(type: number, start: number, end: number) => Node} */
1715
- let _makeLeaf;
1716
- /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1717
- let _makeUrl;
1718
- /** @type {(type: number, start: number, end: number) => Node} */
1719
- let _makeContainer;
1720
- /** @type {(start: number) => Node} */
1721
- let _makeStylesheet;
1722
- // Offsets only — the object backend derives the name string itself (an
1723
- // at-rule's name skips its `@`), so the SoA backend never pays for a slice
1724
- // it would immediately discard (it re-derives names from offsets on read).
1725
- /** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
1726
- let _setName;
1727
- /** @type {(r: Node, v: number) => void} */
1728
- let _setEnd;
1729
- /** @type {(r: Node, blockStart: number, blockEnd: number) => void} */
1730
- let _setBlock;
1731
- /** @type {(r: Node) => void} */
1732
- let _setImportant;
1733
- /** @type {(r: Node, ch: SimpleBlockToken) => void} */
1734
- let _setToken;
1735
- /** @type {(r: Node, list: Node[]) => void} */
1736
- let _setValue;
1737
- /** @type {(r: Node, list: Node[]) => void} */
1738
- let _setPrelude;
1739
- /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
1740
- let _setBody;
1741
- /** @type {(r: Node, list: Node[]) => void} */
1742
- let _setRules;
1743
- /** @type {(r: Node) => number} */
1744
- let _nodeTypeOf;
1745
- /** @type {(r: Node) => number} */
1746
- let _nodeStartOf;
1747
- /** @type {(r: Node) => string} */
1748
- let _nodeValueOf;
1749
- /** @type {(r: Node) => SimpleBlockToken} */
1750
- let _nodeTokenOf;
1751
-
1752
- // Child lists are plain arrays in every backend; refs are pushed by value.
1753
- // Content-list allocation: the object backend uses a per-site `[]` literal
1754
- // (each site keeps its own V8 allocation site — a shared helper collapses them
1755
- // and defeats escape analysis / pretenuring), the SoA backend recycles scratch
1756
- // arrays through a pool — a sealed list is copied into the flat value buffer
1757
- // and its array returned to the pool by `_soaSetValue`, so the streaming parse
1758
- // allocates no per-list array. An abandoned (never-sealed) list simply falls
1759
- // out of the pool. `_soaActive` gates the pool at each site.
1760
- let _soaActive = false;
1505
+ // Scratch content-list pool: a container's value / prelude is built in a plain
1506
+ // array, then sealed into the flat value buffer by `_setValue`, which returns
1507
+ // the array to the pool — so a parse allocates almost no per-list arrays. An
1508
+ // abandoned (never-sealed) list simply falls out of the pool.
1761
1509
  /** @type {Node[][]} */
1762
1510
  const _listPool = [];
1763
- const _soaList = () =>
1511
+ const _takeList = () =>
1764
1512
  _listPool.length > 0
1765
1513
  ? /** @type {Node[]} */ (_listPool.pop())
1766
1514
  : /** @type {Node[]} */ ([]);
1767
1515
 
1768
- // -- object backend: builds the retainable class-instance tree --
1769
- /** @type {typeof _makeLeaf} */
1770
- const _objLeaf = (type, start, end) =>
1771
- new Token(type, start, end, _objLocConverter);
1772
- /** @type {typeof _makeUrl} */
1773
- const _objUrl = (start, end, cs, ce) => {
1774
- const u = /** @type {UrlToken} */ (
1775
- new Token(T_URL, start, end, _objLocConverter)
1776
- );
1777
- u.contentStart = cs;
1778
- u.contentEnd = ce;
1779
- return u;
1780
- };
1781
- /** @type {typeof _makeContainer} */
1782
- const _objContainer = (type, start, end) =>
1783
- new Container(type, start, end, _objLocConverter);
1784
- /** @type {typeof _makeStylesheet} */
1785
- const _objStylesheet = (start) => {
1786
- const s = /** @type {Stylesheet} */ (
1787
- new Node(T_STYLESHEET, start, start, _objLocConverter)
1788
- );
1789
- s.rules = [];
1790
- return s;
1791
- };
1792
- // The backend function sets are module-level constants (not closures rebuilt
1793
- // per parse): the dispatch slots keep one function identity forever, so the
1794
- // per-node call sites in the consume algorithms stay monomorphic instead of
1795
- // seeing a fresh closure per parse. They capture only module-level state.
1796
- /** @type {typeof _setName} */
1797
- const _objSetName = (r, ns, ne) => {
1798
- const c = /** @type {Container} */ (r);
1799
- // An at-rule's `nameStart` points at its `@`, which the name excludes.
1800
- c.name = _objLocConverter._input.slice(
1801
- r.type === T_AT_RULE ? ns + 1 : ns,
1802
- ne
1803
- );
1804
- c.nameStart = ns;
1805
- c.nameEnd = ne;
1806
- };
1807
- /** @type {typeof _setEnd} */
1808
- const _objSetEnd = (r, v) => {
1809
- r.end = v;
1810
- };
1811
- /** @type {typeof _setBlock} */
1812
- const _objSetBlock = (r, bs, be) => {
1813
- const c = /** @type {Container} */ (r);
1814
- c.blockStart = bs;
1815
- c.blockEnd = be;
1816
- };
1817
- /** @type {typeof _setImportant} */
1818
- const _objSetImportant = (r) => {
1819
- /** @type {Container} */ (r).important = true;
1820
- };
1821
- /** @type {typeof _setToken} */
1822
- const _objSetToken = (r, ch) => {
1823
- /** @type {Container} */ (r).token = ch;
1824
- };
1825
- /** @type {typeof _setValue} */
1826
- const _objSetValue = (r, list) => {
1827
- /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1828
- };
1829
- /** @type {typeof _setPrelude} */
1830
- const _objSetPrelude = (r, list) => {
1831
- /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (list);
1832
- };
1833
- /** @type {typeof _setBody} */
1834
- const _objSetBody = (r, decls, childRules) => {
1835
- const c = /** @type {Container} */ (r);
1836
- c.declarations = /** @type {Declaration[]} */ (decls);
1837
- c.childRules = /** @type {Rule[]} */ (childRules);
1838
- };
1839
- /** @type {typeof _setRules} */
1840
- const _objSetRules = (r, list) => {
1841
- /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1842
- };
1843
- /** @type {typeof _nodeTypeOf} */
1844
- const _objNodeTypeOf = (r) => r.type;
1845
- /** @type {typeof _nodeStartOf} */
1846
- const _objNodeStartOf = (r) => r.start;
1847
- /** @type {typeof _nodeValueOf} */
1848
- const _objNodeValueOf = (r) => /** @type {Token} */ (r).value;
1849
- /** @type {typeof _nodeTokenOf} */
1850
- const _objNodeTokenOf = (r) =>
1851
- /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1852
- /** @param {LocConverter} lc loc converter for this parse */
1853
- const useObjectBackend = (lc) => {
1854
- _objLocConverter = lc;
1855
- _soaActive = false;
1856
- // `parseA*` return the full tree, so nothing is skipped.
1857
- _skipTypes = _NO_SKIP_TYPES;
1858
- _skipActive = false;
1859
- _skipSelectorPrelude = false;
1860
- _skipAtRulePrelude = false;
1861
- _makeLeaf = _objLeaf;
1862
- _makeUrl = _objUrl;
1863
- _makeContainer = _objContainer;
1864
- _makeStylesheet = _objStylesheet;
1865
- _setName = _objSetName;
1866
- _setEnd = _objSetEnd;
1867
- _setBlock = _objSetBlock;
1868
- _setImportant = _objSetImportant;
1869
- _setToken = _objSetToken;
1870
- _setValue = _objSetValue;
1871
- _setPrelude = _objSetPrelude;
1872
- _setBody = _objSetBody;
1873
- _setRules = _objSetRules;
1874
- _nodeTypeOf = _objNodeTypeOf;
1875
- _nodeStartOf = _objNodeStartOf;
1876
- _nodeValueOf = _objNodeValueOf;
1877
- _nodeTokenOf = _objNodeTokenOf;
1878
- };
1879
-
1880
- // -- struct-of-arrays backend: writes nodes into reused typed arrays --
1516
+ // -- struct-of-arrays store: nodes live in reused typed-array columns --
1881
1517
  // A node ref is its integer id; fields live in parallel arrays indexed by id.
1882
- // Three reused int slots (`_soaAux0/1/2`) plus a flags byte carry the per-type
1518
+ // Two reused int slots (`_aux0/1`) plus a flags byte carry the per-type
1883
1519
  // extras; child lists hang off three object arrays. Aux slot meaning by type:
1884
1520
  // url: aux0 contentStart, aux1 contentEnd
1885
1521
  // function: aux0 nameEnd
1886
1522
  // declaration: aux0 nameEnd, flags bit0 important
1887
- // at-rule: aux0 nameEnd, aux1 blockStart, aux2 blockEnd
1888
- // qualified: aux1 blockStart, aux2 blockEnd
1523
+ // at-rule: aux0 nameEnd, aux1 blockStart (blockEnd == end)
1524
+ // qualified: aux1 blockStart (blockEnd == end)
1889
1525
  // `name` / `nameStart` / a simple block's `token` are derived from the source
1890
- // on read (see the SoA accessors), so they need no slot. A node's main content
1891
- // (value | prelude | stylesheet rules) is a `_soaFlat` span (see below).
1892
- // `grammar` resets `_soaNodeCount` to 0 after each top-level rule's walk, so the
1526
+ // on read (see the accessors), so they need no slot. A node's main content
1527
+ // (value | prelude | stylesheet rules) is a `_flat` span (see below).
1528
+ // `grammar` resets `_nodeCount` to 0 after each top-level rule's walk, so the
1893
1529
  // buffers are reused across rules and the parse allocates almost nothing.
1894
- let _soaCapacity = 0;
1895
- let _soaNodeCount = 0;
1896
- let _soaTypes = new Uint8Array(0);
1897
- let _soaStarts = new Int32Array(0);
1898
- let _soaEnds = new Int32Array(0);
1899
- let _soaAux0 = new Int32Array(0);
1900
- let _soaAux1 = new Int32Array(0);
1901
- let _soaAux2 = new Int32Array(0);
1902
- let _soaFlags = new Uint8Array(0);
1903
- // Content-list spans: a container's value / prelude is `_soaFlat[start, start+len)`
1530
+ let _capacity = 0;
1531
+ let _nodeCount = 0;
1532
+ let _types = new Uint8Array(0);
1533
+ let _starts = new Int32Array(0);
1534
+ let _ends = new Int32Array(0);
1535
+ let _aux0 = new Int32Array(0);
1536
+ let _aux1 = new Int32Array(0);
1537
+ let _flags = new Uint8Array(0);
1538
+ // Content-list spans: a container's value / prelude is `_flat[start, start+len)`
1904
1539
  // (node refs), recycled per top-level rule like the node columns.
1905
- let _soaListStarts = new Int32Array(0);
1906
- let _soaListLens = new Int32Array(0);
1907
- let _soaFlat = new Int32Array(0);
1908
- let _soaFlatTop = 0;
1540
+ let _listStarts = new Int32Array(0);
1541
+ let _listLens = new Int32Array(0);
1542
+ let _flat = new Int32Array(0);
1543
+ let _flatTop = 0;
1909
1544
  // Peak usage of the current parse, and use-once regrow hints: after an
1910
1545
  // over-capacity shrink the next grow jumps straight back to the previous
1911
1546
  // parse's peak (one exact-fit allocation instead of re-doubling up).
1912
- let _soaPeak = 0;
1913
- let _soaFlatPeak = 0;
1914
- let _soaGrowHint = 0;
1915
- let _soaFlatGrowHint = 0;
1547
+ let _peak = 0;
1548
+ let _flatPeak = 0;
1549
+ let _growHint = 0;
1550
+ let _flatGrowHint = 0;
1916
1551
 
1917
1552
  /** @param {number} need minimum flat-buffer capacity */
1918
- const _soaFlatGrow = (need) => {
1919
- let cap = _soaFlat.length || 4096;
1920
- if (_soaFlatGrowHint > cap) cap = _soaFlatGrowHint;
1921
- _soaFlatGrowHint = 0;
1553
+ const _flatGrow = (need) => {
1554
+ let cap = _flat.length || 4096;
1555
+ if (_flatGrowHint > cap) cap = _flatGrowHint;
1556
+ _flatGrowHint = 0;
1922
1557
  while (cap < need) cap *= 2;
1923
1558
  const next = new Int32Array(cap);
1924
- next.set(_soaFlat);
1925
- _soaFlat = next;
1559
+ next.set(_flat);
1560
+ _flat = next;
1926
1561
  };
1562
+ // Reassigned (not mutated) when a `parseA*` parse hands its columns to a
1563
+ // retained snapshot, so the next parse starts on fresh arrays.
1927
1564
  /** @type {(Node[] | null)[]} */
1928
- const _soaDeclarationLists = [];
1565
+ let _declarationLists = [];
1929
1566
  /** @type {(Node[] | null)[]} */
1930
- const _soaChildRuleLists = [];
1931
- let _soaInput = "";
1932
- let _soaLocConverter = /** @type {LocConverter} */ (
1933
- /** @type {unknown} */ (null)
1934
- );
1567
+ let _childRuleLists = [];
1568
+ let _input = "";
1569
+ let _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1935
1570
 
1936
1571
  // Node refs are integers here but typed `Node` across the parser; these are
1937
1572
  // identity casts that just satisfy the type system at the boundary.
@@ -1941,175 +1576,181 @@ const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1941
1576
  const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1942
1577
 
1943
1578
  /** @param {number} need minimum capacity */
1944
- const _soaGrow = (need) => {
1945
- let cap = _soaCapacity || 4096;
1946
- if (_soaGrowHint > cap) cap = _soaGrowHint;
1947
- _soaGrowHint = 0;
1579
+ const _grow = (need) => {
1580
+ let cap = _capacity || 4096;
1581
+ if (_growHint > cap) cap = _growHint;
1582
+ _growHint = 0;
1948
1583
  while (cap < need) cap *= 2;
1949
1584
  const ty = new Uint8Array(cap);
1950
- ty.set(_soaTypes);
1951
- _soaTypes = ty;
1585
+ ty.set(_types);
1586
+ _types = ty;
1952
1587
  const st = new Int32Array(cap);
1953
- st.set(_soaStarts);
1954
- _soaStarts = st;
1588
+ st.set(_starts);
1589
+ _starts = st;
1955
1590
  const en = new Int32Array(cap);
1956
- en.set(_soaEnds);
1957
- _soaEnds = en;
1591
+ en.set(_ends);
1592
+ _ends = en;
1958
1593
  const a0 = new Int32Array(cap);
1959
- a0.set(_soaAux0);
1960
- _soaAux0 = a0;
1594
+ a0.set(_aux0);
1595
+ _aux0 = a0;
1961
1596
  const a1 = new Int32Array(cap);
1962
- a1.set(_soaAux1);
1963
- _soaAux1 = a1;
1964
- const a2 = new Int32Array(cap);
1965
- a2.set(_soaAux2);
1966
- _soaAux2 = a2;
1597
+ a1.set(_aux1);
1598
+ _aux1 = a1;
1967
1599
  const fl = new Uint8Array(cap);
1968
- fl.set(_soaFlags);
1969
- _soaFlags = fl;
1600
+ fl.set(_flags);
1601
+ _flags = fl;
1970
1602
  const ls = new Int32Array(cap);
1971
- ls.set(_soaListStarts);
1972
- _soaListStarts = ls;
1603
+ ls.set(_listStarts);
1604
+ _listStarts = ls;
1973
1605
  const ll = new Int32Array(cap);
1974
- ll.set(_soaListLens);
1975
- _soaListLens = ll;
1976
- _soaCapacity = cap;
1606
+ ll.set(_listLens);
1607
+ _listLens = ll;
1608
+ _capacity = cap;
1977
1609
  };
1978
1610
  /** @type {(type: number, start: number, end: number) => Node} */
1979
- const _soaAllocNode = (type, start, end) => {
1611
+ const _makeLeaf = (type, start, end) => {
1980
1612
  // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1981
1613
  // so 0 must stay reserved for "no node".
1982
- // Leaves never read the flag / list slots — `_soaAllocContainer` clears
1614
+ // Leaves never read the flag / list slots — `_makeContainer` clears
1983
1615
  // them instead, keeping the dominant leaf allocation at three writes.
1984
- const i = _soaNodeCount + 1;
1985
- if (i >= _soaCapacity) _soaGrow(i + 1);
1986
- _soaTypes[i] = type;
1987
- _soaStarts[i] = start;
1988
- _soaEnds[i] = end;
1989
- _soaNodeCount = i;
1616
+ const i = _nodeCount + 1;
1617
+ if (i >= _capacity) _grow(i + 1);
1618
+ _types[i] = type;
1619
+ _starts[i] = start;
1620
+ _ends[i] = end;
1621
+ _nodeCount = i;
1990
1622
  return _nodeRef(i);
1991
1623
  };
1992
1624
  /** @type {(type: number, start: number, end: number) => Node} */
1993
- const _soaAllocContainer = (type, start, end) => {
1994
- const r = _soaAllocNode(type, start, end);
1625
+ const _makeContainer = (type, start, end) => {
1626
+ const r = _makeLeaf(type, start, end);
1995
1627
  const i = _nodeIndex(r);
1996
- _soaFlags[i] = 0;
1997
- // Clear list slots so a reused id never exposes a previous node's children
1998
- // (content lists are flat spans, so zeroing the length suffices).
1999
- _soaListLens[i] = 0;
2000
- _soaDeclarationLists[i] = null;
2001
- _soaChildRuleLists[i] = null;
1628
+ _flags[i] = 0;
1629
+ // Clear the content-span length so a reused id never exposes a previous
1630
+ // node's children (content lists are flat spans, so zeroing the length
1631
+ // suffices). `blockStart` (aux1) is NOT defaulted here: only at-rules and
1632
+ // qualified rules carry a block, and they set it on every return path
1633
+ // (`_setBlock`, or `-1` for the no-block forms). `blockEnd` is not stored — a
1634
+ // block rule's `end` is its `blockEnd` (see `_setBlock`), else it is `-1`.
1635
+ _listLens[i] = 0;
1636
+ // The decl / child-rule slots are read only for rules (the walk guards on
1637
+ // type; readers treat a missing slot as `null`), so only rules clear them —
1638
+ // clearing every container would write these `id`-indexed arrays at scattered
1639
+ // ids and degrade them to slow dictionary mode on large non-recycling parses.
1640
+ if (type === T_AT_RULE || type === T_QUALIFIED_RULE) {
1641
+ _declarationLists[i] = null;
1642
+ _childRuleLists[i] = null;
1643
+ }
2002
1644
  return r;
2003
1645
  };
2004
1646
  // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
2005
1647
  // one-char prefix, url uses its content range. Shared by the parser's
2006
- // mid-parse reads and the SoA accessor.
1648
+ // mid-parse reads and the accessor.
2007
1649
  /**
2008
1650
  * @param {number} i node id
2009
1651
  * @returns {string} raw token value
2010
1652
  */
2011
- const _soaValueOf = (i) => {
2012
- const ty = _soaTypes[i];
1653
+ const _valueOf = (i) => {
1654
+ const ty = _types[i];
2013
1655
  if (ty === T_HASH || ty === T_AT_KEYWORD) {
2014
- return _soaInput.slice(_soaStarts[i] + 1, _soaEnds[i]);
1656
+ return _input.slice(_starts[i] + 1, _ends[i]);
2015
1657
  }
2016
- if (ty === T_URL) return _soaInput.slice(_soaAux0[i], _soaAux1[i]);
2017
- return _soaInput.slice(_soaStarts[i], _soaEnds[i]);
1658
+ if (ty === T_URL) return _input.slice(_aux0[i], _aux1[i]);
1659
+ return _input.slice(_starts[i], _ends[i]);
2018
1660
  };
2019
- // Module-level constants for the same reason as the `_objSet*` set above.
2020
- /** @type {typeof _makeUrl} */
2021
- const _soaMakeUrl = (start, end, cs, ce) => {
2022
- const r = _soaAllocNode(T_URL, start, end);
2023
- _soaAux0[_nodeIndex(r)] = cs;
2024
- _soaAux1[_nodeIndex(r)] = ce;
1661
+ // Module-level constants (not per-parse closures), so each consume-algorithm
1662
+ // call site keeps one function identity and stays monomorphic.
1663
+ /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1664
+ const _makeUrl = (start, end, cs, ce) => {
1665
+ const r = _makeLeaf(T_URL, start, end);
1666
+ _aux0[_nodeIndex(r)] = cs;
1667
+ _aux1[_nodeIndex(r)] = ce;
2025
1668
  return r;
2026
1669
  };
2027
- /** @type {typeof _makeStylesheet} */
2028
- const _soaMakeStylesheet = (start) =>
2029
- _soaAllocContainer(T_STYLESHEET, start, start);
1670
+ /** @type {(start: number) => Node} */
1671
+ const _makeStylesheet = (start) => _makeContainer(T_STYLESHEET, start, start);
2030
1672
  // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
2031
- /** @type {typeof _setName} */
2032
- const _soaSetName = (r, ns, ne) => {
2033
- _soaAux0[_nodeIndex(r)] = ne;
1673
+ /** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
1674
+ const _setName = (r, ns, ne) => {
1675
+ _aux0[_nodeIndex(r)] = ne;
2034
1676
  };
2035
- /** @type {typeof _setEnd} */
2036
- const _soaSetEnd = (r, v) => {
2037
- _soaEnds[_nodeIndex(r)] = v;
1677
+ /** @type {(r: Node, v: number) => void} */
1678
+ const _setEnd = (r, v) => {
1679
+ _ends[_nodeIndex(r)] = v;
2038
1680
  };
2039
- /** @type {typeof _setBlock} */
2040
- const _soaSetBlock = (r, bs, be) => {
2041
- const i = _nodeIndex(r);
2042
- _soaAux1[i] = bs;
2043
- _soaAux2[i] = be;
1681
+ // `blockEnd` is not stored: for a block rule the parser sets `end` to the
1682
+ // block end right after this (so `A.blockEnd` reads `end`); a `-1` blockStart
1683
+ // marks the no-block forms, where `blockEnd` derives to `-1`.
1684
+ /** @type {(r: Node, blockStart: number) => void} */
1685
+ const _setBlock = (r, bs) => {
1686
+ _aux1[_nodeIndex(r)] = bs;
2044
1687
  };
2045
- /** @type {typeof _setImportant} */
2046
- const _soaSetImportant = (r) => {
2047
- _soaFlags[_nodeIndex(r)] |= 1;
1688
+ /** @type {(r: Node) => void} */
1689
+ const _setImportant = (r) => {
1690
+ _flags[_nodeIndex(r)] |= 1;
2048
1691
  };
2049
1692
  // A simple block's token is derived from its opening char on read.
2050
- /** @type {typeof _setToken} */
2051
- const _soaSetToken = (r, ch) => {};
2052
- /** @type {typeof _setValue} */
2053
- const _soaSetValue = (r, list) => {
1693
+ /** @type {(r: Node, ch: SimpleBlockToken) => void} */
1694
+ const _setToken = (r, ch) => {};
1695
+ /** @type {(r: Node, list: Node[]) => void} */
1696
+ const _setValue = (r, list) => {
2054
1697
  // Seal the finished list: copy its refs into the flat buffer and hand the
2055
1698
  // scratch array back to the pool. The caller never touches `list` again.
2056
- const i = _nodeIndex(r);
2057
1699
  const len = list.length;
2058
- const start = _soaFlatTop;
2059
- if (start + len > _soaFlat.length) _soaFlatGrow(start + len);
2060
- for (let k = 0; k < len; k++) {
2061
- _soaFlat[start + k] = _nodeIndex(list[k]);
2062
- }
2063
- _soaFlatTop = start + len;
2064
- _soaListStarts[i] = start;
2065
- _soaListLens[i] = len;
2066
- list.length = 0;
1700
+ // Empty seal (common in non-modules skip mode, where value/prelude leaves are
1701
+ // dropped): `_makeContainer` already left `_listLens` at 0, so skip the flat
1702
+ // writes entirely and just recycle the scratch array.
1703
+ if (len !== 0) {
1704
+ const i = _nodeIndex(r);
1705
+ const start = _flatTop;
1706
+ if (start + len > _flat.length) _flatGrow(start + len);
1707
+ for (let k = 0; k < len; k++) {
1708
+ _flat[start + k] = _nodeIndex(list[k]);
1709
+ }
1710
+ _flatTop = start + len;
1711
+ _listStarts[i] = start;
1712
+ _listLens[i] = len;
1713
+ list.length = 0;
1714
+ }
2067
1715
  _listPool.push(list);
2068
1716
  };
2069
- /** @type {typeof _setBody} */
2070
- const _soaSetBody = (r, decls, childRules) => {
1717
+ /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
1718
+ const _setBody = (r, decls, childRules) => {
2071
1719
  const i = _nodeIndex(r);
2072
- _soaDeclarationLists[i] = decls;
2073
- _soaChildRuleLists[i] = childRules;
2074
- };
2075
- /** @type {typeof _nodeTypeOf} */
2076
- const _soaNodeTypeOf = (r) => _soaTypes[_nodeIndex(r)];
2077
- /** @type {typeof _nodeStartOf} */
2078
- const _soaNodeStartOf = (r) => _soaStarts[_nodeIndex(r)];
2079
- /** @type {typeof _nodeValueOf} */
2080
- const _soaNodeValueOf = (r) => _soaValueOf(_nodeIndex(r));
2081
- /** @type {typeof _nodeTokenOf} */
2082
- const _soaNodeTokenOf = (r) =>
2083
- /** @type {SimpleBlockToken} */ (_soaInput[_soaStarts[_nodeIndex(r)]]);
2084
- /**
1720
+ _declarationLists[i] = decls;
1721
+ _childRuleLists[i] = childRules;
1722
+ };
1723
+ /** @type {(r: Node) => number} */
1724
+ const _nodeTypeOf = (r) => _types[_nodeIndex(r)];
1725
+ /** @type {(r: Node) => number} */
1726
+ const _nodeStartOf = (r) => _starts[_nodeIndex(r)];
1727
+ /** @type {(r: Node) => string} */
1728
+ const _nodeValueOf = (r) => _valueOf(_nodeIndex(r));
1729
+ /** @type {(r: Node) => SimpleBlockToken} */
1730
+ const _nodeTokenOf = (r) =>
1731
+ /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(r)]]);
1732
+ // A container's value, a rule's prelude, and a stylesheet's rules all seal into
1733
+ // the one content-list writer, so `_setPrelude` / `_setRules` are named views of
1734
+ // `_setValue` that keep the consume algorithms reading in spec terms.
1735
+ const _setPrelude = _setValue;
1736
+ const _setRules = _setValue;
1737
+
1738
+ /**
1739
+ * Start a parse into the store: point it at this source, reset the node /
1740
+ * flat cursors so nodes accumulate from id 1, and clear skip state (`grammar`
1741
+ * sets its own afterwards; `parseA*` leave it off to build the full tree).
2085
1742
  * @param {string} input source
2086
1743
  * @param {LocConverter} lc loc converter
2087
1744
  */
2088
- const useSoaBackend = (input, lc) => {
2089
- _soaInput = input;
2090
- _soaLocConverter = lc;
2091
- _soaNodeCount = 0;
2092
- _soaFlatTop = 0;
2093
- _soaActive = true;
2094
- // The alloc primitives are the slot functions directly — no wrapper hop.
2095
- _makeLeaf = _soaAllocNode;
2096
- _makeUrl = _soaMakeUrl;
2097
- _makeContainer = _soaAllocContainer;
2098
- _makeStylesheet = _soaMakeStylesheet;
2099
- _setName = _soaSetName;
2100
- _setEnd = _soaSetEnd;
2101
- _setBlock = _soaSetBlock;
2102
- _setImportant = _soaSetImportant;
2103
- _setToken = _soaSetToken;
2104
- _setValue = _soaSetValue;
2105
- // value / prelude / rules all land in the one content-list slot.
2106
- _setPrelude = _soaSetValue;
2107
- _setBody = _soaSetBody;
2108
- _setRules = _soaSetValue;
2109
- _nodeTypeOf = _soaNodeTypeOf;
2110
- _nodeStartOf = _soaNodeStartOf;
2111
- _nodeValueOf = _soaNodeValueOf;
2112
- _nodeTokenOf = _soaNodeTokenOf;
1745
+ const _setupParse = (input, lc) => {
1746
+ _input = input;
1747
+ _locConverter = lc;
1748
+ _nodeCount = 0;
1749
+ _flatTop = 0;
1750
+ _skipTypes = _NO_SKIP_TYPES;
1751
+ _skipActive = false;
1752
+ _skipSelectorPrelude = false;
1753
+ _skipAtRulePrelude = false;
2113
1754
  };
2114
1755
 
2115
1756
  /**
@@ -2247,6 +1888,20 @@ class TokenStream {
2247
1888
  }
2248
1889
  }
2249
1890
 
1891
+ /**
1892
+ * Advance past the already-peeked next token, skipping the redundant `next()`
1893
+ * re-check `consume` / `discard` pay. Precondition: the caller has just called
1894
+ * `next()` (so `_tok` is the cached next token and `_hasNext` is true) and that
1895
+ * token is not the `<eof-token>` — the hot "peek, decide, advance" sites where
1896
+ * both always hold. Callers that can't guarantee a non-EOF cached token use
1897
+ * `consume` / `discard` instead.
1898
+ * @returns {void}
1899
+ */
1900
+ advance() {
1901
+ this._pos = this._tok.end;
1902
+ this._hasNext = false;
1903
+ }
1904
+
2250
1905
  /**
2251
1906
  * Mark (CSS Syntax §3 "mark") — push the current cursor position.
2252
1907
  * @returns {void}
@@ -2306,6 +1961,353 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
2306
1961
  * @property {((input: string, start: number, end: number) => number)=} comment optional comment-token callback; the public `parse*` entry points use it to build the `TokenStream` so the outer parser's comment tracker still sees magic comments inside the consumed range
2307
1962
  */
2308
1963
 
1964
+ // === parseA* retained store + property-accessor readers ===
1965
+ // `grammar` recycles the columns per top-level rule, but the `parseA*` entry
1966
+ // points must hand back a tree that outlives the parse. Each `parseA*` run parses
1967
+ // into the columns without recycling, then `_finishStore` hands them to a
1968
+ // snapshot object and resets the module columns to fresh arrays so the next parse
1969
+ // can't clobber it. Nodes are exposed as `parseA*` readers: plain objects sharing
1970
+ // one module-level prototype whose getters index the reader's own snapshot by node
1971
+ // id — no per-node class, no eager string slices, child readers built lazily on
1972
+ // access. A single shared prototype (rather than one per store) keeps the readers
1973
+ // monomorphic across parses, so `_readerAt` and every getter stay on the fast path.
1974
+
1975
+ /**
1976
+ * @typedef {object} NodeReader
1977
+ * @property {NodeStore} _store snapshot this reader indexes
1978
+ * @property {number} _i node id into the snapshot columns
1979
+ * @property {number} type node type
1980
+ * @property {number} start start offset
1981
+ * @property {number} end end offset
1982
+ * @property {[number, number]} range start / end offsets
1983
+ * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location
1984
+ * @property {() => string} toString source slice
1985
+ * @property {string | ComponentValue[]} value token value (leaf) or component-value list (function / block / declaration)
1986
+ * @property {string} unescaped unescaped token value
1987
+ * @property {number} numericValue parsed numeric value
1988
+ * @property {"integer" | "number" | "id" | "unrestricted"} typeFlag spec type flag
1989
+ * @property {"+" | "-" | ""} sign spec sign
1990
+ * @property {string} unit dimension unit (lower-cased)
1991
+ * @property {number} contentStart url content start offset
1992
+ * @property {number} contentEnd url content end offset
1993
+ * @property {string} name rule / declaration / function name
1994
+ * @property {number} nameStart name start offset
1995
+ * @property {number} nameEnd name end offset
1996
+ * @property {string} unescapedName unescaped name
1997
+ * @property {ComponentValue[]} prelude rule prelude
1998
+ * @property {Declaration[] | null} declarations block declarations
1999
+ * @property {Rule[] | null} childRules block child rules
2000
+ * @property {number} blockStart `{` start offset
2001
+ * @property {number} blockEnd `}` end offset
2002
+ * @property {boolean} important `!important` flag
2003
+ * @property {SimpleBlockToken} token simple-block opening char
2004
+ * @property {Rule[]} rules stylesheet rules
2005
+ */
2006
+
2007
+ /**
2008
+ * @typedef {object} NodeStore
2009
+ * @property {string} input source
2010
+ * @property {LocConverter} lc loc converter
2011
+ * @property {Uint8Array} types node-type column
2012
+ * @property {Int32Array} starts start-offset column
2013
+ * @property {Int32Array} ends end-offset column
2014
+ * @property {Int32Array} aux0 aux slot 0
2015
+ * @property {Int32Array} aux1 aux slot 1
2016
+ * @property {Uint8Array} flags flags column
2017
+ * @property {Int32Array} listStarts content-span start column
2018
+ * @property {Int32Array} listLens content-span length column
2019
+ * @property {Int32Array} flat flat node-ref buffer
2020
+ * @property {(Node[] | null)[]} declLists per-node declaration lists
2021
+ * @property {(Node[] | null)[]} childLists per-node child-rule lists
2022
+ */
2023
+
2024
+ // Shared frozen empty list for a block body with no declarations / child rules,
2025
+ // so equal-empty reads return one reference (mirrors `_EMPTY_LIST`).
2026
+ const _READER_EMPTY = /** @type {Rule[]} */ (
2027
+ /** @type {unknown} */ (Object.freeze([]))
2028
+ );
2029
+
2030
+ /**
2031
+ * Raw token value over a snapshot (the lazy `Token.value` form): hash / at-keyword
2032
+ * drop their one-char prefix, url uses its content range.
2033
+ * @param {NodeStore} store snapshot
2034
+ * @param {number} i node id
2035
+ * @returns {string} raw token value
2036
+ */
2037
+ const _storeValueOf = (store, i) => {
2038
+ const ty = store.types[i];
2039
+ if (ty === T_HASH || ty === T_AT_KEYWORD) {
2040
+ return store.input.slice(store.starts[i] + 1, store.ends[i]);
2041
+ }
2042
+ if (ty === T_URL) return store.input.slice(store.aux0[i], store.aux1[i]);
2043
+ return store.input.slice(store.starts[i], store.ends[i]);
2044
+ };
2045
+
2046
+ /**
2047
+ * @param {NodeStore} store snapshot
2048
+ * @param {number} i node id
2049
+ * @returns {Node} reader over node `i`
2050
+ */
2051
+ const _readerAt = (store, i) => {
2052
+ const o = Object.create(NODE_READER_PROTO);
2053
+ o._store = store;
2054
+ o._i = i;
2055
+ return /** @type {Node} */ (o);
2056
+ };
2057
+
2058
+ /**
2059
+ * Readers over a container's flat content span (value / prelude / rules).
2060
+ * @param {NodeStore} store snapshot
2061
+ * @param {number} i container id
2062
+ * @returns {Node[]} child readers
2063
+ */
2064
+ const _readList = (store, i) => {
2065
+ const s = store.listStarts[i];
2066
+ const len = store.listLens[i];
2067
+ const flat = store.flat;
2068
+ /** @type {Node[]} */
2069
+ const out = [];
2070
+ for (let k = 0; k < len; k++) out.push(_readerAt(store, flat[s + k]));
2071
+ return out;
2072
+ };
2073
+
2074
+ /**
2075
+ * Readers over a node-id list (declarations / child rules).
2076
+ * @param {NodeStore} store snapshot
2077
+ * @param {Node[]} list node-id list
2078
+ * @returns {Node[]} child readers
2079
+ */
2080
+ const _readRefList = (store, list) => {
2081
+ /** @type {Node[]} */
2082
+ const out = [];
2083
+ for (let k = 0; k < list.length; k++) {
2084
+ out.push(_readerAt(store, _nodeIndex(list[k])));
2085
+ }
2086
+ return out;
2087
+ };
2088
+
2089
+ // Module-level reader prototype shared by every `parseA*` reader. Getters index
2090
+ // the reader's own `_store` snapshot by its `_i` node id; because the prototype is
2091
+ // created once (not per store), all readers share one hidden map and stay
2092
+ // monomorphic across parses.
2093
+ const NODE_READER_PROTO = /** @type {NodeReader} */ ({
2094
+ _store: /** @type {NodeStore} */ (/** @type {unknown} */ (null)),
2095
+ _i: 0,
2096
+ get type() {
2097
+ return this._store.types[this._i];
2098
+ },
2099
+ get start() {
2100
+ return this._store.starts[this._i];
2101
+ },
2102
+ get end() {
2103
+ return this._store.ends[this._i];
2104
+ },
2105
+ get range() {
2106
+ const store = this._store;
2107
+ const i = this._i;
2108
+ return /** @type {[number, number]} */ ([store.starts[i], store.ends[i]]);
2109
+ },
2110
+ get loc() {
2111
+ const store = this._store;
2112
+ const i = this._i;
2113
+ const lc = store.lc;
2114
+ // `LocConverter#get` mutates and returns itself, so snapshot the first.
2115
+ const s = lc.get(store.starts[i]);
2116
+ const sl = s.line;
2117
+ const sc = s.column;
2118
+ const e = lc.get(store.ends[i]);
2119
+ return {
2120
+ start: { line: sl, column: sc },
2121
+ end: { line: e.line, column: e.column }
2122
+ };
2123
+ },
2124
+ toString() {
2125
+ const store = this._store;
2126
+ const i = this._i;
2127
+ return store.input.slice(store.starts[i], store.ends[i]);
2128
+ },
2129
+ get value() {
2130
+ const store = this._store;
2131
+ const i = this._i;
2132
+ const ty = store.types[i];
2133
+ // function / simple-block / declaration expose their component-value
2134
+ // list; every leaf token exposes its raw string value.
2135
+ return ty === T_FUNCTION || ty === T_SIMPLE_BLOCK || ty === T_DECLARATION
2136
+ ? /** @type {ComponentValue[]} */ (_readList(store, i))
2137
+ : _storeValueOf(store, i);
2138
+ },
2139
+ get unescaped() {
2140
+ const store = this._store;
2141
+ const i = this._i;
2142
+ const v = _storeValueOf(store, i);
2143
+ return store.types[i] === T_STRING
2144
+ ? unescapeIdentifier(v.slice(1, -1))
2145
+ : unescapeIdentifier(v);
2146
+ },
2147
+ get numericValue() {
2148
+ const store = this._store;
2149
+ const i = this._i;
2150
+ const v = _storeValueOf(store, i);
2151
+ if (store.types[i] === T_DIMENSION) {
2152
+ return Number(v.slice(0, _consumeANumber(v, 0)));
2153
+ }
2154
+ if (store.types[i] === T_PERCENTAGE) return Number(v.slice(0, -1));
2155
+ return Number(v);
2156
+ },
2157
+ get typeFlag() {
2158
+ const store = this._store;
2159
+ const i = this._i;
2160
+ if (store.types[i] === T_HASH) {
2161
+ const input = store.input;
2162
+ const p = store.starts[i] + 1;
2163
+ return _ifThreeCodePointsWouldStartAnIdentSequence(
2164
+ input,
2165
+ p,
2166
+ input.charCodeAt(p),
2167
+ input.charCodeAt(p + 1),
2168
+ input.charCodeAt(p + 2)
2169
+ )
2170
+ ? "id"
2171
+ : "unrestricted";
2172
+ }
2173
+ const v = _storeValueOf(store, i);
2174
+ return _typeFlagOf(
2175
+ store.types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
2176
+ );
2177
+ },
2178
+ get sign() {
2179
+ return _signOf(_storeValueOf(this._store, this._i));
2180
+ },
2181
+ get unit() {
2182
+ const v = _storeValueOf(this._store, this._i);
2183
+ return v.slice(_consumeANumber(v, 0)).toLowerCase();
2184
+ },
2185
+ get contentStart() {
2186
+ return this._store.aux0[this._i];
2187
+ },
2188
+ get contentEnd() {
2189
+ return this._store.aux1[this._i];
2190
+ },
2191
+ get name() {
2192
+ const store = this._store;
2193
+ const i = this._i;
2194
+ // An at-rule's name skips its `@`; others start at the node.
2195
+ return store.types[i] === T_AT_RULE
2196
+ ? store.input.slice(store.starts[i] + 1, store.aux0[i])
2197
+ : store.input.slice(store.starts[i], store.aux0[i]);
2198
+ },
2199
+ get nameStart() {
2200
+ return this._store.starts[this._i];
2201
+ },
2202
+ get nameEnd() {
2203
+ return this._store.aux0[this._i];
2204
+ },
2205
+ get unescapedName() {
2206
+ return unescapeIdentifier(this.name);
2207
+ },
2208
+ get prelude() {
2209
+ return /** @type {ComponentValue[]} */ (_readList(this._store, this._i));
2210
+ },
2211
+ get declarations() {
2212
+ const store = this._store;
2213
+ // Non-rule containers never populate this slot (see `_makeContainer`), so a
2214
+ // missing entry (`undefined`) is normalized to `null` — same as a rule with
2215
+ // no block.
2216
+ const list = store.declLists[this._i] || null;
2217
+ return list === null
2218
+ ? null
2219
+ : /** @type {Declaration[]} */ (
2220
+ list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
2221
+ );
2222
+ },
2223
+ get childRules() {
2224
+ const store = this._store;
2225
+ const list = store.childLists[this._i] || null;
2226
+ return list === null
2227
+ ? null
2228
+ : /** @type {Rule[]} */ (
2229
+ list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
2230
+ );
2231
+ },
2232
+ get blockStart() {
2233
+ return this._store.aux1[this._i];
2234
+ },
2235
+ get blockEnd() {
2236
+ const store = this._store;
2237
+ const i = this._i;
2238
+ return store.aux1[i] !== -1 ? store.ends[i] : -1;
2239
+ },
2240
+ get important() {
2241
+ return (this._store.flags[this._i] & 1) !== 0;
2242
+ },
2243
+ get token() {
2244
+ const store = this._store;
2245
+ return /** @type {SimpleBlockToken} */ (store.input[store.starts[this._i]]);
2246
+ },
2247
+ get rules() {
2248
+ return /** @type {Rule[]} */ (_readList(this._store, this._i));
2249
+ }
2250
+ });
2251
+
2252
+ /**
2253
+ * Hand the module's live columns to a retained snapshot, then reset the
2254
+ * module columns to fresh empty arrays so the next parse starts clean and can't
2255
+ * mutate this store. Called once per `parseA*` after all consuming is done.
2256
+ * @returns {NodeStore} the retained snapshot
2257
+ */
2258
+ const _finishStore = () => {
2259
+ /** @type {NodeStore} */
2260
+ const store = {
2261
+ input: _input,
2262
+ lc: _locConverter,
2263
+ types: _types,
2264
+ starts: _starts,
2265
+ ends: _ends,
2266
+ aux0: _aux0,
2267
+ aux1: _aux1,
2268
+ flags: _flags,
2269
+ listStarts: _listStarts,
2270
+ listLens: _listLens,
2271
+ flat: _flat,
2272
+ declLists: _declarationLists,
2273
+ childLists: _childRuleLists
2274
+ };
2275
+ // Carry this parse's size forward as a one-shot grow hint so the next parse
2276
+ // allocates its columns once instead of re-doubling from scratch (node ids
2277
+ // are 1-based, so `+1`).
2278
+ _growHint = _nodeCount + 1;
2279
+ _flatGrowHint = _flatTop;
2280
+ // Reset module state — the columns now belong to `store`.
2281
+ _capacity = 0;
2282
+ _nodeCount = 0;
2283
+ _flatTop = 0;
2284
+ _types = new Uint8Array(0);
2285
+ _starts = new Int32Array(0);
2286
+ _ends = new Int32Array(0);
2287
+ _aux0 = new Int32Array(0);
2288
+ _aux1 = new Int32Array(0);
2289
+ _flags = new Uint8Array(0);
2290
+ _listStarts = new Int32Array(0);
2291
+ _listLens = new Int32Array(0);
2292
+ _flat = new Int32Array(0);
2293
+ _declarationLists = [];
2294
+ _childRuleLists = [];
2295
+ _listPool.length = 0;
2296
+ _input = "";
2297
+ _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
2298
+ return store;
2299
+ };
2300
+
2301
+ /**
2302
+ * Finish the parse and wrap one consumed node ref as a `parseA*` reader, keeping
2303
+ * the caller's node type.
2304
+ * @template {Node} T
2305
+ * @param {T} ref consumed node ref
2306
+ * @returns {T} reader over the retained snapshot
2307
+ */
2308
+ const _finishOne = (ref) =>
2309
+ /** @type {T} */ (_readerAt(_finishStore(), _nodeIndex(ref)));
2310
+
2309
2311
  /**
2310
2312
  * Parse a stylesheet, CSS Syntax Level 3
2311
2313
  * [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
@@ -2318,15 +2320,17 @@ const parseAStylesheet = (input, pos = 0, options = {}) => {
2318
2320
  // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
2319
2321
  // 2. Normalize input, and set input to the result.
2320
2322
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2321
- useObjectBackend(ts.locConverter);
2323
+ _setupParse(ts.input, ts.locConverter);
2322
2324
  // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
2323
2325
  const start = ts.next().start;
2324
- const stylesheet = /** @type {Stylesheet} */ (_makeStylesheet(start));
2326
+ const stylesheet = _makeStylesheet(start);
2325
2327
  // 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
2326
2328
  _setRules(stylesheet, consumeAStylesheetsContents(ts));
2327
2329
  _setEnd(stylesheet, ts.next().start);
2328
2330
  // 5. Return the stylesheet.
2329
- return stylesheet;
2331
+ return /** @type {Stylesheet} */ (
2332
+ _readerAt(_finishStore(), _nodeIndex(stylesheet))
2333
+ );
2330
2334
  };
2331
2335
 
2332
2336
  /**
@@ -2343,9 +2347,11 @@ const parseAStylesheet = (input, pos = 0, options = {}) => {
2343
2347
  const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
2344
2348
  // 1. Normalize input, and set input to the result.
2345
2349
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2346
- useObjectBackend(ts.locConverter);
2350
+ _setupParse(ts.input, ts.locConverter);
2347
2351
  // 2. Consume a stylesheet’s contents from input, and return the result.
2348
- return consumeAStylesheetsContents(ts);
2352
+ const rules = consumeAStylesheetsContents(ts);
2353
+ const store = _finishStore();
2354
+ return /** @type {Rule[]} */ (_readRefList(store, rules));
2349
2355
  };
2350
2356
 
2351
2357
  /**
@@ -2359,9 +2365,17 @@ const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
2359
2365
  const parseABlocksContents = (input, pos = 0, options = {}) => {
2360
2366
  // 1. Normalize input, and set input to the result.
2361
2367
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2362
- useObjectBackend(ts.locConverter);
2368
+ _setupParse(ts.input, ts.locConverter);
2363
2369
  // 2. Consume a block’s contents from input, and return the result.
2364
- return consumeABlocksContents(ts);
2370
+ const { decls, rules } = consumeABlocksContents(ts);
2371
+ const store = _finishStore();
2372
+ return {
2373
+ decls: /** @type {Declaration[]} */ (_readRefList(store, decls)),
2374
+ rules:
2375
+ rules.length > 0
2376
+ ? /** @type {Rule[]} */ (_readRefList(store, rules))
2377
+ : _READER_EMPTY
2378
+ };
2365
2379
  };
2366
2380
 
2367
2381
  /**
@@ -2377,9 +2391,9 @@ const parseABlocksContents = (input, pos = 0, options = {}) => {
2377
2391
  const parseARule = (input, pos = 0, options = {}) => {
2378
2392
  // 1. Normalize input, and set input to the result.
2379
2393
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2380
- useObjectBackend(ts.locConverter);
2394
+ _setupParse(ts.input, ts.locConverter);
2381
2395
  // 2. Discard whitespace from input.
2382
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2396
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2383
2397
  // 3. If the next token from input is an <EOF-token>, return a syntax error.
2384
2398
  // Otherwise, if the next token from input is an <at-keyword-token>, consume an at-rule from input, and let rule be the return value.
2385
2399
  // Otherwise, consume a qualified rule from input and let rule be the return value.
@@ -2392,9 +2406,9 @@ const parseARule = (input, pos = 0, options = {}) => {
2392
2406
  : consumeAQualifiedRule(ts);
2393
2407
  if (!rule) return undefined;
2394
2408
  // 4. Discard whitespace from input.
2395
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2409
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2396
2410
  // 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
2397
- return ts.next().type === TT_EOF ? rule : undefined;
2411
+ return ts.next().type === TT_EOF ? _finishOne(rule) : undefined;
2398
2412
  };
2399
2413
 
2400
2414
  /**
@@ -2408,11 +2422,12 @@ const parseARule = (input, pos = 0, options = {}) => {
2408
2422
  const parseADeclaration = (input, pos = 0, options = {}) => {
2409
2423
  // 1. Normalize input, and set input to the result.
2410
2424
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2411
- useObjectBackend(ts.locConverter);
2425
+ _setupParse(ts.input, ts.locConverter);
2412
2426
  // 2. Discard whitespace from input.
2413
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2427
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2414
2428
  // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
2415
- return consumeADeclaration(ts);
2429
+ const decl = consumeADeclaration(ts);
2430
+ return decl === undefined ? undefined : _finishOne(decl);
2416
2431
  };
2417
2432
 
2418
2433
  /**
@@ -2425,18 +2440,17 @@ const parseADeclaration = (input, pos = 0, options = {}) => {
2425
2440
  const parseAComponentValue = (input, pos = 0, options = {}) => {
2426
2441
  // 1. Normalize input, and set input to the result.
2427
2442
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2428
- useObjectBackend(ts.locConverter);
2443
+ _setupParse(ts.input, ts.locConverter);
2429
2444
  // 2. Discard whitespace from input.
2430
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2445
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2431
2446
  // 3. If input is empty, return a syntax error.
2432
2447
  if (ts.next().type === TT_EOF) return undefined;
2433
2448
  // 4. Consume a component value from input and let value be the return value.
2434
2449
  const result = consumeAComponentValue(ts);
2435
2450
  // 5. Discard whitespace from input.
2436
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2451
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2437
2452
  // 6. If input is empty, return value. Otherwise, return a syntax error.
2438
- if (ts.next().type === TT_EOF) return result;
2439
- return undefined;
2453
+ return ts.next().type === TT_EOF ? _finishOne(result) : undefined;
2440
2454
  };
2441
2455
 
2442
2456
  /**
@@ -2450,10 +2464,12 @@ const parseAComponentValue = (input, pos = 0, options = {}) => {
2450
2464
  const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
2451
2465
  // 1. Normalize input, and set input to the result.
2452
2466
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2453
- useObjectBackend(ts.locConverter);
2467
+ _setupParse(ts.input, ts.locConverter);
2454
2468
  // 2. Consume a list of component values from input, and return the result.
2455
2469
  // (`null` needs `bailOnCurly`, which is not passed here.)
2456
- return /** @type {ComponentValue[]} */ (consumeAListOfComponentValues(ts));
2470
+ const values = /** @type {Node[]} */ (consumeAListOfComponentValues(ts));
2471
+ const store = _finishStore();
2472
+ return /** @type {ComponentValue[]} */ (_readRefList(store, values));
2457
2473
  };
2458
2474
 
2459
2475
  /**
@@ -2470,23 +2486,24 @@ const parseACommaSeparatedListOfComponentValues = (
2470
2486
  ) => {
2471
2487
  // 1. Normalize input, and set input to the result.
2472
2488
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2473
- useObjectBackend(ts.locConverter);
2489
+ _setupParse(ts.input, ts.locConverter);
2474
2490
  // 2. Let groups be an empty list.
2475
- /** @type {ComponentValue[][]} */
2491
+ /** @type {Node[][]} */
2476
2492
  const groups = [];
2477
2493
  // 3. While input is not empty:
2478
2494
  while (ts.next().type !== TT_EOF) {
2479
2495
  // 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
2480
2496
  groups.push(
2481
- /** @type {ComponentValue[]} */ (
2482
- consumeAListOfComponentValues(ts, TT_COMMA)
2483
- )
2497
+ /** @type {Node[]} */ (consumeAListOfComponentValues(ts, TT_COMMA))
2484
2498
  );
2485
2499
  // 3.2 Discard a token from input.
2486
2500
  ts.discard();
2487
2501
  }
2488
- // 4. Return groups.
2489
- return groups;
2502
+ // 4. Return groups — wrap each group's refs against the retained snapshot.
2503
+ const store = _finishStore();
2504
+ return groups.map(
2505
+ (g) => /** @type {ComponentValue[]} */ (_readRefList(store, g))
2506
+ );
2490
2507
  };
2491
2508
 
2492
2509
  // === Parser algorithms (CSS Syntax Level 3 §5.4) ===
@@ -2559,11 +2576,11 @@ const consumeAnAtRule = (ts, nested = false) => {
2559
2576
  _makeContainer(T_AT_RULE, head.start, head.end)
2560
2577
  );
2561
2578
  _setName(rule, head.start, head.end);
2562
- // Sealed (`_setPrelude`) at each return — the SoA backend consumes the
2579
+ // Sealed (`_setPrelude`) at each return — the store consumes the
2563
2580
  // scratch array when sealing, so it must be complete by then.
2564
- const prelude = _soaActive ? _soaList() : [];
2565
- // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2566
- // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
2581
+ const prelude = _takeList();
2582
+ // declarations / childRules stay null (no block); the `;` / EOF / nested-`}`
2583
+ // forms set blockStart / blockEnd to -1 explicitly at their return below.
2567
2584
 
2568
2585
  // Like `consumeAQualifiedRule`: skip mode scans the prelude without
2569
2586
  // materializing it (url tokens / functions kept so `@import url(…)` still
@@ -2580,6 +2597,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2580
2597
  if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
2581
2598
  ts.discard();
2582
2599
  _setPrelude(rule, prelude);
2600
+ _setBlock(rule, -1);
2583
2601
  _setEnd(rule, t.start);
2584
2602
  return rule;
2585
2603
  }
@@ -2589,6 +2607,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2589
2607
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2590
2608
  if (nested) {
2591
2609
  _setPrelude(rule, prelude);
2610
+ _setBlock(rule, -1);
2592
2611
  _setEnd(rule, t.start);
2593
2612
  return rule;
2594
2613
  }
@@ -2602,7 +2621,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2602
2621
  _setPrelude(rule, prelude);
2603
2622
  const block = consumeABlock(ts);
2604
2623
  _setBody(rule, block.decls, block.rules);
2605
- _setBlock(rule, block.blockStart, block.blockEnd);
2624
+ _setBlock(rule, block.blockStart);
2606
2625
  _setEnd(rule, block.blockEnd);
2607
2626
  return rule;
2608
2627
  }
@@ -2649,17 +2668,28 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2649
2668
  _makeContainer(T_QUALIFIED_RULE, start, start)
2650
2669
  );
2651
2670
  // Sealed (`_setPrelude`) at the `{` exit — the only path that returns the
2652
- // rule; the parse-error exits abandon the scratch unsealed.
2653
- const prelude = _soaActive ? _soaList() : [];
2654
- // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2655
- // defaults (no block until a `{` is reached).
2671
+ // rule; the parse-error exits abandon the scratch unsealed. Allocated lazily
2672
+ // on first push: skip mode usually pushes nothing (empty selector prelude),
2673
+ // and `_makeContainer` already zeroed the rule's list length, so a null
2674
+ // prelude reads as empty in the walk.
2675
+ /** @type {Node[] | null} */
2676
+ let prelude = null;
2677
+ // A returned qualified rule always has a block (only the `{` exit returns a
2678
+ // rule), so `_setBlock` always runs — no blockStart / blockEnd default needed.
2656
2679
 
2657
2680
  // Skip mode leaves `prelude` empty (selector text is recovered from the
2658
2681
  // rule's byte range, not its nodes); `first`/`second` still track the first
2659
2682
  // two non-whitespace tokens the `--foo: {` disambiguation below needs.
2660
2683
  const skip = _skipSelectorPrelude;
2684
+ // Non-skip: first two non-whitespace prelude nodes (computed at the `{`).
2661
2685
  let first = /** @type {Node} */ (/** @type {unknown} */ (0));
2662
2686
  let second = /** @type {Node} */ (/** @type {unknown} */ (0));
2687
+ // Skip mode: the same two tokens tracked by token type + start, so a dropped
2688
+ // leaf selector token needs no materialized node just for the disambiguation.
2689
+ // `0` (no token type) = unset.
2690
+ let firstTT = 0;
2691
+ let firstStart = 0;
2692
+ let secondTT = 0;
2663
2693
 
2664
2694
  // Process input
2665
2695
  for (;;) {
@@ -2674,12 +2704,17 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2674
2704
  // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
2675
2705
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2676
2706
  if (nested) return undefined;
2677
- const node = consumeATokenAsNode(ts);
2678
2707
  if (skip) {
2679
- if (!first) first = node;
2680
- else if (!second) second = node;
2708
+ // Stray `}` is a non-ws token; record its type/start, drop the node.
2709
+ if (firstTT === 0) {
2710
+ firstTT = t.type;
2711
+ firstStart = t.start;
2712
+ } else if (secondTT === 0) {
2713
+ secondTT = t.type;
2714
+ }
2715
+ ts.advance();
2681
2716
  } else {
2682
- prelude.push(node);
2717
+ (prelude || (prelude = _takeList())).push(consumeATokenAsNode(ts));
2683
2718
  }
2684
2719
  continue;
2685
2720
  }
@@ -2690,7 +2725,18 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2690
2725
  // (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
2691
2726
  // Otherwise, consume a block from input, and let child rules be the result.
2692
2727
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2693
- if (!skip) {
2728
+ // `--foo: {` disambiguation: are the first two non-ws prelude tokens an
2729
+ // ident starting with `--` followed by a colon? Skip mode reads the
2730
+ // tracked token type/start; non-skip reads the materialized prelude.
2731
+ let dashedDeclaration;
2732
+ if (skip) {
2733
+ dashedDeclaration =
2734
+ firstTT === TT_IDENTIFIER &&
2735
+ ts.input.startsWith("--", firstStart) &&
2736
+ secondTT === TT_COLON;
2737
+ } else if (prelude === null) {
2738
+ dashedDeclaration = false;
2739
+ } else {
2694
2740
  let firstIdx = 0;
2695
2741
  /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2696
2742
  while (
@@ -2708,16 +2754,16 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2708
2754
  }
2709
2755
  first = prelude[firstIdx];
2710
2756
  second = prelude[secondIdx];
2757
+ dashedDeclaration =
2758
+ first &&
2759
+ _nodeTypeOf(first) === T_IDENT &&
2760
+ // Test the source bytes directly — avoids forcing the lazy `value`
2761
+ // slice just to check the `--` custom-property prefix.
2762
+ ts.input.startsWith("--", _nodeStartOf(first)) &&
2763
+ second &&
2764
+ _nodeTypeOf(second) === T_COLON;
2711
2765
  }
2712
- if (
2713
- first &&
2714
- _nodeTypeOf(first) === T_IDENT &&
2715
- // Test the source bytes directly — avoids forcing the lazy `value`
2716
- // slice just to check the `--` custom-property prefix.
2717
- ts.input.startsWith("--", _nodeStartOf(first)) &&
2718
- second &&
2719
- _nodeTypeOf(second) === T_COLON
2720
- ) {
2766
+ if (dashedDeclaration) {
2721
2767
  /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
2722
2768
  if (nested) {
2723
2769
  consumeTheRemnantsOfABadDeclaration(ts, true);
@@ -2726,30 +2772,45 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2726
2772
  }
2727
2773
  return undefined;
2728
2774
  }
2729
- _setPrelude(rule, prelude);
2775
+ if (prelude !== null) _setPrelude(rule, prelude);
2730
2776
  const block = consumeABlock(ts);
2731
2777
  _setBody(rule, block.decls, block.rules);
2732
- _setBlock(rule, block.blockStart, block.blockEnd);
2778
+ _setBlock(rule, block.blockStart);
2733
2779
  _setEnd(rule, block.blockEnd);
2734
2780
  return rule;
2735
2781
  }
2736
2782
 
2737
2783
  // anything else
2738
2784
  // Consume a component value from input and append the result to rule’s prelude.
2739
- const node = consumeAComponentValue(ts, t);
2740
2785
  if (skip) {
2741
- // Keep only url-bearing nodes (url tokens, or functions that may hold a
2742
- // url like `:unknown(url(x))`) so the url visitor still rewrites them;
2743
- // other selector tokens have no non-modules consumer, so drop them.
2744
- const ty = _nodeTypeOf(node);
2745
- if (ty === T_FUNCTION || ty === T_URL) prelude.push(node);
2786
+ const tt = t.type;
2746
2787
  // Track the first two non-whitespace tokens for the disambiguation above.
2747
- if (t.type !== TT_WHITESPACE) {
2748
- if (!first) first = node;
2749
- else if (!second) second = node;
2788
+ if (tt !== TT_WHITESPACE) {
2789
+ if (firstTT === 0) {
2790
+ firstTT = tt;
2791
+ firstStart = t.start;
2792
+ } else if (secondTT === 0) {
2793
+ secondTT = tt;
2794
+ }
2795
+ }
2796
+ // Only functions (which may hold a url like `:unknown(url(x))`) and the
2797
+ // `(` / `[` blocks that must be balanced are materialized; the url
2798
+ // visitor keeps url / function nodes. Every other selector leaf token has
2799
+ // no non-modules consumer — drop it without building a node.
2800
+ if (
2801
+ tt === TT_FUNCTION ||
2802
+ (tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET)
2803
+ ) {
2804
+ const node = consumeAComponentValue(ts, t);
2805
+ const ty = _nodeTypeOf(node);
2806
+ if (ty === T_FUNCTION || ty === T_URL) {
2807
+ (prelude || (prelude = _takeList())).push(node);
2808
+ }
2809
+ } else {
2810
+ ts.advance();
2750
2811
  }
2751
2812
  } else {
2752
- prelude.push(node);
2813
+ (prelude || (prelude = _takeList())).push(consumeAComponentValue(ts, t));
2753
2814
  }
2754
2815
  }
2755
2816
  };
@@ -2838,9 +2899,9 @@ const consumeABlocksContents = (ts, onNode) => {
2838
2899
  const t = ts.next();
2839
2900
 
2840
2901
  // <whitespace-token> / <semicolon-token>
2841
- // Discard a token from input.
2902
+ // Discard a token from input (`t` was just peeked and is non-EOF).
2842
2903
  if (t.type === TT_WHITESPACE || t.type === TT_SEMICOLON) {
2843
- ts.discard();
2904
+ ts.advance();
2844
2905
  }
2845
2906
  // <EOF-token> / <}-token>
2846
2907
  // Return decls and rules.
@@ -2921,8 +2982,8 @@ const consumeADeclaration = (ts, nested = false) => {
2921
2982
  const { input } = ts;
2922
2983
  // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
2923
2984
  const start = ts.next().start;
2924
- // name "" / nameStart / nameEnd (= start) / important (false) keep their
2925
- // `Container` defaults; `value` is set unconditionally at step 5 below.
2985
+ // nameEnd (= start) / important (unset) keep their container defaults;
2986
+ // `value` is set unconditionally at step 5 below.
2926
2987
  const decl = /** @type {Declaration} */ (
2927
2988
  _makeContainer(T_DECLARATION, start, start)
2928
2989
  );
@@ -2938,19 +2999,19 @@ const consumeADeclaration = (ts, nested = false) => {
2938
2999
  }
2939
3000
 
2940
3001
  // 2. Discard whitespace from input.
2941
- while (ts.next().type === TT_WHITESPACE) ts.discard();
3002
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2942
3003
 
2943
3004
  // 3. If the next token is a <colon-token>, discard a token from input.
2944
3005
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2945
3006
  if (ts.next().type === TT_COLON) {
2946
- ts.discard();
3007
+ ts.advance();
2947
3008
  } else {
2948
3009
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2949
3010
  return undefined;
2950
3011
  }
2951
3012
 
2952
3013
  // 4. Discard whitespace from input.
2953
- while (ts.next().type === TT_WHITESPACE) ts.discard();
3014
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2954
3015
 
2955
3016
  // Step 8's custom-property test, computed early so the value parse can bail.
2956
3017
  const isCustomProperty = input.startsWith("--", start);
@@ -2968,7 +3029,7 @@ const consumeADeclaration = (ts, nested = false) => {
2968
3029
  );
2969
3030
  if (value === null) return undefined;
2970
3031
  // `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
2971
- // and the SoA backend consumes it when sealing.
3032
+ // and the store consumes it when sealing.
2972
3033
  _setEnd(decl, ts.next().start);
2973
3034
 
2974
3035
  // 6. If the last two non-<whitespace-token>s in decl's value are a <delim-token> with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important", remove them from decl's value and set decl's important flag.
@@ -3031,7 +3092,7 @@ const consumeAListOfComponentValues = (
3031
3092
  nested = false,
3032
3093
  bailOnCurly = false
3033
3094
  ) => {
3034
- const values = /** @type {ComponentValue[]} */ (_soaActive ? _soaList() : []);
3095
+ const values = /** @type {ComponentValue[]} */ (_takeList());
3035
3096
  // Process input
3036
3097
  for (;;) {
3037
3098
  const t = ts.next();
@@ -3060,7 +3121,7 @@ const consumeAListOfComponentValues = (
3060
3121
  if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) return null;
3061
3122
  // anything else
3062
3123
  // Consume a component value from input, and append the result to values.
3063
- // Skipped leaf types short-circuit before materializing: no SoA slot is
3124
+ // Skipped leaf types short-circuit before materializing: no column slot is
3064
3125
  // written and no node is built (blocks / functions never skip here).
3065
3126
  const tt = t.type;
3066
3127
  if (
@@ -3069,7 +3130,8 @@ const consumeAListOfComponentValues = (
3069
3130
  !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
3070
3131
  _skipTypes[_ttToNodeType[tt]] === 1
3071
3132
  ) {
3072
- ts.consume();
3133
+ // `t` was just peeked and is a skipped value leaf (never EOF).
3134
+ ts.advance();
3073
3135
  continue;
3074
3136
  }
3075
3137
  const node = consumeAComponentValue(ts, t);
@@ -3098,10 +3160,11 @@ const consumeAComponentValue = (ts, t = ts.next()) => {
3098
3160
  }
3099
3161
  // anything else
3100
3162
  // Consume a token from input and return the result. (Asserted: not EOF.)
3101
- // Inlined `consumeATokenAsNode`: `t` is already the peeked next token, so
3102
- // advance past it and materialize it directly — one fewer call per leaf
3103
- // component value (the bulk of the nodes on a large stylesheet).
3104
- ts.consume();
3163
+ // Inlined `consumeATokenAsNode`: `t` is already the peeked next token (and not
3164
+ // EOF), so `advance` past it and materialize it directly — no redundant
3165
+ // `next()` and one fewer call per leaf component value (the bulk of the nodes
3166
+ // on a large stylesheet).
3167
+ ts.advance();
3105
3168
  return /** @type {ComponentValue} */ (tokenToNode(t));
3106
3169
  };
3107
3170
 
@@ -3123,7 +3186,7 @@ const consumeASimpleBlock = (ts) => {
3123
3186
  );
3124
3187
  _setToken(block, token);
3125
3188
  // Sealed (`_setValue`) at the return, once complete.
3126
- const val = _soaActive ? _soaList() : [];
3189
+ const val = _takeList();
3127
3190
 
3128
3191
  // Discard a token from input.
3129
3192
  ts.discard();
@@ -3162,7 +3225,7 @@ const consumeAFunction = (ts) => {
3162
3225
  );
3163
3226
  _setName(fn, tFn.start, tFn.end - 1);
3164
3227
  // Sealed (`_setValue`) at the return, once complete.
3165
- const val = _soaActive ? _soaList() : [];
3228
+ const val = _takeList();
3166
3229
 
3167
3230
  // Process input
3168
3231
  for (;;) {
@@ -3188,7 +3251,8 @@ const consumeAFunction = (ts) => {
3188
3251
  !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
3189
3252
  _skipTypes[_ttToNodeType[tt]] === 1
3190
3253
  ) {
3191
- ts.consume();
3254
+ // `t` was just peeked and is a skipped value leaf (never EOF).
3255
+ ts.advance();
3192
3256
  continue;
3193
3257
  }
3194
3258
  const node = consumeAComponentValue(ts, t);
@@ -3493,14 +3557,15 @@ let _commentBucket;
3493
3557
 
3494
3558
  // Comments reach the visitor map through `NodeType.Comment` instead of a
3495
3559
  // side callback. They fire during tokenization — in source order among
3496
- // comments, not interleaved with the node walk — on a transient SoA node so
3560
+ // comments, not interleaved with the node walk — on a transient store node so
3497
3561
  // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3498
3562
  // the tokenizer skips comments with zero overhead.
3499
3563
  /** @type {(input: string, start: number, end: number) => number} */
3500
- const _grammarOnComment = (_input, start, end) => {
3501
- const node = _soaAllocNode(T_COMMENT, start, end);
3564
+ const _grammarOnComment = (_src, start, end) => {
3565
+ const node = _makeLeaf(T_COMMENT, start, end);
3502
3566
  _currentNode = node;
3503
3567
  _currentParent = null;
3568
+ _currentIndex = 0;
3504
3569
  const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
3505
3570
  const e = bucket.enter;
3506
3571
  for (let i = 0; i < e.length; i++) e[i](A);
@@ -3515,15 +3580,17 @@ const _grammarOnComment = (_input, start, end) => {
3515
3580
  * loops — `for…of` would allocate an iterator per node on this hot path.
3516
3581
  * @param {Node} node component-value root
3517
3582
  * @param {Node | null} parent enclosing node
3583
+ * @param {number} index node's index within its sibling list
3518
3584
  */
3519
- const _walkValue = (node, parent) => {
3520
- const ty = _soaTypes[_nodeIndex(node)];
3585
+ const _walkValue = (node, parent, index) => {
3586
+ const ty = _types[_nodeIndex(node)];
3521
3587
  const b = _visitors[ty];
3522
3588
  let skip = false;
3523
3589
  if (b !== undefined && b.enter.length !== 0) {
3524
3590
  _walkSkip = false;
3525
3591
  _currentNode = node;
3526
3592
  _currentParent = parent;
3593
+ _currentIndex = index;
3527
3594
  const e = b.enter;
3528
3595
  for (let i = 0; i < e.length; i++) e[i](A);
3529
3596
  skip = _walkSkip;
@@ -3531,14 +3598,15 @@ const _walkValue = (node, parent) => {
3531
3598
  }
3532
3599
  if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3533
3600
  const i0 = _nodeIndex(node);
3534
- const vs = _soaListStarts[i0];
3535
- const ve = vs + _soaListLens[i0];
3536
- for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3601
+ const vs = _listStarts[i0];
3602
+ const ve = vs + _listLens[i0];
3603
+ for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_flat[i]), node, i - vs);
3537
3604
  }
3538
3605
  if (b !== undefined) {
3539
3606
  // Rebind: descending into children moved the path.
3540
3607
  _currentNode = node;
3541
3608
  _currentParent = parent;
3609
+ _currentIndex = index;
3542
3610
  const x = b.exit;
3543
3611
  for (let i = 0; i < x.length; i++) x[i](A);
3544
3612
  }
@@ -3549,16 +3617,18 @@ const _walkValue = (node, parent) => {
3549
3617
  * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3550
3618
  * @param {Node} node structural-tree root
3551
3619
  * @param {Node | null} parent enclosing node
3620
+ * @param {number} index node's index within its sibling list (declarations and child rules index independently)
3552
3621
  */
3553
- const _walkRule = (node, parent) => {
3622
+ const _walkRule = (node, parent, index) => {
3554
3623
  const i0 = _nodeIndex(node);
3555
- const ty = _soaTypes[i0];
3624
+ const ty = _types[i0];
3556
3625
  const b = _visitors[ty];
3557
3626
  let skip = false;
3558
3627
  if (b !== undefined && b.enter.length !== 0) {
3559
3628
  _walkSkip = false;
3560
3629
  _currentNode = node;
3561
3630
  _currentParent = parent;
3631
+ _currentIndex = index;
3562
3632
  const e = b.enter;
3563
3633
  for (let i = 0; i < e.length; i++) e[i](A);
3564
3634
  skip = _walkSkip;
@@ -3566,50 +3636,55 @@ const _walkRule = (node, parent) => {
3566
3636
  }
3567
3637
  if (!skip) {
3568
3638
  if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3569
- const ps = _soaListStarts[i0];
3570
- const pe = ps + _soaListLens[i0];
3571
- for (let i = ps; i < pe; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3639
+ const ps = _listStarts[i0];
3640
+ const pe = ps + _listLens[i0];
3641
+ for (let i = ps; i < pe; i++) {
3642
+ _walkValue(_nodeRef(_flat[i]), node, i - ps);
3643
+ }
3572
3644
  if (_recurseBlocks) {
3573
3645
  // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3574
- const decls = _soaDeclarationLists[i0];
3646
+ const decls = _declarationLists[i0];
3575
3647
  if (decls) {
3576
- for (let i = 0; i < decls.length; i++) _walkRule(decls[i], node);
3648
+ for (let i = 0; i < decls.length; i++) _walkRule(decls[i], node, i);
3577
3649
  }
3578
- const ch = _soaChildRuleLists[i0];
3579
- if (ch) for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node);
3650
+ const ch = _childRuleLists[i0];
3651
+ if (ch) for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node, i);
3580
3652
  }
3581
3653
  } else if (ty === T_DECLARATION) {
3582
- const vs = _soaListStarts[i0];
3583
- const ve = vs + _soaListLens[i0];
3584
- for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3654
+ const vs = _listStarts[i0];
3655
+ const ve = vs + _listLens[i0];
3656
+ for (let i = vs; i < ve; i++) {
3657
+ _walkValue(_nodeRef(_flat[i]), node, i - vs);
3658
+ }
3585
3659
  }
3586
3660
  }
3587
3661
  if (b !== undefined) {
3588
3662
  // Rebind: descending into children moved the path.
3589
3663
  _currentNode = node;
3590
3664
  _currentParent = parent;
3665
+ _currentIndex = index;
3591
3666
  const x = b.exit;
3592
3667
  for (let i = 0; i < x.length; i++) x[i](A);
3593
3668
  }
3594
3669
  };
3595
3670
 
3596
3671
  /**
3597
- * The `grammar` streaming sink: walk one top-level node, then recycle the SoA
3672
+ * The `grammar` streaming sink: walk one top-level node, then recycle the
3598
3673
  * buffers for the next.
3599
3674
  * @param {Rule | Declaration} node top-level node
3600
3675
  */
3601
3676
  const _walkTopLevel = (node) => {
3602
- _walkRule(node, null);
3603
- if (_soaNodeCount > _soaPeak) _soaPeak = _soaNodeCount;
3604
- if (_soaFlatTop > _soaFlatPeak) _soaFlatPeak = _soaFlatTop;
3605
- _soaNodeCount = 0;
3606
- _soaFlatTop = 0;
3677
+ _walkRule(node, null, 0);
3678
+ if (_nodeCount > _peak) _peak = _nodeCount;
3679
+ if (_flatTop > _flatPeak) _flatPeak = _flatTop;
3680
+ _nodeCount = 0;
3681
+ _flatTop = 0;
3607
3682
  };
3608
3683
 
3609
- // The SoA buffers grow to the largest single top-level rule ever parsed and
3684
+ // The store buffers grow to the largest single top-level rule ever parsed and
3610
3685
  // live at module level; above this capacity they are re-shrunk after a parse
3611
3686
  // so one pathological rule can't pin megabytes for the process lifetime.
3612
- const _SOA_SHRINK_CAPACITY = 65536;
3687
+ const _SHRINK_CAPACITY = 65536;
3613
3688
 
3614
3689
  /**
3615
3690
  * The CSS `SourceProcessor` grammar: consume top-level rules one at a time
@@ -3623,7 +3698,7 @@ const _SOA_SHRINK_CAPACITY = 65536;
3623
3698
  */
3624
3699
  const grammar = (input, visitors, options) => {
3625
3700
  const locConverter = options.locConverter || new LocConverter(input);
3626
- useSoaBackend(input, locConverter);
3701
+ _setupParse(input, locConverter);
3627
3702
  const skip = options.skip;
3628
3703
  _skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
3629
3704
  _skipActive = _skipTypes !== _NO_SKIP_TYPES;
@@ -3648,39 +3723,36 @@ const grammar = (input, visitors, options) => {
3648
3723
  try {
3649
3724
  consume(ts, _walkTopLevel);
3650
3725
  } finally {
3651
- // Drop the module-level SoA references so the last parsed source (and
3726
+ // Drop the module-level column references so the last parsed source (and
3652
3727
  // its LocConverter / child lists / visitors) don't stay alive between
3653
3728
  // parses.
3654
- _soaInput = "";
3655
- _soaLocConverter = /** @type {LocConverter} */ (
3656
- /** @type {unknown} */ (null)
3657
- );
3658
- _soaDeclarationLists.length = 0;
3659
- _soaChildRuleLists.length = 0;
3660
- _soaFlatTop = 0;
3729
+ _input = "";
3730
+ _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
3731
+ _declarationLists.length = 0;
3732
+ _childRuleLists.length = 0;
3733
+ _flatTop = 0;
3661
3734
  _listPool.length = 0;
3662
- if (_soaFlat.length > _SOA_SHRINK_CAPACITY) {
3663
- _soaFlatGrowHint = _soaFlatPeak;
3664
- _soaFlat = new Int32Array(0);
3735
+ if (_flat.length > _SHRINK_CAPACITY) {
3736
+ _flatGrowHint = _flatPeak;
3737
+ _flat = new Int32Array(0);
3665
3738
  }
3666
3739
  _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3667
3740
  _commentBucket = undefined;
3668
- if (_soaCapacity > _SOA_SHRINK_CAPACITY) {
3741
+ if (_capacity > _SHRINK_CAPACITY) {
3669
3742
  // +1: node ids are 1-based and grow fires at `id >= capacity`.
3670
- _soaGrowHint = _soaPeak + 1;
3671
- _soaCapacity = 0;
3672
- _soaTypes = new Uint8Array(0);
3673
- _soaStarts = new Int32Array(0);
3674
- _soaEnds = new Int32Array(0);
3675
- _soaAux0 = new Int32Array(0);
3676
- _soaAux1 = new Int32Array(0);
3677
- _soaAux2 = new Int32Array(0);
3678
- _soaFlags = new Uint8Array(0);
3679
- _soaListStarts = new Int32Array(0);
3680
- _soaListLens = new Int32Array(0);
3743
+ _growHint = _peak + 1;
3744
+ _capacity = 0;
3745
+ _types = new Uint8Array(0);
3746
+ _starts = new Int32Array(0);
3747
+ _ends = new Int32Array(0);
3748
+ _aux0 = new Int32Array(0);
3749
+ _aux1 = new Int32Array(0);
3750
+ _flags = new Uint8Array(0);
3751
+ _listStarts = new Int32Array(0);
3752
+ _listLens = new Int32Array(0);
3681
3753
  }
3682
- _soaPeak = 0;
3683
- _soaFlatPeak = 0;
3754
+ _peak = 0;
3755
+ _flatPeak = 0;
3684
3756
  }
3685
3757
  };
3686
3758
 
@@ -3733,11 +3805,11 @@ const buildSkipSet = (nodeTypes) => {
3733
3805
  /** @type {(n: Node) => Node[]} */
3734
3806
  const _materializeList = (n) => {
3735
3807
  const i = _nodeIndex(n);
3736
- const start = _soaListStarts[i];
3737
- const len = _soaListLens[i];
3808
+ const start = _listStarts[i];
3809
+ const len = _listLens[i];
3738
3810
  /** @type {Node[]} */
3739
3811
  const out = [];
3740
- for (let k = 0; k < len; k++) out.push(_nodeRef(_soaFlat[start + k]));
3812
+ for (let k = 0; k < len; k++) out.push(_nodeRef(_flat[start + k]));
3741
3813
  return out;
3742
3814
  };
3743
3815
 
@@ -3750,14 +3822,15 @@ let _walkSkip = false;
3750
3822
  let _currentNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3751
3823
  /** @type {Node | null} */
3752
3824
  let _currentParent = null;
3825
+ // Index of the current node within its sibling list (a rule body's declarations
3826
+ // and child rules are separate lists, so each indexes from 0 independently).
3827
+ let _currentIndex = 0;
3753
3828
 
3754
3829
  // AST field-access seam. Every AST-node field read by `CssParser` goes through
3755
- // one of these accessors so the node representation can change underneath the
3756
- // consumer without touching it. Today they are backed by the `Node` / `Token` /
3757
- // `Container` objects (`n` is a node); the Struct-of-Arrays migration rewrites
3758
- // the bodies to index typed arrays (`n` becomes an integer node id) without any
3759
- // consumer edit. `value` is the leaf-token string; container child lists are
3760
- // `children` / `prelude` / `declarations` / `childRules`.
3830
+ // one of these accessors (`n` is an integer node id into the columns), so
3831
+ // the storage stays behind the accessor without any consumer edit. `value` is
3832
+ // the leaf-token string; container child lists are `children` / `prelude` /
3833
+ // `declarations` / `childRules`.
3761
3834
  const A = {
3762
3835
  // === path position (rebound by the walk before every visitor call) ===
3763
3836
  /**
@@ -3772,6 +3845,12 @@ const A = {
3772
3845
  get parent() {
3773
3846
  return _currentParent;
3774
3847
  },
3848
+ /**
3849
+ * @returns {number} index of the current node within its sibling list (0 for a top-level node) — only valid during a visitor callback
3850
+ */
3851
+ get index() {
3852
+ return _currentIndex;
3853
+ },
3775
3854
  /** Stop the walk descending into the current node (enter only). */
3776
3855
  skipChildren() {
3777
3856
  _walkSkip = true;
@@ -3782,21 +3861,21 @@ const A = {
3782
3861
  * @returns {number} node type
3783
3862
  */
3784
3863
  type(n = _currentNode) {
3785
- return _soaTypes[_nodeIndex(n)];
3864
+ return _types[_nodeIndex(n)];
3786
3865
  },
3787
3866
  /**
3788
3867
  * @param {Node=} n node
3789
3868
  * @returns {number} start offset
3790
3869
  */
3791
3870
  start(n = _currentNode) {
3792
- return _soaStarts[_nodeIndex(n)];
3871
+ return _starts[_nodeIndex(n)];
3793
3872
  },
3794
3873
  /**
3795
3874
  * @param {Node=} n node
3796
3875
  * @returns {number} end offset
3797
3876
  */
3798
3877
  end(n = _currentNode) {
3799
- return _soaEnds[_nodeIndex(n)];
3878
+ return _ends[_nodeIndex(n)];
3800
3879
  },
3801
3880
  /**
3802
3881
  * @param {Node=} n node
@@ -3804,7 +3883,7 @@ const A = {
3804
3883
  */
3805
3884
  range(n = _currentNode) {
3806
3885
  const i = _nodeIndex(n);
3807
- return [_soaStarts[i], _soaEnds[i]];
3886
+ return [_starts[i], _ends[i]];
3808
3887
  },
3809
3888
  /**
3810
3889
  * @param {Node=} n node
@@ -3812,11 +3891,11 @@ const A = {
3812
3891
  */
3813
3892
  loc(n = _currentNode) {
3814
3893
  const i = _nodeIndex(n);
3815
- const lc = _soaLocConverter;
3816
- const s = lc.get(_soaStarts[i]);
3894
+ const lc = _locConverter;
3895
+ const s = lc.get(_starts[i]);
3817
3896
  const sl = s.line;
3818
3897
  const sc = s.column;
3819
- const e = lc.get(_soaEnds[i]);
3898
+ const e = lc.get(_ends[i]);
3820
3899
  return {
3821
3900
  start: { line: sl, column: sc },
3822
3901
  end: { line: e.line, column: e.column }
@@ -3828,14 +3907,14 @@ const A = {
3828
3907
  */
3829
3908
  source(n = _currentNode) {
3830
3909
  const i = _nodeIndex(n);
3831
- return _soaInput.slice(_soaStarts[i], _soaEnds[i]);
3910
+ return _input.slice(_starts[i], _ends[i]);
3832
3911
  },
3833
3912
  /**
3834
3913
  * @param {Node=} n node
3835
3914
  * @returns {string} raw token value
3836
3915
  */
3837
3916
  value(n = _currentNode) {
3838
- return _soaValueOf(_nodeIndex(n));
3917
+ return _valueOf(_nodeIndex(n));
3839
3918
  },
3840
3919
  /**
3841
3920
  * @param {Node=} n node
@@ -3843,8 +3922,8 @@ const A = {
3843
3922
  */
3844
3923
  unescaped(n = _currentNode) {
3845
3924
  const i = _nodeIndex(n);
3846
- const v = _soaValueOf(i);
3847
- return _soaTypes[i] === T_STRING
3925
+ const v = _valueOf(i);
3926
+ return _types[i] === T_STRING
3848
3927
  ? unescapeIdentifier(v.slice(1, -1))
3849
3928
  : unescapeIdentifier(v);
3850
3929
  },
@@ -3854,9 +3933,9 @@ const A = {
3854
3933
  */
3855
3934
  typeFlag(n = _currentNode) {
3856
3935
  const i = _nodeIndex(n);
3857
- if (_soaTypes[i] === T_HASH) {
3858
- const input = _soaInput;
3859
- const p = _soaStarts[i] + 1;
3936
+ if (_types[i] === T_HASH) {
3937
+ const input = _input;
3938
+ const p = _starts[i] + 1;
3860
3939
  return _ifThreeCodePointsWouldStartAnIdentSequence(
3861
3940
  input,
3862
3941
  p,
@@ -3867,9 +3946,9 @@ const A = {
3867
3946
  ? "id"
3868
3947
  : "unrestricted";
3869
3948
  }
3870
- const v = _soaValueOf(i);
3949
+ const v = _valueOf(i);
3871
3950
  return _typeFlagOf(
3872
- _soaTypes[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3951
+ _types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3873
3952
  );
3874
3953
  },
3875
3954
  /**
@@ -3877,14 +3956,14 @@ const A = {
3877
3956
  * @returns {number} url content start offset
3878
3957
  */
3879
3958
  contentStart(n = _currentNode) {
3880
- return _soaAux0[_nodeIndex(n)];
3959
+ return _aux0[_nodeIndex(n)];
3881
3960
  },
3882
3961
  /**
3883
3962
  * @param {Node=} n node
3884
3963
  * @returns {number} url content end offset
3885
3964
  */
3886
3965
  contentEnd(n = _currentNode) {
3887
- return _soaAux1[_nodeIndex(n)];
3966
+ return _aux1[_nodeIndex(n)];
3888
3967
  },
3889
3968
  /**
3890
3969
  * @param {Node=} n node
@@ -3892,23 +3971,23 @@ const A = {
3892
3971
  */
3893
3972
  name(n = _currentNode) {
3894
3973
  const i = _nodeIndex(n);
3895
- return _soaTypes[i] === T_AT_RULE
3896
- ? _soaInput.slice(_soaStarts[i] + 1, _soaAux0[i])
3897
- : _soaInput.slice(_soaStarts[i], _soaAux0[i]);
3974
+ return _types[i] === T_AT_RULE
3975
+ ? _input.slice(_starts[i] + 1, _aux0[i])
3976
+ : _input.slice(_starts[i], _aux0[i]);
3898
3977
  },
3899
3978
  /**
3900
3979
  * @param {Node=} n node
3901
3980
  * @returns {number} name start offset
3902
3981
  */
3903
3982
  nameStart(n = _currentNode) {
3904
- return _soaStarts[_nodeIndex(n)];
3983
+ return _starts[_nodeIndex(n)];
3905
3984
  },
3906
3985
  /**
3907
3986
  * @param {Node=} n node
3908
3987
  * @returns {number} name end offset
3909
3988
  */
3910
3989
  nameEnd(n = _currentNode) {
3911
- return _soaAux0[_nodeIndex(n)];
3990
+ return _aux0[_nodeIndex(n)];
3912
3991
  },
3913
3992
  /**
3914
3993
  * @param {Node=} n node
@@ -3931,13 +4010,32 @@ const A = {
3931
4010
  prelude(n = _currentNode) {
3932
4011
  return /** @type {ComponentValue[]} */ (_materializeList(n));
3933
4012
  },
4013
+ /**
4014
+ * @param {Node=} n node
4015
+ * @returns {number} number of children (value / prelude) without materializing the list
4016
+ */
4017
+ childCount(n = _currentNode) {
4018
+ return _listLens[_nodeIndex(n)];
4019
+ },
4020
+ /**
4021
+ * @param {Node} n node
4022
+ * @param {number} i child index (`0 <= i < childCount(n)`)
4023
+ * @returns {ComponentValue} i-th child (value / prelude) without materializing the list
4024
+ */
4025
+ childAt(n, i) {
4026
+ return /** @type {ComponentValue} */ (
4027
+ _nodeRef(_flat[_listStarts[_nodeIndex(n)] + i])
4028
+ );
4029
+ },
3934
4030
  /**
3935
4031
  * @param {Node=} n node
3936
4032
  * @returns {Declaration[] | null} block declarations
3937
4033
  */
3938
4034
  declarations(n = _currentNode) {
4035
+ // `_makeContainer` only populates this slot for rules; a non-rule node
4036
+ // reads `undefined`, normalized to `null` to keep the documented contract.
3939
4037
  return /** @type {Declaration[] | null} */ (
3940
- _soaDeclarationLists[_nodeIndex(n)]
4038
+ _declarationLists[_nodeIndex(n)] || null
3941
4039
  );
3942
4040
  },
3943
4041
  /**
@@ -3945,65 +4043,65 @@ const A = {
3945
4043
  * @returns {Rule[] | null} block child rules
3946
4044
  */
3947
4045
  childRules(n = _currentNode) {
3948
- return /** @type {Rule[] | null} */ (_soaChildRuleLists[_nodeIndex(n)]);
4046
+ return /** @type {Rule[] | null} */ (
4047
+ _childRuleLists[_nodeIndex(n)] || null
4048
+ );
3949
4049
  },
3950
4050
  /**
3951
4051
  * @param {Node=} n node
3952
4052
  * @returns {number} block start offset
3953
4053
  */
3954
4054
  blockStart(n = _currentNode) {
3955
- return _soaAux1[_nodeIndex(n)];
4055
+ return _aux1[_nodeIndex(n)];
3956
4056
  },
3957
4057
  /**
3958
4058
  * @param {Node=} n node
3959
4059
  * @returns {number} block end offset
3960
4060
  */
3961
4061
  blockEnd(n = _currentNode) {
3962
- return _soaAux2[_nodeIndex(n)];
4062
+ const i = _nodeIndex(n);
4063
+ return _aux1[i] !== -1 ? _ends[i] : -1;
3963
4064
  },
3964
4065
  /**
3965
4066
  * @param {Node=} n node
3966
4067
  * @returns {boolean} `!important` flag
3967
4068
  */
3968
4069
  important(n = _currentNode) {
3969
- return (_soaFlags[_nodeIndex(n)] & 1) !== 0;
4070
+ return (_flags[_nodeIndex(n)] & 1) !== 0;
3970
4071
  },
3971
4072
  /**
3972
4073
  * @param {Node=} n node
3973
4074
  * @returns {SimpleBlockToken} block opening token
3974
4075
  */
3975
4076
  blockToken(n = _currentNode) {
3976
- return /** @type {SimpleBlockToken} */ (
3977
- _soaInput[_soaStarts[_nodeIndex(n)]]
3978
- );
4077
+ return /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(n)]]);
3979
4078
  },
3980
4079
  // Writers — `CssParser` rewrites a rule's end / block-end when it folds an
3981
- // inline ICSS `:import` / `:export` body into a single dependency. The
3982
- // node stays explicit here: writes should never be implicit on position.
4080
+ // inline ICSS `:import` / `:export` body into a single dependency. A block
4081
+ // rule's `blockEnd` is its `end`, so `setBlockEnd` writes the same `end` slot.
3983
4082
  /**
3984
4083
  * @param {Node} n node
3985
4084
  * @param {number} v new end offset
3986
4085
  */
3987
4086
  setEnd(n, v) {
3988
- _soaEnds[_nodeIndex(n)] = v;
4087
+ _ends[_nodeIndex(n)] = v;
3989
4088
  },
3990
4089
  /**
3991
4090
  * @param {Node} n node
3992
4091
  * @param {number} v new block end offset
3993
4092
  */
3994
4093
  setBlockEnd(n, v) {
3995
- _soaAux2[_nodeIndex(n)] = v;
4094
+ _ends[_nodeIndex(n)] = v;
3996
4095
  }
3997
4096
  };
3998
4097
 
3999
- // The two AST runtime classes — `Node` and its sole subclass `Token` (the
4000
- // other node shapes are `@typedef`s over `Node`, exported as types only). Plus
4001
- // the full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, `consumeASimpleBlock`
4002
- // (the one §5.4 algorithm exposed as a byte entry point for `CssParser`), the
4003
- // `TokenStream` (so callers can pass a pre-built stream to any `parseA*`), and
4004
- // the `escape` / `unescapeIdentifier` string utils.
4098
+ // The AST node shapes (`Node`, `Token`, and the container typedefs) are types
4099
+ // only — nodes are integer ids into the store, surfaced by the `A` visitor
4100
+ // accessor and the `parseA*` readers. Runtime exports: the `A` accessor, the
4101
+ // full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, the `TokenStream` (so
4102
+ // callers can pass a pre-built stream to any `parseA*`), and the `escape` /
4103
+ // `unescapeIdentifier` string utils.
4005
4104
  module.exports.A = A;
4006
- module.exports.Node = Node;
4007
4105
  module.exports.NodeType = NodeType;
4008
4106
  module.exports.SourceProcessor = SourceProcessor;
4009
4107
  module.exports.TT_AT_KEYWORD = TT_AT_KEYWORD;
@@ -4032,7 +4130,6 @@ module.exports.TT_SEMICOLON = TT_SEMICOLON;
4032
4130
  module.exports.TT_STRING = TT_STRING;
4033
4131
  module.exports.TT_URL = TT_URL;
4034
4132
  module.exports.TT_WHITESPACE = TT_WHITESPACE;
4035
- module.exports.Token = Token;
4036
4133
  module.exports.TokenStream = TokenStream;
4037
4134
  module.exports.buildSkipSet = buildSkipSet;
4038
4135
  module.exports.equalsLowerCase = equalsLowerCase;