eval JIT helper
code-fragment.ts edited
90 lines 2.1 kB view raw
1export class LocalIdent { 2 readonly name: string | undefined; 3 4 constructor(name: string | undefined) { 5 this.name = name; 6 } 7} 8 9export class CodeFragment { 10 readonly strings: readonly string[]; 11 readonly values: readonly unknown[]; 12 13 constructor(strings: readonly string[], values: readonly unknown[]) { 14 this.strings = strings; 15 this.values = values; 16 } 17 18 /** evaluate the fragment via `new Function`, returning the result */ 19 eval(): unknown { 20 const seen = new Map<unknown, string>(); 21 const interpolations: unknown[] = []; 22 23 let uid = 0; 24 let params = ''; 25 26 const resolve = ({ strings, values }: CodeFragment): string => { 27 let source = strings[0]!; 28 29 for (let idx = 0, len = values.length; idx < len; idx++) { 30 const val = values[idx]; 31 32 if (val instanceof CodeFragment) { 33 source += resolve(val); 34 } else if (val instanceof LocalIdent) { 35 let name = seen.get(val); 36 if (name === undefined) { 37 if (val.name !== undefined) { 38 name = `_$${uid++}_${val.name}`; 39 } else { 40 name = `_$${uid++}`; 41 } 42 43 seen.set(val, name); 44 } 45 46 source += name; 47 } else { 48 let name = seen.get(val); 49 if (name === undefined) { 50 name = `_$${uid++}`; 51 params = params ? params + ',' + name : name; 52 53 seen.set(val, name); 54 interpolations.push(val); 55 } 56 57 source += name; 58 } 59 60 source += strings[idx + 1]!; 61 } 62 63 return source; 64 }; 65 66 const source = resolve(this); 67 68 const fn = new Function(`[${params}]`, source); 69 70 return fn(interpolations); 71 } 72} 73 74const EMPTY: readonly unknown[] = []; 75const NONIDENT_RE = /[^A-Za-z0-9_$]/g; 76 77// #__NO_SIDE_EFFECTS__ 78export const raw = (source: string): CodeFragment => { 79 return new CodeFragment([source], EMPTY); 80}; 81 82// #__NO_SIDE_EFFECTS__ 83export const x = (strings: TemplateStringsArray, ...values: unknown[]): CodeFragment => { 84 return new CodeFragment(strings, values); 85}; 86 87// #__NO_SIDE_EFFECTS__ 88export const local = (name?: string): LocalIdent => { 89 return new LocalIdent(name?.replace(NONIDENT_RE, '_')); 90};