@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
184 lines • 7.87 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.StaticSliceFlags = exports.SharedSliceFlags = void 0;
exports.sliceDirectionParser = sliceDirectionParser;
exports.sliceQueryOptionsParser = sliceQueryOptionsParser;
exports.describeSliceFlags = describeSliceFlags;
exports.warnAboutSliceFlags = warnAboutSliceFlags;
exports.queryLineCode = queryLineCode;
exports.sliceCriterionParser = sliceCriterionParser;
exports.sliceCriteriaParser = sliceCriteriaParser;
exports.sliceFlagCompletions = sliceFlagCompletions;
exports.criteriaQueryCompleter = criteriaQueryCompleter;
exports.diceCriteriaParser = diceCriteriaParser;
const slice_direction_1 = require("../../../util/slice-direction");
const core_1 = require("../core");
const ansi_1 = require("../../../util/text/ansi");
/**
* Splits `(criteria)flags` into its two parts, matching the closing bracket by depth: a criterion may well
* carry brackets of its own (the `(file-regex)` suffix of e.g. `2@x(tmp/.*)`), so the first `)` is not it.
* Returns `undefined` if the argument does not start with a bracket, or if that bracket is never closed.
*/
function splitCriteriaArgument(argument) {
if (!argument?.startsWith('(')) {
return undefined;
}
let depth = 0;
for (let i = 0; i < argument.length; i++) {
if (argument[i] === '(') {
depth++;
}
else if (argument[i] === ')' && --depth === 0) {
return { criteria: argument.slice(1, i), flags: argument.slice(i + 1) };
}
}
return undefined;
}
function sliceFlagSuffix(argument) {
return splitCriteriaArgument(argument)?.flags ?? '';
}
/** the flags every slicing query understands, see {@link sliceQueryOptionsParser} */
exports.SharedSliceFlags = [
{ flag: 'i', describe: 'inline sources', conflicts: ['I'] },
{ flag: 'c', describe: 'include callees' },
{ flag: 'I', describe: 'inline all files', conflicts: ['i'] },
{ flag: 'B', describe: 'banners', requires: 'I' }
];
/** the flags of the `static-slice` query: a dice fixes both directions, so only it can be told to slice forward */
exports.StaticSliceFlags = [{ flag: 'f', describe: 'slice forward' }, ...exports.SharedSliceFlags];
/**
* Checks whether the given argument represents a slicing direction with an `f` suffix (in any flag order).
*/
function sliceDirectionParser(argument) {
return sliceFlagSuffix(argument).includes('f') ? slice_direction_1.SliceDirection.Forward : slice_direction_1.SliceDirection.Backward;
}
/**
* The {@link SliceQueryOptions} the flag suffix of `argument` requests, see {@link SharedSliceFlags}.
* An absent flag is left out entirely, so the default of the query applies.
*/
function sliceQueryOptionsParser(argument) {
const flags = sliceFlagSuffix(argument);
return {
...(flags.includes('i') ? { inlineSources: true } : {}),
...(flags.includes('I') ? { inlineFull: flags.includes('B') ? 'banner' : true } : {}),
...(flags.includes('c') ? { includeCallees: true } : {})
};
}
/** the given flags and what they do, e.g. for a help text: `f (slice forward), B (banners, needs I)` */
function describeSliceFlags(flags) {
return flags.map(f => `${f.flag} (${f.describe}${f.requires ? `, needs ${f.requires}` : ''})`).join(', ');
}
function warn(output, message) {
output.stderr(output.formatter.format(message, { color: 3 /* Colors.Yellow */, effect: ansi_1.ColorEffect.Foreground }));
}
/**
* Warns about the flags of `argument` that `flags` does not know (a `f` on a dice, a typo, ...) or that
* {@link SliceFlag#conflicts|conflict} with each other, as they are applied silently otherwise.
*/
function warnAboutSliceFlags(output, argument, flags) {
const given = [...new Set(sliceFlagSuffix(argument))];
const known = new Map(flags.map(f => [f.flag, f]));
const unknown = given.filter(f => !known.has(f));
if (unknown.length > 0) {
warn(output, `Ignoring unknown flag${unknown.length > 1 ? 's' : ''} ${unknown.map(f => `'${f}'`).join(', ')}. Known flags: ${describeSliceFlags(flags)}.`);
}
for (const [i, f] of given.entries()) {
const clash = known.get(f)?.conflicts?.filter(c => given.indexOf(c) > i) ?? [];
if (clash.length > 0) {
warn(output, `The flag '${f}' cannot be combined with ${clash.map(c => `'${c}'`).join(', ')}, the latter wins.`);
}
}
}
/**
* The R code of a query line, i.e. everything after the argument at `from`. The line is split at whitespace, so
* unquoted code arrives as several parts and only re-joining them yields all of it.
*/
function queryLineCode(line, from = 1) {
const code = line.slice(from).join(' ').trim();
return code.length > 0 ? code : undefined;
}
/**
* Parses a single slicing criterion from the given argument.
*/
function sliceCriterionParser(argument) {
return splitCriteriaArgument(argument)?.criteria;
}
/**
* Parses multiple slicing criteria from the given argument.
*/
function sliceCriteriaParser(argument) {
return splitCriteriaArgument(argument)?.criteria.split(';');
}
/** Last partial criterion fragment after the most recent `;` or after `(`. */
function lastCriterionFragment(arg) {
return arg.slice(Math.max(arg.indexOf('(') + 1, arg.lastIndexOf(';') + 1));
}
/**
* The completions for the flag suffix of `arg`: every flag that fits the ones it carries already, plus a
* trailing space to move on to the code. Returns `undefined` while the criteria are still open.
*/
function sliceFlagCompletions(arg, flags) {
const split = splitCriteriaArgument(arg);
if (!split) {
return undefined;
}
const has = (flag) => split.flags.includes(flag);
const offered = flags.filter(f => !has(f.flag)
&& (f.requires === undefined || has(f.requires))
&& !f.conflicts?.some(has));
return {
completions: [...offered.map(f => arg + f.flag), arg + ' '],
labels: new Map([
...offered.map(f => [arg + f.flag, (0, core_1.describeCompletion)(f.flag, f.describe)]),
[arg + ' ', (0, core_1.describeCompletion)('<space>', 'then the code')]
]),
argumentPart: arg
};
}
/**
* Tab-completer for query arguments of the form `(line@var;line@var;...)`.
* Guides the user step by step: `(` then digits then `@` then variable then `)`, then the flags.
*/
function criteriaQueryCompleter(line, startingNewArg, _config) {
if (line.length === 0) {
return { completions: ['('] };
}
if (startingNewArg || line.length !== 1) {
return { completions: [] };
}
const arg = line[0];
const flags = sliceFlagCompletions(arg, exports.StaticSliceFlags);
if (flags) {
return flags;
}
const fragment = lastCriterionFragment(arg);
if (/^\d+$/.test(fragment)) {
return { completions: [`${arg}@`], argumentPart: arg };
}
if (/^\d+@\w+$/.test(fragment)) {
return { completions: [`${arg})`], argumentPart: arg };
}
return { completions: [] };
}
/**
* Parses a dice argument of the form `(from1;from2->to1;to2)`.
* Returns `{ from, to }` on success, or `undefined` if the argument is malformed.
* Each side is a semicolon-separated list of slicing criteria; a single criterion needs no semicolon.
*/
function diceCriteriaParser(argument) {
const inner = splitCriteriaArgument(argument)?.criteria;
if (inner === undefined) {
return undefined;
}
const arrowIdx = inner.indexOf('->');
if (arrowIdx < 0) {
return undefined;
}
const from = inner.slice(0, arrowIdx).split(';').filter(s => s.length > 0);
const to = inner.slice(arrowIdx + 2).split(';').filter(s => s.length > 0);
if (from.length === 0 || to.length === 0) {
return undefined;
}
return { from, to };
}
//# sourceMappingURL=slice-query-parser.js.map