export class LocalIdent { readonly name: string | undefined; constructor(name: string | undefined) { this.name = name; } } export class CodeFragment { readonly strings: readonly string[]; readonly values: readonly unknown[]; constructor(strings: readonly string[], values: readonly unknown[]) { this.strings = strings; this.values = values; } /** evaluate the fragment via `new Function`, returning the result */ eval(): unknown { const seen = new Map(); const interpolations: unknown[] = []; let uid = 0; let params = ''; const resolve = ({ strings, values }: CodeFragment): string => { let source = strings[0]!; for (let idx = 0, len = values.length; idx < len; idx++) { const val = values[idx]; if (val instanceof CodeFragment) { source += resolve(val); } else if (val instanceof LocalIdent) { let name = seen.get(val); if (name === undefined) { if (val.name !== undefined) { name = `_$${uid++}_${val.name}`; } else { name = `_$${uid++}`; } seen.set(val, name); } source += name; } else { let name = seen.get(val); if (name === undefined) { name = `_$${uid++}`; params = params ? params + ',' + name : name; seen.set(val, name); interpolations.push(val); } source += name; } source += strings[idx + 1]!; } return source; }; const source = resolve(this); const fn = new Function(`[${params}]`, source); return fn(interpolations); } } const EMPTY: readonly unknown[] = []; const NONIDENT_RE = /[^A-Za-z0-9_$]/g; // #__NO_SIDE_EFFECTS__ export const raw = (source: string): CodeFragment => { return new CodeFragment([source], EMPTY); }; // #__NO_SIDE_EFFECTS__ export const x = (strings: TemplateStringsArray, ...values: unknown[]): CodeFragment => { return new CodeFragment(strings, values); }; // #__NO_SIDE_EFFECTS__ export const local = (name?: string): LocalIdent => { return new LocalIdent(name?.replace(NONIDENT_RE, '_')); };