UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

184 lines 7.13 kB
"use strict"; 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