UNPKG

@specs-feup/alpakka

Version:

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

164 lines (136 loc) 4.31 kB
import cytoscape from "cytoscape"; import Graph from "./Graph.js"; import { NodeBuilder, NodeConstructor, NodeTypeGuard } from "./Node.js"; import BaseGraph from "./BaseGraph.js"; import BaseEdge from "./BaseEdge.js"; namespace BaseNode { export class Class< D extends Data = Data, S extends ScratchData = ScratchData, > { #graph: BaseGraph.Class; #node: cytoscape.NodeSingular; // _d and _sd are a hack to force typescript to typecheck // D and S in .as() method. constructor( graph: BaseGraph.Class, node: cytoscape.NodeSingular, _d: D = {} as any, _sd: S = {} as any, ) { this.#graph = graph; this.#node = node; } get data(): D { return this.#node.data(); } get scratchData(): S { return this.#node.scratch(Graph.scratchNamespace); } get id(): string { return this.#node.id(); } get incomers(): BaseEdge.Class[] { return this.#node .incomers() .edges() .map((edge) => new BaseEdge.Class(this.#graph, edge)); } get outgoers(): BaseEdge.Class[] { return this.#node .outgoers() .edges() .map((edge) => new BaseEdge.Class(this.#graph, edge)); } is<D2 extends Data, S2 extends ScratchData>( guard: NodeTypeGuard<D2, S2>, ): this is BaseNode.Class<D2, S2> { const data = this.data; const scratchData = this.scratchData; const result = guard.isDataCompatible(data) && guard.isScratchDataCompatible(scratchData); // Have typescript statically check that the types are correct // in the implementation of this function. result && (data satisfies D2) && (scratchData satisfies S2); return result; } as<N extends BaseNode.Class<D, S>>(NodeType: NodeConstructor<D, S, N>): N { return new NodeType(this.#graph, this.#node, this.data, this.scratchData); } init<D2 extends BaseNode.Data, S2 extends BaseNode.ScratchData>( builder: NodeBuilder<D2, S2>, ): BaseNode.Class<D2, S2> { const initedData = builder.buildData(this.data); const initedScratchData = builder.buildScratchData(this.scratchData); this.#node.data(initedData); this.#node.scratch(Graph.scratchNamespace, initedScratchData); return new BaseNode.Class( this.#graph, this.#node, initedData, initedScratchData, ); } remove() { this.#node.remove(); } // cytoscape's dfs is not flexible enough for clava-flow purposes // at least as far as I can tell // Returns a generator that yields [node, path, index] bfs( propagate: (edge: BaseEdge.Class) => boolean, ): Generator<[BaseNode.Class, BaseEdge.Class[], number]> { function* inner( root: BaseNode.Class, ): Generator<[BaseNode.Class, BaseEdge.Class[], number]> { const toVisit: [BaseNode.Class, BaseEdge.Class[]][] = [[root, []]]; const visited = new Set(); let idx = 0; while (toVisit.length > 0) { const [node, path] = toVisit.pop()!; if (visited.has(node.id)) { continue; } if (path.length > 0 && !propagate(path[path.length - 1])) { continue; } yield [node, path, idx]; idx++; visited.add(node.id); for (const out of node.outgoers) { toVisit.push([out.target, [...path, out]]); } } } return inner(this); } get graph(): BaseGraph.Class { return this.#graph; } toCy(): cytoscape.NodeSingular { return this.#node; } } export class Builder implements NodeBuilder<Data, ScratchData> { buildData(data: BaseNode.Data): Data { return data; } buildScratchData(scratchData: BaseNode.ScratchData): ScratchData { return scratchData; } } export const TypeGuard: NodeTypeGuard<Data, ScratchData> = { isDataCompatible(data: BaseNode.Data): data is Data { return true; }, isScratchDataCompatible(sData: BaseNode.ScratchData): sData is ScratchData { return true; }, }; export interface Data { id: string; } export interface ScratchData {} } export default BaseNode;