@specs-feup/alpakka
Version:
A Smali/APK source-to-source compiler written in Typescript
183 lines (158 loc) • 5.19 kB
text/typescript
import ControlFlowEdge from "../edge/ControlFlowEdge.js";
import InstructionNode from "./instruction/InstructionNode.js";
// import ConditionNode from "../node/condition/ConditionNode.js";
import BaseEdge from "../../graph/BaseEdge.js";
import BaseNode from "../../graph/BaseNode.js";
import { NodeBuilder, NodeTypeGuard } from "../../graph/Node.js";
import { Joinpoint } from "../../../../../Joinpoints.js";
namespace FlowNode {
export class Class<
D extends Data = Data,
S extends ScratchData = ScratchData,
> extends BaseNode.Class<D, S> {
insertBefore(node: InstructionNode.Class) {
this.insertSubgraphBefore(node, [node]);
}
insertSubgraphBefore(head: BaseNode.Class, tail: InstructionNode.Class[]) {
this.incomers.forEach((edge) => {
edge.target = head;
});
for (const tailNode of tail) {
tailNode.nextNode = this;
}
}
removeFromFlow() {
// TODO improve ergonomics with Collection class
const incomers = this.incomers.filter((edge) =>
edge.is(ControlFlowEdge.TypeGuard),
);
const outgoers = this.outgoers.filter((edge) =>
edge.is(ControlFlowEdge.TypeGuard),
);
if (incomers.length === 0 || outgoers.length === 0) {
for (const edge of incomers) {
edge.remove();
}
for (const edge of outgoers) {
edge.remove();
}
} else if (outgoers.length === 1) {
for (const edge of incomers) {
edge.target = outgoers[0].target;
}
outgoers[0].remove();
} else {
throw new Error(
"Cannot remove node with at least one incomer and multiple outgoers.",
);
}
}
get reachableNodes(): FlowNode.Class[] {
const result: FlowNode.Class[] = [];
for (const [node] of this.bfs((e) => e.is(ControlFlowEdge.TypeGuard))) {
if (node.is(FlowNode.TypeGuard)) {
result.push(node.as(FlowNode.Class));
}
}
return result;
}
get previousEdges(): ControlFlowEdge.Class[] {
return this.incomers
.filter((edge) => edge.is(ControlFlowEdge.TypeGuard))
.map(
(edge) =>
edge as BaseEdge.Class<
ControlFlowEdge.Data,
ControlFlowEdge.ScratchData
>,
)
.map((edge) => edge.as(ControlFlowEdge.Class));
}
get previousNodes(): FlowNode.Class[] {
return this.previousEdges
.map((edge) => edge.source)
.filter((node) => node.is(FlowNode.TypeGuard))
.map(
(node) => node as BaseNode.Class<FlowNode.Data, FlowNode.ScratchData>,
)
.map((node) => node.as(FlowNode.Class));
}
get nextEdges(): ControlFlowEdge.Class[] {
return this.outgoers
.filter((edge) => edge.is(ControlFlowEdge.TypeGuard))
.map(
(edge) =>
edge as BaseEdge.Class<
ControlFlowEdge.Data,
ControlFlowEdge.ScratchData
>,
)
.map((edge) => edge.as(ControlFlowEdge.Class));
}
get nextNodes(): FlowNode.Class[] {
return this.nextEdges
.map((edge) => edge.target)
.filter((node) => node.is(FlowNode.TypeGuard))
.map(
(node) => node as BaseNode.Class<FlowNode.Data, FlowNode.ScratchData>,
)
.map((node) => node.as(FlowNode.Class));
}
get jp(): Joinpoint | undefined {
return this.scratchData.$jp;
}
}
export abstract class Builder
extends BaseNode.Builder
implements NodeBuilder<Data, ScratchData>
{
#$jp: Joinpoint | undefined;
#flowNodeType: Type;
constructor(type: Type, $jp: Joinpoint | undefined) {
super();
this.#$jp = $jp;
this.#flowNodeType = type;
}
override buildData(data: BaseNode.Data): Data {
return {
...super.buildData(data),
flowNodeType: this.#flowNodeType,
};
}
override buildScratchData(scratchData: BaseNode.ScratchData): ScratchData {
return {
...super.buildScratchData(scratchData),
$jp: this.#$jp,
};
}
}
export const TypeGuard: NodeTypeGuard<Data, ScratchData> = {
isDataCompatible(data: BaseNode.Data): data is Data {
if (!BaseNode.TypeGuard.isDataCompatible(data)) return false;
const d = data as Data;
if (!Object.values(Type).includes(d.flowNodeType as Type)) return false;
return true;
},
isScratchDataCompatible(
scratchData: BaseNode.ScratchData,
): scratchData is ScratchData {
if (!BaseNode.TypeGuard.isScratchDataCompatible(scratchData))
return false;
const s = scratchData as ScratchData;
if (s.$jp !== undefined && !(s.$jp instanceof Joinpoint)) return false;
return true;
},
};
export interface Data extends BaseNode.Data {
flowNodeType: Type;
}
export interface ScratchData extends BaseNode.ScratchData {
$jp: Joinpoint | undefined;
}
// ------------------------------------------------------------
export enum Type {
INSTRUCTION = "instruction",
CONDITION = "condition",
}
}
export default FlowNode;