UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

162 lines 6.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processAccess = processAccess; exports.symbolArgumentsToStrings = symbolArgumentsToStrings; const known_call_handling_1 = require("../known-call-handling"); const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const node_id_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id"); const logger_1 = require("../../../../../logger"); const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type"); const edge_1 = require("../../../../../graph/edge"); const built_in_assignment_1 = require("./built-in-assignment"); const identifier_1 = require("../../../../../environments/identifier"); const reference_to_maybe_1 = require("../../../../../environments/reference-to-maybe"); const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name"); function tableAssignmentProcessor(name, args, rootId, data, outInfo) { outInfo.definitionRootNodes.push(rootId); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: built_in_proc_name_1.BuiltInProcName.TableAssignment }).information; } /** * Processes different types of access operations. * * Example: * ```r * a[i] * a$foo * a[[i]] * a@foo * ``` */ function processAccess(name, args, rootId, data, config) { if (args.length < 1) { logger_1.dataflowLogger.warn(`Access ${identifier_1.Identifier.getName(name.content)} has less than 1 argument, skipping`); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: 'default' }).information; } const head = args[0]; let fnCall; if (head === r_function_call_1.EmptyArgument) { // in this case we may be within a pipe fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: built_in_proc_name_1.BuiltInProcName.Access }); } else if (config.treatIndicesAsString) { fnCall = processStringBasedAccess(args, data, name, rootId, config); } else { /* within an access operation which treats its fields, we redefine the table assignment ':=' as a trigger if this is to be treated as a definition */ // do we have a local definition that needs to be recovered? fnCall = processNumberBasedAccess(data, name, args, rootId, config, head); } const info = fnCall.information; if (head !== r_function_call_1.EmptyArgument) { info.graph.addEdge(name.info.id, fnCall.processedArguments[0]?.entryPoint ?? head.info.id, edge_1.EdgeType.Returns); } /* access always reads all of its indices */ for (const arg of fnCall.processedArguments) { if (arg) { info.graph.addEdge(name.info.id, arg.entryPoint, edge_1.EdgeType.Reads); } /* we include the read edges to the constant arguments as well so that they are included if necessary */ } return { ...info, /* * Keep active nodes in case of assignments etc. * We make them maybe as a kind of hack. * This way when using * ```ts * a[[1]] <- 3 * a[[2]] <- 4 * a * ``` * the read for a will use both accesses as potential definitions and not just the last one! */ unknownReferences: (0, reference_to_maybe_1.makeAllMaybe)(info.unknownReferences, info.graph, info.environment, false), entryPoint: rootId, /** it is, to be precise, the accessed element we want to map to maybe */ in: head === r_function_call_1.EmptyArgument ? info.in : info.in.map(ref => { if (ref.nodeId === head.value?.info.id) { return (0, reference_to_maybe_1.makeReferenceMaybe)(ref, info.graph, info.environment, false); } else { return ref; } }) }; } /** * Processes different types of number-based access operations. * * Example: * ```r * a[i] * a[[i]] * ``` */ function processNumberBasedAccess(data, name, args, rootId, config, head) { const existing = data.environment.current.memory.get(':='); const outInfo = { definitionRootNodes: [] }; const tableAssignId = node_id_1.NodeId.toBuiltIn(':=-table'); data.environment.current.memory.set(':=', [{ type: identifier_1.ReferenceType.BuiltInFunction, definedAt: tableAssignId, cds: undefined, processor: (name, args, rootId, data) => tableAssignmentProcessor(name, args, rootId, data, outInfo), name: ':=', nodeId: tableAssignId }]); const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: built_in_proc_name_1.BuiltInProcName.Access }); /* recover the environment */ if (existing !== undefined) { data.environment.current.memory.set(':=', existing); } if (head.value && outInfo.definitionRootNodes.length > 0) { (0, built_in_assignment_1.markAsAssignment)(fnCall.information, { type: identifier_1.ReferenceType.Variable, name: head.value.lexeme ?? '', nodeId: head.value.info.id, definedAt: rootId, cds: [] }, outInfo.definitionRootNodes, rootId, data); } return fnCall; } /** * Converts symbol arguments to string arguments within the specified range. */ function symbolArgumentsToStrings(args, firstIndexInclusive = 1, lastIndexInclusive = args.length - 1) { const newArgs = args.slice(); // if the argument is a symbol, we convert it to a string for this perspective for (let i = firstIndexInclusive; i <= lastIndexInclusive; i++) { const arg = newArgs[i]; if (arg !== r_function_call_1.EmptyArgument && arg.value?.type === type_1.RType.Symbol) { newArgs[i] = { ...arg, value: { type: type_1.RType.String, info: arg.value.info, lexeme: arg.value.lexeme, location: arg.value.location, content: { quotes: 'none', str: arg.value.lexeme } } }; } } return newArgs; } /** * Processes different types of string-based access operations. * * Example: * ```r * a$foo * a@foo * ``` */ function processStringBasedAccess(args, data, name, rootId, config) { return (0, known_call_handling_1.processKnownFunctionCall)({ name, args: symbolArgumentsToStrings(args), rootId, data, forceArgs: config.forceArgs, origin: built_in_proc_name_1.BuiltInProcName.Access }); } //# sourceMappingURL=built-in-access.js.map