import { assertExhaustive } from "../Utils/utils";
import {
BlockId,
Instruction,
InstructionValue,
makeInstructionId,
Pattern,
Place,
ReactiveInstruction,
ReactiveValue,
SpreadPattern,
Terminal,
} from "./HIR";
export function* eachInstructionLValue(
instr: ReactiveInstruction
): Iterable<Place> {
if (instr.lvalue !== null) {
yield instr.lvalue;
}
yield* eachInstructionValueLValue(instr.value);
}
export function* eachInstructionValueLValue(
value: ReactiveValue
): Iterable<Place> {
switch (value.kind) {
case "DeclareContext":
case "StoreContext":
case "DeclareLocal":
case "StoreLocal": {
yield value.lvalue.place;
break;
}
case "Destructure": {
yield* eachPatternOperand(value.lvalue.pattern);
break;
}
case "PostfixUpdate":
case "PrefixUpdate": {
yield value.lvalue;
break;
}
}
}
export function* eachInstructionOperand(instr: Instruction): Iterable<Place> {
yield* eachInstructionValueOperand(instr.value);
}
export function* eachInstructionValueOperand(
instrValue: InstructionValue
): Iterable<Place> {
switch (instrValue.kind) {
case "NewExpression":
case "CallExpression": {
yield instrValue.callee;
yield* eachCallArgument(instrValue.args);
break;
}
case "BinaryExpression": {
yield instrValue.left;
yield instrValue.right;
break;
}
case "MethodCall": {
yield instrValue.receiver;
yield instrValue.property;
yield* eachCallArgument(instrValue.args);
break;
}
case "DeclareContext":
case "DeclareLocal": {
break;
}
case "LoadLocal":
case "LoadContext": {
yield instrValue.place;
break;
}
case "StoreLocal": {
yield instrValue.value;
break;
}
case "StoreContext": {
yield instrValue.lvalue.place;
yield instrValue.value;
break;
}
case "StoreGlobal": {
yield instrValue.value;
break;
}
case "Destructure": {
yield instrValue.value;
break;
}
case "PropertyLoad": {
yield instrValue.object;
break;
}
case "PropertyDelete": {
yield instrValue.object;
break;
}
case "PropertyStore": {
yield instrValue.object;
yield instrValue.value;
break;
}
case "ComputedLoad": {
yield instrValue.object;
yield instrValue.property;
break;
}
case "ComputedDelete": {
yield instrValue.object;
yield instrValue.property;
break;
}
case "ComputedStore": {
yield instrValue.object;
yield instrValue.property;
yield instrValue.value;
break;
}
case "UnaryExpression": {
yield instrValue.value;
break;
}
case "JsxExpression": {
if (instrValue.tag.kind === "Identifier") {
yield instrValue.tag;
}
for (const attribute of instrValue.props) {
switch (attribute.kind) {
case "JsxAttribute": {
yield attribute.place;
break;
}
case "JsxSpreadAttribute": {
yield attribute.argument;
break;
}
default: {
assertExhaustive(
attribute,
`Unexpected attribute kind \`${(attribute as any).kind}\``
);
}
}
}
if (instrValue.children) {
yield* instrValue.children;
}
break;
}
case "JsxFragment": {
yield* instrValue.children;
break;
}
case "ObjectExpression": {
for (const property of instrValue.properties) {
if (
property.kind === "ObjectProperty" &&
property.key.kind === "computed"
) {
yield property.key.name;
}
yield property.place;
}
break;
}
case "ArrayExpression": {
for (const element of instrValue.elements) {
if (element.kind === "Identifier") {
yield element;
} else if (element.kind === "Spread") {
yield element.place;
}
}
break;
}
case "ObjectMethod":
case "FunctionExpression": {
yield* instrValue.loweredFunc.dependencies;
break;
}
case "TaggedTemplateExpression": {
yield instrValue.tag;
break;
}
case "TypeCastExpression": {
yield instrValue.value;
break;
}
case "TemplateLiteral": {
yield* instrValue.subexprs;
break;
}
case "Await": {
yield instrValue.value;
break;
}
case "GetIterator": {
yield instrValue.collection;
break;
}
case "IteratorNext": {
yield instrValue.iterator;
yield instrValue.collection;
break;
}
case "NextPropertyOf": {
yield instrValue.value;
break;
}
case "PostfixUpdate":
case "PrefixUpdate": {
yield instrValue.value;
break;
}
case "StartMemoize": {
if (instrValue.deps != null) {
for (const dep of instrValue.deps) {
if (dep.root.kind === "NamedLocal") {
yield dep.root.value;
}
}
}
break;
}
case "FinishMemoize": {
yield instrValue.decl;
break;
}
case "Debugger":
case "RegExpLiteral":
case "MetaProperty":
case "LoadGlobal":
case "UnsupportedNode":
case "Primitive":
case "JSXText": {
break;
}
default: {
assertExhaustive(
instrValue,
`Unexpected instruction kind \`${(instrValue as any).kind}\``
);
}
}
}
export function* eachCallArgument(
args: Array<Place | SpreadPattern>
): Iterable<Place> {
for (const arg of args) {
if (arg.kind === "Identifier") {
yield arg;
} else {
yield arg.place;
}
}
}
export function doesPatternContainSpreadElement(pattern: Pattern): boolean {
switch (pattern.kind) {
case "ArrayPattern": {
for (const item of pattern.items) {
if (item.kind === "Spread") {
return true;
}
}
break;
}
case "ObjectPattern": {
for (const property of pattern.properties) {
if (property.kind === "Spread") {
return true;
}
}
break;
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind \`${(pattern as any).kind}\``
);
}
}
return false;
}
export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {
switch (pattern.kind) {
case "ArrayPattern": {
for (const item of pattern.items) {
if (item.kind === "Identifier") {
yield item;
} else if (item.kind === "Spread") {
yield item.place;
} else if (item.kind === "Hole") {
continue;
} else {
assertExhaustive(
item,
`Unexpected item kind \`${(item as any).kind}\``
);
}
}
break;
}
case "ObjectPattern": {
for (const property of pattern.properties) {
if (property.kind === "ObjectProperty") {
yield property.place;
} else if (property.kind === "Spread") {
yield property.place;
} else {
assertExhaustive(
property,
`Unexpected item kind \`${(property as any).kind}\``
);
}
}
break;
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind \`${(pattern as any).kind}\``
);
}
}
}
export function mapInstructionLValues(
instr: Instruction,
fn: (place: Place) => Place
): void {
switch (instr.value.kind) {
case "DeclareLocal":
case "StoreLocal": {
const lvalue = instr.value.lvalue;
lvalue.place = fn(lvalue.place);
break;
}
case "Destructure": {
mapPatternOperands(instr.value.lvalue.pattern, fn);
break;
}
case "PostfixUpdate":
case "PrefixUpdate": {
instr.value.lvalue = fn(instr.value.lvalue);
break;
}
}
if (instr.lvalue !== null) {
instr.lvalue = fn(instr.lvalue);
}
}
export function mapInstructionOperands(
instr: Instruction,
fn: (place: Place) => Place
): void {
mapInstructionValueOperands(instr.value, fn);
}
export function mapInstructionValueOperands(
instrValue: InstructionValue,
fn: (place: Place) => Place
): void {
switch (instrValue.kind) {
case "BinaryExpression": {
instrValue.left = fn(instrValue.left);
instrValue.right = fn(instrValue.right);
break;
}
case "PropertyLoad": {
instrValue.object = fn(instrValue.object);
break;
}
case "PropertyDelete": {
instrValue.object = fn(instrValue.object);
break;
}
case "PropertyStore": {
instrValue.object = fn(instrValue.object);
instrValue.value = fn(instrValue.value);
break;
}
case "ComputedLoad": {
instrValue.object = fn(instrValue.object);
instrValue.property = fn(instrValue.property);
break;
}
case "ComputedDelete": {
instrValue.object = fn(instrValue.object);
instrValue.property = fn(instrValue.property);
break;
}
case "ComputedStore": {
instrValue.object = fn(instrValue.object);
instrValue.property = fn(instrValue.property);
instrValue.value = fn(instrValue.value);
break;
}
case "DeclareContext":
case "DeclareLocal": {
break;
}
case "LoadLocal":
case "LoadContext": {
instrValue.place = fn(instrValue.place);
break;
}
case "StoreLocal": {
instrValue.value = fn(instrValue.value);
break;
}
case "StoreContext": {
instrValue.lvalue.place = fn(instrValue.lvalue.place);
instrValue.value = fn(instrValue.value);
break;
}
case "StoreGlobal": {
instrValue.value = fn(instrValue.value);
break;
}
case "Destructure": {
instrValue.value = fn(instrValue.value);
break;
}
case "NewExpression":
case "CallExpression": {
instrValue.callee = fn(instrValue.callee);
instrValue.args = mapCallArguments(instrValue.args, fn);
break;
}
case "MethodCall": {
instrValue.receiver = fn(instrValue.receiver);
instrValue.property = fn(instrValue.property);
instrValue.args = mapCallArguments(instrValue.args, fn);
break;
}
case "UnaryExpression": {
instrValue.value = fn(instrValue.value);
break;
}
case "JsxExpression": {
if (instrValue.tag.kind === "Identifier") {
instrValue.tag = fn(instrValue.tag);
}
for (const attribute of instrValue.props) {
switch (attribute.kind) {
case "JsxAttribute": {
attribute.place = fn(attribute.place);
break;
}
case "JsxSpreadAttribute": {
attribute.argument = fn(attribute.argument);
break;
}
default: {
assertExhaustive(
attribute,
`Unexpected attribute kind \`${(attribute as any).kind}\``
);
}
}
}
if (instrValue.children) {
instrValue.children = instrValue.children.map((p) => fn(p));
}
break;
}
case "ObjectExpression": {
for (const property of instrValue.properties) {
if (
property.kind === "ObjectProperty" &&
property.key.kind === "computed"
) {
property.key.name = fn(property.key.name);
}
property.place = fn(property.place);
}
break;
}
case "ArrayExpression": {
instrValue.elements = instrValue.elements.map((element) => {
if (element.kind === "Identifier") {
return fn(element);
} else if (element.kind === "Spread") {
element.place = fn(element.place);
return element;
} else {
return element;
}
});
break;
}
case "JsxFragment": {
instrValue.children = instrValue.children.map((e) => fn(e));
break;
}
case "ObjectMethod":
case "FunctionExpression": {
instrValue.loweredFunc.dependencies =
instrValue.loweredFunc.dependencies.map((d) => fn(d));
break;
}
case "TaggedTemplateExpression": {
instrValue.tag = fn(instrValue.tag);
break;
}
case "TypeCastExpression": {
instrValue.value = fn(instrValue.value);
break;
}
case "TemplateLiteral": {
instrValue.subexprs = instrValue.subexprs.map(fn);
break;
}
case "Await": {
instrValue.value = fn(instrValue.value);
break;
}
case "GetIterator": {
instrValue.collection = fn(instrValue.collection);
break;
}
case "IteratorNext": {
instrValue.iterator = fn(instrValue.iterator);
instrValue.collection = fn(instrValue.collection);
break;
}
case "NextPropertyOf": {
instrValue.value = fn(instrValue.value);
break;
}
case "PostfixUpdate":
case "PrefixUpdate": {
instrValue.value = fn(instrValue.value);
break;
}
case "StartMemoize": {
if (instrValue.deps != null) {
for (const dep of instrValue.deps) {
if (dep.root.kind === "NamedLocal") {
dep.root.value = fn(dep.root.value);
}
}
}
break;
}
case "FinishMemoize": {
instrValue.decl = fn(instrValue.decl);
break;
}
case "Debugger":
case "RegExpLiteral":
case "MetaProperty":
case "LoadGlobal":
case "UnsupportedNode":
case "Primitive":
case "JSXText": {
break;
}
default: {
assertExhaustive(instrValue, "Unexpected instruction kind");
}
}
}
export function mapCallArguments(
args: Array<Place | SpreadPattern>,
fn: (place: Place) => Place
): Array<Place | SpreadPattern> {
return args.map((arg) => {
if (arg.kind === "Identifier") {
return fn(arg);
} else {
arg.place = fn(arg.place);
return arg;
}
});
}
export function mapPatternOperands(
pattern: Pattern,
fn: (place: Place) => Place
): void {
switch (pattern.kind) {
case "ArrayPattern": {
pattern.items = pattern.items.map((item) => {
if (item.kind === "Identifier") {
return fn(item);
} else if (item.kind === "Spread") {
item.place = fn(item.place);
return item;
} else {
return item;
}
});
break;
}
case "ObjectPattern": {
for (const property of pattern.properties) {
property.place = fn(property.place);
}
break;
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind \`${(pattern as any).kind}\``
);
}
}
}
export function mapTerminalSuccessors(
terminal: Terminal,
fn: (block: BlockId) => BlockId
): Terminal {
switch (terminal.kind) {
case "goto": {
const target = fn(terminal.block);
return {
kind: "goto",
block: target,
variant: terminal.variant,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "if": {
const consequent = fn(terminal.consequent);
const alternate = fn(terminal.alternate);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "if",
test: terminal.test,
consequent,
alternate,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "branch": {
const consequent = fn(terminal.consequent);
const alternate = fn(terminal.alternate);
return {
kind: "branch",
test: terminal.test,
consequent,
alternate,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "switch": {
const cases = terminal.cases.map((case_) => {
const target = fn(case_.block);
return {
test: case_.test,
block: target,
};
});
const fallthrough = fn(terminal.fallthrough);
return {
kind: "switch",
test: terminal.test,
cases,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "logical": {
const test = fn(terminal.test);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "logical",
test,
fallthrough,
operator: terminal.operator,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "ternary": {
const test = fn(terminal.test);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "ternary",
test,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "optional": {
const test = fn(terminal.test);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "optional",
optional: terminal.optional,
test,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "return": {
return {
kind: "return",
loc: terminal.loc,
value: terminal.value,
id: makeInstructionId(0),
};
}
case "throw": {
return terminal;
}
case "do-while": {
const loop = fn(terminal.loop);
const test = fn(terminal.test);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "do-while",
loc: terminal.loc,
test,
loop,
fallthrough,
id: makeInstructionId(0),
};
}
case "while": {
const test = fn(terminal.test);
const loop = fn(terminal.loop);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "while",
loc: terminal.loc,
test,
loop,
fallthrough,
id: makeInstructionId(0),
};
}
case "for": {
const init = fn(terminal.init);
const test = fn(terminal.test);
const update = terminal.update !== null ? fn(terminal.update) : null;
const loop = fn(terminal.loop);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "for",
loc: terminal.loc,
init,
test,
update,
loop,
fallthrough,
id: makeInstructionId(0),
};
}
case "for-of": {
const init = fn(terminal.init);
const loop = fn(terminal.loop);
const test = fn(terminal.test);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "for-of",
loc: terminal.loc,
init,
test,
loop,
fallthrough,
id: makeInstructionId(0),
};
}
case "for-in": {
const init = fn(terminal.init);
const loop = fn(terminal.loop);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "for-in",
loc: terminal.loc,
init,
loop,
fallthrough,
id: makeInstructionId(0),
};
}
case "label": {
const block = fn(terminal.block);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "label",
block,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "sequence": {
const block = fn(terminal.block);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "sequence",
block,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "maybe-throw": {
const continuation = fn(terminal.continuation);
const handler = fn(terminal.handler);
return {
kind: "maybe-throw",
continuation,
handler,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "try": {
const block = fn(terminal.block);
const handler = fn(terminal.handler);
const fallthrough = fn(terminal.fallthrough);
return {
kind: "try",
block,
handlerBinding: terminal.handlerBinding,
handler,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "scope":
case "pruned-scope": {
const block = fn(terminal.block);
const fallthrough = fn(terminal.fallthrough);
return {
kind: terminal.kind,
scope: terminal.scope,
block,
fallthrough,
id: makeInstructionId(0),
loc: terminal.loc,
};
}
case "unreachable":
case "unsupported": {
return terminal;
}
default: {
assertExhaustive(
terminal,
`Unexpected terminal kind \`${(terminal as any as Terminal).kind}\``
);
}
}
}
export function terminalHasFallthrough<
T extends Terminal,
U extends T & { fallthrough: BlockId },
>(terminal: T): terminal is U {
switch (terminal.kind) {
case "maybe-throw":
case "branch":
case "goto":
case "return":
case "throw":
case "unreachable":
case "unsupported": {
const _: undefined = terminal.fallthrough;
return false;
}
case "try":
case "do-while":
case "for-of":
case "for-in":
case "for":
case "if":
case "label":
case "logical":
case "optional":
case "sequence":
case "switch":
case "ternary":
case "while":
case "scope":
case "pruned-scope": {
const _: BlockId = terminal.fallthrough;
return true;
}
default: {
assertExhaustive(
terminal,
`Unexpected terminal kind \`${(terminal as any).kind}\``
);
}
}
}
export function terminalFallthrough(terminal: Terminal): BlockId | null {
if (terminalHasFallthrough(terminal)) {
return terminal.fallthrough;
} else {
return null;
}
}
export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
switch (terminal.kind) {
case "goto": {
yield terminal.block;
break;
}
case "if": {
yield terminal.consequent;
yield terminal.alternate;
break;
}
case "branch": {
yield terminal.consequent;
yield terminal.alternate;
break;
}
case "switch": {
for (const case_ of terminal.cases) {
yield case_.block;
}
break;
}
case "optional":
case "ternary":
case "logical": {
yield terminal.test;
break;
}
case "return": {
break;
}
case "throw": {
break;
}
case "do-while": {
yield terminal.loop;
break;
}
case "while": {
yield terminal.test;
break;
}
case "for": {
yield terminal.init;
break;
}
case "for-of": {
yield terminal.init;
break;
}
case "for-in": {
yield terminal.init;
break;
}
case "label": {
yield terminal.block;
break;
}
case "sequence": {
yield terminal.block;
break;
}
case "maybe-throw": {
yield terminal.continuation;
yield terminal.handler;
break;
}
case "try": {
yield terminal.block;
break;
}
case "scope":
case "pruned-scope": {
yield terminal.block;
break;
}
case "unreachable":
case "unsupported":
break;
default: {
assertExhaustive(
terminal,
`Unexpected terminal kind \`${(terminal as any as Terminal).kind}\``
);
}
}
}
export function mapTerminalOperands(
terminal: Terminal,
fn: (place: Place) => Place
): void {
switch (terminal.kind) {
case "if": {
terminal.test = fn(terminal.test);
break;
}
case "branch": {
terminal.test = fn(terminal.test);
break;
}
case "switch": {
terminal.test = fn(terminal.test);
for (const case_ of terminal.cases) {
if (case_.test === null) {
continue;
}
case_.test = fn(case_.test);
}
break;
}
case "return":
case "throw": {
terminal.value = fn(terminal.value);
break;
}
case "try": {
if (terminal.handlerBinding !== null) {
terminal.handlerBinding = fn(terminal.handlerBinding);
} else {
terminal.handlerBinding = null;
}
break;
}
case "maybe-throw":
case "sequence":
case "label":
case "optional":
case "ternary":
case "logical":
case "do-while":
case "while":
case "for":
case "for-of":
case "for-in":
case "goto":
case "unreachable":
case "unsupported":
case "scope":
case "pruned-scope": {
break;
}
default: {
assertExhaustive(
terminal,
`Unexpected terminal kind \`${(terminal as any).kind}\``
);
}
}
}
export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
switch (terminal.kind) {
case "if": {
yield terminal.test;
break;
}
case "branch": {
yield terminal.test;
break;
}
case "switch": {
yield terminal.test;
for (const case_ of terminal.cases) {
if (case_.test === null) {
continue;
}
yield case_.test;
}
break;
}
case "return":
case "throw": {
yield terminal.value;
break;
}
case "try": {
if (terminal.handlerBinding !== null) {
yield terminal.handlerBinding;
}
break;
}
case "maybe-throw":
case "sequence":
case "label":
case "optional":
case "ternary":
case "logical":
case "do-while":
case "while":
case "for":
case "for-of":
case "for-in":
case "goto":
case "unreachable":
case "unsupported":
case "scope":
case "pruned-scope": {
break;
}
default: {
assertExhaustive(
terminal,
`Unexpected terminal kind \`${(terminal as any).kind}\``
);
}
}
}