UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

187 lines 10 kB
"use strict"; 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