@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
184 lines • 7.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.cancelRevivedKills = cancelRevivedKills;
exports.applyKills = applyKills;
exports.makeKillsMaybe = makeKillsMaybe;
const info_1 = require("../info");
const identifier_1 = require("./identifier");
const reference_to_maybe_1 = require("./reference-to-maybe");
/**
* Accounts for the writes after a kill: a re-definition revives a `named` removal (dropping it), a conditional
* one only revives it in its branch, and a wholesale (`all`/`unknown`) removal stays but spares the names written
* after it.
*/
function cancelRevivedKills(kills, writes) {
const written = new Set();
const writtenMaybe = new Map();
for (const w of writes) {
if (w.name === undefined) {
continue;
}
const name = identifier_1.Identifier.getName(w.name);
written.add(name);
if (!(0, info_1.happensInEveryBranch)(w.cds)) {
const cds = writtenMaybe.get(name);
if (cds) {
cds.push(...w.cds);
}
else {
writtenMaybe.set(name, [...w.cds]);
}
}
else {
writtenMaybe.delete(name);
}
}
if (written.size === 0) {
return kills;
}
/* conditional writes that cover every branch together revive the name just as an unconditional one does */
for (const [name, cds] of writtenMaybe) {
if ((0, info_1.happensInEveryBranch)(cds)) {
writtenMaybe.delete(name);
}
}
const remaining = [];
for (const kill of kills) {
if (kill.kind === 'named') {
const name = kill.reference.name === undefined ? undefined : identifier_1.Identifier.getName(kill.reference.name);
const maybeCds = name === undefined ? undefined : writtenMaybe.get(name);
if (maybeCds !== undefined) {
/* the removal still stands wherever the conditional re-definition did not happen */
remaining.push({ kind: 'named', reference: { ...kill.reference, cds: (0, info_1.withCds)(kill.reference.cds, maybeCds.map(info_1.negateControlDependency)) } });
}
else if (name === undefined || !written.has(name)) {
remaining.push(kill);
}
}
else {
remaining.push({ ...kill, except: kill.except ? written.union(kill.except) : written });
}
}
return remaining;
}
function isBuiltInDef(d) {
return d.type === identifier_1.ReferenceType.BuiltInFunction || d.type === identifier_1.ReferenceType.BuiltInConstant;
}
/** attaches `cds` to every user definition of `name` along the environment chain */
function weakenName(env, name, cds) {
const [plainName, ns] = identifier_1.Identifier.toArray(name);
let current = env;
while (current && !current.builtInEnv) {
if (ns === undefined || current.n === ns) {
const defs = current.memory.get(plainName);
if (defs !== undefined && defs.some(d => !isBuiltInDef(d))) {
current.writableMemory.set(plainName, defs.map(d => isBuiltInDef(d) ? d : (0, reference_to_maybe_1.withAppliedCds)(d, cds)));
current.cache?.delete(plainName);
}
}
current = current.parent;
}
}
/** attaches `cds` to every user definition in the current frame, except the re-defined names */
function weakenAll(env, cds, except) {
if (env.builtInEnv) {
return;
}
for (const [key, defs] of env.memory) {
if (except?.has(key)) {
continue;
}
if (defs.some(d => !isBuiltInDef(d))) {
env.writableMemory.set(key, defs.map(d => isBuiltInDef(d) ? d : (0, reference_to_maybe_1.withAppliedCds)(d, cds)));
}
}
env.cache?.clear();
}
/** removes every user definition from the current frame (e.g., `rm(list = ls())`), except the re-defined names */
function removeAllInFrame(env, except) {
if (env.builtInEnv) {
return;
}
for (const [key, defs] of env.memory) {
if (except?.has(key) || defs.every(isBuiltInDef)) {
continue;
}
const kept = defs.filter(isBuiltInDef);
if (kept.length === 0) {
env.writableMemory.delete(key);
}
else {
env.writableMemory.set(key, kept);
}
}
env.cache?.clear();
}
function applyNamedKill(env, name, refs) {
// certain if any kill is unconditional or the kills together cover every branch
const certain = refs.some(r => (0, info_1.happensInEveryBranch)(r.cds)) || (0, info_1.happensInEveryBranch)(refs.flatMap(r => r.cds ?? []));
if (certain) {
env.remove(name);
}
else {
// the definition survives unless the killing branch executed
for (const ref of refs) {
weakenName(env, name, (ref.cds ?? []).map(info_1.negateControlDependency));
}
}
}
/**
* Applies the given {@link KillReference|kills} to a copy of `env`. `named` kills remove (or, when conditional,
* weaken to maybe) a single definition; `all` kills clear the current frame; `unknown` kills weaken every
* in-scope definition to maybe. Returns `env` unchanged when there is nothing to apply.
*/
function applyKills(env, kills) {
if (!kills || kills.length === 0) {
return env;
}
const current = env.current.clone(true);
// group named kills by name so removals that together cover all branches become a hard removal
const named = new Map();
for (const kill of kills) {
switch (kill.kind) {
case 'named':
if (kill.reference.name !== undefined) {
const group = named.get(kill.reference.name);
if (group) {
group.push(kill.reference);
}
else {
named.set(kill.reference.name, [kill.reference]);
}
}
break;
case 'all':
if ((0, info_1.happensInEveryBranch)(kill.cds)) {
removeAllInFrame(current, kill.except);
}
else {
weakenAll(current, (kill.cds ?? []).map(info_1.negateControlDependency), kill.except);
}
break;
case 'unknown':
weakenAll(current, kill.cds ?? [], kill.except);
break;
}
}
for (const [name, refs] of named) {
applyNamedKill(current, name, refs);
}
return { current, level: env.level };
}
/** Attaches `cds` to a list of kills, turning them into conditional (maybe) kills. */
function makeKillsMaybe(kills, cds) {
if (!kills || kills.length === 0) {
return [];
}
return kills.map(k => {
if (k.kind === 'named') {
return { kind: 'named', reference: { ...k.reference, cds: (0, info_1.withCds)(k.reference.cds, cds) } };
}
return { ...k, cds: (0, info_1.withCds)(k.cds, cds) };
});
}
//# sourceMappingURL=apply-kill.js.map