@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
187 lines • 10 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.signatureParamNames = signatureParamNames;
exports.resolveConstantString = resolveConstantString;
exports.findReturnsEnvState = findReturnsEnvState;
exports.resolveArgToEnvir = resolveArgToEnvir;
exports.resolveEnvirArg = resolveEnvirArg;
exports.resolveSymbolToEnvir = resolveSymbolToEnvir;
exports.routeWrittenToCustomEnv = routeWrittenToCustomEnv;
exports.resolveAttachPosition = resolveAttachPosition;
const r_value_1 = require("../../../../../eval/values/r-value");
const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const unpack_argument_1 = require("../argument/unpack-argument");
const decode_1 = require("../../../../../../project/sigdb/decode");
const identifier_1 = require("../../../../../environments/identifier");
const define_1 = require("../../../../../environments/define");
const environment_1 = require("../../../../../environments/environment");
const prefix_1 = require("../../../../../../util/prefix");
const built_in_stack_env_1 = require("./built-in-stack-env");
const node_value_1 = require("../../../../../eval/resolve/node-value");
const resolve_strings_1 = require("../../../../../eval/resolve/resolve-strings");
const logger_1 = require("../../../../../logger");
const resolve_helper_1 = require("../../../../../environments/resolve-helper");
/** A tracked env is a real stack environment (not a private custom env) when its current layer is the global or the built-in/base env. */
function isStackEnvState(envState) {
return envState.current.globalEnv === true || envState.current.builtInEnv === true;
}
/** Maps a list of identifier definitions (from {@link Resolve.byNameAndType}) to an {@link EnvirResolution}, merging the envStates of multiple reaching definitions. */
function resolveDefsToEnvirResolution(defs, nodeId, data) {
if (!defs || defs.length === 0) {
return undefined;
}
const inDefs = defs;
if (inDefs.length === 1) {
const envState = inDefs[0].envState;
if (!envState) {
return undefined;
}
const envDef = inDefs[0];
return { envirData: { ...data, environment: envState }, envDef, envirNodeId: nodeId, isStackEnv: isStackEnvState(envState) };
}
if (!inDefs.every(d => d.envState !== undefined)) {
return undefined;
}
let mergedEnvState = inDefs[0].envState;
for (let i = 1; i < inDefs.length; i++) {
for (const [, varDefs] of inDefs[i].envState.current.memory) {
for (const varDef of varDefs) {
const named = varDef;
if (named.name !== undefined) {
mergedEnvState = (0, define_1.define)(named, false, mergedEnvState);
}
}
}
}
const envDef = {
...inDefs[0],
envState: mergedEnvState
};
return { envirData: { ...data, environment: mergedEnvState }, envDef, envirNodeId: nodeId };
}
/**
* The formal parameter names of the qualified call `id` (a `pkg::fn` {@link Identifier}) from the signature
* database (excluding `...`), or `fallback` when the database is disabled or does not carry the function. Lets a
* built-in argument matcher use R's real signature (via {@link ReadOnlyFlowrAnalyzerDependenciesContext#signatureOf})
* instead of a hardcoded formal list, while staying correct -- and graph-invariant -- when no signature is available.
*/
function signatureParamNames(data, id, fallback) {
const sig = data.ctx.deps.signatureOf(id)?.signature;
const names = sig ? (0, decode_1.signatureParameterNames)(sig) : [];
return names.length > 0 ? names : fallback;
}
/** The constant string a name-position node denotes at construction time (string literal, aliased variable, or a paste-like join of such); `undefined` if any part is dynamic or the paste builtin is user-shadowed. */
function resolveConstantString(node, data) {
const info = node_value_1.NodeValue.infoOf(data);
const unshadowed = new Map();
const fold = (n) => {
if (!r_function_call_1.RFunctionCall.isNamed(n)) {
return resolve_helper_1.Resolve.toSingleString(n.info.id, info);
}
const fnName = identifier_1.Identifier.getName(n.functionName.content);
if (!resolve_strings_1.PasteLikeCalls.has(fnName)) {
return undefined;
}
let ok = unshadowed.get(fnName);
if (ok === undefined) {
const defs = resolve_helper_1.Resolve.byNameAndType(n.functionName.content, data.environment, identifier_1.ReferenceType.Function);
ok = defs === undefined || defs.every(d => (0, identifier_1.isReferenceType)(d.type, identifier_1.ReferenceType.BuiltInFunction));
unshadowed.set(fnName, ok);
}
const folded = ok ? (0, resolve_strings_1.foldStringCall)(n, fold) : undefined;
return typeof folded === 'string' ? folded : undefined;
};
return fold(node);
}
/** The `returnsEnvState` of the first reaching definition that carries one, else `undefined`. */
function findReturnsEnvState(defs) {
return defs?.find((d) => d.returnsEnvState !== undefined)?.returnsEnvState;
}
/** Resolves a single already-found argument (e.g. from {@link RFunctionCall.matchArgsToParams}) to an {@link EnvirResolution} when it is a symbol holding a tracked envState. */
function resolveArgToEnvir(arg, data) {
if (arg === r_function_call_1.EmptyArgument) {
return undefined;
}
const node = (0, unpack_argument_1.unpackArg)(arg);
// `.GlobalEnv`/`.BaseEnv` or a `globalenv()`/`baseenv()`/`emptyenv()` call resolves to the corresponding stack env
const stackEnv = (0, built_in_stack_env_1.resolveNodeToStackEnv)(node, data);
if (stackEnv !== undefined && node !== undefined) {
return stackEnvirResolution(stackEnv, node.info.id, node.lexeme ?? '', data);
}
if (node?.type !== type_1.RType.Symbol) {
return undefined;
}
return resolveDefsToEnvirResolution(resolve_helper_1.Resolve.byNameAndType(node.content, data.environment, identifier_1.ReferenceType.Variable), node.info.id, data);
}
/** Builds an {@link EnvirResolution} for an environment obtained directly (not via a holder variable), e.g. `globalenv()` / `.GlobalEnv`. */
function stackEnvirResolution(envState, nodeId, lexeme, data) {
// no holder variable: envDef is only a carrier for envState/nodeId
const envDef = {
name: lexeme,
nodeId,
type: identifier_1.ReferenceType.Variable,
definedAt: nodeId,
envState,
};
return { envirData: { ...data, environment: envState }, envDef, envirNodeId: nodeId, isStackEnv: true };
}
/** Resolves the `argName` argument (default `'envir'`), named with pmatch, to an {@link EnvirResolution}. */
function resolveEnvirArg(args, data, argName = 'envir') {
for (const arg of args) {
if (arg !== r_function_call_1.EmptyArgument && arg.name !== undefined && (0, prefix_1.findByPrefixIfUnique)(arg.name.content, [argName]) === argName) {
return resolveArgToEnvir(arg, data);
}
}
return undefined;
}
/** Resolves a symbol by name to an {@link EnvirResolution} when it holds a tracked environment. */
function resolveSymbolToEnvir(symbolName, nodeId, data) {
return resolveDefsToEnvirResolution(resolve_helper_1.Resolve.byNameAndType(symbolName, data.environment, identifier_1.ReferenceType.Variable), nodeId, data);
}
/** Moves definitions written into a custom environment from the caller's scope into `envDef`'s tracked `envState`, re-defining the holder variable. */
function routeWrittenToCustomEnv(result, envDef, newDefAt, definedAt) {
const written = result.out.filter((d) => d.name !== undefined && 'definedAt' in d &&
(definedAt === undefined || d.definedAt === definedAt));
let newEnvState = envDef.envState;
const namesToRemove = written.map(w => ({ name: w.name }));
for (const w of written) {
newEnvState = (0, define_1.define)(w, false, newEnvState);
}
const newEnvironment = (0, define_1.define)({ ...envDef, definedAt: newDefAt, envState: newEnvState }, false, { current: result.environment.current.removeAll(namesToRemove), level: result.environment.level });
return { ...result, environment: newEnvironment };
}
/** A `search()` position must be an integer and may never displace the global environment (R rejects `pos = 1`). */
function clampAttachPosition(pos) {
return Number.isFinite(pos) ? Math.max(environment_1.DefaultAttachPosition, Math.trunc(pos)) : undefined;
}
/**
* The `search()` position the `pos` argument of a `library()` call requests, either given as a number or as the name of
* an existing entry (`pos = "package:base"`). Returns `undefined` when there is no such argument, its value is unknown
* or ambiguous, or it names an entry that is not on the search path; callers then attach at {@link DefaultAttachPosition}
* (as R does, which warns in the last case).
*/
function resolveAttachPosition(posId, data) {
if (posId === undefined) {
return undefined;
}
const element = node_value_1.NodeValue.soleOf(posId, data);
if (element === undefined) {
return undefined;
}
const asNumber = r_value_1.RValue.numberOf(element);
if (asNumber !== undefined) {
return clampAttachPosition(asNumber);
}
const asString = r_value_1.RValue.stringOf(element);
if (asString !== undefined) {
const found = environment_1.REnvironment.searchPosition(data.environment.current, asString);
if (found === undefined) {
logger_1.dataflowLogger.warn(`search-path entry '${asString}' does not exist, attaching at the default position`);
return undefined;
}
return clampAttachPosition(found);
}
return undefined;
}
//# sourceMappingURL=built-in-envir-utils.js.map