mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Codegen is from ReactiveFunction->AST only
Other than BuildReactiveFunction (HIR -> ReactiveFunction), Codegen.ts was the only other remaining place that we use HIRTreeVisitor. However, we've already switched the compiler to use the new form of codegen, CodegenReactiveFunction.ts. This PR extracts the shared code from Codegen.ts into the latter, and deletes the unused bits of Codegen.ts which relied on the visitor API. This now frees us up to merge BuildReactiveFunction and HIRTreeVisitor, removing all the complexity of the visitor trait and type params. Note that the `ReactiveFunction` data type can represent non-reactive functions. So we can still compile non-React code after this change, the pipeline is `AST` -> (BuildHIR) -> `HIR` -> (BuildReactiveFunction) -> `ReactiveFunction` -> (CodegenReactiveFunction) -> `AST`. Which is the exact sequence I mapped out at the start of this project ;-) (happy that worked out!)
This commit is contained in:
@@ -1,575 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as t from "@babel/types";
|
||||
import { invariant } from "../Utils/CompilerError";
|
||||
import { todoInvariant } from "../Utils/todo";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import {
|
||||
BlockId,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
LValue,
|
||||
Place,
|
||||
ReactiveInstruction,
|
||||
SourceLocation,
|
||||
} from "./HIR";
|
||||
import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor";
|
||||
|
||||
function withLoc<TNode extends t.Node, T extends (...args: any[]) => TNode>(
|
||||
fn: T
|
||||
): (
|
||||
loc: SourceLocation | null | undefined,
|
||||
...args: Parameters<T>
|
||||
) => ReturnType<T> {
|
||||
return (
|
||||
loc: SourceLocation | null | undefined,
|
||||
...args: Parameters<T>
|
||||
): ReturnType<T> => {
|
||||
const node = fn(...args);
|
||||
if (loc != null && loc != GeneratedSource) {
|
||||
node.loc = loc;
|
||||
}
|
||||
// @ts-ignore
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
export const createBinaryExpression = withLoc(t.binaryExpression);
|
||||
export const createCallExpression = withLoc(t.callExpression);
|
||||
export const createExpressionStatement = withLoc(t.expressionStatement);
|
||||
export const createFunctionDeclaration = withLoc(t.functionDeclaration);
|
||||
export const createLabelledStatement = withLoc(t.labeledStatement);
|
||||
export const createVariableDeclaration = withLoc(t.variableDeclaration);
|
||||
export const createWhileStatement = withLoc(t.whileStatement);
|
||||
|
||||
/**
|
||||
* Converts HIR into Babel nodes, which can then be printed into source text.
|
||||
* Note that converting source to HIR and back is not guaranteed to produce
|
||||
* identicl source text: instead, it is guaranteed to produce semantically
|
||||
* equivalent JavaScript. Where possible the original shape of the source
|
||||
* code is preserved. Notably, temporary variables are only introduced
|
||||
* where strictly necessary such that in general the only variable declarations
|
||||
* that appear in the output are those that appeared in the input.
|
||||
*
|
||||
* However, it is expected that minor changes may occur, such as splitting
|
||||
* multiple variable declarations into one, converting `else` branches
|
||||
* into fallthrough branches, etc.
|
||||
*
|
||||
* Also, the *semantics* of variable resolution are preserved, but the exact
|
||||
* original block structure is *not* guaranteed to be preserved. As such,
|
||||
* variable names in the output may have a suffix attached to distinguish them.
|
||||
* It is expected that the output will be passed through a minifier which can
|
||||
* rename variables to reduce code size. In theory minification could be
|
||||
* performed as an HIR optimization pass, that is left todo for the time being.
|
||||
*/
|
||||
export default function codegen(fn: HIRFunction): t.Function {
|
||||
const visitor = new CodegenVisitor();
|
||||
const body = visitTree(fn, visitor);
|
||||
invariant(t.isBlockStatement(body), "Expected a block statement");
|
||||
const params = fn.params.map((param) => convertIdentifier(param.identifier));
|
||||
return createFunctionDeclaration(
|
||||
fn.loc,
|
||||
fn.id !== null ? convertIdentifier(fn.id) : null,
|
||||
params,
|
||||
body,
|
||||
fn.generator,
|
||||
fn.async
|
||||
);
|
||||
}
|
||||
|
||||
export type Temporaries = Map<IdentifierId, t.Expression>;
|
||||
|
||||
class CodegenVisitor
|
||||
implements
|
||||
Visitor<
|
||||
Array<t.Statement>,
|
||||
t.Statement,
|
||||
Array<t.Statement>,
|
||||
Array<t.Statement>,
|
||||
t.Expression,
|
||||
t.Statement,
|
||||
t.SwitchCase
|
||||
>
|
||||
{
|
||||
depth: number = 0;
|
||||
temp: Map<IdentifierId, t.Expression> = new Map();
|
||||
|
||||
enterBlock(): t.Statement[] {
|
||||
this.depth++;
|
||||
return [];
|
||||
}
|
||||
appendBlock(
|
||||
block: t.Statement[],
|
||||
item: t.Statement,
|
||||
blockId?: BlockId | undefined
|
||||
): void {
|
||||
if (item.type === "EmptyStatement") {
|
||||
return;
|
||||
}
|
||||
if (blockId !== undefined) {
|
||||
block.push(
|
||||
createLabelledStatement(
|
||||
item.loc,
|
||||
t.identifier(codegenLabel(blockId)),
|
||||
item
|
||||
)
|
||||
);
|
||||
} else {
|
||||
block.push(item);
|
||||
}
|
||||
}
|
||||
leaveBlock(block: t.Statement[]): t.Statement {
|
||||
this.depth--;
|
||||
return t.blockStatement(block);
|
||||
}
|
||||
enterValueBlock(): t.Statement[] {
|
||||
return this.enterBlock();
|
||||
}
|
||||
appendValueBlock(block: t.Statement[], item: t.Statement): void {
|
||||
this.appendBlock(block, item);
|
||||
}
|
||||
leaveValueBlock(
|
||||
block: t.Statement[],
|
||||
place: { value: t.Expression; id: InstructionId } | null
|
||||
): t.Expression {
|
||||
this.depth--;
|
||||
if (block.length === 0) {
|
||||
invariant(place !== null, "Unexpected empty value block");
|
||||
return place.value;
|
||||
}
|
||||
const expressions = block.map((stmt) => {
|
||||
switch (stmt.type) {
|
||||
case "ExpressionStatement":
|
||||
return stmt.expression;
|
||||
default:
|
||||
todoInvariant(
|
||||
false,
|
||||
`Handle conversion of ${stmt.type} to expression`
|
||||
);
|
||||
}
|
||||
});
|
||||
if (place !== null) {
|
||||
expressions.push(place.value);
|
||||
}
|
||||
return t.sequenceExpression(expressions);
|
||||
}
|
||||
|
||||
enterInitBlock(block: t.Statement[]): t.Statement[] {
|
||||
return this.enterBlock();
|
||||
}
|
||||
|
||||
appendInitBlock(block: t.Statement[], item: t.Statement): void {
|
||||
this.appendBlock(block, item);
|
||||
}
|
||||
leaveInitBlock(block: t.Statement[]): t.Statement[] {
|
||||
switch (block.length) {
|
||||
case 0: {
|
||||
return [t.emptyStatement()];
|
||||
}
|
||||
case 1: {
|
||||
return [block[0]];
|
||||
}
|
||||
default: {
|
||||
return [t.blockStatement(block)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitValue(value: InstructionValue): t.Expression {
|
||||
return codegenInstructionValue(this.temp, value);
|
||||
}
|
||||
visitInstruction(instr: Instruction, value: t.Expression): t.Statement {
|
||||
return codegenInstruction(this.temp, instr, value);
|
||||
}
|
||||
visitTerminalId(id: InstructionId): void {}
|
||||
visitImplicitTerminal(): t.Statement | null {
|
||||
return null;
|
||||
}
|
||||
visitTerminal(
|
||||
terminal: BlockTerminal<
|
||||
t.Statement[],
|
||||
t.Expression,
|
||||
t.Statement,
|
||||
t.SwitchCase
|
||||
>
|
||||
): t.Statement {
|
||||
switch (terminal.kind) {
|
||||
case "break": {
|
||||
if (terminal.label) {
|
||||
return t.breakStatement(t.identifier(codegenLabel(terminal.label)));
|
||||
} else {
|
||||
return t.breakStatement();
|
||||
}
|
||||
}
|
||||
case "continue": {
|
||||
if (terminal.label) {
|
||||
return t.continueStatement(
|
||||
t.identifier(codegenLabel(terminal.label))
|
||||
);
|
||||
} else {
|
||||
return t.continueStatement();
|
||||
}
|
||||
}
|
||||
case "if": {
|
||||
return t.ifStatement(
|
||||
terminal.test,
|
||||
terminal.consequent,
|
||||
terminal.alternate
|
||||
);
|
||||
}
|
||||
case "switch": {
|
||||
return t.switchStatement(terminal.test, terminal.cases);
|
||||
}
|
||||
case "while": {
|
||||
return createWhileStatement(terminal.loc, terminal.test, terminal.loop);
|
||||
}
|
||||
case "for": {
|
||||
const initBlock = terminal.init;
|
||||
invariant(
|
||||
initBlock.length === 1,
|
||||
"Expected for init to be a single expression or statement"
|
||||
);
|
||||
const initStatement = initBlock[0]!;
|
||||
let init;
|
||||
if (initStatement.type === "VariableDeclaration") {
|
||||
init = initStatement;
|
||||
} else if (initStatement.type === "ExpressionStatement") {
|
||||
init = initStatement.expression;
|
||||
} else {
|
||||
invariant(
|
||||
false,
|
||||
`Expected 'for' init block to contain variable declaration or an expression, got '${initStatement.type}'.`
|
||||
);
|
||||
}
|
||||
return t.forStatement(
|
||||
init,
|
||||
terminal.test,
|
||||
terminal.update,
|
||||
terminal.loop
|
||||
);
|
||||
}
|
||||
case "return": {
|
||||
const createReturnStatement = withLoc(t.returnStatement);
|
||||
if (terminal.value !== null) {
|
||||
return createReturnStatement(terminal.loc, terminal.value);
|
||||
} else if (this.depth === 1) {
|
||||
// A return at the top-level of a function must be the last instruction,
|
||||
// and functions implicitly return after the last instruction of the top-level.
|
||||
// Elide the return.
|
||||
return t.emptyStatement();
|
||||
} else {
|
||||
return createReturnStatement(terminal.loc);
|
||||
}
|
||||
}
|
||||
case "throw": {
|
||||
return t.throwStatement(terminal.value);
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
`Unexpected terminal kind '${(terminal as any).kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
visitCase(test: t.Expression | null, block: t.Statement): t.SwitchCase {
|
||||
return t.switchCase(test, [block]);
|
||||
}
|
||||
}
|
||||
|
||||
export function codegenLabel(id: BlockId): string {
|
||||
return `bb${id}`;
|
||||
}
|
||||
|
||||
export function codegenInstruction(
|
||||
temp: Temporaries,
|
||||
instr: ReactiveInstruction,
|
||||
value: t.Expression
|
||||
): t.Statement {
|
||||
if (t.isStatement(value)) {
|
||||
return value;
|
||||
}
|
||||
if (instr.lvalue === null) {
|
||||
return t.expressionStatement(value);
|
||||
}
|
||||
if (instr.lvalue.place.identifier.name === null) {
|
||||
// temporary
|
||||
temp.set(instr.lvalue.place.identifier.id, value);
|
||||
return t.emptyStatement();
|
||||
} else {
|
||||
switch (instr.lvalue.kind) {
|
||||
case InstructionKind.Const: {
|
||||
return createVariableDeclaration(instr.loc, "const", [
|
||||
t.variableDeclarator(codegenLVal(instr.lvalue), value),
|
||||
]);
|
||||
}
|
||||
case InstructionKind.Let: {
|
||||
return createVariableDeclaration(instr.loc, "let", [
|
||||
t.variableDeclarator(codegenLVal(instr.lvalue), value),
|
||||
]);
|
||||
}
|
||||
case InstructionKind.Reassign: {
|
||||
return createExpressionStatement(
|
||||
instr.loc,
|
||||
t.assignmentExpression("=", codegenLVal(instr.lvalue), value)
|
||||
);
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instr.lvalue.kind,
|
||||
`Unexpected instruction kind '${instr.lvalue.kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function codegenInstructionValue(
|
||||
temp: Temporaries,
|
||||
instrValue: InstructionValue
|
||||
): t.Expression {
|
||||
let value: t.Expression;
|
||||
switch (instrValue.kind) {
|
||||
case "ArrayExpression": {
|
||||
const elements = instrValue.elements.map((element) =>
|
||||
codegenPlace(temp, element)
|
||||
);
|
||||
value = t.arrayExpression(elements);
|
||||
break;
|
||||
}
|
||||
case "BinaryExpression": {
|
||||
const left = codegenPlace(temp, instrValue.left);
|
||||
const right = codegenPlace(temp, instrValue.right);
|
||||
value = createBinaryExpression(
|
||||
instrValue.loc,
|
||||
instrValue.operator,
|
||||
left,
|
||||
right
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "UnaryExpression": {
|
||||
value = t.unaryExpression(
|
||||
instrValue.operator as "throw", // todo
|
||||
codegenPlace(temp, instrValue.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Primitive": {
|
||||
value = codegenValue(temp, instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "CallExpression": {
|
||||
const callee = codegenPlace(temp, instrValue.callee);
|
||||
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
|
||||
value = createCallExpression(instrValue.loc, callee, args);
|
||||
break;
|
||||
}
|
||||
case "NewExpression": {
|
||||
const callee = codegenPlace(temp, instrValue.callee);
|
||||
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
|
||||
value = t.newExpression(callee, args);
|
||||
break;
|
||||
}
|
||||
case "ObjectExpression": {
|
||||
const properties = [];
|
||||
if (instrValue.properties !== null) {
|
||||
for (const [property, value] of instrValue.properties) {
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
t.stringLiteral(property),
|
||||
codegenPlace(temp, value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
value = t.objectExpression(properties);
|
||||
break;
|
||||
}
|
||||
case "JSXText": {
|
||||
value = t.stringLiteral(instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "JsxExpression": {
|
||||
const attributes: Array<t.JSXAttribute> = [];
|
||||
for (const [prop, value] of instrValue.props) {
|
||||
attributes.push(
|
||||
t.jsxAttribute(
|
||||
t.jsxIdentifier(prop),
|
||||
t.jsxExpressionContainer(codegenPlace(temp, value))
|
||||
)
|
||||
);
|
||||
}
|
||||
let tagValue = codegenPlace(temp, instrValue.tag);
|
||||
let tag: string;
|
||||
if (tagValue.type === "Identifier") {
|
||||
tag = tagValue.name;
|
||||
} else {
|
||||
invariant(
|
||||
tagValue.type === "StringLiteral",
|
||||
"Expected JSX tag to be an identifier or string"
|
||||
);
|
||||
tag = tagValue.value;
|
||||
}
|
||||
const children =
|
||||
instrValue.children !== null
|
||||
? instrValue.children.map((child) => codegenJsxElement(temp, child))
|
||||
: [];
|
||||
value = t.jsxElement(
|
||||
t.jsxOpeningElement(
|
||||
t.jsxIdentifier(tag),
|
||||
attributes,
|
||||
instrValue.children === null
|
||||
),
|
||||
instrValue.children !== null
|
||||
? t.jsxClosingElement(t.jsxIdentifier(tag))
|
||||
: null,
|
||||
children,
|
||||
instrValue.children === null
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "JsxFragment": {
|
||||
value = t.jsxFragment(
|
||||
t.jsxOpeningFragment(),
|
||||
t.jsxClosingFragment(),
|
||||
instrValue.children.map((child) => codegenJsxElement(temp, child))
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "OtherStatement": {
|
||||
const node = instrValue.node;
|
||||
if (!t.isExpression(node)) {
|
||||
return node as any; // TODO handle statements, jsx fragments
|
||||
}
|
||||
value = node;
|
||||
break;
|
||||
}
|
||||
case "PropertyStore": {
|
||||
value = t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
t.identifier(instrValue.property)
|
||||
),
|
||||
codegenPlace(temp, instrValue.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
value = t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
t.identifier(instrValue.property)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "ComputedStore": {
|
||||
value = t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
codegenPlace(temp, instrValue.property),
|
||||
true
|
||||
),
|
||||
codegenPlace(temp, instrValue.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "ComputedLoad": {
|
||||
value = t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
codegenPlace(temp, instrValue.property),
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Identifier": {
|
||||
value = codegenPlace(temp, instrValue);
|
||||
break;
|
||||
}
|
||||
case "FunctionExpression": {
|
||||
const id =
|
||||
instrValue.name !== null ? t.identifier(instrValue.name) : null;
|
||||
const params = instrValue.params.map((p) => t.identifier(p));
|
||||
value = t.functionExpression(id, params, instrValue.body);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instrValue,
|
||||
`Unexpected instruction value kind '${(instrValue as any).kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function codegenJsxElement(
|
||||
temp: Temporaries,
|
||||
place: Place
|
||||
):
|
||||
| t.JSXText
|
||||
| t.JSXExpressionContainer
|
||||
| t.JSXSpreadChild
|
||||
| t.JSXElement
|
||||
| t.JSXFragment {
|
||||
const value = codegenPlace(temp, place);
|
||||
switch (value.type) {
|
||||
case "StringLiteral": {
|
||||
return t.jsxText(value.value);
|
||||
}
|
||||
default: {
|
||||
return t.jsxExpressionContainer(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function codegenLVal(lval: LValue): t.LVal {
|
||||
return convertIdentifier(lval.place.identifier);
|
||||
}
|
||||
|
||||
function codegenValue(
|
||||
temp: Temporaries,
|
||||
value: boolean | number | string | null | undefined
|
||||
): t.Expression {
|
||||
if (typeof value === "number") {
|
||||
return t.numericLiteral(value);
|
||||
} else if (typeof value === "boolean") {
|
||||
return t.booleanLiteral(value);
|
||||
} else if (typeof value === "string") {
|
||||
return t.stringLiteral(value);
|
||||
} else if (value === null) {
|
||||
return t.nullLiteral();
|
||||
} else if (value === undefined) {
|
||||
return t.identifier("undefined");
|
||||
} else {
|
||||
assertExhaustive(value, "Unexpected primitive value kind");
|
||||
}
|
||||
}
|
||||
|
||||
export function codegenPlace(temp: Temporaries, place: Place): t.Expression {
|
||||
todoInvariant(place.kind === "Identifier", "support scope values");
|
||||
let tmp = temp.get(place.identifier.id);
|
||||
if (tmp != null) {
|
||||
return tmp;
|
||||
}
|
||||
return convertIdentifier(place.identifier);
|
||||
}
|
||||
|
||||
export function convertIdentifier(identifier: Identifier): t.Identifier {
|
||||
if (identifier.name !== null) {
|
||||
return t.identifier(`${identifier.name}`);
|
||||
}
|
||||
return t.identifier(`t${identifier.id}`);
|
||||
}
|
||||
@@ -8,17 +8,14 @@
|
||||
import * as t from "@babel/types";
|
||||
import invariant from "invariant";
|
||||
import {
|
||||
codegenInstruction,
|
||||
codegenInstructionValue,
|
||||
codegenLabel,
|
||||
codegenPlace,
|
||||
convertIdentifier,
|
||||
createFunctionDeclaration,
|
||||
Temporaries,
|
||||
} from "../HIR/Codegen";
|
||||
import {
|
||||
BlockId,
|
||||
GeneratedSource,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
LValue,
|
||||
Place,
|
||||
ReactiveBlock,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
@@ -26,6 +23,7 @@ import {
|
||||
ReactiveScopeDependency,
|
||||
ReactiveTerminal,
|
||||
ReactiveValueBlock,
|
||||
SourceLocation,
|
||||
} from "../HIR/HIR";
|
||||
import { todoInvariant } from "../Utils/todo";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
@@ -299,7 +297,7 @@ function codegenTerminal(cx: Context, terminal: ReactiveTerminal): t.Statement {
|
||||
}
|
||||
}
|
||||
|
||||
export function codegenInstructionNullable(
|
||||
function codegenInstructionNullable(
|
||||
cx: Context,
|
||||
instr: ReactiveInstruction,
|
||||
value: t.Expression
|
||||
@@ -390,3 +388,318 @@ function codegenDependency(
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
function withLoc<TNode extends t.Node, T extends (...args: any[]) => TNode>(
|
||||
fn: T
|
||||
): (
|
||||
loc: SourceLocation | null | undefined,
|
||||
...args: Parameters<T>
|
||||
) => ReturnType<T> {
|
||||
return (
|
||||
loc: SourceLocation | null | undefined,
|
||||
...args: Parameters<T>
|
||||
): ReturnType<T> => {
|
||||
const node = fn(...args);
|
||||
if (loc != null && loc != GeneratedSource) {
|
||||
node.loc = loc;
|
||||
}
|
||||
// @ts-ignore
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
const createBinaryExpression = withLoc(t.binaryExpression);
|
||||
const createCallExpression = withLoc(t.callExpression);
|
||||
const createExpressionStatement = withLoc(t.expressionStatement);
|
||||
const createFunctionDeclaration = withLoc(t.functionDeclaration);
|
||||
const createLabelledStatement = withLoc(t.labeledStatement);
|
||||
const createVariableDeclaration = withLoc(t.variableDeclaration);
|
||||
const createWhileStatement = withLoc(t.whileStatement);
|
||||
|
||||
type Temporaries = Map<IdentifierId, t.Expression>;
|
||||
|
||||
function codegenLabel(id: BlockId): string {
|
||||
return `bb${id}`;
|
||||
}
|
||||
|
||||
function codegenInstruction(
|
||||
temp: Temporaries,
|
||||
instr: ReactiveInstruction,
|
||||
value: t.Expression
|
||||
): t.Statement {
|
||||
if (t.isStatement(value)) {
|
||||
return value;
|
||||
}
|
||||
if (instr.lvalue === null) {
|
||||
return t.expressionStatement(value);
|
||||
}
|
||||
if (instr.lvalue.place.identifier.name === null) {
|
||||
// temporary
|
||||
temp.set(instr.lvalue.place.identifier.id, value);
|
||||
return t.emptyStatement();
|
||||
} else {
|
||||
switch (instr.lvalue.kind) {
|
||||
case InstructionKind.Const: {
|
||||
return createVariableDeclaration(instr.loc, "const", [
|
||||
t.variableDeclarator(codegenLVal(instr.lvalue), value),
|
||||
]);
|
||||
}
|
||||
case InstructionKind.Let: {
|
||||
return createVariableDeclaration(instr.loc, "let", [
|
||||
t.variableDeclarator(codegenLVal(instr.lvalue), value),
|
||||
]);
|
||||
}
|
||||
case InstructionKind.Reassign: {
|
||||
return createExpressionStatement(
|
||||
instr.loc,
|
||||
t.assignmentExpression("=", codegenLVal(instr.lvalue), value)
|
||||
);
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instr.lvalue.kind,
|
||||
`Unexpected instruction kind '${instr.lvalue.kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function codegenInstructionValue(
|
||||
temp: Temporaries,
|
||||
instrValue: InstructionValue
|
||||
): t.Expression {
|
||||
let value: t.Expression;
|
||||
switch (instrValue.kind) {
|
||||
case "ArrayExpression": {
|
||||
const elements = instrValue.elements.map((element) =>
|
||||
codegenPlace(temp, element)
|
||||
);
|
||||
value = t.arrayExpression(elements);
|
||||
break;
|
||||
}
|
||||
case "BinaryExpression": {
|
||||
const left = codegenPlace(temp, instrValue.left);
|
||||
const right = codegenPlace(temp, instrValue.right);
|
||||
value = createBinaryExpression(
|
||||
instrValue.loc,
|
||||
instrValue.operator,
|
||||
left,
|
||||
right
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "UnaryExpression": {
|
||||
value = t.unaryExpression(
|
||||
instrValue.operator as "throw", // todo
|
||||
codegenPlace(temp, instrValue.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Primitive": {
|
||||
value = codegenValue(temp, instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "CallExpression": {
|
||||
const callee = codegenPlace(temp, instrValue.callee);
|
||||
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
|
||||
value = createCallExpression(instrValue.loc, callee, args);
|
||||
break;
|
||||
}
|
||||
case "NewExpression": {
|
||||
const callee = codegenPlace(temp, instrValue.callee);
|
||||
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
|
||||
value = t.newExpression(callee, args);
|
||||
break;
|
||||
}
|
||||
case "ObjectExpression": {
|
||||
const properties = [];
|
||||
if (instrValue.properties !== null) {
|
||||
for (const [property, value] of instrValue.properties) {
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
t.stringLiteral(property),
|
||||
codegenPlace(temp, value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
value = t.objectExpression(properties);
|
||||
break;
|
||||
}
|
||||
case "JSXText": {
|
||||
value = t.stringLiteral(instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "JsxExpression": {
|
||||
const attributes: Array<t.JSXAttribute> = [];
|
||||
for (const [prop, value] of instrValue.props) {
|
||||
attributes.push(
|
||||
t.jsxAttribute(
|
||||
t.jsxIdentifier(prop),
|
||||
t.jsxExpressionContainer(codegenPlace(temp, value))
|
||||
)
|
||||
);
|
||||
}
|
||||
let tagValue = codegenPlace(temp, instrValue.tag);
|
||||
let tag: string;
|
||||
if (tagValue.type === "Identifier") {
|
||||
tag = tagValue.name;
|
||||
} else {
|
||||
invariant(
|
||||
tagValue.type === "StringLiteral",
|
||||
"Expected JSX tag to be an identifier or string"
|
||||
);
|
||||
tag = tagValue.value;
|
||||
}
|
||||
const children =
|
||||
instrValue.children !== null
|
||||
? instrValue.children.map((child) => codegenJsxElement(temp, child))
|
||||
: [];
|
||||
value = t.jsxElement(
|
||||
t.jsxOpeningElement(
|
||||
t.jsxIdentifier(tag),
|
||||
attributes,
|
||||
instrValue.children === null
|
||||
),
|
||||
instrValue.children !== null
|
||||
? t.jsxClosingElement(t.jsxIdentifier(tag))
|
||||
: null,
|
||||
children,
|
||||
instrValue.children === null
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "JsxFragment": {
|
||||
value = t.jsxFragment(
|
||||
t.jsxOpeningFragment(),
|
||||
t.jsxClosingFragment(),
|
||||
instrValue.children.map((child) => codegenJsxElement(temp, child))
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "OtherStatement": {
|
||||
const node = instrValue.node;
|
||||
if (!t.isExpression(node)) {
|
||||
return node as any; // TODO handle statements, jsx fragments
|
||||
}
|
||||
value = node;
|
||||
break;
|
||||
}
|
||||
case "PropertyStore": {
|
||||
value = t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
t.identifier(instrValue.property)
|
||||
),
|
||||
codegenPlace(temp, instrValue.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
value = t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
t.identifier(instrValue.property)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "ComputedStore": {
|
||||
value = t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
codegenPlace(temp, instrValue.property),
|
||||
true
|
||||
),
|
||||
codegenPlace(temp, instrValue.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "ComputedLoad": {
|
||||
value = t.memberExpression(
|
||||
codegenPlace(temp, instrValue.object),
|
||||
codegenPlace(temp, instrValue.property),
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Identifier": {
|
||||
value = codegenPlace(temp, instrValue);
|
||||
break;
|
||||
}
|
||||
case "FunctionExpression": {
|
||||
const id =
|
||||
instrValue.name !== null ? t.identifier(instrValue.name) : null;
|
||||
const params = instrValue.params.map((p) => t.identifier(p));
|
||||
value = t.functionExpression(id, params, instrValue.body);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instrValue,
|
||||
`Unexpected instruction value kind '${(instrValue as any).kind}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function codegenJsxElement(
|
||||
temp: Temporaries,
|
||||
place: Place
|
||||
):
|
||||
| t.JSXText
|
||||
| t.JSXExpressionContainer
|
||||
| t.JSXSpreadChild
|
||||
| t.JSXElement
|
||||
| t.JSXFragment {
|
||||
const value = codegenPlace(temp, place);
|
||||
switch (value.type) {
|
||||
case "StringLiteral": {
|
||||
return t.jsxText(value.value);
|
||||
}
|
||||
default: {
|
||||
return t.jsxExpressionContainer(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function codegenLVal(lval: LValue): t.LVal {
|
||||
return convertIdentifier(lval.place.identifier);
|
||||
}
|
||||
|
||||
function codegenValue(
|
||||
temp: Temporaries,
|
||||
value: boolean | number | string | null | undefined
|
||||
): t.Expression {
|
||||
if (typeof value === "number") {
|
||||
return t.numericLiteral(value);
|
||||
} else if (typeof value === "boolean") {
|
||||
return t.booleanLiteral(value);
|
||||
} else if (typeof value === "string") {
|
||||
return t.stringLiteral(value);
|
||||
} else if (value === null) {
|
||||
return t.nullLiteral();
|
||||
} else if (value === undefined) {
|
||||
return t.identifier("undefined");
|
||||
} else {
|
||||
assertExhaustive(value, "Unexpected primitive value kind");
|
||||
}
|
||||
}
|
||||
|
||||
function codegenPlace(temp: Temporaries, place: Place): t.Expression {
|
||||
todoInvariant(place.kind === "Identifier", "support scope values");
|
||||
let tmp = temp.get(place.identifier.id);
|
||||
if (tmp != null) {
|
||||
return tmp;
|
||||
}
|
||||
return convertIdentifier(place.identifier);
|
||||
}
|
||||
|
||||
function convertIdentifier(identifier: Identifier): t.Identifier {
|
||||
if (identifier.name !== null) {
|
||||
return t.identifier(`${identifier.name}`);
|
||||
}
|
||||
return t.identifier(`t${identifier.id}`);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import traverse, { NodePath } from "@babel/traverse";
|
||||
import * as t from "@babel/types";
|
||||
import { compile, run } from "./CompilerPipeline";
|
||||
import { lower } from "./HIR/BuildHIR";
|
||||
import codegen from "./HIR/Codegen";
|
||||
import { Environment } from "./HIR/HIRBuilder";
|
||||
import printHIR, { printFunction } from "./HIR/PrintHIR";
|
||||
import { inferMutableRanges, inferReferenceEffects } from "./Inference";
|
||||
@@ -56,7 +55,6 @@ function parseFunctions(
|
||||
|
||||
export const HIR = {
|
||||
buildReactiveFunction,
|
||||
codegen,
|
||||
codegenReactiveFunction,
|
||||
compile,
|
||||
eliminateRedundantPhi,
|
||||
|
||||
Reference in New Issue
Block a user