@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
363 lines • 15.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataflowGraphBuilder = void 0;
exports.emptyGraph = emptyGraph;
exports.getBuiltInSideEffect = getBuiltInSideEffect;
const objects_1 = require("../../util/objects");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const graph_1 = require("./graph");
const environment_1 = require("../environments/environment");
const vertex_1 = require("./vertex");
const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const built_in_1 = require("../environments/built-in");
const edge_1 = require("./edge");
const default_builtin_config_1 = require("../environments/default-builtin-config");
const flowr_search_executor_1 = require("../../search/flowr-search-executor");
const assert_1 = require("../../util/assert");
function emptyGraph(idMap) {
return new DataflowGraphBuilder(idMap);
}
/**
* This DataflowGraphBuilder extends {@link DataflowGraph} with builder methods to
* easily and compactly add vertices and edges to a dataflow graph. Its usage thus
* simplifies writing tests for dataflow graphs.
*/
class DataflowGraphBuilder extends graph_1.DataflowGraph {
/**
* Adds a **vertex** for a **function definition** (V1).
*
* @param id - AST node ID
* @param subflow - Subflow data graph for the defined function.
* @param exitPoints - Node IDs for exit point vertices.
* @param info - Additional/optional properties.
* @param asRoot - should the vertex be part of the root vertex set of the graph
* (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition)
*/
defineFunction(id, exitPoints, subflow, info, asRoot = true) {
return this.addVertex({
tag: vertex_1.VertexType.FunctionDefinition,
id: (0, node_id_1.normalizeIdToNumberIfPossible)(id),
subflow: {
...subflow,
entryPoint: (0, node_id_1.normalizeIdToNumberIfPossible)(subflow.entryPoint),
graph: new Set([...subflow.graph].map(node_id_1.normalizeIdToNumberIfPossible)),
out: subflow.out.map(o => ({ ...o, nodeId: (0, node_id_1.normalizeIdToNumberIfPossible)(o.nodeId), controlDependencies: o.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })) })),
in: subflow.in.map(o => ({ ...o, nodeId: (0, node_id_1.normalizeIdToNumberIfPossible)(o.nodeId), controlDependencies: o.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })) })),
unknownReferences: subflow.unknownReferences.map(o => ({ ...o, nodeId: (0, node_id_1.normalizeIdToNumberIfPossible)(o.nodeId), controlDependencies: o.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })) }))
},
exitPoints: exitPoints.map(node_id_1.normalizeIdToNumberIfPossible),
cds: info?.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })),
environment: info?.environment
}, asRoot);
}
/**
* Adds a **vertex** for a **function call** (V2).
*
* @param id - AST node ID
* @param name - Function name
* @param args - Function arguments; may be empty
* @param info - Additional/optional properties.
* @param asRoot - should the vertex be part of the root vertex set of the graph
* (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition)
*/
call(id, name, args, info, asRoot = true) {
const onlyBuiltInAuto = info?.reads?.length === 1 && (0, built_in_1.isBuiltIn)(info?.reads[0]);
this.addVertex({
tag: vertex_1.VertexType.FunctionCall,
id: (0, node_id_1.normalizeIdToNumberIfPossible)(id),
name,
args: args.map(a => a === r_function_call_1.EmptyArgument ? r_function_call_1.EmptyArgument : { ...a, nodeId: (0, node_id_1.normalizeIdToNumberIfPossible)(a.nodeId), controlDependencies: undefined }),
environment: (info?.onlyBuiltIn || onlyBuiltInAuto) ? undefined : info?.environment ?? (0, environment_1.initializeCleanEnvironments)(),
cds: info?.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })),
onlyBuiltin: info?.onlyBuiltIn ?? onlyBuiltInAuto ?? false,
origin: info?.origin ?? [(0, default_builtin_config_1.getDefaultProcessor)(name) ?? 'function'],
link: info?.link
}, asRoot);
this.addArgumentLinks(id, args);
if (info?.returns) {
for (const ret of info.returns) {
this.returns(id, ret);
}
}
if (info?.reads) {
for (const call of info.reads) {
this.reads(id, call);
}
}
return this;
}
/** automatically adds argument links if they do not already exist */
addArgumentLinks(id, args) {
for (const arg of args) {
if (arg === r_function_call_1.EmptyArgument) {
continue;
}
if ((0, graph_1.isPositionalArgument)(arg)) {
this.argument(id, arg.nodeId);
if (typeof arg.nodeId === 'string' && arg.nodeId.endsWith('-arg')) {
const withoutSuffix = arg.nodeId.slice(0, -4);
this.reads(arg.nodeId, withoutSuffix);
}
}
else if (!this.hasVertex(arg.nodeId, true)) {
this.use(arg.nodeId, arg.name, { cds: arg.controlDependencies });
this.argument(id, arg.nodeId);
}
}
}
/**
* Adds a **vertex** for a **variable definition** (V4).
*
* @param id - AST node ID
* @param name - Variable name
* @param info - Additional/optional properties.
* @param asRoot - Should the vertex be part of the root vertex set of the graph
* (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition)
*/
defineVariable(id, name, info, asRoot = true) {
this.addVertex({
tag: vertex_1.VertexType.VariableDefinition,
id: (0, node_id_1.normalizeIdToNumberIfPossible)(id),
name,
cds: info?.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })),
}, asRoot);
if (info?.definedBy) {
for (const def of info.definedBy) {
this.definedBy(id, def);
}
}
return this;
}
/**
* Adds a **vertex** for **variable use** (V5). Intended for creating dataflow graphs as part of function tests.
*
* @param id - AST node id
* @param name - Variable name
* @param info - Additional/optional properties; i.e., scope, when, or environment.
* @param asRoot - should the vertex be part of the root vertex set of the graph
* (i.e., be a valid entry point) or is it nested (e.g., as part of a function definition)
*/
use(id, name, info, asRoot = true) {
return this.addVertex((0, objects_1.deepMergeObject)({
tag: vertex_1.VertexType.Use,
id: (0, node_id_1.normalizeIdToNumberIfPossible)(id),
name,
cds: undefined,
environment: undefined
}, {
...info,
cds: info?.cds?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) }))
}), asRoot);
}
/**
* Adds a **vertex** for a **constant value** (V6).
*
* @param id - AST node ID
* @param options - Additional/optional properties;
* @param asRoot - should the vertex be part of the root vertex set of the graph
* (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition)
*/
constant(id, options, asRoot = true) {
return this.addVertex({
tag: vertex_1.VertexType.Value,
id: (0, node_id_1.normalizeIdToNumberIfPossible)(id),
cds: options?.controlDependencies?.map(c => ({ ...c, id: (0, node_id_1.normalizeIdToNumberIfPossible)(c.id) })),
environment: undefined
}, asRoot);
}
edgeHelper(from, to, type) {
if (Array.isArray(to)) {
for (const t of to) {
this.edgeHelper(from, t, type);
}
return this;
}
return this.addEdge((0, node_id_1.normalizeIdToNumberIfPossible)(from), (0, node_id_1.normalizeIdToNumberIfPossible)(to), type);
}
queryHelper(from, to, data, type) {
let fromId;
if ('nodeId' in from) {
fromId = from.nodeId;
}
else {
const result = (0, flowr_search_executor_1.runSearch)(from.query, data);
(0, assert_1.guard)(result.length === 1, `from query result should yield exactly one node, but yielded ${result.length}`);
fromId = result[0].node.info.id;
}
let toIds;
if ('target' in to) {
toIds = to.target;
}
else {
const result = (0, flowr_search_executor_1.runSearch)(to.query, data);
toIds = result.map(r => r.node.info.id);
}
return this.edgeHelper(fromId, toIds, type);
}
/**
* Adds a **read edge**.
*
* @param from - NodeId of the source vertex
* @param to - Either a single or multiple target ids.
* If you pass multiple this will construct a single edge for each of them.
*/
reads(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.Reads);
}
/**
* Adds a **read edge** with a query for the from and/or to vertices.
*
* @param from - Either a node id or a query to find the node id.
* @param to - Either a node id or a query to find the node id.
* @param data - The data to search in i.e. the dataflow graph.
*/
readsQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.Reads);
}
/**
* Adds a **defined-by edge** with from as defined variable, and to
* as a variable/function contributing to its definition.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
definedBy(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.DefinedBy);
}
/**
* Adds a **defined-by edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
definedByQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.DefinedBy);
}
/**
* Adds a **call edge** with from as caller, and to as callee.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
calls(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.Calls);
}
/**
* Adds a **call edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
callsQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.Calls);
}
/**
* Adds a **return edge** with from as function, and to as exit point.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
returns(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.Returns);
}
/**
* Adds a **return edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
returnsQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.Returns);
}
/**
* Adds a **defines-on-call edge** with from as variable, and to as its definition
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
definesOnCall(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.DefinesOnCall);
}
/**
* Adds a **defines-on-call edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
definesOnCallQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.DefinesOnCall);
}
/**
* Adds a **defined-by-on-call edge** with from as definition, and to as variable.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
definedByOnCall(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.DefinedByOnCall);
}
/**
* Adds a **defined-by-on-call edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
definedByOnCallQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.DefinedByOnCall);
}
/**
* Adds an **argument edge** with from as function call, and to as argument.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
argument(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.Argument);
}
/**
* Adds a **argument edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
argumentQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.Argument);
}
/**
* Adds a **non-standard evaluation edge** with from as vertex, and to as vertex.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
nse(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.NonStandardEvaluation);
}
/**
* Adds a **non-standard evaluation edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
nseQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.NonStandardEvaluation);
}
/**
* Adds a **side-effect-on-call edge** with from as vertex, and to as vertex.
*
* @see {@link DataflowGraphBuilder#reads|reads} for parameters.
*/
sideEffectOnCall(from, to) {
return this.edgeHelper(from, to, edge_1.EdgeType.SideEffectOnCall);
}
/**
* Adds a **side-effect-on-call edge** with a query for the from and/or to vertices.
*
* @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters.
*/
sideEffectOnCallQuery(from, to, data) {
return this.queryHelper(from, to, data, edge_1.EdgeType.SideEffectOnCall);
}
/**
* explicitly overwrite the root ids of the graph,
* this is just an easier variant in case you working with a lot of functions this saves you a lot of `false` flags.
*/
overwriteRootIds(ids) {
this.rootVertices = new Set(ids.map(node_id_1.normalizeIdToNumberIfPossible));
return this;
}
}
exports.DataflowGraphBuilder = DataflowGraphBuilder;
function getBuiltInSideEffect(name) {
const got = default_builtin_config_1.DefaultBuiltinConfig.find(e => e.names.includes(name));
if (got?.type !== 'function') {
return undefined;
}
return (got?.config).hasUnknownSideEffects;
}
//# sourceMappingURL=dataflowgraph-builder.js.map