@anireact/d
Version:
Dedent templates, autoindent interpolations, and more.
120 lines • 4.28 kB
JavaScript
/**
* Match the first non-whitespace and the rest of line.
*/
const PAT = /\S.*\s*$/gm;
/**
* Getter for the `raw` property.
*/
export const getRaw = (s) => s.raw;
/**
* Template arguments tokenizer.
*
* @param s Literals array; must be non-empty.
* @param q Quasis array; must be one item shorted than {@linkcode s}.
* @returns Interleaved sequence of literal and quasi tokens, with literals
* properly dedented and trimmed, and quasis with inferred autoindent
* strings.
*/
export const scan = (s, q) => {
// ================================================================== //
// Reduce the string: //
// 1. Replace quasis with `!`. //
// 2. In each line, drop everything after the first non-WS char. //
// 3. Transform non-WS substrings to `!`. //
// 4. Drop trailing WS. //
// 5. Collapse blank lines. //
// ================================================================== //
/** Shape string. */
let shape = s.join('!');
// 1. Don’t process completely blank literals:
if (shape.trim() === '') {
return [
{
lit: true,
value: shape,
},
];
}
// 2. Use a single regexp to:
// 1. In each line, replace the first non-WS and the rest
// with the `!` char.
// 2. In each line, trim trailing WS.
// 3. Collapse blank lines.
// 4. Trim trailing WS and blanks.
shape = shape.replace(PAT, '!');
/** Line vector. */ let lines = shape.split('\n');
/** Least padding. */ let shift = Infinity;
// ======================== //
// Detect the dedent width: //
// ======================== //
// 1. For each line except the first one:
for (let line of lines.slice(1)) {
// Get the minimum of:
// a) The current least padding.
// b) The current line padding.
shift = Math.min(shift, line.length - 1);
}
// 2. Use zero padding for a single-line template:
if (shift === Infinity)
shift = 0;
// ============================== //
// Actually process the template: //
// ============================== //
// 1. Clear temporaries:
shape = lines = null;
/** Output buffer. */ let v = [];
/** Current literal index. */ let i = 0;
/** Last literal index. */ let z = q.length;
/** Quasi autoindent string. */ let pad = '';
// 2. For each quasi (and preceding literal):
for (let value of q) {
// 1. Consume and push the preceding literal
// and infer the padding for the current quasi:
lit();
// 2. Push the token with the value and inferred padding:
v.push({
lit: false,
value,
pad,
});
}
// 3. Consume and push the final literal:
lit();
// 4. Return the result:
return v;
/**
* Consume a literal chunk and push a corresponding token.
*
* @returns The chunk’s line vector.
*/
function lit() {
/** Current chunk’s line vector. */ let lv = s[i].split('\n');
/** Current chunk’s line count. */ let lc = lv.length;
// 1. Dedent the tail lines of the chunk:
for (let i = 1; i < lc; i++) {
lv[i] = lv[i].slice(shift);
}
// 2. Drop the first line of the first chunk unless it’s
// a) non-blank, OR
// b) the only line of the chunk.
if (i === 0 && lv[0].trim() === '' && lc > 1) {
lv.shift();
}
// 3. Trim the very final newline of the final chunk:
if (i === z && lv.at(-1) === '') {
lv.pop();
}
// 4. Push the token:
v.push({
lit: true,
value: lv.join('\n'),
});
// 5. Update the quasi padding:
if (lc > 1) {
pad = lv.at(-1).replace(PAT, '');
}
// 6. Increment the literal index:
i++;
}
};
//# sourceMappingURL=private.mjs.map