@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
224 lines • 8.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReferenceTypeReverseMapping = exports.ReferenceType = exports.Identifier = void 0;
exports.isReferenceType = isReferenceType;
const strings_1 = require("../../util/text/strings");
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',
/**
* Create an identifier from its name and optional namespace.
* Please note that for `internal` to count, a namespace must be provided!
*/
make(name, namespace, internal = false) {
if ((0, strings_1.startAndEndsWith)(name, '`')) {
name = name.substring(1, name.length - 1);
}
if (namespace) {
return [name, namespace, internal];
}
else {
return name;
}
},
/**
* 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 internal = str.includes(':::');
const parts = str.split(internal ? ':::' : '::');
if (parts.length === 2) {
return [parts[1], parts[0], internal];
}
return parts[0];
},
/**
* 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;
}
const targetInternal = exports.Identifier.accessesInternal(target);
return idInternal === targetInternal;
},
/** 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];
}
}
};
/**
* 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";
})(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