UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

224 lines 8.67 kB
"use strict"; 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