@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
163 lines • 6.35 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");
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";
})(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 = JSON.stringify((0, search_enrichers_1.enrichmentContent)(e, args.enrichment));
return content !== undefined && args.test.test(content);
})
};
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;
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})`;
}
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 }, { dataflow: { graph }, element }) => graph.getVertex(element.node.info.id)?.tag === value,
'special': ({ value }, { 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);
}
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);
}
function evalFilter(filter, data) {
/* common lift, this can be improved easily :D */
const tree = FlowrFilterCombinator.is(filter);
return evalTree(tree.get(), data);
}
//# sourceMappingURL=flowr-search-filters.js.map