UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

130 lines 5.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processReplacementFunction = processReplacementFunction; const info_1 = require("../../../../../info"); const known_call_handling_1 = require("../known-call-handling"); const log_1 = require("../../../../../../util/log"); const built_in_assignment_1 = require("./built-in-assignment"); const common_1 = require("../common"); const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const logger_1 = require("../../../../../logger"); const graph_1 = require("../../../../../graph/graph"); const edge_1 = require("../../../../../graph/edge"); const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type"); const containers_1 = require("../../../../../../util/containers"); const config_1 = require("../../../../../../config"); const unpack_argument_1 = require("../argument/unpack-argument"); const built_in_access_1 = require("./built-in-access"); function processReplacementFunction(name, /** The last one has to be the value */ args, rootId, data, config) { if (args.length < 2) { logger_1.dataflowLogger.warn(`Replacement ${name.content} has less than 2 arguments, skipping`); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information; } /* we only get here if <-, <<-, ... or whatever is part of the replacement is not overwritten */ (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Replacement ${name.content} with ${JSON.stringify(args)}, processing`); let indices = config.activeIndices; if ((0, config_1.getConfig)().solver.pointerTracking) { indices ??= constructAccessedIndices(name.content, args); } /* we assign the first argument by the last for now and maybe mark as maybe!, we can keep the symbol as we now know we have an assignment */ const res = (0, built_in_assignment_1.processAssignment)(name, [args[0], args[args.length - 1]], rootId, data, { superAssignment: config.assignmentOperator === '<<-', makeMaybe: indices !== undefined ? false : config.makeMaybe, indicesCollection: indices, canBeReplacement: true }); const convertedArgs = config.readIndices ? args.slice(1, -1) : (0, built_in_access_1.symbolArgumentsToStrings)(args.slice(1, -1), 0); /* now, we soft-inject other arguments, so that calls like `x[y] <- 3` are linked correctly */ const { callArgs } = (0, common_1.processAllArguments)({ functionName: (0, info_1.initializeCleanDataflowInformation)(rootId, data), args: convertedArgs, data, functionRootId: rootId, finalGraph: res.graph, forceArgs: config.forceArgs, }); (0, common_1.patchFunctionCall)({ nextGraph: res.graph, data, rootId, name, argumentProcessResult: args.map(a => a === r_function_call_1.EmptyArgument ? undefined : { entryPoint: (0, unpack_argument_1.unpackArgument)(a)?.info.id }) }); const firstArg = (0, unpack_argument_1.unpackArgument)(args[0])?.info.id; if (firstArg) { res.graph.addEdge(firstArg, rootId, edge_1.EdgeType.DefinedBy | edge_1.EdgeType.Reads); } /* a replacement reads all of its call args as well, at least as far as I am aware of */ for (const arg of callArgs) { const ref = (0, graph_1.getReferenceOfArgument)(arg); if (ref !== undefined) { res.graph.addEdge(rootId, ref, edge_1.EdgeType.Reads); } } return res; } /** * Constructs accessed indices of replacement function recursively. * * Example: * ```r * a$b <- 1 * # results in index with lexeme b as identifier * * a[[1]]$b * # results in index with index 1 as identifier with a sub-index with lexeme b as identifier * ``` * * @param operation - Operation of replacement function e.g. '$\<-', '[\<-', '[[\<-' * @param args - Arguments of the replacement function * @returns Accessed indices construct */ function constructAccessedIndices(operation, args) { const { accessedArg, accessArg } = (0, containers_1.getAccessOperands)(args); if (accessedArg === undefined || accessArg?.value === undefined || !isSupportedOperation(operation, accessArg.value)) { return undefined; } const constructIdentifier = getIdentifierBuilder(operation); const leafIndex = { identifier: constructIdentifier(accessArg), nodeId: accessedArg.info.parent ?? '' }; const accessIndices = { indices: [leafIndex], isContainer: false }; // Check for nested access let indicesCollection = undefined; if (accessedArg.value?.type === type_1.RType.Access) { indicesCollection = (0, containers_1.constructNestedAccess)(accessedArg.value, accessIndices, constructIdentifier); } else { // use access node as reference to get complete line in slice indicesCollection = [accessIndices]; } return indicesCollection; } function isSupportedOperation(operation, value) { const isNameBasedAccess = (operation === '$<-' || operation === '@<-') && value.type === type_1.RType.Symbol; const isNumericalIndexBasedAccess = (operation === '[[<-' || operation === '[<-') && value.type === type_1.RType.Number; return isNameBasedAccess || isNumericalIndexBasedAccess; } function getIdentifierBuilder(operation) { if (operation === '$<-' || operation == '@<-') { return (arg) => { return { index: undefined, lexeme: arg.lexeme, }; }; } // [[<- and [<- return (arg) => { return { index: Number(arg.lexeme), }; }; } //# sourceMappingURL=built-in-replacement.js.map