subscript
Version:
Modular expression parser & evaluator
34 lines (29 loc) • 1.53 kB
JavaScript
/**
* Template literals - parse half
* `a ${x} b` → ['`', [,'a '], 'x', [,' b']]
* Tagged: tag`...` → ['``', 'tag', ...]
*/
import { parse, skip, err, expr, lookup, cur, idx } from '../parse.js';
const ACCESS = 170, BACKTICK = 96, DOLLAR = 36, OBRACE = 123, BSLASH = 92;
const esc = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' };
// Parse template body after opening ` — string and expression segments
// strictly alternate (string, expr, string, …), so every interpolation is
// flanked by a string part even when empty: `${x}${y}` → [,''] x [,''] y [,''].
const parseBody = () => {
const parts = [];
let s = '', c;
for (; (c = cur.charCodeAt(idx)) !== BACKTICK; )
!c ? err('Unterminated template') :
c === BSLASH ? (skip(), s += esc[cur[idx]] || cur[idx], skip()) :
c === DOLLAR && cur.charCodeAt(idx + 1) === OBRACE ? (parts.push([, s]), s = '', skip(2), parts.push(expr(0, 125))) :
(s += cur[idx], skip());
return parts.push([, s]), skip(), parts;
};
// Collapse a single-part body to that part (string or expression); else keep as ['`', ...parts]
const wrapBody = p => p.length < 2 && p[0]?.[0] === undefined ? p[0] || [,''] : ['`', ...p];
const prev = lookup[BACKTICK];
// Tagged templates: decline when ASI with newline (return undefined to let ASI handle)
lookup[BACKTICK] = (a, prec) =>
a && prec < ACCESS ? (parse.asi && parse.newline ? void 0 : (skip(), ['``', a, ...parseBody()])) : // tagged
!a ? (skip(), wrapBody(parseBody())) : // plain
prev?.(a, prec);