UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

234 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveByName = resolveByName; exports.resolveByNameAnyType = resolveByNameAnyType; exports.resolvesToBuiltInConstant = resolvesToBuiltInConstant; const environment_1 = require("./environment"); const logic_1 = require("../../util/logic"); const identifier_1 = require("./identifier"); const info_1 = require("../info"); const built_in_s_seven_dispatch_1 = require("../internal/process/functions/call/built-in/built-in-s-seven-dispatch"); const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); /** A namespaced lookup only sees its matching layer; a bare lookup skips loaded-but-unattached namespaces (`requireNamespace`). */ function layerSkipped(layer, ns) { if (ns !== undefined) { return layer.n !== ns; } return layer.t === environment_1.EnvType.LoadedNamespace; } const FunctionTargetTypes = identifier_1.ReferenceType.Function | identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Parameter; const VariableTargetTypes = identifier_1.ReferenceType.Variable | identifier_1.ReferenceType.Parameter | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Unknown; const ConstantTargetTypes = identifier_1.ReferenceType.Constant | identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown; const BuiltInConstantTargetTypes = identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown; const BuiltInFunctionTargetTypes = identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown; const NonFunctionTargetTypes = identifier_1.ReferenceType.Variable | identifier_1.ReferenceType.Parameter | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Unknown | identifier_1.ReferenceType.Constant | identifier_1.ReferenceType.BuiltInConstant; const inEveryBranch = (d) => (0, info_1.happensInEveryBranch)(d.cds); const notAParameter = (d) => d.type !== identifier_1.ReferenceType.Parameter; const TargetTypePredicate = { [identifier_1.ReferenceType.Unknown]: () => true, [identifier_1.ReferenceType.Function]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes), [identifier_1.ReferenceType.Variable]: ({ type }) => (0, identifier_1.isReferenceType)(type, VariableTargetTypes), [identifier_1.ReferenceType.Constant]: ({ type }) => (0, identifier_1.isReferenceType)(type, ConstantTargetTypes), [identifier_1.ReferenceType.Parameter]: () => true, [identifier_1.ReferenceType.Argument]: () => true, [identifier_1.ReferenceType.BuiltInConstant]: ({ type }) => (0, identifier_1.isReferenceType)(type, BuiltInConstantTargetTypes), [identifier_1.ReferenceType.BuiltInFunction]: ({ type }) => (0, identifier_1.isReferenceType)(type, BuiltInFunctionTargetTypes), [identifier_1.ReferenceType.S3MethodPrefix]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes), [identifier_1.ReferenceType.S7MethodPrefix]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes), [identifier_1.ReferenceType.NonFunction]: ({ type }) => (0, identifier_1.isReferenceType)(type, NonFunctionTargetTypes), }; /** * What the script itself defines under `id`, ignoring everything flowR carries or a database attached. * This is the fallback of a value position ({@link ReferenceType.NonFunction}), and the two halves answer * the two ways a value position meets a function of its name. In a project that attaches packages, * `library(dplyr)` puts a function `id` in scope, and `filter(df, id > 2)` means the column, never that * function. A script writing `x <- function() 1` and then `x > 2` has only that one `x` to mean, wrong as * the comparison is, so the read stays: dropping it would leave the definition looking unused. */ function ownDefinitions(id, environment) { const own = resolveByNameAnyType(id, environment)?.filter(d => !node_id_1.NodeId.isBuiltIn(d.nodeId)); return own !== undefined && own.length > 0 ? own : undefined; } /** * Resolves a given identifier name to a list of its possible definition location using R scoping and resolving rules. * If the type you want to reference is unknown, please use {@link resolveByNameAnyType} instead. * @param id - The identifier to resolve (optionally namespaced) * @param environment - The current environment used for name resolution * @param target - The target (meta) type of the identifier to resolve * @returns A list of possible identifier definitions (one if the definition location is exactly and always known), or `undefined` * if the identifier is undefined in the current scope/with the current environment information. * @useInstead {@link Resolve.byNameAndType} */ function resolveByName(id, environment, target) { if (target === identifier_1.ReferenceType.Unknown) { return resolveByNameAnyType(id, environment); } const found = resolveByTargetType(id, environment, target); return found === undefined && target === identifier_1.ReferenceType.NonFunction ? ownDefinitions(id, environment) : found; } /** {@link resolveByName} without its two special targets, i.e. the plain walk up the environments. */ function resolveByTargetType(id, environment, target) { /* read piecewise, `Identifier.toArray` would allocate a tuple per resolution */ const name = identifier_1.Identifier.getName(id); const ns = identifier_1.Identifier.getNamespace(id); const internal = identifier_1.Identifier.accessesInternal(id); let current = environment.current; /* `current` can already be the built-in environment itself (e.g. `get(x, envir=baseenv())`); * it has no parent to walk to, so resolve directly instead of entering the loop below. */ if (current.builtInEnv) { return current.memory.get(name); } let definitions = undefined; const wantedType = TargetTypePredicate[target]; do { if (layerSkipped(current, ns)) { current = current.parent; continue; } let definition; if (target === identifier_1.ReferenceType.S3MethodPrefix || target === identifier_1.ReferenceType.S7MethodPrefix) { // S3 method prefixes only resolve to functions, S3s must not match the exported criteria! const prefix = name + (target === identifier_1.ReferenceType.S3MethodPrefix ? '.' : built_in_s_seven_dispatch_1.S7DispatchSeparator); definition = current.memory.entries() .filter(([defName]) => defName.startsWith(prefix)) .flatMap(([, defs]) => defs) .toArray(); } else { definition = current.memory.get(name); if (internal === false) { definition = definition?.filter(({ name }) => name === undefined || !identifier_1.Identifier.accessesInternal(name)); } } if (definition !== undefined && definition.length > 0) { /* ask before filtering: the common case wants all of them and needs no copy */ const allWanted = definition.every(wantedType); if (allWanted && (target !== identifier_1.ReferenceType.Function || definition.every(notAParameter)) && definition.every(inEveryBranch)) { return definition; } /* never alias the environment's own array, it is appended to below */ const filtered = allWanted ? definition.slice() : definition.filter(wantedType); if (filtered.length > 0) { if (definitions) { definitions.push(...filtered); } else { definitions = filtered; } } } current = current.parent; } while (!current.builtInEnv); /* the built-in layer is handed over as it is, except to a value position: `filter(df, c > 2)` means the column `c`, never `base::c`, and only with nothing left does `ownDefinitions` have its say */ const known = current.memory.get(name); const builtIns = target === identifier_1.ReferenceType.NonFunction ? known?.filter(wantedType) : known; if (definitions) { return builtIns === undefined || builtIns.length === 0 ? definitions : definitions.concat(builtIns); } return builtIns === undefined || builtIns.length > 0 ? builtIns : undefined; } /** * The more performant version of {@link resolveByName} when the target type is unknown. * @useInstead {@link Resolve.byName} */ function resolveByNameAnyType(id, environment) { let current = environment.current; /* only cache plain names: namespaced ids are arrays (no stable map key) and must not answer plain lookups */ const cacheable = typeof id === 'string'; if (cacheable) { const g = current.cache?.get(id); if (g !== undefined) { return g; } } const name = identifier_1.Identifier.getName(id); const ns = identifier_1.Identifier.getNamespace(id); const internal = identifier_1.Identifier.accessesInternal(id); /* `current` can already be the built-in environment itself */ if (current.builtInEnv) { const ret = current.memory.get(name); if (ret && cacheable) { current.cache ??= new Map(); current.cache.set(id, ret); } return ret; } let definitions = undefined; do { if (layerSkipped(current, ns)) { current = current.parent; continue; } let definition = current.memory.get(name); if (definition) { if (internal === false) { definition = definition.filter(({ name }) => name === undefined || !identifier_1.Identifier.accessesInternal(name)); } if (definition.every(inEveryBranch)) { if (cacheable) { environment.current.cache ??= new Map(); environment.current.cache.set(id, definition); } return definition; } else if (definition.length > 0) { if (definitions) { definitions = definitions.concat(definition); } else { definitions = definition; } } } current = current.parent; } while (!current.builtInEnv); const builtIns = current.memory.get(name); let ret; if (definitions) { ret = builtIns === undefined ? definitions : definitions.concat(builtIns); } else { ret = builtIns; } if (ret && cacheable) { environment.current.cache ??= new Map(); environment.current.cache.set(id, ret); } return ret; } /** * Checks whether the given identifier name resolves to a built-in constant with the given value. * @param name - The name of the identifier to resolve * @param environment - The current environment used for name resolution * @param wantedValue - The built-in constant value to check for * @returns Whether the identifier always, never, or maybe resolves to the given built-in constant value * @useInstead {@link Resolve.toBuiltIn} */ function resolvesToBuiltInConstant(name, environment, wantedValue) { if (name === undefined) { return logic_1.Ternary.Never; } const definition = resolveByName(name, environment, identifier_1.ReferenceType.Constant); if (definition === undefined) { return logic_1.Ternary.Never; } let all = true; let some = false; for (const def of definition) { if (def.type === identifier_1.ReferenceType.BuiltInConstant && def.value === wantedValue) { some = true; } else { all = false; } } if (all) { return logic_1.Ternary.Always; } else { return some ? logic_1.Ternary.Maybe : logic_1.Ternary.Never; } } //# sourceMappingURL=resolve-by-name.js.map