Playground uses run() to automatically sync w compiler passes
Changes playground to use the modified `run()` function of the compiler, polling the generator and building up a Map of tabs automatically based on the passes that the compiler runs. This means tabs are always derived from the current state of the compiler and we can never forget to add a pass. <img width="1497" alt="Screen Shot 2023-01-10 at 11 06 03 AM" src="https://user-images.githubusercontent.com/6425824/211639442-da421f73-e19e-4b63-9f33-0ce5a68cceb7.png"> Note the inclusion of some recently added passes that weren't added to the playground — which was my fault but only bc i intended to ship this PR soon :-)
Joe Savona committed
Jan 10, 2023 at 11:05 UTC
27b37341e5efc3d3a465a8d16962bd8d9aa62760
3 files changed
+106
-204
compiler/forget/packages/playground/components/Editor/Output.tsx
+87
-169
@@ -9,59 +9,22 @@ import MonacoEditor from "@monaco-editor/react";
9
import { HIR } from "babel-plugin-react-forget";
10
import prettier from "prettier";
11
import prettierParserBabel from "prettier/parser-babel";
12
-import { memo, useMemo } from "react";
12
+import { memo, useMemo, useState } from "react";
13
import type { Store } from "../../lib/stores";
14
-import TabbedWindow, { TabTypes } from "../TabbedWindow";
14
+import TabbedWindow from "../TabbedWindow";
15
import { monacoOptions } from "./monacoOptions";
16
-const {
17
- buildReactiveFunction,
18
- codegenReactiveFunction,
19
- eliminateRedundantPhi,
20
- enterSSA,
21
- Environment,
22
- flattenReactiveLoops,
23
- inferTypes,
24
- inferMutableRanges,
25
- inferReactiveScopes,
26
- inferReactiveScopeVariables,
27
- inferReferenceEffects,
28
- leaveSSA,
29
- lower,
30
- parseFunctions,
31
- printHIR,
32
- printReactiveFunction,
33
- propagateScopeDependencies,
34
- pruneUnusedLabels,
35
- pruneUnusedScopes,
36
-} = HIR;
16
+const { parseFunctions, printHIR, printReactiveFunction } = HIR;
17
const MemoizedOutput = memo(Output);
18
19
export default MemoizedOutput;
20
21
type Props = {
22
store: Store;
43
- tabsOpen: Map<TabTypes, boolean>;
44
- setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
45
-};
46
-
47
-type CompilerOutput = {
48
- ssaOutput: string;
49
- hirOutput: string;
50
- eliminateRedundantPhiOutput: string;
51
- inferTypesOutput: string;
52
- inferReferenceEffectsOutput: string;
53
- inferMutableRangesOutput: string;
54
- inferReactiveScopeVariablesOutput: string;
55
- inferReactiveScopesOutput: string;
56
- reactiveFunctionOutput: string;
57
- leaveSSAOutput: string;
58
- codegenOutput: string;
59
- sourceMapUrl: string | null;
23
};
24
25
type CompilerError = string;
26
64
-function compile(source: string): CompilerOutput | CompilerError {
27
+function compile(source: string): Map<string, React.ReactNode> | CompilerError {
28
try {
29
const astFunctions = parseFunctions(source);
30
if (astFunctions.length === 0) {
@@ -70,82 +33,96 @@ function compile(source: string): CompilerOutput | CompilerError {
33
34
// TODO: Handle multiple functions
35
const func = astFunctions[0];
73
- const env = new Environment();
74
- const ir = lower(func, env);
75
-
76
- const hirOutput = printHIR(ir.body);
77
-
78
- enterSSA(ir, env);
79
- const ssaOutput = printHIR(ir.body);
80
-
81
- eliminateRedundantPhi(ir);
82
- const eliminateRedundantPhiOutput = printHIR(ir.body);
83
-
84
- inferTypes(ir);
85
- const inferTypesOutput = printHIR(ir.body);
86
-
87
- inferReferenceEffects(ir);
88
- const inferReferenceEffectsOutput = printHIR(ir.body);
89
-
90
- inferMutableRanges(ir);
91
- const inferMutableRangesOutput = printHIR(ir.body);
92
-
93
- leaveSSA(ir);
94
- const leaveSSAOutput = printHIR(ir.body);
95
-
96
- inferReactiveScopeVariables(ir);
97
- const inferReactiveScopeVariablesOutput = printHIR(ir.body);
36
99
- inferReactiveScopes(ir);
100
- const inferReactiveScopesOutput = printHIR(ir.body);
101
-
102
- const reactiveFunction = buildReactiveFunction(ir);
103
- pruneUnusedLabels(reactiveFunction);
104
- flattenReactiveLoops(reactiveFunction);
105
- propagateScopeDependencies(reactiveFunction);
106
- pruneUnusedScopes(reactiveFunction);
107
- const reactiveFunctionOutput = printReactiveFunction(reactiveFunction);
108
-
109
- const ast = codegenReactiveFunction(reactiveFunction);
110
- const generated = generate(
111
- ast,
112
- {
113
- sourceMaps: true,
114
- sourceFileName: "input.js",
115
- },
116
- source
117
- );
118
- const sourceMapUrl = getSourceMapUrl(
119
- generated.code,
120
- JSON.stringify(generated.map)
121
- );
122
- const codegenOutput = prettier.format(generated.code, {
123
- semi: true,
124
- parser: "babel",
125
- plugins: [prettierParserBabel],
37
+ const tabs = new Map<string, React.ReactNode>();
38
+ const generator = HIR.run(func);
39
+ let ast;
40
+ while (true) {
41
+ const next = generator.next();
42
+ if (next.done) {
43
+ ast = next.value;
44
+ break;
45
+ }
46
+ const result = next.value;
47
+ switch (result.kind) {
48
+ case "ast": {
49
+ const { code } = codegen(result.value, source);
50
+ tabs.set(
51
+ result.name,
52
+ <TextTabContent output={code}></TextTabContent>
53
+ );
54
+ break;
55
+ }
56
+ case "hir": {
57
+ const text = printHIR(result.value.body);
58
+ tabs.set(
59
+ result.name,
60
+ <TextTabContent output={text}></TextTabContent>
61
+ );
62
+ break;
63
+ }
64
+ case "reactive": {
65
+ const text = printReactiveFunction(result.value);
66
+ tabs.set(
67
+ result.name,
68
+ <TextTabContent output={text}></TextTabContent>
69
+ );
70
+ break;
71
+ }
72
+ default: {
73
+ throw new Error("Unexpected result kind");
74
+ }
75
+ }
76
+ }
77
+ // Ensure that JS and the JS source map come first
78
+ const reorderedTabs = new Map();
79
+ const { code, sourceMapUrl } = codegen(ast, source);
80
+ reorderedTabs.set("JS", <TextTabContent output={code}></TextTabContent>);
81
+ if (sourceMapUrl) {
82
+ reorderedTabs.set(
83
+ "SourceMap",
84
+ <>
85
+ <iframe
86
+ src={sourceMapUrl}
87
+ className="w-full h-96"
88
+ title="Generated Code"
89
+ />
90
+ </>
91
+ );
92
+ }
93
+ tabs.forEach((tab, name) => {
94
+ reorderedTabs.set(name, tab);
95
});
127
-
128
- return {
129
- hirOutput,
130
- ssaOutput,
131
- eliminateRedundantPhiOutput,
132
- inferTypesOutput,
133
- inferReferenceEffectsOutput,
134
- inferMutableRangesOutput,
135
- inferReactiveScopeVariablesOutput,
136
- inferReactiveScopesOutput,
137
- reactiveFunctionOutput,
138
- leaveSSAOutput,
139
- codegenOutput,
140
- sourceMapUrl,
141
- };
96
+ return reorderedTabs;
97
} catch (e: any) {
98
return e.toString();
99
}
100
}
101
102
+function codegen(
103
+ ast: any,
104
+ source: string
105
+): { code: any; sourceMapUrl: string | null } {
106
+ const generated = generate(
107
+ ast,
108
+ { sourceMaps: true, sourceFileName: "input.js" },
109
+ source
110
+ );
111
+ const sourceMapUrl = getSourceMapUrl(
112
+ generated.code,
113
+ JSON.stringify(generated.map)
114
+ );
115
+ const codegenOutput = prettier.format(generated.code, {
116
+ semi: true,
117
+ parser: "babel",
118
+ plugins: [prettierParserBabel],
119
+ });
120
+ return { code: codegenOutput, sourceMapUrl };
121
+}
122
+
123
// TODO(gsn: Update diagnostics ƒrom HIR output
148
-function Output({ store, setTabsOpen, tabsOpen }: Props) {
124
+function Output({ store }: Props) {
125
+ const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set());
126
const compilerOutput = useMemo(() => compile(store.source), [store.source]);
127
128
if (typeof compilerOutput === "string") {
@@ -158,66 +135,7 @@ function Output({ store, setTabsOpen, tabsOpen }: Props) {
135
defaultTab="HIR"
136
setTabsOpen={setTabsOpen}
137
tabsOpen={tabsOpen}
161
- tabs={{
162
- JS: <TextTabContent output={compilerOutput.codegenOutput} />,
163
- HIR: (
164
- <TextTabContent output={compilerOutput.hirOutput}></TextTabContent>
165
- ),
166
- SSA: (
167
- <TextTabContent output={compilerOutput.ssaOutput}></TextTabContent>
168
- ),
169
- EliminateRedundantPhi: (
170
- <TextTabContent
171
- output={compilerOutput.eliminateRedundantPhiOutput}
172
- ></TextTabContent>
173
- ),
174
- InferTypes: (
175
- <TextTabContent
176
- output={compilerOutput.inferTypesOutput}
177
- ></TextTabContent>
178
- ),
179
- InferReferenceEffects: (
180
- <TextTabContent
181
- output={compilerOutput.inferReferenceEffectsOutput}
182
- ></TextTabContent>
183
- ),
184
- InferMutableRanges: (
185
- <TextTabContent
186
- output={compilerOutput.inferMutableRangesOutput}
187
- ></TextTabContent>
188
- ),
189
- LeaveSSA: (
190
- <TextTabContent
191
- output={compilerOutput.leaveSSAOutput}
192
- ></TextTabContent>
193
- ),
194
- InferReactiveScopeVariables: (
195
- <TextTabContent
196
- output={compilerOutput.inferReactiveScopeVariablesOutput}
197
- ></TextTabContent>
198
- ),
199
- InferReactiveScopes: (
200
- <TextTabContent
201
- output={compilerOutput.inferReactiveScopesOutput}
202
- ></TextTabContent>
203
- ),
204
- ReactiveFunctions: (
205
- <TextTabContent
206
- output={compilerOutput.reactiveFunctionOutput}
207
- ></TextTabContent>
208
- ),
209
- SourceMap: (
210
- <>
211
- {compilerOutput.sourceMapUrl && (
212
- <iframe
213
- src={compilerOutput.sourceMapUrl}
214
- className="w-full h-96"
215
- title="Generated Code"
216
- />
217
- )}
218
- </>
219
- ),
220
- }}
138
+ tabs={compilerOutput}
139
/>
140
);
141
}
compiler/forget/packages/playground/components/Editor/index.tsx
+2
-8
@@ -8,7 +8,7 @@
8
import clsx from "clsx";
9
import invariant from "invariant";
10
import { useSnackbar } from "notistack";
11
-import { useDeferredValue, useState } from "react";
11
+import { useDeferredValue } from "react";
12
import { useMountEffect } from "../../hooks";
13
import { defaultStore } from "../../lib/defaultStore";
14
import {
@@ -19,7 +19,6 @@ import {
19
type Store,
20
} from "../../lib/stores";
21
import { useStore, useStoreDispatch } from "../StoreContext";
22
-import { TabTypes } from "../TabbedWindow";
22
import Input from "./Input";
23
import Output from "./Output";
24
@@ -28,7 +27,6 @@ export default function Editor() {
27
const deferredStore = useDeferredValue(store);
28
const dispatchStore = useStoreDispatch();
29
const { enqueueSnackbar } = useSnackbar();
31
- const [tabsOpen, setTabsOpen] = useState<Map<TabTypes, boolean>>(new Map());
30
31
useMountEffect(() => {
32
let mountStore: Store;
@@ -62,11 +60,7 @@ export default function Editor() {
60
<Input />
61
</div>
62
<div className={clsx("flex sm:flex")}>
65
- <Output
66
- tabsOpen={tabsOpen}
67
- setTabsOpen={setTabsOpen}
68
- store={deferredStore}
69
- />
63
+ <Output store={deferredStore} />
64
</div>
65
</div>
66
</>
compiler/forget/packages/playground/components/TabbedWindow.tsx
+17
-27
@@ -7,35 +7,21 @@
7
8
import React, { useCallback } from "react";
9
10
-export type TabTypes =
11
- | "HIR"
12
- | "SSA"
13
- | "EliminateRedundantPhi"
14
- | "InferTypes"
15
- | "InferReferenceEffects"
16
- | "InferMutableRanges"
17
- | "InferReactiveScopeVariables"
18
- | "InferReactiveScopes"
19
- | "ReactiveFunctions"
20
- | "LeaveSSA"
21
- | "JS"
22
- | "SourceMap";
23
-
24
-type TabsRecord = Record<TabTypes, React.ReactNode>;
10
+type TabsRecord = Map<string, React.ReactNode>;
11
12
export default function TabbedWindow(props: {
13
defaultTab: string | null;
14
tabs: TabsRecord;
29
- tabsOpen: Map<TabTypes, boolean>;
30
- setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
15
+ tabsOpen: Set<string>;
16
+ setTabsOpen: (newTab: Set<string>) => void;
17
}): React.ReactElement {
18
return (
19
<div className="flex flex-row h-full">
34
- {Object.keys(props.tabs).map((name, index, all) => {
20
+ {Array.from(props.tabs.keys()).map((name) => {
21
return (
22
<TabbedWindowItem
37
- name={name as TabTypes}
38
- key={index}
23
+ name={name}
24
+ key={name}
25
tabs={props.tabs}
26
tabsOpen={props.tabsOpen}
27
setTabsOpen={props.setTabsOpen}
@@ -52,16 +38,20 @@ function TabbedWindowItem({
38
tabsOpen,
39
setTabsOpen,
40
}: {
55
- name: TabTypes;
41
+ name: string;
42
tabs: TabsRecord;
57
- tabsOpen: Map<TabTypes, boolean>;
58
- setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
43
+ tabsOpen: Set<string>;
44
+ setTabsOpen: (newTab: Set<string>) => void;
45
}): React.ReactElement {
60
- const isShow = tabsOpen.get(name) ?? false;
46
+ const isShow = tabsOpen.has(name);
47
48
const toggleTabs = useCallback(() => {
63
- const nextState = new Map(tabsOpen);
64
- nextState.set(name, !isShow);
49
+ const nextState = new Set(tabsOpen);
50
+ if (nextState.has(name)) {
51
+ nextState.delete(name);
52
+ } else {
53
+ nextState.add(name);
54
+ }
55
setTabsOpen(nextState);
56
}, [tabsOpen, name, isShow, setTabsOpen]);
57
@@ -75,7 +65,7 @@ function TabbedWindowItem({
65
>
66
- {name}
67
</h2>
78
- {tabs[name]}
68
+ {tabs.get(name) ?? <div>No output for {name}</div>}
69
</div>
70
) : (
71
<div className="relative items-center h-full px-4 py-8 align-middle border-r border-grey-200">