UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

184 lines 7.87 kB
"use strict"; 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