UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

223 lines 9.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FlowrFilterCombinator = exports.FlowrFilters = exports.ValidFlowrFiltersReverse = exports.ValidFlowrFilters = exports.FlowrFilter = void 0; exports.testFunctionsIgnoringPackage = testFunctionsIgnoringPackage; exports.binaryTreeToString = binaryTreeToString; exports.isBinaryTree = isBinaryTree; 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 identifier_1 = require("../dataflow/environments/identifier"); 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 testFunctionsIgnoringPackage} 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"; })(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) => { if (args.enrichment === search_enrichers_1.Enrichment.CallTargets) { const c = (0, search_enrichers_1.enrichmentContent)(e, search_enrichers_1.Enrichment.CallTargets); if (c === undefined || c.targets === undefined) { return false; } for (const fn of c.targets) { if (typeof fn === 'string' && args.test.test(fn)) { return true; } if (typeof fn === 'object' && 'node' in fn && fn.node.type === type_1.RType.FunctionCall && fn.node.named && args.test.test(identifier_1.Identifier.getName(fn.node.functionName.content))) { return true; } } return false; } else { const content = JSON.stringify((0, search_enrichers_1.enrichmentContent)(e, args.enrichment)); return content !== undefined && args.test.test(content); } }), [FlowrFilter.OriginKind]: ((e, args, data) => { const dfgNode = data.dataflow.graph.getVertex(e.node.info.id); if (!dfgNode || dfgNode.tag !== vertex_1.VertexType.FunctionCall) { 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); }) }; /** * Helper to create a regular expression that matches function names, ignoring their package. */ function testFunctionsIgnoringPackage(functions) { return new RegExp(`^(.+:::?)?(${functions.join('|')})$`); } /** * @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}`); } } 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; /** * 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 evalVisit = { and: ({ left, right }, data) => evalTree(left, data) && evalTree(right, data), or: ({ left, right }, data) => evalTree(left, data) || evalTree(right, data), xor: ({ left, right }, data) => evalTree(left, data) !== evalTree(right, data), not: ({ operand }, data) => !evalTree(operand, data), 'r-type': ({ value }, { element }) => element.node.type === value, 'vertex-type': ({ value }, { data, element }) => data.dataflow.graph.getVertex(element.node.info.id)?.tag === value, 'special': ({ value }, { data, element }) => { const name = typeof value === 'string' ? value : value.name; const args = typeof value === 'string' ? undefined : value.args; const getHandler = exports.FlowrFilters[name]; if (getHandler) { return getHandler(element, args, data); } throw new Error(`Couldn't find special filter with name ${name}`); } }; function evalTree(tree, data) { /* we ensure that the types fit */ return evalVisit[tree.type](tree, data); } /** * Evaluates the given filter expression against the provided data. */ function evalFilter(filter, data) { if (filter instanceof FlowrFilterCombinator) { return evalTree(filter.get(), data); } else if (typeof filter === 'string' && exports.ValidFlowrFilters.has(filter)) { const handler = exports.FlowrFilters[filter]; return handler(data.element, undefined, data.data); } else if (typeof filter === 'object' && 'name' in filter) { const handler = exports.FlowrFilters[filter.name]; const args = ('args' in filter ? filter.args : undefined); return handler(data.element, args, data.data); } else { const tree = FlowrFilterCombinator.is(filter); return evalTree(tree.get(), data); } } //# sourceMappingURL=flowr-search-filters.js.map