@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
162 lines • 6.9 kB
JavaScript
;
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