@specs-feup/alpakka
Version:
A Smali/APK source-to-source compiler written in Typescript
164 lines (136 loc) • 4.31 kB
text/typescript
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;