UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

264 lines 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.F = exports.FlowrFilterCombinator = exports.FlowrFilters = exports.ValidFlowrFiltersReverse = exports.ValidFlowrFilters = exports.FlowrFilter = void 0; exports.binaryTreeToString = binaryTreeToString; exports.isBinaryTree = isBinaryTree; exports.prepareFilter = prepareFilter; exports.evalFilter = evalFilter; const type_1 = require("../r-bridge/lang-4.x/ast/model/type"); const vertex_1 = require("../dataflow/graph/vertex"); const search_enrichers_1 = require("./search-executor/search-enrichers"); const objects_1 = require("../util/objects"); const search_generators_1 = require("./search-executor/search-generators"); const query_fn_props_1 = require("../dataflow/environments/query-fn-props"); var FlowrFilter; (function (FlowrFilter) { /** * Drops search elements that represent empty arguments. Specifically, all nodes that are arguments and have an undefined name are skipped. * This filter does not accept any arguments. */ FlowrFilter["DropEmptyArguments"] = "drop-empty-arguments"; /** * Only returns search elements whose enrichments' JSON representations match a given test regular expression. * This filter accepts {@link MatchesEnrichmentArgs}, which includes the enrichment to match for, as well as the regular expression to test the enrichment's (non-pretty-printed) JSON representation for. * To test for included function names in an enrichment like {@link Enrichment.CallTargets}, the helper function {@link matchIdentifiers} can be used. */ FlowrFilter["MatchesEnrichment"] = "matches-enrichment"; /** * Only returns search elements whose {@link FunctionOriginInformation} match a given pattern or value. * This filter accepts {@link OriginKindArgs}, which includes the {@link DataflowGraphVertexFunctionCall.origin} to match for, whether to match for every or some origins, and whether to include non-function-calls in the filtered query. */ FlowrFilter["OriginKind"] = "origin-kind"; /** * Only returns search element whose {@link RoleInParent} matches a given {@link RoleInParent}. * This filter accepts an object containing a `roleInParent` argument of type {@link RoleInParent}. */ FlowrFilter["RoleInParent"] = "role-in-parent"; /** * Only returns search elements whose file path matches the given regular expression. * This filter accepts {@link FilePathFilterArgs}, which includes the file path regex to test against. */ FlowrFilter["FilePathFilter"] = "file-path-filter"; /** * Only returns function calls whose {@link CallProp} bits match the given mask, so that _every call that asks * the user_ or _every call that closes a device_ can be searched for without naming a single function. * This filter accepts {@link CallPropsArgs}. */ FlowrFilter["CallProps"] = "call-props"; })(FlowrFilter || (exports.FlowrFilter = FlowrFilter = {})); exports.ValidFlowrFilters = new Set(Object.values(FlowrFilter)); exports.ValidFlowrFiltersReverse = Object.fromEntries(Object.entries(FlowrFilter).map(([k, v]) => [v, k])); exports.FlowrFilters = { [FlowrFilter.DropEmptyArguments]: ((e, _args) => { return e.node.type !== type_1.RType.Argument || e.node.name !== undefined; }), [FlowrFilter.MatchesEnrichment]: ((e, args) => { const content = (0, search_enrichers_1.enrichmentContent)(e, args.enrichment); return content && (0, objects_1.looselyCompareObjects)(content, args.test, args.arrayMatch, search_generators_1.searchLogger); }), [FlowrFilter.OriginKind]: ((e, args, data) => { const dfgNode = data.dataflow.graph.getVertex(e.node.info.id); if (!dfgNode || !vertex_1.FunctionCallVertex.is(dfgNode)) { return args.keepNonFunctionCalls ?? false; } const match = typeof args.origin === 'string' ? (origin) => args.origin === origin : (origin) => args.origin.test(origin); const origins = Array.isArray(dfgNode.origin) ? dfgNode.origin : [dfgNode.origin]; return args.matchType === 'every' ? origins.every(match) : origins.some(match); }), [FlowrFilter.RoleInParent]: ((e, { roleInParent }) => { return e.node.info.role === roleInParent; }), [FlowrFilter.FilePathFilter]: ((e, args) => { const file = e.node.info.file; const rx = args.filePathRegex instanceof RegExp ? args.filePathRegex : new RegExp(args.filePathRegex); return rx.test(file ?? ''); }), [FlowrFilter.CallProps]: ((e, args, data) => { const props = (0, query_fn_props_1.callFnProps)(e.node.info.id, data.dataflow)?.props ?? 0; return args.matchType === 'every' ? (props & args.props) === args.props : (props & args.props) !== 0; }) }; /** * @see {@link FlowrFilterCombinator.is} * @see {@link evalFilter} * @see {@link binaryTreeToString} */ class FlowrFilterCombinator { tree; constructor(init) { this.tree = this.unpack(init); } static is(value) { if (typeof value === 'string' && exports.ValidFlowrFilters.has(value)) { return new this({ type: 'special', value: value }); } else if (typeof value === 'object') { const name = value?.name; if (name && exports.ValidFlowrFilters.has(name)) { return new this({ type: 'special', value: value }); } else { return new this(value); } } else if (type_1.ValidRTypes.has(value)) { return new this({ type: 'r-type', value: value }); } else if (vertex_1.ValidVertexTypes.has(value)) { return new this({ type: 'vertex-type', value: value }); } else { throw new Error(`Invalid filter value: ${value}`); } } static and(left, right) { return FlowrFilterCombinator.is(left).and(right); } static or(left, right) { return FlowrFilterCombinator.is(left).or(right); } static xor(left, right) { return FlowrFilterCombinator.is(left).xor(right); } static not(value) { return FlowrFilterCombinator.is(value).not(); } and(right) { return this.binaryRight('and', right); } or(right) { return this.binaryRight('or', right); } xor(right) { return this.binaryRight('xor', right); } binaryRight(op, right) { this.tree = { type: op, left: this.tree, right: this.unpack(FlowrFilterCombinator.is(right)) }; return this; } not() { return this.unary('not'); } unary(op) { this.tree = { type: op, operand: this.tree }; return this; } unpack(val) { return val instanceof FlowrFilterCombinator ? val.tree : val; } get() { return this.tree; } } exports.FlowrFilterCombinator = FlowrFilterCombinator; exports.F = FlowrFilterCombinator; /** * Converts the given binary tree to a string representation. */ function binaryTreeToString(tree) { const res = treeToStringImpl(tree, 0); // drop outer parens if (res.startsWith('(') && res.endsWith(')')) { return res.slice(1, -1); } else { return res; } } const typeToSymbol = { 'and': '∧', 'or': '∨', 'xor': '⊕', 'not': '¬' }; function treeToStringImpl(tree, depth) { if (tree.type === 'r-type' || tree.type === 'vertex-type' || tree.type === 'special') { return typeof tree.value === 'string' ? tree.value : `${tree.value.name}@${JSON.stringify(tree.value.args)}`; } if (tree.type === 'not') { return `${typeToSymbol[tree.type]}${treeToStringImpl(tree.operand, depth)}`; } const left = treeToStringImpl(tree.left, depth + 1); const right = treeToStringImpl(tree.right, depth + 1); return `(${left} ${typeToSymbol[tree.type]} ${right})`; } /** * Checks whether the given value is a binary tree combinator. * @see {@link FlowrFilterCombinator} */ function isBinaryTree(tree) { return typeof tree === 'object' && tree !== null && 'tree' in tree; } const compileVisit = { and: ({ left, right }) => { const l = compileTree(left), r = compileTree(right); return (e, d) => l(e, d) && r(e, d); }, or: ({ left, right }) => { const l = compileTree(left), r = compileTree(right); return (e, d) => l(e, d) || r(e, d); }, xor: ({ left, right }) => { const l = compileTree(left), r = compileTree(right); return (e, d) => l(e, d) !== r(e, d); }, not: ({ operand }) => { const o = compileTree(operand); return (e, d) => !o(e, d); }, 'r-type': ({ value }) => e => e.node.type === value, 'vertex-type': ({ value }) => (e, d) => d.dataflow.graph.getVertex(e.node.info.id)?.tag === value, 'special': ({ value }) => { const name = typeof value === 'string' ? value : value.name; const args = typeof value === 'string' ? undefined : value.args; const handler = exports.FlowrFilters[name]; if (!handler) { throw new Error(`Couldn't find special filter with name ${name}`); } return (e, d) => handler(e, args, d); } }; function compileTree(tree) { /* we ensure that the types fit */ return compileVisit[tree.type](tree); } /** * Resolve a filter expression to the function that tests one element. * Nothing here depends on the element, so a search over `n` elements should do this once instead of `n` times: * a bare {@link VertexType}/{@link RType} filter otherwise builds a {@link FlowrFilterCombinator} per element. * @see {@link evalFilter} - the one-shot form, if you only test a single element */ function prepareFilter(filter) { if (filter instanceof FlowrFilterCombinator) { return compileTree(filter.get()); } else if (typeof filter === 'string' && exports.ValidFlowrFilters.has(filter)) { const handler = exports.FlowrFilters[filter]; return (e, d) => handler(e, undefined, d); } else if (typeof filter === 'object' && 'name' in filter) { const handler = exports.FlowrFilters[filter.name]; const args = ('args' in filter ? filter.args : undefined); return (e, d) => handler(e, args, d); } else { return compileTree(FlowrFilterCombinator.is(filter).get()); } } /** * Evaluates the given filter expression against the provided data. * @see {@link prepareFilter} - resolve once when testing more than one element */ function evalFilter(filter, data) { return prepareFilter(filter)(data.element, data.data); } //# sourceMappingURL=flowr-search-filters.js.map