@samitouri / QOS-React-2 / commits / bd7e19b85f

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!)

Joe Savona committed Jan 12, 2023 at 11:49 UTC bd7e19b85f42f757b59e74cf2eb9ffe736916ae6
3 files changed +323 -587
compiler/forget/src/HIR/Codegen.ts deleted
-575
@@ -1,575 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -import * as t from "@babel/types";
9 -import { invariant } from "../Utils/CompilerError";
10 -import { todoInvariant } from "../Utils/todo";
11 -import { assertExhaustive } from "../Utils/utils";
12 -import {
13 - BlockId,
14 - GeneratedSource,
15 - HIRFunction,
16 - Identifier,
17 - IdentifierId,
18 - Instruction,
19 - InstructionId,
20 - InstructionKind,
21 - InstructionValue,
22 - LValue,
23 - Place,
24 - ReactiveInstruction,
25 - SourceLocation,
26 -} from "./HIR";
27 -import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor";
28 -
29 -function withLoc<TNode extends t.Node, T extends (...args: any[]) => TNode>(
30 - fn: T
31 -): (
32 - loc: SourceLocation | null | undefined,
33 - ...args: Parameters<T>
34 -) => ReturnType<T> {
35 - return (
36 - loc: SourceLocation | null | undefined,
37 - ...args: Parameters<T>
38 - ): ReturnType<T> => {
39 - const node = fn(...args);
40 - if (loc != null && loc != GeneratedSource) {
41 - node.loc = loc;
42 - }
43 - // @ts-ignore
44 - return node;
45 - };
46 -}
47 -
48 -export const createBinaryExpression = withLoc(t.binaryExpression);
49 -export const createCallExpression = withLoc(t.callExpression);
50 -export const createExpressionStatement = withLoc(t.expressionStatement);
51 -export const createFunctionDeclaration = withLoc(t.functionDeclaration);
52 -export const createLabelledStatement = withLoc(t.labeledStatement);
53 -export const createVariableDeclaration = withLoc(t.variableDeclaration);
54 -export const createWhileStatement = withLoc(t.whileStatement);
55 -
56 -/**
57 - * Converts HIR into Babel nodes, which can then be printed into source text.
58 - * Note that converting source to HIR and back is not guaranteed to produce
59 - * identicl source text: instead, it is guaranteed to produce semantically
60 - * equivalent JavaScript. Where possible the original shape of the source
61 - * code is preserved. Notably, temporary variables are only introduced
62 - * where strictly necessary such that in general the only variable declarations
63 - * that appear in the output are those that appeared in the input.
64 - *
65 - * However, it is expected that minor changes may occur, such as splitting
66 - * multiple variable declarations into one, converting `else` branches
67 - * into fallthrough branches, etc.
68 - *
69 - * Also, the *semantics* of variable resolution are preserved, but the exact
70 - * original block structure is *not* guaranteed to be preserved. As such,
71 - * variable names in the output may have a suffix attached to distinguish them.
72 - * It is expected that the output will be passed through a minifier which can
73 - * rename variables to reduce code size. In theory minification could be
74 - * performed as an HIR optimization pass, that is left todo for the time being.
75 - */
76 -export default function codegen(fn: HIRFunction): t.Function {
77 - const visitor = new CodegenVisitor();
78 - const body = visitTree(fn, visitor);
79 - invariant(t.isBlockStatement(body), "Expected a block statement");
80 - const params = fn.params.map((param) => convertIdentifier(param.identifier));
81 - return createFunctionDeclaration(
82 - fn.loc,
83 - fn.id !== null ? convertIdentifier(fn.id) : null,
84 - params,
85 - body,
86 - fn.generator,
87 - fn.async
88 - );
89 -}
90 -
91 -export type Temporaries = Map<IdentifierId, t.Expression>;
92 -
93 -class CodegenVisitor
94 - implements
95 - Visitor<
96 - Array<t.Statement>,
97 - t.Statement,
98 - Array<t.Statement>,
99 - Array<t.Statement>,
100 - t.Expression,
101 - t.Statement,
102 - t.SwitchCase
103 - >
104 -{
105 - depth: number = 0;
106 - temp: Map<IdentifierId, t.Expression> = new Map();
107 -
108 - enterBlock(): t.Statement[] {
109 - this.depth++;
110 - return [];
111 - }
112 - appendBlock(
113 - block: t.Statement[],
114 - item: t.Statement,
115 - blockId?: BlockId | undefined
116 - ): void {
117 - if (item.type === "EmptyStatement") {
118 - return;
119 - }
120 - if (blockId !== undefined) {
121 - block.push(
122 - createLabelledStatement(
123 - item.loc,
124 - t.identifier(codegenLabel(blockId)),
125 - item
126 - )
127 - );
128 - } else {
129 - block.push(item);
130 - }
131 - }
132 - leaveBlock(block: t.Statement[]): t.Statement {
133 - this.depth--;
134 - return t.blockStatement(block);
135 - }
136 - enterValueBlock(): t.Statement[] {
137 - return this.enterBlock();
138 - }
139 - appendValueBlock(block: t.Statement[], item: t.Statement): void {
140 - this.appendBlock(block, item);
141 - }
142 - leaveValueBlock(
143 - block: t.Statement[],
144 - place: { value: t.Expression; id: InstructionId } | null
145 - ): t.Expression {
146 - this.depth--;
147 - if (block.length === 0) {
148 - invariant(place !== null, "Unexpected empty value block");
149 - return place.value;
150 - }
151 - const expressions = block.map((stmt) => {
152 - switch (stmt.type) {
153 - case "ExpressionStatement":
154 - return stmt.expression;
155 - default:
156 - todoInvariant(
157 - false,
158 - `Handle conversion of ${stmt.type} to expression`
159 - );
160 - }
161 - });
162 - if (place !== null) {
163 - expressions.push(place.value);
164 - }
165 - return t.sequenceExpression(expressions);
166 - }
167 -
168 - enterInitBlock(block: t.Statement[]): t.Statement[] {
169 - return this.enterBlock();
170 - }
171 -
172 - appendInitBlock(block: t.Statement[], item: t.Statement): void {
173 - this.appendBlock(block, item);
174 - }
175 - leaveInitBlock(block: t.Statement[]): t.Statement[] {
176 - switch (block.length) {
177 - case 0: {
178 - return [t.emptyStatement()];
179 - }
180 - case 1: {
181 - return [block[0]];
182 - }
183 - default: {
184 - return [t.blockStatement(block)];
185 - }
186 - }
187 - }
188 -
189 - visitValue(value: InstructionValue): t.Expression {
190 - return codegenInstructionValue(this.temp, value);
191 - }
192 - visitInstruction(instr: Instruction, value: t.Expression): t.Statement {
193 - return codegenInstruction(this.temp, instr, value);
194 - }
195 - visitTerminalId(id: InstructionId): void {}
196 - visitImplicitTerminal(): t.Statement | null {
197 - return null;
198 - }
199 - visitTerminal(
200 - terminal: BlockTerminal<
201 - t.Statement[],
202 - t.Expression,
203 - t.Statement,
204 - t.SwitchCase
205 - >
206 - ): t.Statement {
207 - switch (terminal.kind) {
208 - case "break": {
209 - if (terminal.label) {
210 - return t.breakStatement(t.identifier(codegenLabel(terminal.label)));
211 - } else {
212 - return t.breakStatement();
213 - }
214 - }
215 - case "continue": {
216 - if (terminal.label) {
217 - return t.continueStatement(
218 - t.identifier(codegenLabel(terminal.label))
219 - );
220 - } else {
221 - return t.continueStatement();
222 - }
223 - }
224 - case "if": {
225 - return t.ifStatement(
226 - terminal.test,
227 - terminal.consequent,
228 - terminal.alternate
229 - );
230 - }
231 - case "switch": {
232 - return t.switchStatement(terminal.test, terminal.cases);
233 - }
234 - case "while": {
235 - return createWhileStatement(terminal.loc, terminal.test, terminal.loop);
236 - }
237 - case "for": {
238 - const initBlock = terminal.init;
239 - invariant(
240 - initBlock.length === 1,
241 - "Expected for init to be a single expression or statement"
242 - );
243 - const initStatement = initBlock[0]!;
244 - let init;
245 - if (initStatement.type === "VariableDeclaration") {
246 - init = initStatement;
247 - } else if (initStatement.type === "ExpressionStatement") {
248 - init = initStatement.expression;
249 - } else {
250 - invariant(
251 - false,
252 - `Expected 'for' init block to contain variable declaration or an expression, got '${initStatement.type}'.`
253 - );
254 - }
255 - return t.forStatement(
256 - init,
257 - terminal.test,
258 - terminal.update,
259 - terminal.loop
260 - );
261 - }
262 - case "return": {
263 - const createReturnStatement = withLoc(t.returnStatement);
264 - if (terminal.value !== null) {
265 - return createReturnStatement(terminal.loc, terminal.value);
266 - } else if (this.depth === 1) {
267 - // A return at the top-level of a function must be the last instruction,
268 - // and functions implicitly return after the last instruction of the top-level.
269 - // Elide the return.
270 - return t.emptyStatement();
271 - } else {
272 - return createReturnStatement(terminal.loc);
273 - }
274 - }
275 - case "throw": {
276 - return t.throwStatement(terminal.value);
277 - }
278 - default: {
279 - assertExhaustive(
280 - terminal,
281 - `Unexpected terminal kind '${(terminal as any).kind}'`
282 - );
283 - }
284 - }
285 - }
286 - visitCase(test: t.Expression | null, block: t.Statement): t.SwitchCase {
287 - return t.switchCase(test, [block]);
288 - }
289 -}
290 -
291 -export function codegenLabel(id: BlockId): string {
292 - return `bb${id}`;
293 -}
294 -
295 -export function codegenInstruction(
296 - temp: Temporaries,
297 - instr: ReactiveInstruction,
298 - value: t.Expression
299 -): t.Statement {
300 - if (t.isStatement(value)) {
301 - return value;
302 - }
303 - if (instr.lvalue === null) {
304 - return t.expressionStatement(value);
305 - }
306 - if (instr.lvalue.place.identifier.name === null) {
307 - // temporary
308 - temp.set(instr.lvalue.place.identifier.id, value);
309 - return t.emptyStatement();
310 - } else {
311 - switch (instr.lvalue.kind) {
312 - case InstructionKind.Const: {
313 - return createVariableDeclaration(instr.loc, "const", [
314 - t.variableDeclarator(codegenLVal(instr.lvalue), value),
315 - ]);
316 - }
317 - case InstructionKind.Let: {
318 - return createVariableDeclaration(instr.loc, "let", [
319 - t.variableDeclarator(codegenLVal(instr.lvalue), value),
320 - ]);
321 - }
322 - case InstructionKind.Reassign: {
323 - return createExpressionStatement(
324 - instr.loc,
325 - t.assignmentExpression("=", codegenLVal(instr.lvalue), value)
326 - );
327 - }
328 - default: {
329 - assertExhaustive(
330 - instr.lvalue.kind,
331 - `Unexpected instruction kind '${instr.lvalue.kind}'`
332 - );
333 - }
334 - }
335 - }
336 -}
337 -
338 -export function codegenInstructionValue(
339 - temp: Temporaries,
340 - instrValue: InstructionValue
341 -): t.Expression {
342 - let value: t.Expression;
343 - switch (instrValue.kind) {
344 - case "ArrayExpression": {
345 - const elements = instrValue.elements.map((element) =>
346 - codegenPlace(temp, element)
347 - );
348 - value = t.arrayExpression(elements);
349 - break;
350 - }
351 - case "BinaryExpression": {
352 - const left = codegenPlace(temp, instrValue.left);
353 - const right = codegenPlace(temp, instrValue.right);
354 - value = createBinaryExpression(
355 - instrValue.loc,
356 - instrValue.operator,
357 - left,
358 - right
359 - );
360 - break;
361 - }
362 - case "UnaryExpression": {
363 - value = t.unaryExpression(
364 - instrValue.operator as "throw", // todo
365 - codegenPlace(temp, instrValue.value)
366 - );
367 - break;
368 - }
369 - case "Primitive": {
370 - value = codegenValue(temp, instrValue.value);
371 - break;
372 - }
373 - case "CallExpression": {
374 - const callee = codegenPlace(temp, instrValue.callee);
375 - const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
376 - value = createCallExpression(instrValue.loc, callee, args);
377 - break;
378 - }
379 - case "NewExpression": {
380 - const callee = codegenPlace(temp, instrValue.callee);
381 - const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
382 - value = t.newExpression(callee, args);
383 - break;
384 - }
385 - case "ObjectExpression": {
386 - const properties = [];
387 - if (instrValue.properties !== null) {
388 - for (const [property, value] of instrValue.properties) {
389 - properties.push(
390 - t.objectProperty(
391 - t.stringLiteral(property),
392 - codegenPlace(temp, value)
393 - )
394 - );
395 - }
396 - }
397 - value = t.objectExpression(properties);
398 - break;
399 - }
400 - case "JSXText": {
401 - value = t.stringLiteral(instrValue.value);
402 - break;
403 - }
404 - case "JsxExpression": {
405 - const attributes: Array<t.JSXAttribute> = [];
406 - for (const [prop, value] of instrValue.props) {
407 - attributes.push(
408 - t.jsxAttribute(
409 - t.jsxIdentifier(prop),
410 - t.jsxExpressionContainer(codegenPlace(temp, value))
411 - )
412 - );
413 - }
414 - let tagValue = codegenPlace(temp, instrValue.tag);
415 - let tag: string;
416 - if (tagValue.type === "Identifier") {
417 - tag = tagValue.name;
418 - } else {
419 - invariant(
420 - tagValue.type === "StringLiteral",
421 - "Expected JSX tag to be an identifier or string"
422 - );
423 - tag = tagValue.value;
424 - }
425 - const children =
426 - instrValue.children !== null
427 - ? instrValue.children.map((child) => codegenJsxElement(temp, child))
428 - : [];
429 - value = t.jsxElement(
430 - t.jsxOpeningElement(
431 - t.jsxIdentifier(tag),
432 - attributes,
433 - instrValue.children === null
434 - ),
435 - instrValue.children !== null
436 - ? t.jsxClosingElement(t.jsxIdentifier(tag))
437 - : null,
438 - children,
439 - instrValue.children === null
440 - );
441 - break;
442 - }
443 - case "JsxFragment": {
444 - value = t.jsxFragment(
445 - t.jsxOpeningFragment(),
446 - t.jsxClosingFragment(),
447 - instrValue.children.map((child) => codegenJsxElement(temp, child))
448 - );
449 - break;
450 - }
451 - case "OtherStatement": {
452 - const node = instrValue.node;
453 - if (!t.isExpression(node)) {
454 - return node as any; // TODO handle statements, jsx fragments
455 - }
456 - value = node;
457 - break;
458 - }
459 - case "PropertyStore": {
460 - value = t.assignmentExpression(
461 - "=",
462 - t.memberExpression(
463 - codegenPlace(temp, instrValue.object),
464 - t.identifier(instrValue.property)
465 - ),
466 - codegenPlace(temp, instrValue.value)
467 - );
468 - break;
469 - }
470 - case "PropertyLoad": {
471 - value = t.memberExpression(
472 - codegenPlace(temp, instrValue.object),
473 - t.identifier(instrValue.property)
474 - );
475 - break;
476 - }
477 - case "ComputedStore": {
478 - value = t.assignmentExpression(
479 - "=",
480 - t.memberExpression(
481 - codegenPlace(temp, instrValue.object),
482 - codegenPlace(temp, instrValue.property),
483 - true
484 - ),
485 - codegenPlace(temp, instrValue.value)
486 - );
487 - break;
488 - }
489 - case "ComputedLoad": {
490 - value = t.memberExpression(
491 - codegenPlace(temp, instrValue.object),
492 - codegenPlace(temp, instrValue.property),
493 - true
494 - );
495 - break;
496 - }
497 - case "Identifier": {
498 - value = codegenPlace(temp, instrValue);
499 - break;
500 - }
501 - case "FunctionExpression": {
502 - const id =
503 - instrValue.name !== null ? t.identifier(instrValue.name) : null;
504 - const params = instrValue.params.map((p) => t.identifier(p));
505 - value = t.functionExpression(id, params, instrValue.body);
506 - break;
507 - }
508 - default: {
509 - assertExhaustive(
510 - instrValue,
511 - `Unexpected instruction value kind '${(instrValue as any).kind}'`
512 - );
513 - }
514 - }
515 - return value;
516 -}
517 -
518 -function codegenJsxElement(
519 - temp: Temporaries,
520 - place: Place
521 -):
522 - | t.JSXText
523 - | t.JSXExpressionContainer
524 - | t.JSXSpreadChild
525 - | t.JSXElement
526 - | t.JSXFragment {
527 - const value = codegenPlace(temp, place);
528 - switch (value.type) {
529 - case "StringLiteral": {
530 - return t.jsxText(value.value);
531 - }
532 - default: {
533 - return t.jsxExpressionContainer(value);
534 - }
535 - }
536 -}
537 -
538 -export function codegenLVal(lval: LValue): t.LVal {
539 - return convertIdentifier(lval.place.identifier);
540 -}
541 -
542 -function codegenValue(
543 - temp: Temporaries,
544 - value: boolean | number | string | null | undefined
545 -): t.Expression {
546 - if (typeof value === "number") {
547 - return t.numericLiteral(value);
548 - } else if (typeof value === "boolean") {
549 - return t.booleanLiteral(value);
550 - } else if (typeof value === "string") {
551 - return t.stringLiteral(value);
552 - } else if (value === null) {
553 - return t.nullLiteral();
554 - } else if (value === undefined) {
555 - return t.identifier("undefined");
556 - } else {
557 - assertExhaustive(value, "Unexpected primitive value kind");
558 - }
559 -}
560 -
561 -export function codegenPlace(temp: Temporaries, place: Place): t.Expression {
562 - todoInvariant(place.kind === "Identifier", "support scope values");
563 - let tmp = temp.get(place.identifier.id);
564 - if (tmp != null) {
565 - return tmp;
566 - }
567 - return convertIdentifier(place.identifier);
568 -}
569 -
570 -export function convertIdentifier(identifier: Identifier): t.Identifier {
571 - if (identifier.name !== null) {
572 - return t.identifier(`${identifier.name}`);
573 - }
574 - return t.identifier(`t${identifier.id}`);
575 -}
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+323 -10
@@ -8,17 +8,14 @@
8 import * as t from "@babel/types";
9 import invariant from "invariant";
10 import {
11 - codegenInstruction,
12 - codegenInstructionValue,
13 - codegenLabel,
14 - codegenPlace,
15 - convertIdentifier,
16 - createFunctionDeclaration,
17 - Temporaries,
18 -} from "../HIR/Codegen";
19 -import {
11 + BlockId,
12 + GeneratedSource,
13 Identifier,
14 + IdentifierId,
15 InstructionKind,
16 + InstructionValue,
17 + LValue,
18 + Place,
19 ReactiveBlock,
20 ReactiveFunction,
21 ReactiveInstruction,
@@ -26,6 +23,7 @@ import {
23 ReactiveScopeDependency,
24 ReactiveTerminal,
25 ReactiveValueBlock,
26 + SourceLocation,
27 } from "../HIR/HIR";
28 import { todoInvariant } from "../Utils/todo";
29 import { assertExhaustive } from "../Utils/utils";
@@ -299,7 +297,7 @@ function codegenTerminal(cx: Context, terminal: ReactiveTerminal): t.Statement {
297 }
298 }
299
302 -export function codegenInstructionNullable(
300 +function codegenInstructionNullable(
301 cx: Context,
302 instr: ReactiveInstruction,
303 value: t.Expression
@@ -390,3 +388,318 @@ function codegenDependency(
388 }
389 return object;
390 }
391 +
392 +function withLoc<TNode extends t.Node, T extends (...args: any[]) => TNode>(
393 + fn: T
394 +): (
395 + loc: SourceLocation | null | undefined,
396 + ...args: Parameters<T>
397 +) => ReturnType<T> {
398 + return (
399 + loc: SourceLocation | null | undefined,
400 + ...args: Parameters<T>
401 + ): ReturnType<T> => {
402 + const node = fn(...args);
403 + if (loc != null && loc != GeneratedSource) {
404 + node.loc = loc;
405 + }
406 + // @ts-ignore
407 + return node;
408 + };
409 +}
410 +
411 +const createBinaryExpression = withLoc(t.binaryExpression);
412 +const createCallExpression = withLoc(t.callExpression);
413 +const createExpressionStatement = withLoc(t.expressionStatement);
414 +const createFunctionDeclaration = withLoc(t.functionDeclaration);
415 +const createLabelledStatement = withLoc(t.labeledStatement);
416 +const createVariableDeclaration = withLoc(t.variableDeclaration);
417 +const createWhileStatement = withLoc(t.whileStatement);
418 +
419 +type Temporaries = Map<IdentifierId, t.Expression>;
420 +
421 +function codegenLabel(id: BlockId): string {
422 + return `bb${id}`;
423 +}
424 +
425 +function codegenInstruction(
426 + temp: Temporaries,
427 + instr: ReactiveInstruction,
428 + value: t.Expression
429 +): t.Statement {
430 + if (t.isStatement(value)) {
431 + return value;
432 + }
433 + if (instr.lvalue === null) {
434 + return t.expressionStatement(value);
435 + }
436 + if (instr.lvalue.place.identifier.name === null) {
437 + // temporary
438 + temp.set(instr.lvalue.place.identifier.id, value);
439 + return t.emptyStatement();
440 + } else {
441 + switch (instr.lvalue.kind) {
442 + case InstructionKind.Const: {
443 + return createVariableDeclaration(instr.loc, "const", [
444 + t.variableDeclarator(codegenLVal(instr.lvalue), value),
445 + ]);
446 + }
447 + case InstructionKind.Let: {
448 + return createVariableDeclaration(instr.loc, "let", [
449 + t.variableDeclarator(codegenLVal(instr.lvalue), value),
450 + ]);
451 + }
452 + case InstructionKind.Reassign: {
453 + return createExpressionStatement(
454 + instr.loc,
455 + t.assignmentExpression("=", codegenLVal(instr.lvalue), value)
456 + );
457 + }
458 + default: {
459 + assertExhaustive(
460 + instr.lvalue.kind,
461 + `Unexpected instruction kind '${instr.lvalue.kind}'`
462 + );
463 + }
464 + }
465 + }
466 +}
467 +
468 +function codegenInstructionValue(
469 + temp: Temporaries,
470 + instrValue: InstructionValue
471 +): t.Expression {
472 + let value: t.Expression;
473 + switch (instrValue.kind) {
474 + case "ArrayExpression": {
475 + const elements = instrValue.elements.map((element) =>
476 + codegenPlace(temp, element)
477 + );
478 + value = t.arrayExpression(elements);
479 + break;
480 + }
481 + case "BinaryExpression": {
482 + const left = codegenPlace(temp, instrValue.left);
483 + const right = codegenPlace(temp, instrValue.right);
484 + value = createBinaryExpression(
485 + instrValue.loc,
486 + instrValue.operator,
487 + left,
488 + right
489 + );
490 + break;
491 + }
492 + case "UnaryExpression": {
493 + value = t.unaryExpression(
494 + instrValue.operator as "throw", // todo
495 + codegenPlace(temp, instrValue.value)
496 + );
497 + break;
498 + }
499 + case "Primitive": {
500 + value = codegenValue(temp, instrValue.value);
501 + break;
502 + }
503 + case "CallExpression": {
504 + const callee = codegenPlace(temp, instrValue.callee);
505 + const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
506 + value = createCallExpression(instrValue.loc, callee, args);
507 + break;
508 + }
509 + case "NewExpression": {
510 + const callee = codegenPlace(temp, instrValue.callee);
511 + const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
512 + value = t.newExpression(callee, args);
513 + break;
514 + }
515 + case "ObjectExpression": {
516 + const properties = [];
517 + if (instrValue.properties !== null) {
518 + for (const [property, value] of instrValue.properties) {
519 + properties.push(
520 + t.objectProperty(
521 + t.stringLiteral(property),
522 + codegenPlace(temp, value)
523 + )
524 + );
525 + }
526 + }
527 + value = t.objectExpression(properties);
528 + break;
529 + }
530 + case "JSXText": {
531 + value = t.stringLiteral(instrValue.value);
532 + break;
533 + }
534 + case "JsxExpression": {
535 + const attributes: Array<t.JSXAttribute> = [];
536 + for (const [prop, value] of instrValue.props) {
537 + attributes.push(
538 + t.jsxAttribute(
539 + t.jsxIdentifier(prop),
540 + t.jsxExpressionContainer(codegenPlace(temp, value))
541 + )
542 + );
543 + }
544 + let tagValue = codegenPlace(temp, instrValue.tag);
545 + let tag: string;
546 + if (tagValue.type === "Identifier") {
547 + tag = tagValue.name;
548 + } else {
549 + invariant(
550 + tagValue.type === "StringLiteral",
551 + "Expected JSX tag to be an identifier or string"
552 + );
553 + tag = tagValue.value;
554 + }
555 + const children =
556 + instrValue.children !== null
557 + ? instrValue.children.map((child) => codegenJsxElement(temp, child))
558 + : [];
559 + value = t.jsxElement(
560 + t.jsxOpeningElement(
561 + t.jsxIdentifier(tag),
562 + attributes,
563 + instrValue.children === null
564 + ),
565 + instrValue.children !== null
566 + ? t.jsxClosingElement(t.jsxIdentifier(tag))
567 + : null,
568 + children,
569 + instrValue.children === null
570 + );
571 + break;
572 + }
573 + case "JsxFragment": {
574 + value = t.jsxFragment(
575 + t.jsxOpeningFragment(),
576 + t.jsxClosingFragment(),
577 + instrValue.children.map((child) => codegenJsxElement(temp, child))
578 + );
579 + break;
580 + }
581 + case "OtherStatement": {
582 + const node = instrValue.node;
583 + if (!t.isExpression(node)) {
584 + return node as any; // TODO handle statements, jsx fragments
585 + }
586 + value = node;
587 + break;
588 + }
589 + case "PropertyStore": {
590 + value = t.assignmentExpression(
591 + "=",
592 + t.memberExpression(
593 + codegenPlace(temp, instrValue.object),
594 + t.identifier(instrValue.property)
595 + ),
596 + codegenPlace(temp, instrValue.value)
597 + );
598 + break;
599 + }
600 + case "PropertyLoad": {
601 + value = t.memberExpression(
602 + codegenPlace(temp, instrValue.object),
603 + t.identifier(instrValue.property)
604 + );
605 + break;
606 + }
607 + case "ComputedStore": {
608 + value = t.assignmentExpression(
609 + "=",
610 + t.memberExpression(
611 + codegenPlace(temp, instrValue.object),
612 + codegenPlace(temp, instrValue.property),
613 + true
614 + ),
615 + codegenPlace(temp, instrValue.value)
616 + );
617 + break;
618 + }
619 + case "ComputedLoad": {
620 + value = t.memberExpression(
621 + codegenPlace(temp, instrValue.object),
622 + codegenPlace(temp, instrValue.property),
623 + true
624 + );
625 + break;
626 + }
627 + case "Identifier": {
628 + value = codegenPlace(temp, instrValue);
629 + break;
630 + }
631 + case "FunctionExpression": {
632 + const id =
633 + instrValue.name !== null ? t.identifier(instrValue.name) : null;
634 + const params = instrValue.params.map((p) => t.identifier(p));
635 + value = t.functionExpression(id, params, instrValue.body);
636 + break;
637 + }
638 + default: {
639 + assertExhaustive(
640 + instrValue,
641 + `Unexpected instruction value kind '${(instrValue as any).kind}'`
642 + );
643 + }
644 + }
645 + return value;
646 +}
647 +
648 +function codegenJsxElement(
649 + temp: Temporaries,
650 + place: Place
651 +):
652 + | t.JSXText
653 + | t.JSXExpressionContainer
654 + | t.JSXSpreadChild
655 + | t.JSXElement
656 + | t.JSXFragment {
657 + const value = codegenPlace(temp, place);
658 + switch (value.type) {
659 + case "StringLiteral": {
660 + return t.jsxText(value.value);
661 + }
662 + default: {
663 + return t.jsxExpressionContainer(value);
664 + }
665 + }
666 +}
667 +
668 +function codegenLVal(lval: LValue): t.LVal {
669 + return convertIdentifier(lval.place.identifier);
670 +}
671 +
672 +function codegenValue(
673 + temp: Temporaries,
674 + value: boolean | number | string | null | undefined
675 +): t.Expression {
676 + if (typeof value === "number") {
677 + return t.numericLiteral(value);
678 + } else if (typeof value === "boolean") {
679 + return t.booleanLiteral(value);
680 + } else if (typeof value === "string") {
681 + return t.stringLiteral(value);
682 + } else if (value === null) {
683 + return t.nullLiteral();
684 + } else if (value === undefined) {
685 + return t.identifier("undefined");
686 + } else {
687 + assertExhaustive(value, "Unexpected primitive value kind");
688 + }
689 +}
690 +
691 +function codegenPlace(temp: Temporaries, place: Place): t.Expression {
692 + todoInvariant(place.kind === "Identifier", "support scope values");
693 + let tmp = temp.get(place.identifier.id);
694 + if (tmp != null) {
695 + return tmp;
696 + }
697 + return convertIdentifier(place.identifier);
698 +}
699 +
700 +function convertIdentifier(identifier: Identifier): t.Identifier {
701 + if (identifier.name !== null) {
702 + return t.identifier(`${identifier.name}`);
703 + }
704 + return t.identifier(`t${identifier.id}`);
705 +}
compiler/forget/src/index.ts
-2
@@ -16,7 +16,6 @@ import traverse, { NodePath } from "@babel/traverse";
16 import * as t from "@babel/types";
17 import { compile, run } from "./CompilerPipeline";
18 import { lower } from "./HIR/BuildHIR";
19 -import codegen from "./HIR/Codegen";
19 import { Environment } from "./HIR/HIRBuilder";
20 import printHIR, { printFunction } from "./HIR/PrintHIR";
21 import { inferMutableRanges, inferReferenceEffects } from "./Inference";
@@ -56,7 +55,6 @@ function parseFunctions(
55
56 export const HIR = {
57 buildReactiveFunction,
59 - codegen,
58 codegenReactiveFunction,
59 compile,
60 eliminateRedundantPhi,