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