@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
321 lines • 13.3 kB
JavaScript
;
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