UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

174 lines 8.6 kB
"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