@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
252 lines • 14.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DispatchCallees = exports.FnSig = exports.PropagatedProps = exports.FileInputProps = exports.SigDbInferable = exports.InputProps = exports.ExclusiveCallProps = exports.ImpureProps = exports.CallProp = exports.ArgProp = void 0;
exports.callPropWords = callPropWords;
exports.fnInfoFromSignature = fnInfoFromSignature;
/**
* What a single argument of a call is used for, as a bitmask.
* @see {@link BuiltInFnInfo#sig}
*/
var ArgProp;
(function (ArgProp) {
/** the result is this argument, handed back unchanged, like `x` in `identity(x)`; this is what draws the `Returns` edge */
ArgProp[ArgProp["Alias"] = 1] = "Alias";
/** the result is computed from the argument's value, like `x` in `sum(x)` */
ArgProp[ArgProp["Value"] = 2] = "Value";
/** only the shape is used (length, dimensions, names, other attributes), like `x` in `nrow(x)` */
ArgProp[ArgProp["Shape"] = 4] = "Shape";
/** selects a behavior instead of carrying data, like `na.rm` in `sum(x, na.rm = TRUE)` */
ArgProp[ArgProp["Flag"] = 8] = "Flag";
/** names the resource the call reads or writes, like `file` in `write.csv(x, file)` */
ArgProp[ArgProp["Resource"] = 16] = "Resource";
/** what it refers to may be modified, like `envir` in `assign(x, v, envir = e)` */
ArgProp[ArgProp["Written"] = 32] = "Written";
/** evaluated whenever the call happens, even if the result goes unused, like `x` in `force(x)` */
ArgProp[ArgProp["Forced"] = 64] = "Forced";
/** quoted or evaluated in another frame, like `expr` in `quote(expr)` */
ArgProp[ArgProp["Nse"] = 128] = "Nse";
/** called as a function, like `FUN` in `lapply(x, FUN)` */
ArgProp[ArgProp["Callee"] = 256] = "Callee";
/** only whether it was supplied matters, as with `missing()` */
ArgProp[ArgProp["Presence"] = 512] = "Presence";
/**
* the result is one of this argument's values, like `choices` in `match.arg(arg, choices)`. The bounding
* argument of a {@link CallProp.Narrows} call; without one such a call yields a value of its own making.
*/
ArgProp[ArgProp["Bounds"] = 1024] = "Bounds";
/**
* only atomic data works here, never a closure, as with `e1` in `e1 > e2`. A bare symbol in such an
* argument therefore names a variable even when a function of that name is in scope.
*/
ArgProp[ArgProp["Atomic"] = 2048] = "Atomic";
/** the open handle the call acts on, like `con` in `close(con)` */
ArgProp[ArgProp["Handle"] = 4096] = "Handle";
})(ArgProp || (exports.ArgProp = ArgProp = {}));
/**
* What the call as a whole does, as a bitmask. The resource bits ({@link CallProp.File} and its neighbors)
* say where the call gets its data from, which is what {@link InputProps} collects.
* @see {@link BuiltInFnInfo#props}
*/
var CallProp;
(function (CallProp) {
/** computes a result and nothing else, the positive counterpart of `hasUnknownSideEffects` (excludes {@link ImpureProps}) */
CallProp[CallProp["Pure"] = 1] = "Pure";
/**
* pure on its own, but it runs code it is handed, so whatever that code does happens too.
* The parameter it runs is marked {@link ArgProp.Callee} or {@link ArgProp.Nse}, as with `lapply(x, f)`.
*/
CallProp[CallProp["MayPure"] = 2] = "MayPure";
/** may signal an error, like `stop()` (see {@link SigDbInferable}) */
CallProp[CallProp["Throws"] = 4] = "Throws";
/** returns invisibly, so the result is not auto-printed */
CallProp[CallProp["Invisible"] = 8] = "Invisible";
/** dispatches on the class of an argument (S3, S4, or S7), a group generic like `+` on either operand */
CallProp[CallProp["Generic"] = 16] = "Generic";
/** a method that is reached by dispatch, like `print.foo` (see {@link SigDbInferable}) */
CallProp[CallProp["Method"] = 32] = "Method";
/** binds, rebinds, or removes names outside of its own frame, like `assign` or `library` */
CallProp[CallProp["Scope"] = 64] = "Scope";
/** the result may differ between two identical calls for a reason neither `Random` nor `Ambient` covers (see {@link SigDbInferable}) */
CallProp[CallProp["NonDet"] = 128] = "NonDet";
/** draws from the random number generator, or sets its state (stated instead of `NonDet`) */
CallProp[CallProp["Random"] = 256] = "Random";
/** depends on ambient state like the clock, the locale, environment variables, or global options (stated instead of `NonDet`) */
CallProp[CallProp["Ambient"] = 512] = "Ambient";
/** touches the file system */
CallProp[CallProp["File"] = 1024] = "File";
/** produces a temporary path; on its own this touches no file system, so a call that also does states `File` too */
CallProp[CallProp["TempFile"] = 2048] = "TempFile";
/**
* always reaches the network, like `curl::curl_download`. Calls that only do so for some arguments, like
* `read.csv` of a URL, are left to the `network-functions` rule, which decides that per call site.
*/
CallProp[CallProp["Network"] = 4096] = "Network";
/** runs a system command */
CallProp[CallProp["Process"] = 8192] = "Process";
/** calls native code through the foreign function interface, like `.Call` */
CallProp[CallProp["Ffi"] = 16384] = "Ffi";
/** produces a language object, like `quote` or `deparse` */
CallProp[CallProp["Lang"] = 32768] = "Lang";
/** asks the user, like `readline` or a file chooser */
CallProp[CallProp["User"] = 65536] = "User";
/** draws on a graphics device */
CallProp[CallProp["Graphics"] = 131072] = "Graphics";
/** talks to a database */
CallProp[CallProp["Database"] = 262144] = "Database";
/** reads the resource its `Resource` arguments name */
CallProp[CallProp["Reads"] = 524288] = "Reads";
/** writes the resource its `Resource` arguments name */
CallProp[CallProp["Writes"] = 1048576] = "Writes";
/** may emit to standard output, like `print` or a `cat` without a `file`, and follows a `sink` when one is active */
CallProp[CallProp["Prints"] = 2097152] = "Prints";
/**
* the result is bounded no matter what flows in: a count, an index, a logical, or one of the values of the
* argument marked {@link ArgProp.Bounds}. So nothing an argument carries reaches the result, which is what
* lets the input-sources query stop tracing at `length(x)` or `match.arg(arg, choices)`.
*/
CallProp[CallProp["Narrows"] = 4194304] = "Narrows";
/**
* sets ambient state later calls read back: the working directory, environment variables, options, the
* locale, the RNG seed. The counterpart of {@link CallProp.Ambient}; a call doing both states both.
*/
CallProp[CallProp["Configures"] = 8388608] = "Configures";
/** ends what an opener started: a graphics device, a connection, a sink. Narrower than {@link CallProp.Graphics}. */
CallProp[CallProp["Closes"] = 16777216] = "Closes";
/** yields the paths it matches at run time rather than one it was handed (`list.files`, `Sys.glob`); empty is an answer */
CallProp[CallProp["Glob"] = 33554432] = "Glob";
/** hands back what the program was invoked with, as `commandArgs` and the option parsers built on it do */
CallProp[CallProp["CommandLine"] = 67108864] = "CommandLine";
/** hands back a handle the program is expected to close again, like `file` or `DBI::dbConnect` */
CallProp[CallProp["Opens"] = 134217728] = "Opens";
})(CallProp || (exports.CallProp = CallProp = {}));
/**
* The {@link CallProp} bits that state an effect beyond computing a result, so no {@link CallProp.Pure}
* definition may carry any of them.
*/
exports.ImpureProps = CallProp.MayPure | CallProp.Scope | CallProp.NonDet | CallProp.Random | CallProp.Ambient
| CallProp.File | CallProp.TempFile | CallProp.Network | CallProp.Process | CallProp.Ffi | CallProp.Lang
| CallProp.User | CallProp.Graphics | CallProp.Database | CallProp.Reads | CallProp.Writes | CallProp.Prints
| CallProp.Configures | CallProp.Closes | CallProp.Opens | CallProp.CommandLine;
/**
* Which {@link CallProp} bits rule each other out, as `[bit, everything stating it forbids]`. A definition
* that carries the left bit must carry none of the right ones; a test checks the {@link DefaultBuiltinConfig}
* (and any configured built-ins) against this. Every other pair of bits combines freely.
*/
exports.ExclusiveCallProps = [
[CallProp.Pure, exports.ImpureProps],
[CallProp.NonDet, CallProp.Random | CallProp.Ambient],
[CallProp.Random, CallProp.Ambient]
];
/**
* The {@link CallProp} bits of calls that bring in data of their own. A function that states its props and
* carries none of these derives its result from its arguments, which is what {@link BuiltInIndex#without}
* looks for.
*/
exports.InputProps = CallProp.NonDet | CallProp.Random | CallProp.Ambient | CallProp.File
| CallProp.TempFile | CallProp.Network | CallProp.Process | CallProp.Ffi | CallProp.Lang | CallProp.User
| CallProp.CommandLine;
/**
* The {@link CallProp} bits the signature database states itself, so {@link fnInfoFromSignature} can read them
* off any package function without anyone writing them down.
*/
exports.SigDbInferable = CallProp.Throws | CallProp.NonDet | CallProp.Method | CallProp.Generic;
/**
* The {@link CallProp} bits that say a call takes its data from a file, as {@link CallProp.File} alone also
* covers the calls that only write one.
*/
exports.FileInputProps = CallProp.File | CallProp.Reads;
/**
* The {@link CallProp} bits that carry over from a callee to its caller: what the called function does, the
* calling one does too. Purity does not travel this way, which is why it is not in here.
*/
exports.PropagatedProps = CallProp.Throws | CallProp.Scope | CallProp.NonDet | CallProp.Prints
| CallProp.Random | CallProp.Ambient | CallProp.File | CallProp.TempFile | CallProp.Network | CallProp.Process
| CallProp.Ffi | CallProp.Lang | CallProp.User | CallProp.Graphics | CallProp.Database | CallProp.Reads | CallProp.Writes
| CallProp.Configures | CallProp.CommandLine;
/**
* Utility functions for {@link FnSig|function signatures}.
*/
exports.FnSig = {
name: 'FnSig',
/** The positional view of a signature; see {@link sigLayout}. */
layout: sigLayout,
/** The roles of the argument at a position; see {@link argProp}. */
propAt: argProp,
/** The positions carrying any of the given roles; see {@link argsWith}. */
posWith: argsWith
};
/** the {@link CallProp} bits as the words a reader wants, in the order they are declared */
const CallPropNames = [
[CallProp.Pure, 'pure'], [CallProp.Throws, 'can throw'], [CallProp.Invisible, 'invisible'],
[CallProp.Generic, 'generic'], [CallProp.Method, 's3 method'], [CallProp.Scope, 'changes scope'],
[CallProp.NonDet, 'non deterministic'], [CallProp.Random, 'random'], [CallProp.Ambient, 'ambient state'],
[CallProp.File, 'file system'], [CallProp.Reads, 'reads'], [CallProp.Writes, 'writes'],
[CallProp.Network, 'network'], [CallProp.Prints, 'prints']
];
/** What a call states about itself, as words rather than as a bit mask, for anything showing it to a reader. */
function callPropWords(props) {
return props === undefined ? [] : CallPropNames.filter(([bit]) => (props & bit) !== 0).map(([, word]) => word);
}
const layouts = new WeakMap();
/**
* The positional view of a {@link FnSig}, computed on first use and cached per signature object,
* so declaring a signature costs nothing until a call actually needs it.
*/
function sigLayout(sig) {
let layout = layouts.get(sig);
if (layout === undefined) {
const props = sig.map(p => p[1]);
layout = {
props,
rest: sig.findIndex(p => p[0] === '...'),
any: props.reduce((acc, p) => acc | p, 0),
alias: props.findIndex(p => (p & ArgProp.Alias) !== 0)
};
layouts.set(sig, layout);
}
return layout;
}
/** The {@link ArgProp} bits of the argument at `index`, with `...` covering every position from where it appears. */
function argProp({ props, rest }, index) {
return (rest >= 0 && index >= rest ? props[rest] : props[index]) ?? 0;
}
/** The positions of the first `count` arguments that carry any of `prop`. */
function argsWith(layout, count, prop) {
const found = [];
for (let i = 0; i < count; i++) {
if ((exports.FnSig.propAt(layout, i) & prop) !== 0) {
found.push(i);
}
}
return found;
}
/** the {@link DecodedFunction#props} names that have a {@link CallProp} counterpart, together {@link SigDbInferable} */
const SigDbProps = {
'can-throw': CallProp.Throws,
'non-deterministic': CallProp.NonDet,
's3-method': CallProp.Method
};
/** the callees that make the calling function itself a generic ({@link CallProp.Generic}) */
exports.DispatchCallees = new Set(['UseMethod', 'standardGeneric', 'S7_dispatch']);
/**
* The part of a {@link BuiltInFnInfo} that the signature database already knows: the parameter names in order
* (`...` included) with the ones R always forces, plus the properties listed in {@link SigDbProps}. Everything
* else the database records (`higher-order`, `deprecated`, `recursive`, ...) has no counterpart here and is
* dropped, and anything it cannot see (purity, resources, what an argument is used for) stays unset.
*/
function fnInfoFromSignature(fn) {
let props = 0;
for (const name of fn.props) {
props |= SigDbProps[name] ?? 0;
}
/* a function whose own body dispatches is the generic, which no property of the database states */
if (fn.callees.some(c => exports.DispatchCallees.has(c))) {
props |= CallProp.Generic;
}
return {
sig: fn.signature.map(p => [p.name,
(p.forced ? ArgProp.Forced : 0) | (p.default === 'TRUE' || p.default === 'FALSE' ? ArgProp.Flag : 0)]),
props
};
}
//# sourceMappingURL=built-in-props.js.map