UNPKG

@specs-feup/alpakka

Version:

A Smali/APK source-to-source compiler written in Typescript

183 lines (158 loc) 5.19 kB
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;