@specs-feup/alpakka
Version:
A Smali/APK source-to-source compiler written in Typescript
165 lines (136 loc) • 4.4 kB
text/typescript
import cytoscape from "cytoscape";
import Graph, {
GraphBuilder,
GraphConstructor,
GraphTransformation,
GraphTypeGuard,
} from "./Graph.js";
import BaseNode from "./BaseNode.js";
import BaseEdge from "./BaseEdge.js";
import { JavaClasses } from "@specs-feup/lara/api/lara/util/JavaTypes.js";
import DotFormatter from "../dot/DotFormatter.js";
import Io from "@specs-feup/lara/api/lara/Io.js";
namespace BaseGraph {
export class Class<
D extends Data = Data,
S extends ScratchData = ScratchData,
> {
#graph: cytoscape.Core;
// _d and _sd are a hack to force typescript to typecheck
// D and S in .as() method.
constructor(graph: cytoscape.Core, _d: D = {} as any, _sd: S = {} as any) {
this.#graph = graph;
}
get data(): D {
return this.#graph.data();
}
get scratchData(): S {
return this.#graph.scratch(Graph.scratchNamespace);
}
addNode(id?: string): BaseNode.Class {
const newNode = this.#graph.add({
group: "nodes",
data: { id },
});
return new BaseNode.Class(this, newNode);
}
addEdge(
source: BaseNode.Class,
target: BaseNode.Class,
id?: string,
): BaseEdge.Class {
const newEdge = this.#graph.add({
group: "edges",
data: { id, source: source.id, target: target.id },
});
return new BaseEdge.Class(this, newEdge);
}
getNodeById(id: string): BaseNode.Class | undefined {
const node = this.#graph.getElementById(id);
if (node.isNode()) {
return new BaseNode.Class(this, node);
}
return undefined;
}
getEdgeById(id: string): BaseEdge.Class | undefined {
const edge = this.#graph.getElementById(id);
if (edge.isEdge()) {
return new BaseEdge.Class(this, edge);
}
return undefined;
}
// TODO
get nodes(): BaseNode.Class[] {
return this.#graph.nodes().map((node) => new BaseNode.Class(this, node));
}
// TODO
get edges(): BaseEdge.Class[] {
return this.#graph.edges().map((edge) => new BaseEdge.Class(this, edge));
}
is<D2 extends Data, S2 extends ScratchData>(
guard: GraphTypeGuard<D2, S2>,
): this is BaseGraph.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<G extends BaseGraph.Class<D, S>>(
GraphType: GraphConstructor<D, S, G>,
): G {
return new GraphType(this.#graph, this.data, this.scratchData);
}
init<D2 extends BaseGraph.Data, S2 extends BaseGraph.ScratchData>(
builder: GraphBuilder<D2, S2>,
): BaseGraph.Class<D2, S2> {
const initedData = builder.buildData(this.data);
const initedScratchData = builder.buildScratchData(this.scratchData);
this.#graph.data(initedData);
this.#graph.scratch(Graph.scratchNamespace, initedScratchData);
return new BaseGraph.Class(this.#graph, initedData, initedScratchData);
}
apply(transformation: GraphTransformation): this {
transformation.apply(this);
return this;
}
toDot(dotFormatter: DotFormatter, label?: string): string {
return dotFormatter.format(this, label);
}
toDotFile(
dotFormatter: DotFormatter,
filename: string,
label?: string,
): JavaClasses.File {
return Io.writeFile(filename, this.toDot(dotFormatter, label));
}
toCy(): cytoscape.Core {
return this.#graph;
}
}
export class Builder implements GraphBuilder<Data, ScratchData> {
buildData(data: BaseGraph.Data): Data {
return data;
}
buildScratchData(scratchData: BaseGraph.ScratchData): ScratchData {
return scratchData;
}
}
export const TypeGuard: GraphTypeGuard<Data, ScratchData> = {
isDataCompatible(data: BaseGraph.Data): data is Data {
return true;
},
isScratchDataCompatible(
sData: BaseGraph.ScratchData,
): sData is ScratchData {
return true;
},
};
export interface Data {}
export interface ScratchData {}
}
export default BaseGraph;