@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
98 lines • 4.72 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.linkPlotsToDevices = linkPlotsToDevices;
const query_fn_props_1 = require("../../../dataflow/environments/query-fn-props");
const built_in_props_1 = require("../../../dataflow/environments/built-in-props");
const vertex_1 = require("../../../dataflow/graph/vertex");
const range_1 = require("../../../util/range");
/** the {@link CallProp} bits of the call `id` makes */
function propsOf(id, dataflow) {
return (0, query_fn_props_1.callFnProps)(id, dataflow)?.props ?? 0;
}
/**
* Names the file each visualization lands in, and the statements that build it.
*
* A device opener (`pdf("a.pdf")`) takes every plot until the device is closed (`dev.off()`), so those plots
* end up in its file instead of on screen; a plot outside any open device keeps the value it had. Every plot
* that lands in a file also reports the statements it takes to produce that file in
* {@link DependencyInfo#parts}: the addons drawn onto it, plus the device opener and closer around it.
* Closing a device ends the plot, so an addon after it builds whatever device is open then, not this file.
*
* Openers are the `write` entries that draw ({@link CallProp.Graphics}), closers the calls that end a device
* ({@link CallProp.Closes}), and the order is the one the source states.
*/
function linkPlotsToDevices(written, plots, dataflow, ast) {
const opened = new Map(written
.filter(w => w.value !== undefined && (propsOf(w.nodeId, dataflow) & built_in_props_1.CallProp.Graphics) !== 0)
.map(w => [w.nodeId, w.value]));
const closed = new Set(dataflow.graph.vertices(true)
.filter(([id, v]) => vertex_1.FunctionCallVertex.is(v) && (propsOf(id, dataflow) & built_in_props_1.CallProp.Closes) !== 0)
.map(([id]) => id));
const plotAt = new Map(plots.map((p, index) => [p.nodeId, index]));
const located = [...opened.keys(), ...closed, ...plotAt.keys()]
.flatMap(id => {
const at = ast.idMap.get(id)?.location;
return at ? [[id, at]] : [];
})
.sort(([, a], [, b]) => range_1.SourceRange.compare(a, b));
/* an addon points at the creation it belongs to, a creation points at nothing */
const linkOf = new Map(plots.filter(p => p.linkedIds?.length).map(p => [p.nodeId, p.linkedIds]));
const devices = [];
/* the opener/closer pair enclosing a plot, so it can report what it takes to produce its file */
const around = new Map();
/* what each addon draws onto, and the creation each device window is currently showing */
const builds = new Map();
const showing = new Map();
for (const [id] of located) {
const file = opened.get(id);
const plot = plotAt.get(id);
if (file !== undefined) {
devices.push({ file, opener: id, plots: [] });
continue;
}
else if (closed.has(id)) {
const device = devices.pop();
for (const on of device?.plots ?? []) {
around.set(on, [device?.opener, id]);
}
continue;
}
else if (plot === undefined) {
continue;
}
const device = devices[devices.length - 1];
if (device !== undefined) {
device.plots.push(id);
around.set(id, [device.opener]);
plots[plot] = { ...plots[plot], value: device.file };
}
const window = device?.opener;
const linked = linkOf.get(id);
if (linked === undefined) {
/* a creation: whatever is drawn on this device from here on adds to it, as R keeps it current */
showing.set(window, id);
continue;
}
/*
* Prefer what the dataflow linked the addon to, which knows about `p <- ggplot(); p + geom_point()`.
* It only tracks position, though, so an addon after a nested device closed still points into that
* device; R hands the drawing back to the plot the restored device is showing, so take that instead.
*/
const inWindow = linked.find(to => (around.get(to)?.[0]) === window);
const target = inWindow ?? showing.get(window);
if (target !== undefined) {
builds.set(id, target);
}
}
const addonsOf = new Map();
for (const [addon, creation] of builds) {
addonsOf.set(creation, [...addonsOf.get(creation) ?? [], addon]);
}
for (const [id, index] of plotAt) {
const parts = [...addonsOf.get(id) ?? [], ...around.get(id) ?? []];
if (parts.length > 0) {
plots[index] = { ...plots[index], parts };
}
}
}
//# sourceMappingURL=link-devices.js.map