mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
811 lines
22 KiB
TypeScript
811 lines
22 KiB
TypeScript
/**
|
|
* 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 "invariant";
|
|
import {
|
|
BlockId,
|
|
GeneratedSource,
|
|
Identifier,
|
|
IdentifierId,
|
|
InstructionKind,
|
|
LValue,
|
|
Place,
|
|
ReactiveBlock,
|
|
ReactiveFunction,
|
|
ReactiveInstruction,
|
|
ReactiveScope,
|
|
ReactiveScopeDependency,
|
|
ReactiveTerminal,
|
|
ReactiveValue,
|
|
ReactiveValueBlock,
|
|
SourceLocation,
|
|
} from "../HIR/HIR";
|
|
import { todoInvariant } from "../Utils/todo";
|
|
import { assertExhaustive } from "../Utils/utils";
|
|
|
|
export function codegenReactiveFunction(
|
|
fn: ReactiveFunction
|
|
): t.FunctionDeclaration {
|
|
const cx = new Context();
|
|
const params = fn.params.map((param) => convertIdentifier(param.identifier));
|
|
const body = codegenBlock(cx, fn.body);
|
|
const statements = body.body;
|
|
if (statements.length !== 0) {
|
|
const last = statements[statements.length - 1];
|
|
if (last.type === "ReturnStatement" && last.argument == null) {
|
|
statements.pop();
|
|
}
|
|
}
|
|
if (cx.nextCacheIndex !== 0) {
|
|
statements.unshift(
|
|
t.variableDeclaration("const", [
|
|
t.variableDeclarator(
|
|
t.identifier("$"),
|
|
t.callExpression(
|
|
t.memberExpression(
|
|
t.identifier("React"),
|
|
t.identifier("useMemoCache")
|
|
),
|
|
[]
|
|
)
|
|
),
|
|
])
|
|
);
|
|
}
|
|
return createFunctionDeclaration(
|
|
fn.loc,
|
|
fn.id !== null ? convertIdentifier(fn.id) : null,
|
|
params,
|
|
body,
|
|
fn.generator,
|
|
fn.async
|
|
);
|
|
}
|
|
|
|
class Context {
|
|
#nextCacheIndex: number = 0;
|
|
#identifiers: Set<Identifier> = new Set();
|
|
temp: Temporaries = new Map();
|
|
|
|
get nextCacheIndex(): number {
|
|
return this.#nextCacheIndex++;
|
|
}
|
|
|
|
declare(identifier: Identifier): void {
|
|
this.#identifiers.add(identifier);
|
|
}
|
|
|
|
declared(identifier: Identifier): boolean {
|
|
return this.#identifiers.has(identifier);
|
|
}
|
|
}
|
|
|
|
function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
|
|
const statements: Array<t.Statement> = [];
|
|
for (const item of block) {
|
|
switch (item.kind) {
|
|
case "instruction": {
|
|
const statement = codegenInstructionNullable(
|
|
cx,
|
|
item.instruction,
|
|
codegenInstructionValue(cx, item.instruction.value)
|
|
);
|
|
if (statement !== null) {
|
|
statements.push(statement);
|
|
}
|
|
break;
|
|
}
|
|
case "scope": {
|
|
codegenReactiveScope(cx, statements, item.scope, item.instructions);
|
|
break;
|
|
}
|
|
case "terminal": {
|
|
const statement = codegenTerminal(cx, item.terminal);
|
|
if (statement === null) {
|
|
break;
|
|
}
|
|
if (item.label !== null) {
|
|
statements.push(
|
|
t.labeledStatement(
|
|
t.identifier(codegenLabel(item.label)),
|
|
statement
|
|
)
|
|
);
|
|
} else {
|
|
statements.push(statement);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assertExhaustive(item, `Unexpected item kind '${(item as any).kind}'`);
|
|
}
|
|
}
|
|
}
|
|
return t.blockStatement(statements);
|
|
}
|
|
|
|
function codegenReactiveScope(
|
|
cx: Context,
|
|
statements: Array<t.Statement>,
|
|
scope: ReactiveScope,
|
|
block: ReactiveBlock
|
|
): void {
|
|
const cacheStoreStatements: Array<t.Statement> = [];
|
|
const cacheLoadStatements: Array<t.Statement> = [];
|
|
const changeIdentifiers: Array<t.Identifier> = [];
|
|
for (const dep of scope.dependencies) {
|
|
const index = cx.nextCacheIndex;
|
|
const changeIdentifier = t.identifier(`c_${index}`);
|
|
const depValue = codegenDependency(cx, dep);
|
|
|
|
changeIdentifiers.push(changeIdentifier);
|
|
statements.push(
|
|
t.variableDeclaration("const", [
|
|
t.variableDeclarator(
|
|
changeIdentifier,
|
|
t.binaryExpression(
|
|
"!==",
|
|
t.memberExpression(
|
|
t.identifier("$"),
|
|
t.numericLiteral(index),
|
|
true
|
|
),
|
|
depValue
|
|
)
|
|
),
|
|
])
|
|
);
|
|
cacheStoreStatements.push(
|
|
t.expressionStatement(
|
|
t.assignmentExpression(
|
|
"=",
|
|
t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
|
|
depValue
|
|
)
|
|
)
|
|
);
|
|
}
|
|
let firstOutputIndex: number | null = null;
|
|
for (const output of scope.outputs) {
|
|
const index = cx.nextCacheIndex;
|
|
if (firstOutputIndex === null) {
|
|
firstOutputIndex = index;
|
|
}
|
|
|
|
// TODO @josephsavona: ensure change and temp variables have non-conflicting names
|
|
output.name ??= `t${index}`;
|
|
|
|
const name = convertIdentifier(output);
|
|
cx.declare(output);
|
|
statements.push(t.variableDeclaration("let", [t.variableDeclarator(name)]));
|
|
cacheStoreStatements.push(
|
|
t.expressionStatement(
|
|
t.assignmentExpression(
|
|
"=",
|
|
t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
|
|
name
|
|
)
|
|
)
|
|
);
|
|
cacheLoadStatements.push(
|
|
t.expressionStatement(
|
|
t.assignmentExpression(
|
|
"=",
|
|
name,
|
|
t.memberExpression(t.identifier("$"), t.numericLiteral(index), true)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
invariant(
|
|
firstOutputIndex !== null,
|
|
"Expected scope '@%s' to have at least one output",
|
|
scope.id
|
|
);
|
|
let testCondition = (changeIdentifiers as Array<t.Expression>).reduce(
|
|
(acc: t.Expression | null, ident: t.Expression) => {
|
|
if (acc == null) {
|
|
return ident;
|
|
}
|
|
return t.logicalExpression("||", acc, ident);
|
|
},
|
|
null as t.Expression | null
|
|
);
|
|
if (testCondition === null) {
|
|
testCondition = t.binaryExpression(
|
|
"===",
|
|
t.memberExpression(
|
|
t.identifier("$"),
|
|
t.numericLiteral(firstOutputIndex),
|
|
true
|
|
),
|
|
t.callExpression(
|
|
t.memberExpression(t.identifier("Symbol"), t.identifier("for")),
|
|
[t.stringLiteral("react.memo_cache_sentinel")]
|
|
)
|
|
);
|
|
}
|
|
|
|
const computationBlock = codegenBlock(cx, block);
|
|
computationBlock.body.push(...cacheStoreStatements);
|
|
const memoBlock = t.blockStatement(cacheLoadStatements);
|
|
statements.push(t.ifStatement(testCondition, computationBlock, memoBlock));
|
|
}
|
|
|
|
function codegenTerminal(
|
|
cx: Context,
|
|
terminal: ReactiveTerminal
|
|
): t.Statement | null {
|
|
switch (terminal.kind) {
|
|
case "break": {
|
|
if (terminal.implicit) {
|
|
return null;
|
|
}
|
|
return t.breakStatement(
|
|
terminal.label !== null
|
|
? t.identifier(codegenLabel(terminal.label))
|
|
: null
|
|
);
|
|
}
|
|
case "continue": {
|
|
if (terminal.implicit) {
|
|
return null;
|
|
}
|
|
return t.continueStatement(
|
|
terminal.label !== null
|
|
? t.identifier(codegenLabel(terminal.label))
|
|
: null
|
|
);
|
|
}
|
|
case "for": {
|
|
return t.forStatement(
|
|
codegenForInit(cx, terminal.init),
|
|
codegenInstructionValue(cx, terminal.test),
|
|
codegenValueBlock(cx, terminal.update),
|
|
codegenBlock(cx, terminal.loop)
|
|
);
|
|
}
|
|
case "if": {
|
|
return t.ifStatement(
|
|
codegenPlace(cx, terminal.test),
|
|
codegenBlock(cx, terminal.consequent),
|
|
terminal.alternate !== null
|
|
? codegenBlock(cx, terminal.alternate)
|
|
: null
|
|
);
|
|
}
|
|
case "return": {
|
|
return t.returnStatement(
|
|
terminal.value !== null ? codegenPlace(cx, terminal.value) : null
|
|
);
|
|
}
|
|
case "switch": {
|
|
return t.switchStatement(
|
|
codegenPlace(cx, terminal.test),
|
|
terminal.cases.map((case_) => {
|
|
const test =
|
|
case_.test !== null ? codegenPlace(cx, case_.test) : null;
|
|
const block = codegenBlock(cx, case_.block!);
|
|
return t.switchCase(test, [block]);
|
|
})
|
|
);
|
|
}
|
|
case "throw": {
|
|
return t.throwStatement(codegenPlace(cx, terminal.value));
|
|
}
|
|
case "while": {
|
|
const test = codegenInstructionValue(cx, terminal.test);
|
|
return t.whileStatement(test, codegenBlock(cx, terminal.loop));
|
|
}
|
|
default: {
|
|
assertExhaustive(
|
|
terminal,
|
|
`Unexpected terminal kind '${(terminal as any).kind}'`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function codegenInstructionNullable(
|
|
cx: Context,
|
|
instr: ReactiveInstruction,
|
|
value: t.Expression
|
|
): t.Statement | null {
|
|
let statement;
|
|
if (instr.lvalue !== null && cx.declared(instr.lvalue.place.identifier)) {
|
|
statement = codegenInstruction(
|
|
cx,
|
|
{
|
|
...instr,
|
|
lvalue: {
|
|
...instr.lvalue,
|
|
kind: InstructionKind.Reassign,
|
|
},
|
|
},
|
|
value
|
|
);
|
|
} else {
|
|
statement = codegenInstruction(cx, instr, value);
|
|
}
|
|
if (statement.type === "EmptyStatement") {
|
|
return null;
|
|
}
|
|
return statement;
|
|
}
|
|
|
|
function codegenForInit(
|
|
cx: Context,
|
|
init: ReactiveValueBlock
|
|
): t.Expression | t.VariableDeclaration | null {
|
|
const body = codegenBlock(cx, init.instructions).body;
|
|
if (init.last !== null) {
|
|
invariant(
|
|
body.length === 0,
|
|
"Expected for init block to produce only temporaries"
|
|
);
|
|
return codegenInstructionValue(cx, init.last.value);
|
|
} else {
|
|
invariant(
|
|
body.length === 1,
|
|
"Expected for init to have a variable declaration"
|
|
);
|
|
const declaration = body[0]!;
|
|
invariant(
|
|
declaration.type === "VariableDeclaration",
|
|
"Expected a variable declaration"
|
|
);
|
|
return declaration;
|
|
}
|
|
}
|
|
|
|
function codegenValueBlock(
|
|
cx: Context,
|
|
block: ReactiveValueBlock
|
|
): t.Expression {
|
|
const body = codegenBlock(cx, block.instructions).body;
|
|
const expressions = body.map((stmt) => {
|
|
if (stmt.type === "ExpressionStatement") {
|
|
return stmt.expression;
|
|
} else {
|
|
todoInvariant(false, `Handle conversion of ${stmt.type} to expression`);
|
|
}
|
|
});
|
|
if (block.last !== null) {
|
|
const value = codegenInstructionValue(cx, block.last.value);
|
|
expressions.push(value);
|
|
}
|
|
invariant(
|
|
expressions.length !== 0,
|
|
"Expected a value block to produce one or more expressions"
|
|
);
|
|
if (expressions.length === 1) {
|
|
return expressions[0];
|
|
} else {
|
|
return t.sequenceExpression(expressions);
|
|
}
|
|
}
|
|
|
|
function codegenDependency(
|
|
cx: Context,
|
|
dependency: ReactiveScopeDependency
|
|
): t.Expression {
|
|
let object: t.Expression = convertIdentifier(dependency.place.identifier);
|
|
if (dependency.path !== null) {
|
|
for (const path of dependency.path) {
|
|
object = t.memberExpression(object, t.identifier(path));
|
|
}
|
|
}
|
|
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);
|
|
const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);
|
|
const createLogicalExpression = withLoc(t.logicalExpression);
|
|
const createSequenceExpression = withLoc(t.sequenceExpression);
|
|
const createConditionalExpression = withLoc(t.conditionalExpression);
|
|
|
|
type Temporaries = Map<IdentifierId, t.Expression>;
|
|
|
|
function codegenLabel(id: BlockId): string {
|
|
return `bb${id}`;
|
|
}
|
|
|
|
function codegenInstruction(
|
|
cx: Context,
|
|
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
|
|
cx.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(
|
|
cx: Context,
|
|
instrValue: ReactiveValue
|
|
): t.Expression {
|
|
let value: t.Expression;
|
|
switch (instrValue.kind) {
|
|
case "ArrayExpression": {
|
|
const elements = instrValue.elements.map((element) =>
|
|
codegenPlace(cx, element)
|
|
);
|
|
value = t.arrayExpression(elements);
|
|
break;
|
|
}
|
|
case "BinaryExpression": {
|
|
const left = codegenPlace(cx, instrValue.left);
|
|
const right = codegenPlace(cx, instrValue.right);
|
|
value = createBinaryExpression(
|
|
instrValue.loc,
|
|
instrValue.operator,
|
|
left,
|
|
right
|
|
);
|
|
break;
|
|
}
|
|
case "UnaryExpression": {
|
|
value = t.unaryExpression(
|
|
instrValue.operator as "throw", // todo
|
|
codegenPlace(cx, instrValue.value)
|
|
);
|
|
break;
|
|
}
|
|
case "Primitive": {
|
|
value = codegenValue(cx, instrValue.value);
|
|
break;
|
|
}
|
|
case "CallExpression": {
|
|
const callee = codegenPlace(cx, instrValue.callee);
|
|
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
|
|
value = createCallExpression(instrValue.loc, callee, args);
|
|
break;
|
|
}
|
|
case "PropertyCall": {
|
|
const receiver = codegenPlace(cx, instrValue.receiver);
|
|
const callee = t.memberExpression(
|
|
receiver,
|
|
t.identifier(instrValue.property)
|
|
);
|
|
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
|
|
value = createCallExpression(instrValue.loc, callee, args);
|
|
break;
|
|
}
|
|
case "ComputedCall": {
|
|
const receiver = codegenPlace(cx, instrValue.receiver);
|
|
const property = codegenPlace(cx, instrValue.property);
|
|
const callee = t.memberExpression(receiver, property, true);
|
|
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
|
|
value = createCallExpression(instrValue.loc, callee, args);
|
|
break;
|
|
}
|
|
case "NewExpression": {
|
|
const callee = codegenPlace(cx, instrValue.callee);
|
|
const args = instrValue.args.map((arg) => codegenPlace(cx, 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(cx, 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(cx, value))
|
|
)
|
|
);
|
|
}
|
|
let tagValue = codegenPlace(cx, 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(cx, 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(cx, child))
|
|
);
|
|
break;
|
|
}
|
|
case "UnsupportedNode": {
|
|
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(cx, instrValue.object),
|
|
t.identifier(instrValue.property)
|
|
),
|
|
codegenPlace(cx, instrValue.value)
|
|
);
|
|
break;
|
|
}
|
|
case "PropertyLoad": {
|
|
if (instrValue.optional) {
|
|
value = t.optionalMemberExpression(
|
|
codegenPlace(cx, instrValue.object),
|
|
t.identifier(instrValue.property),
|
|
undefined,
|
|
true
|
|
);
|
|
} else {
|
|
value = t.memberExpression(
|
|
codegenPlace(cx, instrValue.object),
|
|
t.identifier(instrValue.property)
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "ComputedStore": {
|
|
value = t.assignmentExpression(
|
|
"=",
|
|
t.memberExpression(
|
|
codegenPlace(cx, instrValue.object),
|
|
codegenPlace(cx, instrValue.property),
|
|
true
|
|
),
|
|
codegenPlace(cx, instrValue.value)
|
|
);
|
|
break;
|
|
}
|
|
case "ComputedLoad": {
|
|
value = t.memberExpression(
|
|
codegenPlace(cx, instrValue.object),
|
|
codegenPlace(cx, instrValue.property),
|
|
true
|
|
);
|
|
break;
|
|
}
|
|
case "Identifier": {
|
|
value = codegenPlace(cx, instrValue);
|
|
break;
|
|
}
|
|
case "FunctionExpression": {
|
|
const id =
|
|
instrValue.name !== null ? t.identifier(instrValue.name) : null;
|
|
const params = instrValue.params.map((p) => t.identifier(p));
|
|
value = instrValue.expr;
|
|
break;
|
|
}
|
|
case "TaggedTemplateExpression": {
|
|
value = createTaggedTemplateExpression(
|
|
instrValue.loc,
|
|
codegenPlace(cx, instrValue.tag),
|
|
t.templateLiteral([t.templateElement(instrValue.value)], [])
|
|
);
|
|
break;
|
|
}
|
|
case "LogicalExpression": {
|
|
value = createLogicalExpression(
|
|
instrValue.loc,
|
|
instrValue.operator,
|
|
codegenInstructionValue(cx, instrValue.left),
|
|
codegenInstructionValue(cx, instrValue.right)
|
|
);
|
|
break;
|
|
}
|
|
case "ConditionalExpression": {
|
|
value = createConditionalExpression(
|
|
instrValue.loc,
|
|
codegenInstructionValue(cx, instrValue.test),
|
|
codegenInstructionValue(cx, instrValue.consequent),
|
|
codegenInstructionValue(cx, instrValue.alternate)
|
|
);
|
|
break;
|
|
}
|
|
case "SequenceExpression": {
|
|
const body = codegenBlock(
|
|
cx,
|
|
instrValue.instructions.map((instruction) => ({
|
|
kind: "instruction",
|
|
instruction,
|
|
}))
|
|
).body;
|
|
const expressions = body.map((stmt) => {
|
|
if (stmt.type === "ExpressionStatement") {
|
|
return stmt.expression;
|
|
} else {
|
|
if (t.isVariableDeclaration(stmt)) {
|
|
const declarator = stmt.declarations[0];
|
|
todoInvariant(
|
|
false,
|
|
`Cannot declare variables in a value block, tried to declare '${
|
|
(declarator.id as t.Identifier).name
|
|
}'`
|
|
);
|
|
}
|
|
todoInvariant(
|
|
false,
|
|
`Handle conversion of ${stmt.type} to expression`
|
|
);
|
|
}
|
|
});
|
|
if (expressions.length === 0) {
|
|
value = codegenInstructionValue(cx, instrValue.value);
|
|
} else {
|
|
value = createSequenceExpression(instrValue.loc, [
|
|
...expressions,
|
|
codegenInstructionValue(cx, instrValue.value),
|
|
]);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assertExhaustive(
|
|
instrValue,
|
|
`Unexpected instruction value kind '${(instrValue as any).kind}'`
|
|
);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function codegenJsxElement(
|
|
cx: Context,
|
|
place: Place
|
|
):
|
|
| t.JSXText
|
|
| t.JSXExpressionContainer
|
|
| t.JSXSpreadChild
|
|
| t.JSXElement
|
|
| t.JSXFragment {
|
|
const value = codegenPlace(cx, 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(
|
|
cx: Context,
|
|
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(cx: Context, place: Place): t.Expression {
|
|
todoInvariant(place.kind === "Identifier", "support scope values");
|
|
let tmp = cx.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}`);
|
|
}
|