eval JIT helper
code-fragment.ts
edited
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};