UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

321 lines 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReferenceTypeReverseMapping = exports.ReferenceType = exports.Identifier = void 0; exports.isReferenceType = isReferenceType; const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); const strings_1 = require("../../util/text/strings"); const r_base_packages_1 = require("../../util/r-base-packages"); const dotDotDotAccess = /^\.\.\d+$/; /** * Helper functions to work with {@link Identifier|identifiers}. * Use {@link Identifier.matches} to check if two identifiers match according to R's scoping rules! * @example * ```ts * const id1 = Identifier.make('a', 'pkg'); * const id2 = Identifier.parse('pkg::a'); * const id3 = Identifier.parse('a'); * Identifier.matches(id1, id2); // true * Identifier.matches(id3, id2); // true, as id3 has no namespace * ``` */ exports.Identifier = { name: 'Identifier', /** * Creates an identifier. Strips surrounding backticks from the name. * Prefer {@link Identifier.from} for static config entries where namespace is always present and name has no backticks. */ make(name, namespace, internal = false) { if ((0, strings_1.startAndEndsWith)(name, '`')) { name = name.slice(1, -1); } if (namespace) { return [name, namespace, internal]; } else { return name; } }, /** * Fast-path factory: returns the tuple as-is with no allocation or runtime checks. * Use for static built-in config entries where name is a compile-time constant with no backticks. * @example * ```ts * Identifier.from(['map', 'purrr']) // ['map', 'purrr'] * Identifier.from(['map', 'purrr', true]) // ['map', 'purrr', true] * ``` */ from(arr) { return arr; }, /** * The same fast path for many names of one package: the given array of names is cast in place, * so the literal you pass is the only array involved. * @example * ```ts * Identifier.fromAll('purrr', ['map', 'walk']) // [['map', 'purrr'], ['walk', 'purrr']] * ``` */ fromAll(namespace, names) { const ids = names; for (let i = 0; i < names.length; i++) { ids[i] = [names[i], namespace]; } return ids; }, /** * Verify whether an unknown element has a valid identifier shape! */ is(id) { if (typeof id === 'string') { return true; } if (Array.isArray(id)) { if (id.length === 2) { return typeof id[0] === 'string' && typeof id[1] === 'string'; } else if (id.length === 3) { return typeof id[0] === 'string' && typeof id[1] === 'string' && typeof id[2] === 'boolean'; } } return false; }, /** * Parse an identifier from its string representation, * Please note, that in R if one writes `"pkg::a"` this refers to a symbol named `pkg::a` and NOT to the namespaced identifier `a` in package `pkg`. * In this scenario, see {@link Identifier.make} instead. */ parse(str) { const at = namespaceSeparatorAt(str); if (at < 0) { return str; } const internal = str[at + 2] === ':'; return [str.slice(at + (internal ? 3 : 2)), str.slice(0, at), internal]; }, /** * Get the name part of the identifier */ getName(id) { return Array.isArray(id) ? id[0] : id; }, /** * Get the namespace part of the identifier, undefined if there is none */ getNamespace(id) { return Array.isArray(id) ? id[1] : undefined; }, /** * Check if the identifier accesses internal objects (`:::`) */ accessesInternal(id) { return Array.isArray(id) ? id[2] : undefined; }, /** * Convert the identifier to a **valid R** string representation, * this will properly quote namespaces that contain `::` to avoid confusion. * @example * ```ts * Identifier.toString('a') // 'a' * Identifier.toString(['a', 'pkg']) // 'pkg::a' * Identifier.toString(['a', 'pkg:::internal', true]) // '"pkg:::internal":::a' * ``` */ toString(id) { if (Array.isArray(id)) { if (id[1].includes('::')) { return `${JSON.stringify(id[1])}${id[2] ? ':::' : '::'}${id[0]}`; } return `${id[1]}${id[2] ? ':::' : '::'}${id[0]}`; } else { if (id.includes('::')) { return JSON.stringify(id); } return id; } }, /** * Check if two identifiers match. * This differs from eq! * If the first identifier is not namespaced, it will match any namespace! * If we search for S3 methods (s3=true), the target may have an additional suffix after a dot. * If the first identifier is internal, it will match any target (internal or not). */ matches(id, target, s3 = false) { const idName = exports.Identifier.getName(id); const targetName = exports.Identifier.getName(target); if (idName !== targetName) { return s3 ? targetName.startsWith(idName + '.') : false; } const idNs = exports.Identifier.getNamespace(id); if (idNs === undefined) { return true; } const targetNs = exports.Identifier.getNamespace(target); if (idNs !== targetNs) { return false; } const idInternal = exports.Identifier.accessesInternal(id); if (idInternal === true) { return true; } /* an omitted flag is the same as an explicit `false`, `pkg::fn` written either way is one identifier */ return (idInternal ?? false) === (exports.Identifier.accessesInternal(target) ?? false); }, /** * Helper to create a regular expression that matches against an array of {@link Identifier} values. If both the passed identifier and the matched identifier are namespaced, their namespaces are expected to match. If either is not namespaced, the namespace is ignored on both. */ regex(...identifiers) { // if the passed identifier is not namespaced, we match against *any* namespace. if it is namespaced, we match against the correct namespace or no namespace return new RegExp(`^(${identifiers.map(i => `(${exports.Identifier.getNamespace(i) ?? '.+'}:::?)?${exports.Identifier.getName(i)}`).join('|')})$`); }, /** Special identifier for the `...` argument */ dotdotdot() { return '...'; }, /** * Check if the identifier is the special `...` argument / or one of its accesses like `..1`, `..2`, etc. * This always returns false for namespaced identifiers. */ isDotDotDotAccess(id) { return !Array.isArray(id) && (dotDotDotAccess.test(id) || id === '...'); }, /** * Functor over the name of the identifier */ mapName(id, fn) { if (Array.isArray(id)) { return [fn(id[0]), id[1], id[2]]; } else { return fn(id); } }, /** * Functor over the namespace of the identifier */ mapNamespace(id, fn) { if (Array.isArray(id)) { return [id[0], fn(id[1]), id[2]]; } else { return id; } }, /** * Convert the identifier to its array representation */ toArray(id) { if (Array.isArray(id)) { return [id[0], id[1], id[2]]; } else { return [id, undefined, undefined]; } }, /** * The package-qualified identifier of a call, resolved in order of decreasing certainty: * 1. a package export the {@link Origin|origins} resolve to (`map()` with `purrr` loaded yields `purrr::map`), * 2. an already-namespaced `name` returned unchanged (an explicit `pkg::fn` call), * 3. with `qualifyBaseR`, a bare base-R call qualified from its exporting package via {@link baseRExportOwner} * (`sd` yields `stats::sd`), needing no loaded database or graph edge and skipped when the call resolves to * a user definition, so a local `sd()` stays bare. * * Returns `undefined` when none apply. Steps 2 and 3 need the call's `name`. * @param qualifyBaseR - whether to also qualify a bare base-R call from its exporting package (default `true`) * @see {@link Dataflow.qualify} - the compact form, if you have the call's id and its graph */ toQualified(origins, name, qualifyBaseR = true) { let sawUserDefinition = false; for (const origin of origins ?? []) { if ('proc' in origin) { const pkgFn = node_id_1.NodeId.toPkgFn(origin.proc); if (pkgFn) { return exports.Identifier.make(pkgFn[1], pkgFn[0]); } else if (exports.Identifier.getNamespace(origin.fn.name) !== undefined) { return origin.fn.name; } } else { sawUserDefinition = true; } } if (name === undefined) { return undefined; } if (exports.Identifier.getNamespace(name) !== undefined) { return name; } if (qualifyBaseR && !sawUserDefinition) { const bare = exports.Identifier.getName(name); const owner = (0, r_base_packages_1.baseRExportOwner)(bare); if (owner !== undefined) { return exports.Identifier.make(bare, owner); } } return undefined; } }; /** The index of the `::` separating namespace from name, skipping backtick-quoted spans; `-1` if there is none. */ function namespaceSeparatorAt(str) { let quoted = false; for (let i = 0; i < str.length; i++) { if (str[i] === '`') { quoted = !quoted; } else if (!quoted && str[i] === ':' && str[i + 1] === ':') { return i; } } return -1; } /** * Each reference has exactly one reference type, stored as the respective number. * However, when checking, we may want to allow for one of several types, * allowing the combination of the respective bitmasks. * * Having reference types is important as R separates a variable definition from * a function when resolving an {@link Identifier|identifier}. * In `c <- 3; print(c(1, 2))` the call to `c` works normally (as the vector constructor), * while writing `c <- function(...) ..1` overshadows the built-in and causes `print` to only output the first element. * @see {@link isReferenceType} - for checking if a (potentially joint) reference type contains a certain type * @see {@link ReferenceTypeReverseMapping} - for debugging */ var ReferenceType; (function (ReferenceType) { /** The identifier type is unknown */ ReferenceType[ReferenceType["Unknown"] = 1] = "Unknown"; /** The identifier is defined by a function (includes built-in function) */ ReferenceType[ReferenceType["Function"] = 2] = "Function"; /** The identifier is defined by a variable (includes parameter and argument) */ ReferenceType[ReferenceType["Variable"] = 4] = "Variable"; /** The identifier is defined by a constant (includes built-in constant) */ ReferenceType[ReferenceType["Constant"] = 8] = "Constant"; /** The identifier is defined by a parameter (which we know nothing about at the moment) */ ReferenceType[ReferenceType["Parameter"] = 16] = "Parameter"; /** The identifier is defined by an argument (which we know nothing about at the moment) */ ReferenceType[ReferenceType["Argument"] = 32] = "Argument"; /** The identifier is defined by a built-in value/constant */ ReferenceType[ReferenceType["BuiltInConstant"] = 64] = "BuiltInConstant"; /** The identifier is defined by a built-in function */ ReferenceType[ReferenceType["BuiltInFunction"] = 128] = "BuiltInFunction"; /** Prefix to identify S3 methods, use this, to for example dispatch a call to `f` which will then link to `f.*` */ ReferenceType[ReferenceType["S3MethodPrefix"] = 256] = "S3MethodPrefix"; /** Prefix to identify S7 methods, use this, to for example dispatch a call to `f` which will then link to `f<7>*` */ ReferenceType[ReferenceType["S7MethodPrefix"] = 512] = "S7MethodPrefix"; /** * Only ever a lookup target, never the type of a definition: everything a value position may see. * `id` in `id > 2` names data, so a function `id` in scope is not what the comparison reads. */ ReferenceType[ReferenceType["NonFunction"] = 1024] = "NonFunction"; })(ReferenceType || (exports.ReferenceType = ReferenceType = {})); /** Reverse mapping of the reference types so you can get the name from the bitmask (useful for debugging) */ exports.ReferenceTypeReverseMapping = new Map(Object.entries(ReferenceType).map(([k, v]) => [v, k])); /** * Check if the reference types have an overlapping type! */ function isReferenceType(t, target) { return (t & target) !== 0; } //# sourceMappingURL=identifier.js.map