@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
95 lines • 4.21 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFunctionHigherOrder = isFunctionHigherOrder;
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const identifier_1 = require("../environments/identifier");
const vertex_1 = require("../graph/vertex");
const assert_1 = require("../../util/assert");
const edge_1 = require("../graph/edge");
const node_value_1 = require("../eval/resolve/node-value");
const config_1 = require("../../config");
const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const resolve_helper_1 = require("../environments/resolve-helper");
const graph_1 = require("../graph/graph");
function isAnyReturnAFunction(def, graph) {
const workingQueue = def.exitPoints.map(d => graph.getVertex(d.nodeId)).filter(assert_1.isNotUndefined);
const seen = new Set();
while (workingQueue.length > 0) {
const current = workingQueue.pop();
if (seen.has(current.id)) {
continue;
}
seen.add(current.id);
if (vertex_1.FunctionDefinitionVertex.is(current)) {
return true;
}
const next = graph.outgoingEdges(current.id) ?? graph_1.NoEdges;
for (const [t, e] of next) {
if (edge_1.DfEdge.includesType(e, edge_1.EdgeType.Returns)) {
const v = graph.getVertex(t);
if (v) {
workingQueue.push(v);
}
}
}
}
return false;
}
/**
* Whether the argument hands over a built-in function (`f(print)`), which has no definition in the graph and
* hence no `function-definition` value to resolve to.
*/
function readsBuiltInFunction(id, graph, ctx) {
for (const [target, edge] of graph.outgoingEdges(id) ?? graph_1.NoEdges) {
if (!edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Reads) || !node_id_1.NodeId.isBuiltIn(target)) {
continue;
}
const defs = resolve_helper_1.Resolve.byNameAndType(node_id_1.NodeId.fromBuiltIn(target), ctx.env.makeCleanEnv(), identifier_1.ReferenceType.Function);
if (defs?.some(d => (0, identifier_1.isReferenceType)(d.type, identifier_1.ReferenceType.BuiltInFunction))) {
return true;
}
}
return false;
}
function inspectCallSitesArgumentsFns(def, graph, ctx, invertedGraph) {
const callSites = invertedGraph?.outgoingEdges(def.id) ?? graph.ingoingEdges(def.id);
for (const [callerId, e] of callSites ?? []) {
if (!edge_1.DfEdge.includesType(e, edge_1.EdgeType.Calls)) {
continue;
}
const caller = graph.getVertex(callerId);
if (!caller || !vertex_1.FunctionCallVertex.is(caller)) {
continue;
}
for (const arg of caller.args) {
if (arg === r_function_call_1.EmptyArgument) {
continue;
}
const value = node_value_1.NodeValue.inGraph.setOf(arg.nodeId, graph, ctx, { resolve: config_1.VariableResolve.Alias });
if (value?.elements.some(e => e.type === 'function-definition') || readsBuiltInFunction(arg.nodeId, graph, ctx)) {
return true;
}
}
}
return false;
}
/**
* Determines whether the function with the given id is a higher-order function, i.e.,
* either takes a function as an argument or (may) returns a function.
* If the return is an identity, e.g., `function(x) x`, this is not considered higher-order,
* if no function is passed as an argument.
* Please note that inspecting higher order functions can be sped up (if queries multiple times) by providing an inverted graph as well!
*/
function isFunctionHigherOrder(id, graph, ctx, invertedGraph) {
const vert = graph.getVertex(id);
if (!vert || !vertex_1.FunctionDefinitionVertex.is(vert)) {
return false;
}
// 1. check whether any of the exit types is a function
if (isAnyReturnAFunction(vert, graph)) {
return true;
}
// 2. check whether any of the callsites passes a function
return inspectCallSitesArgumentsFns(vert, graph, ctx, invertedGraph);
}
//# sourceMappingURL=higher-order-function.js.map