| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- // Character classification via a lookup table. A single bitmask test
- // replaces the multi-comparison chains in each inner loop phase.
- //
- // BIT layout per character:
- // bit 0 (STOP1 = 1): stops phase-1 scan (\n ; { })
- // bit 1 (CONT2 = 2): continues phase-2 scan (; { } space \r \t)
- //
- // Phase 1: scan regular source chars that are NOT a phase-1 stop.
- // Phase 2: consume runs of statement-boundary / whitespace chars.
- // Phase 3: consume a trailing \n if present.
- const STOP1 = 1;
- const CONT2 = 2;
- /** @type {Uint8Array} */
- const CF = new Uint8Array(128);
- CF[10] = STOP1; // \n – stops phase 1, NOT consumed in phase 2
- CF[59] = STOP1 | CONT2; // ;
- CF[123] = STOP1 | CONT2; // {
- CF[125] = STOP1 | CONT2; // }
- CF[32] = CONT2; // space
- CF[13] = CONT2; // \r
- CF[9] = CONT2; // \t
- /**
- * @callback OnPotentialToken
- * @param {number} start start offset (inclusive)
- * @param {number} end end offset (exclusive)
- * @param {boolean} newline whether the token ends with a `\n`
- * @returns {void}
- */
- /**
- * Streaming core: report each potential token by its `[start, end)` bounds
- * instead of materialising substrings. The single real consumer
- * (`OriginalSource.streamChunks`) slices on demand — and skips slicing
- * entirely when emitting the final source (the `map()` / `sourceAndMap()`
- * paths, which discard the chunk text) — so this avoids both the
- * intermediate results array and every per-token `String.slice` allocation
- * in the dominant case.
- * @param {string} str string
- * @param {OnPotentialToken} onToken called for each token
- * @returns {void}
- */
- const eachPotentialToken = (str, onToken) => {
- const len = str.length;
- let i = 0;
- outer: while (i < len) {
- const start = i;
- // Phase 1 – skip regular (non-stop) characters
- let cc = str.charCodeAt(i);
- while (cc > 127 || !(CF[cc] & STOP1)) {
- if (++i >= len) {
- onToken(start, i, false);
- break outer;
- }
- cc = str.charCodeAt(i);
- }
- // Phase 2 – consume delimiter / whitespace run (; { } space \r \t)
- while (cc < 128 && CF[cc] & CONT2) {
- if (++i >= len) {
- onToken(start, i, false);
- break outer;
- }
- cc = str.charCodeAt(i);
- }
- // Phase 3 – consume trailing newline
- if (cc === 10) {
- i++;
- onToken(start, i, true);
- } else {
- onToken(start, i, false);
- }
- }
- };
- /**
- * Array-returning variant. Kept as a standalone loop rather than wrapping
- * `eachPotentialToken` with a per-token callback: the callback indirection
- * measurably slows this hot scan (V8 can no longer inline the slice/push),
- * and the two only share the same small, well-tested classification table.
- * @param {string} str string
- * @returns {string[] | null} array of string separated by potential tokens
- */
- const splitIntoPotentialTokens = (str) => {
- const len = str.length;
- if (len === 0) return null;
- const results = [];
- let i = 0;
- outer: while (i < len) {
- const start = i;
- // Phase 1 – skip regular (non-stop) characters
- let cc = str.charCodeAt(i);
- while (cc > 127 || !(CF[cc] & STOP1)) {
- if (++i >= len) {
- results.push(str.slice(start, i));
- break outer;
- }
- cc = str.charCodeAt(i);
- }
- // Phase 2 – consume delimiter / whitespace run (; { } space \r \t)
- while (cc < 128 && CF[cc] & CONT2) {
- if (++i >= len) {
- results.push(str.slice(start, i));
- break outer;
- }
- cc = str.charCodeAt(i);
- }
- // Phase 3 – consume trailing newline
- if (cc === 10) {
- i++;
- }
- results.push(str.slice(start, i));
- }
- return results;
- };
- module.exports = splitIntoPotentialTokens;
- module.exports.eachPotentialToken = eachPotentialToken;
|