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 :-)
This commit is contained in:
Joe Savona
2023-01-10 11:05:32 -08:00
parent 7751202433
commit 27b37341e5
3 changed files with 106 additions and 204 deletions
@@ -9,59 +9,22 @@ import MonacoEditor from "@monaco-editor/react";
import { HIR } from "babel-plugin-react-forget";
import prettier from "prettier";
import prettierParserBabel from "prettier/parser-babel";
import { memo, useMemo } from "react";
import { memo, useMemo, useState } from "react";
import type { Store } from "../../lib/stores";
import TabbedWindow, { TabTypes } from "../TabbedWindow";
import TabbedWindow from "../TabbedWindow";
import { monacoOptions } from "./monacoOptions";
const {
buildReactiveFunction,
codegenReactiveFunction,
eliminateRedundantPhi,
enterSSA,
Environment,
flattenReactiveLoops,
inferTypes,
inferMutableRanges,
inferReactiveScopes,
inferReactiveScopeVariables,
inferReferenceEffects,
leaveSSA,
lower,
parseFunctions,
printHIR,
printReactiveFunction,
propagateScopeDependencies,
pruneUnusedLabels,
pruneUnusedScopes,
} = HIR;
const { parseFunctions, printHIR, printReactiveFunction } = HIR;
const MemoizedOutput = memo(Output);
export default MemoizedOutput;
type Props = {
store: Store;
tabsOpen: Map<TabTypes, boolean>;
setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
};
type CompilerOutput = {
ssaOutput: string;
hirOutput: string;
eliminateRedundantPhiOutput: string;
inferTypesOutput: string;
inferReferenceEffectsOutput: string;
inferMutableRangesOutput: string;
inferReactiveScopeVariablesOutput: string;
inferReactiveScopesOutput: string;
reactiveFunctionOutput: string;
leaveSSAOutput: string;
codegenOutput: string;
sourceMapUrl: string | null;
};
type CompilerError = string;
function compile(source: string): CompilerOutput | CompilerError {
function compile(source: string): Map<string, React.ReactNode> | CompilerError {
try {
const astFunctions = parseFunctions(source);
if (astFunctions.length === 0) {
@@ -70,82 +33,96 @@ function compile(source: string): CompilerOutput | CompilerError {
// TODO: Handle multiple functions
const func = astFunctions[0];
const env = new Environment();
const ir = lower(func, env);
const hirOutput = printHIR(ir.body);
enterSSA(ir, env);
const ssaOutput = printHIR(ir.body);
eliminateRedundantPhi(ir);
const eliminateRedundantPhiOutput = printHIR(ir.body);
inferTypes(ir);
const inferTypesOutput = printHIR(ir.body);
inferReferenceEffects(ir);
const inferReferenceEffectsOutput = printHIR(ir.body);
inferMutableRanges(ir);
const inferMutableRangesOutput = printHIR(ir.body);
leaveSSA(ir);
const leaveSSAOutput = printHIR(ir.body);
inferReactiveScopeVariables(ir);
const inferReactiveScopeVariablesOutput = printHIR(ir.body);
inferReactiveScopes(ir);
const inferReactiveScopesOutput = printHIR(ir.body);
const reactiveFunction = buildReactiveFunction(ir);
pruneUnusedLabels(reactiveFunction);
flattenReactiveLoops(reactiveFunction);
propagateScopeDependencies(reactiveFunction);
pruneUnusedScopes(reactiveFunction);
const reactiveFunctionOutput = printReactiveFunction(reactiveFunction);
const ast = codegenReactiveFunction(reactiveFunction);
const generated = generate(
ast,
{
sourceMaps: true,
sourceFileName: "input.js",
},
source
);
const sourceMapUrl = getSourceMapUrl(
generated.code,
JSON.stringify(generated.map)
);
const codegenOutput = prettier.format(generated.code, {
semi: true,
parser: "babel",
plugins: [prettierParserBabel],
const tabs = new Map<string, React.ReactNode>();
const generator = HIR.run(func);
let ast;
while (true) {
const next = generator.next();
if (next.done) {
ast = next.value;
break;
}
const result = next.value;
switch (result.kind) {
case "ast": {
const { code } = codegen(result.value, source);
tabs.set(
result.name,
<TextTabContent output={code}></TextTabContent>
);
break;
}
case "hir": {
const text = printHIR(result.value.body);
tabs.set(
result.name,
<TextTabContent output={text}></TextTabContent>
);
break;
}
case "reactive": {
const text = printReactiveFunction(result.value);
tabs.set(
result.name,
<TextTabContent output={text}></TextTabContent>
);
break;
}
default: {
throw new Error("Unexpected result kind");
}
}
}
// Ensure that JS and the JS source map come first
const reorderedTabs = new Map();
const { code, sourceMapUrl } = codegen(ast, source);
reorderedTabs.set("JS", <TextTabContent output={code}></TextTabContent>);
if (sourceMapUrl) {
reorderedTabs.set(
"SourceMap",
<>
<iframe
src={sourceMapUrl}
className="w-full h-96"
title="Generated Code"
/>
</>
);
}
tabs.forEach((tab, name) => {
reorderedTabs.set(name, tab);
});
return {
hirOutput,
ssaOutput,
eliminateRedundantPhiOutput,
inferTypesOutput,
inferReferenceEffectsOutput,
inferMutableRangesOutput,
inferReactiveScopeVariablesOutput,
inferReactiveScopesOutput,
reactiveFunctionOutput,
leaveSSAOutput,
codegenOutput,
sourceMapUrl,
};
return reorderedTabs;
} catch (e: any) {
return e.toString();
}
}
function codegen(
ast: any,
source: string
): { code: any; sourceMapUrl: string | null } {
const generated = generate(
ast,
{ sourceMaps: true, sourceFileName: "input.js" },
source
);
const sourceMapUrl = getSourceMapUrl(
generated.code,
JSON.stringify(generated.map)
);
const codegenOutput = prettier.format(generated.code, {
semi: true,
parser: "babel",
plugins: [prettierParserBabel],
});
return { code: codegenOutput, sourceMapUrl };
}
// TODO(gsn: Update diagnostics ƒrom HIR output
function Output({ store, setTabsOpen, tabsOpen }: Props) {
function Output({ store }: Props) {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set());
const compilerOutput = useMemo(() => compile(store.source), [store.source]);
if (typeof compilerOutput === "string") {
@@ -158,66 +135,7 @@ function Output({ store, setTabsOpen, tabsOpen }: Props) {
defaultTab="HIR"
setTabsOpen={setTabsOpen}
tabsOpen={tabsOpen}
tabs={{
JS: <TextTabContent output={compilerOutput.codegenOutput} />,
HIR: (
<TextTabContent output={compilerOutput.hirOutput}></TextTabContent>
),
SSA: (
<TextTabContent output={compilerOutput.ssaOutput}></TextTabContent>
),
EliminateRedundantPhi: (
<TextTabContent
output={compilerOutput.eliminateRedundantPhiOutput}
></TextTabContent>
),
InferTypes: (
<TextTabContent
output={compilerOutput.inferTypesOutput}
></TextTabContent>
),
InferReferenceEffects: (
<TextTabContent
output={compilerOutput.inferReferenceEffectsOutput}
></TextTabContent>
),
InferMutableRanges: (
<TextTabContent
output={compilerOutput.inferMutableRangesOutput}
></TextTabContent>
),
LeaveSSA: (
<TextTabContent
output={compilerOutput.leaveSSAOutput}
></TextTabContent>
),
InferReactiveScopeVariables: (
<TextTabContent
output={compilerOutput.inferReactiveScopeVariablesOutput}
></TextTabContent>
),
InferReactiveScopes: (
<TextTabContent
output={compilerOutput.inferReactiveScopesOutput}
></TextTabContent>
),
ReactiveFunctions: (
<TextTabContent
output={compilerOutput.reactiveFunctionOutput}
></TextTabContent>
),
SourceMap: (
<>
{compilerOutput.sourceMapUrl && (
<iframe
src={compilerOutput.sourceMapUrl}
className="w-full h-96"
title="Generated Code"
/>
)}
</>
),
}}
tabs={compilerOutput}
/>
);
}
@@ -8,7 +8,7 @@
import clsx from "clsx";
import invariant from "invariant";
import { useSnackbar } from "notistack";
import { useDeferredValue, useState } from "react";
import { useDeferredValue } from "react";
import { useMountEffect } from "../../hooks";
import { defaultStore } from "../../lib/defaultStore";
import {
@@ -19,7 +19,6 @@ import {
type Store,
} from "../../lib/stores";
import { useStore, useStoreDispatch } from "../StoreContext";
import { TabTypes } from "../TabbedWindow";
import Input from "./Input";
import Output from "./Output";
@@ -28,7 +27,6 @@ export default function Editor() {
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
const { enqueueSnackbar } = useSnackbar();
const [tabsOpen, setTabsOpen] = useState<Map<TabTypes, boolean>>(new Map());
useMountEffect(() => {
let mountStore: Store;
@@ -62,11 +60,7 @@ export default function Editor() {
<Input />
</div>
<div className={clsx("flex sm:flex")}>
<Output
tabsOpen={tabsOpen}
setTabsOpen={setTabsOpen}
store={deferredStore}
/>
<Output store={deferredStore} />
</div>
</div>
</>
@@ -7,35 +7,21 @@
import React, { useCallback } from "react";
export type TabTypes =
| "HIR"
| "SSA"
| "EliminateRedundantPhi"
| "InferTypes"
| "InferReferenceEffects"
| "InferMutableRanges"
| "InferReactiveScopeVariables"
| "InferReactiveScopes"
| "ReactiveFunctions"
| "LeaveSSA"
| "JS"
| "SourceMap";
type TabsRecord = Record<TabTypes, React.ReactNode>;
type TabsRecord = Map<string, React.ReactNode>;
export default function TabbedWindow(props: {
defaultTab: string | null;
tabs: TabsRecord;
tabsOpen: Map<TabTypes, boolean>;
setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
}): React.ReactElement {
return (
<div className="flex flex-row h-full">
{Object.keys(props.tabs).map((name, index, all) => {
{Array.from(props.tabs.keys()).map((name) => {
return (
<TabbedWindowItem
name={name as TabTypes}
key={index}
name={name}
key={name}
tabs={props.tabs}
tabsOpen={props.tabsOpen}
setTabsOpen={props.setTabsOpen}
@@ -52,16 +38,20 @@ function TabbedWindowItem({
tabsOpen,
setTabsOpen,
}: {
name: TabTypes;
name: string;
tabs: TabsRecord;
tabsOpen: Map<TabTypes, boolean>;
setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
}): React.ReactElement {
const isShow = tabsOpen.get(name) ?? false;
const isShow = tabsOpen.has(name);
const toggleTabs = useCallback(() => {
const nextState = new Map(tabsOpen);
nextState.set(name, !isShow);
const nextState = new Set(tabsOpen);
if (nextState.has(name)) {
nextState.delete(name);
} else {
nextState.add(name);
}
setTabsOpen(nextState);
}, [tabsOpen, name, isShow, setTabsOpen]);
@@ -75,7 +65,7 @@ function TabbedWindowItem({
>
- {name}
</h2>
{tabs[name]}
{tabs.get(name) ?? <div>No output for {name}</div>}
</div>
) : (
<div className="relative items-center h-full px-4 py-8 align-middle border-r border-grey-200">