UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

361 lines 15.5 kB
"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 && info?.reads[0] === built_in_1.BuiltIn; 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 }, 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