@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
174 lines • 8.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processRm = processRm;
const known_call_handling_1 = require("../known-call-handling");
const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const logger_1 = require("../../../../../logger");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const identifier_1 = require("../../../../../environments/identifier");
const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name");
const prefix_1 = require("../../../../../../util/prefix");
const node_value_1 = require("../../../../../eval/resolve/node-value");
const r_value_1 = require("../../../../../eval/values/r-value");
const apply_kill_1 = require("../../../../../environments/apply-kill");
const define_1 = require("../../../../../environments/define");
const built_in_envir_utils_1 = require("./built-in-envir-utils");
const resolve_helper_1 = require("../../../../../environments/resolve-helper");
/** whether `value` is a call to the *built-in* `ls`/`objects` that lists (and thus clears) the whole scope */
function isBuiltInLsCall(value, data) {
if (value.type !== type_1.RType.FunctionCall || !value.named) {
return false;
}
const [fn, ns] = identifier_1.Identifier.toArray(value.functionName.content);
if ((fn !== 'ls' && fn !== 'objects') || (ns !== undefined && ns !== 'base')) {
return false;
}
// a `pattern`/`envir` argument restricts the listing, so only a plain listing clears everything
const listsEverything = value.arguments.every(a => a !== r_function_call_1.EmptyArgument && a.name !== undefined && (a.name.content === 'all.names' || a.name.content === 'sorted'));
if (!listsEverything) {
return false;
}
// an explicit `base::` is always the built-in; otherwise only when `ls` is not shadowed
if (ns === 'base') {
return true;
}
const resolved = resolve_helper_1.Resolve.byNameAndType(fn, data.environment, identifier_1.ReferenceType.Function);
return resolved === undefined || resolved.every(d => (0, identifier_1.isReferenceType)(d.type, identifier_1.ReferenceType.BuiltInFunction));
}
/** formal parameters of `rm` that may be given by name; every other positional argument belongs to `...` */
const RmNamedFormals = ['list', 'pos', 'envir', 'inherits'];
/** Adds the name a single `...` argument (an unquoted symbol or quoted string) refers to. */
function collectDotArg(targets, value) {
if (value?.type === type_1.RType.Symbol) {
targets.names.push({ name: value.content, nodeId: value.info.id });
}
else if (value?.type === type_1.RType.String) {
targets.names.push({ name: value.content.str, nodeId: value.info.id });
}
else if (value !== undefined) {
logger_1.dataflowLogger.warn(`argument is not a symbol or string in rm, skipping ${JSON.stringify(value)}`);
targets.unknown = true;
}
}
/** Resolves the `list=` argument, recognizing a whole-scope clear via built-in `ls()` and concrete string vectors. */
function collectListArg(targets, value, data) {
if (!value) {
return;
}
if (value.type === type_1.RType.String) {
targets.names.push({ name: value.content.str, nodeId: value.info.id });
}
else if (isBuiltInLsCall(value, data)) {
targets.all = true;
}
else {
const elements = node_value_1.NodeValue.setOf(value.info.id, data)?.elements;
if (!elements || elements.length === 0) {
targets.unknown = true;
}
else {
for (const r of elements) {
if (r.type === 'string' && (0, r_value_1.isValue)(r.value)) {
targets.names.push({ name: r.value.str, nodeId: value.info.id });
}
else {
targets.unknown = true;
}
}
}
}
}
/**
* Collects the removal targets of an `rm` call: every unnamed argument names a variable (`rm`'s `...` swallows
* all positionals), `list=` is resolved separately, and `pos`/`envir`/`inherits` contribute no names.
*/
function collectRmTargets(args, data) {
const targets = { names: [], all: false, unknown: false };
for (const arg of args) {
if (arg === r_function_call_1.EmptyArgument) {
continue;
}
if (arg.name === undefined) {
collectDotArg(targets, arg.value);
continue;
}
const formal = (0, prefix_1.findByPrefixIfUnique)(arg.name.content, RmNamedFormals);
if (formal === 'list') {
collectListArg(targets, arg.value, data);
}
else if (formal === undefined) {
// an unknown named argument falls into `...` in R, but we cannot tell which name it removes
targets.unknown = true;
}
}
return targets;
}
/** Builds the {@link KillReference|kills} produced by an `rm` call from its resolved {@link RmTargets}. */
function buildKills(targets, cds) {
const kills = [];
if (targets.all) {
kills.push({ kind: 'all', cds });
}
if (targets.unknown) {
kills.push({ kind: 'unknown', cds });
}
for (const { name, nodeId } of targets.names) {
kills.push({ kind: 'named', reference: { nodeId, name, cds, type: identifier_1.ReferenceType.Variable } });
}
return kills;
}
/** Removes the targets from a tracked custom environment (`rm(..., envir=e)`) instead of the lexical scope. */
function removeFromCustomEnv(res, envir, targets, rootId, cds) {
const newEnvState = (0, apply_kill_1.applyKills)(envir.envDef.envState, buildKills(targets, cds));
const environment = (0, define_1.define)({ ...envir.envDef, definedAt: rootId, envState: newEnvState }, false, res.environment);
return { ...res, environment };
}
/** The arguments `rm` swallows with its `...`: they name what to remove instead of being evaluated. */
function nonStandardArguments(args) {
const indices = new Set();
for (const [i, arg] of args.entries()) {
if (arg !== r_function_call_1.EmptyArgument && (arg.name === undefined || (0, prefix_1.findByPrefixIfUnique)(arg.name.content, RmNamedFormals) === undefined)) {
indices.add(i);
}
}
return indices;
}
/**
* Process an `rm` call, marking the removed variables as {@link KillReference|killed} so the removal is
* carried to the enclosing scope even when it happens nested within a branch or block.
* As in R, the names to remove are not evaluated, so `rm(x)` does not read `x`.
*/
function processRm(name, args, rootId, data) {
if (args.length === 0) {
logger_1.dataflowLogger.warn('empty rm, skipping');
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' }).information;
}
const nse = nonStandardArguments(args);
const { information, processedArguments, fnRef } = (0, known_call_handling_1.processKnownFunctionCall)({
name, args, rootId, data,
origin: built_in_proc_name_1.BuiltInProcName.Rm,
/* an unevaluated name must not resolve against the environment */
patchData: (d, i) => nse.has(i) ? { ...d, environment: d.ctx.env.makeCleanEnv() } : d
});
(0, known_call_handling_1.markArgumentsAsNonStandardEvaluation)(information.graph, rootId, processedArguments, [...nse]);
/* the enclosing scope would link the unevaluated names, so they must not escape this call */
const evaluated = processedArguments.filter((p, i) => p !== undefined && !nse.has(i));
const res = {
...information,
in: [fnRef, ...evaluated.flatMap(p => p.in)],
out: evaluated.flatMap(p => p.out),
unknownReferences: evaluated.flatMap(p => p.unknownReferences)
};
const targets = collectRmTargets(args, data);
// `rm(x, envir=e)` removes from a tracked custom environment instead of the lexical scope
if (data.ctx.config.solver.trackEnvironments) {
const envir = (0, built_in_envir_utils_1.resolveEnvirArg)(args, data, 'envir');
if (envir) {
return removeFromCustomEnv(res, envir, targets, rootId, data.cds);
}
}
// apply to our own environment so threading reflects it, and emit the kills so a merging parent can re-apply
const kills = buildKills(targets, data.cds);
return kills.length > 0 ? { ...res, environment: (0, apply_kill_1.applyKills)(res.environment, kills), kill: kills } : res;
}
//# sourceMappingURL=built-in-rm.js.map