splitIntoPotentialTokens.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. // Character classification via a lookup table. A single bitmask test
  7. // replaces the multi-comparison chains in each inner loop phase.
  8. //
  9. // BIT layout per character:
  10. // bit 0 (STOP1 = 1): stops phase-1 scan (\n ; { })
  11. // bit 1 (CONT2 = 2): continues phase-2 scan (; { } space \r \t)
  12. //
  13. // Phase 1: scan regular source chars that are NOT a phase-1 stop.
  14. // Phase 2: consume runs of statement-boundary / whitespace chars.
  15. // Phase 3: consume a trailing \n if present.
  16. const STOP1 = 1;
  17. const CONT2 = 2;
  18. /** @type {Uint8Array} */
  19. const CF = new Uint8Array(128);
  20. CF[10] = STOP1; // \n – stops phase 1, NOT consumed in phase 2
  21. CF[59] = STOP1 | CONT2; // ;
  22. CF[123] = STOP1 | CONT2; // {
  23. CF[125] = STOP1 | CONT2; // }
  24. CF[32] = CONT2; // space
  25. CF[13] = CONT2; // \r
  26. CF[9] = CONT2; // \t
  27. /**
  28. * @callback OnPotentialToken
  29. * @param {number} start start offset (inclusive)
  30. * @param {number} end end offset (exclusive)
  31. * @param {boolean} newline whether the token ends with a `\n`
  32. * @returns {void}
  33. */
  34. /**
  35. * Streaming core: report each potential token by its `[start, end)` bounds
  36. * instead of materialising substrings. The single real consumer
  37. * (`OriginalSource.streamChunks`) slices on demand — and skips slicing
  38. * entirely when emitting the final source (the `map()` / `sourceAndMap()`
  39. * paths, which discard the chunk text) — so this avoids both the
  40. * intermediate results array and every per-token `String.slice` allocation
  41. * in the dominant case.
  42. * @param {string} str string
  43. * @param {OnPotentialToken} onToken called for each token
  44. * @returns {void}
  45. */
  46. const eachPotentialToken = (str, onToken) => {
  47. const len = str.length;
  48. let i = 0;
  49. outer: while (i < len) {
  50. const start = i;
  51. // Phase 1 – skip regular (non-stop) characters
  52. let cc = str.charCodeAt(i);
  53. while (cc > 127 || !(CF[cc] & STOP1)) {
  54. if (++i >= len) {
  55. onToken(start, i, false);
  56. break outer;
  57. }
  58. cc = str.charCodeAt(i);
  59. }
  60. // Phase 2 – consume delimiter / whitespace run (; { } space \r \t)
  61. while (cc < 128 && CF[cc] & CONT2) {
  62. if (++i >= len) {
  63. onToken(start, i, false);
  64. break outer;
  65. }
  66. cc = str.charCodeAt(i);
  67. }
  68. // Phase 3 – consume trailing newline
  69. if (cc === 10) {
  70. i++;
  71. onToken(start, i, true);
  72. } else {
  73. onToken(start, i, false);
  74. }
  75. }
  76. };
  77. /**
  78. * Array-returning variant. Kept as a standalone loop rather than wrapping
  79. * `eachPotentialToken` with a per-token callback: the callback indirection
  80. * measurably slows this hot scan (V8 can no longer inline the slice/push),
  81. * and the two only share the same small, well-tested classification table.
  82. * @param {string} str string
  83. * @returns {string[] | null} array of string separated by potential tokens
  84. */
  85. const splitIntoPotentialTokens = (str) => {
  86. const len = str.length;
  87. if (len === 0) return null;
  88. const results = [];
  89. let i = 0;
  90. outer: while (i < len) {
  91. const start = i;
  92. // Phase 1 – skip regular (non-stop) characters
  93. let cc = str.charCodeAt(i);
  94. while (cc > 127 || !(CF[cc] & STOP1)) {
  95. if (++i >= len) {
  96. results.push(str.slice(start, i));
  97. break outer;
  98. }
  99. cc = str.charCodeAt(i);
  100. }
  101. // Phase 2 – consume delimiter / whitespace run (; { } space \r \t)
  102. while (cc < 128 && CF[cc] & CONT2) {
  103. if (++i >= len) {
  104. results.push(str.slice(start, i));
  105. break outer;
  106. }
  107. cc = str.charCodeAt(i);
  108. }
  109. // Phase 3 – consume trailing newline
  110. if (cc === 10) {
  111. i++;
  112. }
  113. results.push(str.slice(start, i));
  114. }
  115. return results;
  116. };
  117. module.exports = splitIntoPotentialTokens;
  118. module.exports.eachPotentialToken = eachPotentialToken;