@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
223 lines • 9.13 kB
JavaScript
;
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