@eagleoutice/flowr 2.1.9 → 2.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +3 -0
  2. package/config.d.ts +21 -0
  3. package/config.js +19 -2
  4. package/dataflow/environments/built-in.d.ts +2 -0
  5. package/dataflow/environments/built-in.js +2 -0
  6. package/dataflow/environments/default-builtin-config.js +3 -2
  7. package/dataflow/environments/define.js +78 -0
  8. package/dataflow/environments/identifier.d.ts +11 -3
  9. package/dataflow/environments/resolve-by-name.d.ts +10 -5
  10. package/dataflow/environments/resolve-by-name.js +103 -5
  11. package/dataflow/graph/vertex.d.ts +56 -1
  12. package/dataflow/graph/vertex.js +4 -0
  13. package/dataflow/internal/process/functions/call/built-in/built-in-access.d.ts +11 -0
  14. package/dataflow/internal/process/functions/call/built-in/built-in-access.js +141 -49
  15. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.d.ts +6 -3
  16. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +35 -8
  17. package/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.js +3 -3
  18. package/dataflow/internal/process/functions/call/built-in/built-in-list.d.ts +15 -0
  19. package/dataflow/internal/process/functions/call/built-in/built-in-list.js +50 -0
  20. package/dataflow/internal/process/functions/call/built-in/built-in-replacement.d.ts +1 -1
  21. package/dataflow/internal/process/functions/call/built-in/built-in-replacement.js +29 -1
  22. package/dataflow/internal/process/functions/call/common.js +15 -1
  23. package/dataflow/internal/process/functions/call/known-call-handling.d.ts +2 -1
  24. package/dataflow/internal/process/functions/call/known-call-handling.js +3 -2
  25. package/documentation/print-interface-wiki.js +6 -1
  26. package/package.json +1 -1
  27. package/util/list-access.d.ts +48 -0
  28. package/util/list-access.js +115 -0
  29. package/util/version.js +1 -1
@@ -11,10 +11,23 @@ const environment_1 = require("../../../../../environments/environment");
11
11
  const built_in_1 = require("../../../../../environments/built-in");
12
12
  const built_in_assignment_1 = require("./built-in-assignment");
13
13
  const identifier_1 = require("../../../../../environments/identifier");
14
+ const vertex_1 = require("../../../../../graph/vertex");
15
+ const list_access_1 = require("../../../../../../util/list-access");
14
16
  function tableAssignmentProcessor(name, args, rootId, data, outInfo) {
15
17
  outInfo.definitionRootNodes.push(rootId);
16
18
  return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
17
19
  }
20
+ /**
21
+ * Processes different types of access operations.
22
+ *
23
+ * Example:
24
+ * ```r
25
+ * a[i]
26
+ * a$foo
27
+ * a[[i]]
28
+ * a@foo
29
+ * ```
30
+ */
18
31
  function processAccess(name, args, rootId, data, config) {
19
32
  if (args.length < 2) {
20
33
  logger_1.dataflowLogger.warn(`Access ${name.content} has less than 2 arguments, skipping`);
@@ -26,47 +39,10 @@ function processAccess(name, args, rootId, data, config) {
26
39
  if (!config.treatIndicesAsString) {
27
40
  /* 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 */
28
41
  // do we have a local definition that needs to be recovered?
29
- const existing = data.environment.current.memory.get(':=');
30
- const outInfo = { definitionRootNodes: [] };
31
- data.environment.current.memory.set(':=', [{
32
- type: identifier_1.ReferenceType.BuiltInFunction,
33
- definedAt: built_in_1.BuiltIn,
34
- controlDependencies: undefined,
35
- processor: (name, args, rootId, data) => tableAssignmentProcessor(name, args, rootId, data, outInfo),
36
- name: ':=',
37
- nodeId: built_in_1.BuiltIn
38
- }]);
39
- fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs });
40
- /* recover the environment */
41
- if (existing !== undefined) {
42
- data.environment.current.memory.set(':=', existing);
43
- }
44
- if (head.value && outInfo.definitionRootNodes.length > 0) {
45
- (0, built_in_assignment_1.markAsAssignment)(fnCall.information, { type: identifier_1.ReferenceType.Variable, name: head.value.lexeme ?? '', nodeId: head.value.info.id, definedAt: rootId, controlDependencies: [] }, outInfo.definitionRootNodes, rootId);
46
- }
42
+ fnCall = processNumberBasedAccess(data, name, args, rootId, config, head);
47
43
  }
48
44
  else {
49
- const newArgs = [...args];
50
- // if the argument is a symbol, we convert it to a string for this perspective
51
- for (let i = 1; i < newArgs.length; i++) {
52
- const arg = newArgs[i];
53
- if (arg !== r_function_call_1.EmptyArgument && arg.value?.type === type_1.RType.Symbol) {
54
- newArgs[i] = {
55
- ...arg,
56
- value: {
57
- type: type_1.RType.String,
58
- info: arg.value.info,
59
- lexeme: arg.value.lexeme,
60
- location: arg.value.location,
61
- content: {
62
- quotes: 'none',
63
- str: arg.value.lexeme
64
- }
65
- }
66
- };
67
- }
68
- }
69
- fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: newArgs, rootId, data, forceArgs: config.forceArgs });
45
+ fnCall = processStringBasedAccess(args, data, name, rootId, config);
70
46
  }
71
47
  const info = fnCall.information;
72
48
  info.graph.addEdge(name.info.id, fnCall.processedArguments[0]?.entryPoint ?? head.info.id, edge_1.EdgeType.Returns);
@@ -80,16 +56,16 @@ function processAccess(name, args, rootId, data, config) {
80
56
  return {
81
57
  ...info,
82
58
  /*
83
- * Keep active nodes in case of assignments etc.
84
- * We make them maybe as a kind of hack.
85
- * This way when using
86
- * ```ts
87
- * a[[1]] <- 3
88
- * a[[2]] <- 4
89
- * a
90
- * ```
91
- * the read for a will use both accesses as potential definitions and not just the last one!
92
- */
59
+ * Keep active nodes in case of assignments etc.
60
+ * We make them maybe as a kind of hack.
61
+ * This way when using
62
+ * ```ts
63
+ * a[[1]] <- 3
64
+ * a[[2]] <- 4
65
+ * a
66
+ * ```
67
+ * the read for a will use both accesses as potential definitions and not just the last one!
68
+ */
93
69
  unknownReferences: (0, environment_1.makeAllMaybe)(info.unknownReferences, info.graph, info.environment, false),
94
70
  entryPoint: rootId,
95
71
  /** it is, to be precise, the accessed element we want to map to maybe */
@@ -103,4 +79,120 @@ function processAccess(name, args, rootId, data, config) {
103
79
  })
104
80
  };
105
81
  }
82
+ /**
83
+ * Processes different types of number-based access operations.
84
+ *
85
+ * Example:
86
+ * ```r
87
+ * a[i]
88
+ * a[[i]]
89
+ * ```
90
+ */
91
+ function processNumberBasedAccess(data, name, args, rootId, config, head) {
92
+ const existing = data.environment.current.memory.get(':=');
93
+ const outInfo = { definitionRootNodes: [] };
94
+ data.environment.current.memory.set(':=', [{
95
+ type: identifier_1.ReferenceType.BuiltInFunction,
96
+ definedAt: built_in_1.BuiltIn,
97
+ controlDependencies: undefined,
98
+ processor: (name, args, rootId, data) => tableAssignmentProcessor(name, args, rootId, data, outInfo),
99
+ name: ':=',
100
+ nodeId: built_in_1.BuiltIn,
101
+ }]);
102
+ const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs });
103
+ /* recover the environment */
104
+ if (existing !== undefined) {
105
+ data.environment.current.memory.set(':=', existing);
106
+ }
107
+ if (head.value && outInfo.definitionRootNodes.length > 0) {
108
+ (0, built_in_assignment_1.markAsAssignment)(fnCall.information, { type: identifier_1.ReferenceType.Variable, name: head.value.lexeme ?? '', nodeId: head.value.info.id, definedAt: rootId, controlDependencies: [] }, outInfo.definitionRootNodes, rootId);
109
+ }
110
+ return fnCall;
111
+ }
112
+ /**
113
+ * Processes different types of string-based access operations.
114
+ *
115
+ * Example:
116
+ * ```r
117
+ * a$foo
118
+ * a@foo
119
+ * ```
120
+ */
121
+ function processStringBasedAccess(args, data, name, rootId, config) {
122
+ const newArgs = [...args];
123
+ // if the argument is a symbol, we convert it to a string for this perspective
124
+ for (let i = 1; i < newArgs.length; i++) {
125
+ const arg = newArgs[i];
126
+ if (arg !== r_function_call_1.EmptyArgument && arg.value?.type === type_1.RType.Symbol) {
127
+ newArgs[i] = {
128
+ ...arg,
129
+ value: {
130
+ type: type_1.RType.String,
131
+ info: arg.value.info,
132
+ lexeme: arg.value.lexeme,
133
+ location: arg.value.location,
134
+ content: {
135
+ quotes: 'none',
136
+ str: arg.value.lexeme
137
+ }
138
+ }
139
+ };
140
+ }
141
+ }
142
+ const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: newArgs, rootId, data, forceArgs: config.forceArgs });
143
+ // Resolve access on the way up the fold
144
+ const nonEmptyArgs = newArgs.filter(arg => arg !== r_function_call_1.EmptyArgument);
145
+ const accessedArg = nonEmptyArgs.find(arg => arg.info.role === "accessed" /* RoleInParent.Accessed */);
146
+ const accessArg = nonEmptyArgs.find(arg => arg.info.role === "index-access" /* RoleInParent.IndexAccess */);
147
+ if (accessedArg === undefined || accessArg === undefined) {
148
+ return fnCall;
149
+ }
150
+ let accessedIndicesCollection;
151
+ // If the accessedArg is a symbol, it's either a simple access or the base case of a nested access
152
+ if (accessedArg.value?.type === type_1.RType.Symbol) {
153
+ accessedIndicesCollection = (0, list_access_1.resolveSingleIndex)(accessedArg, accessArg, data.environment);
154
+ }
155
+ else {
156
+ // Higher access call
157
+ const underlyingAccessId = accessedArg.value?.info.id ?? -1;
158
+ const vertex = fnCall.information.graph.getVertex(underlyingAccessId);
159
+ const subIndices = vertex?.indicesCollection
160
+ ?.flatMap(indices => indices.indices)
161
+ ?.flatMap(index => index?.subIndices ?? []);
162
+ if (subIndices) {
163
+ accessedIndicesCollection = (0, list_access_1.filterIndices)(subIndices, accessArg);
164
+ }
165
+ }
166
+ // Add indices to vertex afterward
167
+ if (accessedIndicesCollection) {
168
+ const vertex = fnCall.information.graph.getVertex(rootId);
169
+ if (vertex) {
170
+ vertex.indicesCollection = accessedIndicesCollection;
171
+ }
172
+ // When access has no access as parent, it's the top most
173
+ const rootNode = data.completeAst.idMap.get(rootId);
174
+ const parentNode = data.completeAst.idMap.get(rootNode?.info.parent ?? -1);
175
+ if (parentNode?.type !== type_1.RType.Access) {
176
+ // Only reference indices in top most access
177
+ referenceIndices(accessedIndicesCollection, fnCall, name.info.id);
178
+ }
179
+ }
180
+ return fnCall;
181
+ }
182
+ /**
183
+ * Creates edges of type {@link EdgeType.Reads} to the accessed Indices and their sub-indices starting from
184
+ * the node with {@link parentNodeId}.
185
+ *
186
+ * @param accessedIndicesCollection - All indices that were accessed by the access operation
187
+ * @param fnCall - The {@link ProcessKnownFunctionCallResult} of the access operation
188
+ * @param parentNodeId - {@link NodeId} of the parent from which the edge starts
189
+ */
190
+ function referenceIndices(accessedIndicesCollection, fnCall, parentNodeId) {
191
+ const accessedIndices = accessedIndicesCollection?.flatMap(indices => indices.indices);
192
+ for (const accessedIndex of accessedIndices ?? []) {
193
+ fnCall.information.graph.addEdge(parentNodeId, accessedIndex.nodeId, edge_1.EdgeType.Reads);
194
+ const accessedSubIndices = (0, vertex_1.isParentContainerIndex)(accessedIndex) ? accessedIndex.subIndices : undefined;
195
+ referenceIndices(accessedSubIndices, fnCall, accessedIndex.nodeId);
196
+ }
197
+ }
106
198
  //# sourceMappingURL=built-in-access.js.map
@@ -4,19 +4,22 @@ import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/
4
4
  import type { RNode } from '../../../../../../r-bridge/lang-4.x/ast/model/model';
5
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
6
6
  import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
7
- import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
8
- import type { IdentifierDefinition } from '../../../../../environments/identifier';
7
+ import { type NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
8
+ import type { InGraphIdentifierDefinition } from '../../../../../environments/identifier';
9
+ import type { ContainerIndicesCollection } from '../../../../../graph/vertex';
9
10
  import type { ForceArguments } from '../common';
10
11
  import type { REnvironmentInformation } from '../../../../../environments/environment';
11
12
  import type { DataflowGraph } from '../../../../../graph/graph';
12
13
  export interface AssignmentConfiguration extends ForceArguments {
13
14
  readonly superAssignment?: boolean;
14
15
  readonly swapSourceAndTarget?: boolean;
16
+ /** Make maybe if assigned to symbol */
15
17
  readonly makeMaybe?: boolean;
16
18
  readonly quoteSource?: boolean;
17
19
  readonly canBeReplacement?: boolean;
18
20
  /** is the target a variable pointing at the actual name? */
19
21
  readonly targetVariable?: boolean;
22
+ readonly indicesCollection?: ContainerIndicesCollection;
20
23
  }
21
24
  /**
22
25
  * Processes an assignment, i.e., `<target> <- <source>`.
@@ -45,4 +48,4 @@ export interface AssignmentToSymbolParameters<OtherInfo> extends AssignmentConfi
45
48
  export declare function markAsAssignment(information: {
46
49
  environment: REnvironmentInformation;
47
50
  graph: DataflowGraph;
48
- }, nodeToDefine: IdentifierDefinition, sourceIds: readonly NodeId[], rootIdOfAssignment: NodeId, quoteSource?: boolean, superAssignment?: boolean): void;
51
+ }, nodeToDefine: InGraphIdentifierDefinition, sourceIds: readonly NodeId[], rootIdOfAssignment: NodeId, config?: AssignmentConfiguration | undefined): void;
@@ -16,6 +16,8 @@ const retriever_1 = require("../../../../../../r-bridge/retriever");
16
16
  const vertex_1 = require("../../../../../graph/vertex");
17
17
  const define_1 = require("../../../../../environments/define");
18
18
  const edge_1 = require("../../../../../graph/edge");
19
+ const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
20
+ const list_access_1 = require("../../../../../../util/list-access");
19
21
  function toReplacementSymbol(target, prefix, superAssignment) {
20
22
  return {
21
23
  type: type_1.RType.Symbol,
@@ -119,12 +121,16 @@ function extractSourceAndTarget(args, name) {
119
121
  (0, assert_1.guard)(target !== undefined, () => `Assignment ${name.content} has no target, impossible!`);
120
122
  return { source, target };
121
123
  }
122
- function produceWrittenNodes(rootId, target, referenceType, data, makeMaybe) {
124
+ /**
125
+ * Promotes the ingoing/unknown references of target (an assignment) to definitions
126
+ */
127
+ function produceWrittenNodes(rootId, target, referenceType, data, makeMaybe, value) {
123
128
  return [...target.in, ...target.unknownReferences].map(ref => ({
124
129
  ...ref,
125
130
  type: referenceType,
126
131
  definedAt: rootId,
127
- controlDependencies: data.controlDependencies ?? (makeMaybe ? [] : undefined)
132
+ controlDependencies: data.controlDependencies ?? (makeMaybe ? [] : undefined),
133
+ value: value
128
134
  }));
129
135
  }
130
136
  function processAssignmentToString(target, args, name, rootId, data, config, source) {
@@ -181,10 +187,29 @@ function checkTargetReferenceType(source, sourceInfo) {
181
187
  * @param quoteSource - whether to quote the source (i.e., define `x` without a direct reference to `v`)
182
188
  * @param superAssignment - whether this is a super assignment (i.e., `<<-`)
183
189
  */
184
- function markAsAssignment(information, nodeToDefine, sourceIds, rootIdOfAssignment, quoteSource, superAssignment) {
185
- information.environment = (0, define_1.define)(nodeToDefine, superAssignment, information.environment);
190
+ function markAsAssignment(information, nodeToDefine, sourceIds, rootIdOfAssignment, config) {
191
+ let indicesCollection = undefined;
192
+ if (sourceIds.length === 1) {
193
+ // support for tracking indices
194
+ // Indices were defined for the vertex e.g. a <- list(c = 1) or a$b <- list(c = 1)
195
+ indicesCollection = information.graph.getVertex(sourceIds[0])?.indicesCollection;
196
+ }
197
+ // Indices defined by replacement operation e.g. $<-
198
+ if (config?.indicesCollection !== undefined) {
199
+ // If there were indices stored in the vertex, then a container was defined
200
+ // and assigned to the index of another container e.g. a$b <- list(c = 1)
201
+ if (indicesCollection) {
202
+ indicesCollection = (0, list_access_1.addSubIndicesToLeafIndices)(config.indicesCollection, indicesCollection);
203
+ }
204
+ else {
205
+ // No indices were defined for the vertex e.g. a$b <- 2
206
+ indicesCollection = config.indicesCollection;
207
+ }
208
+ }
209
+ nodeToDefine.indicesCollection ??= indicesCollection;
210
+ information.environment = (0, define_1.define)(nodeToDefine, config?.superAssignment, information.environment);
186
211
  information.graph.setDefinitionOfVertex(nodeToDefine);
187
- if (!quoteSource) {
212
+ if (!config?.quoteSource) {
188
213
  for (const sourceId of sourceIds) {
189
214
  information.graph.addEdge(nodeToDefine, sourceId, edge_1.EdgeType.DefinedBy);
190
215
  }
@@ -202,9 +227,11 @@ function markAsAssignment(information, nodeToDefine, sourceIds, rootIdOfAssignme
202
227
  /**
203
228
  * Helper function whenever it is known that the _target_ of an assignment is a (single) symbol (i.e. `x <- ...`, but not `names(x) <- ...`).
204
229
  */
205
- function processAssignmentToSymbol({ nameOfAssignmentFunction, source, args: [targetArg, sourceArg], target, rootId, data, information, superAssignment, makeMaybe, quoteSource }) {
230
+ function processAssignmentToSymbol(config) {
231
+ const { nameOfAssignmentFunction, source, args: [targetArg, sourceArg], target, rootId, data, information, makeMaybe, quoteSource } = config;
206
232
  const referenceType = checkTargetReferenceType(source, sourceArg);
207
- const writeNodes = produceWrittenNodes(rootId, targetArg, referenceType, data, makeMaybe ?? false);
233
+ const aliases = (0, resolve_by_name_1.getAliases)([source.info.id], information.graph, information.environment);
234
+ const writeNodes = produceWrittenNodes(rootId, targetArg, referenceType, data, makeMaybe ?? false, aliases);
208
235
  if (writeNodes.length !== 1 && log_1.log.settings.minLevel <= 4 /* LogLevel.Warn */) {
209
236
  log_1.log.warn(`Unexpected write number in assignment: ${JSON.stringify(writeNodes)}`);
210
237
  }
@@ -217,7 +244,7 @@ function processAssignmentToSymbol({ nameOfAssignmentFunction, source, args: [ta
217
244
  information.environment = (0, overwrite_1.overwriteEnvironment)(targetArg.environment, sourceArg.environment);
218
245
  // install assigned variables in environment
219
246
  for (const write of writeNodes) {
220
- markAsAssignment(information, write, [source.info.id], rootId, quoteSource, superAssignment);
247
+ markAsAssignment(information, write, [source.info.id], rootId, config);
221
248
  }
222
249
  information.graph.addEdge(rootId, targetArg.entryPoint, edge_1.EdgeType.Returns);
223
250
  if (quoteSource) {
@@ -33,9 +33,9 @@ function processIfThenElse(name, args, rootId, data) {
33
33
  let then;
34
34
  let makeThenMaybe = false;
35
35
  // we should defer this to the abstract interpretation
36
- const definitions = (0, resolve_by_name_1.resolveToConstants)(condArg?.lexeme, data.environment);
37
- const conditionIsAlwaysFalse = definitions?.every(d => d.value === false) ?? false;
38
- const conditionIsAlwaysTrue = definitions?.every(d => d.value === true) ?? false;
36
+ const values = (0, resolve_by_name_1.resolveValueOfVariable)(condArg?.lexeme, data.environment, cond.graph);
37
+ const conditionIsAlwaysFalse = values?.every(d => d === false) ?? false;
38
+ const conditionIsAlwaysTrue = values?.every(d => d === true) ?? false;
39
39
  if (!conditionIsAlwaysFalse) {
40
40
  then = (0, processor_1.processDataflowFor)(thenArg, data);
41
41
  if (then.entryPoint) {
@@ -0,0 +1,15 @@
1
+ import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
2
+ import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
3
+ import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
4
+ import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
5
+ import type { DataflowInformation } from '../../../../../info';
6
+ import type { DataflowProcessorInformation } from '../../../../../processor';
7
+ /**
8
+ * Process a list call.
9
+ *
10
+ * Example:
11
+ * ```r
12
+ * list(a = 1, b = 2)
13
+ * ```
14
+ */
15
+ export declare function processList<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>): DataflowInformation;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processList = processList;
4
+ const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
5
+ const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
6
+ const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
7
+ const known_call_handling_1 = require("../known-call-handling");
8
+ const config_1 = require("../../../../../../config");
9
+ /**
10
+ * Process a list call.
11
+ *
12
+ * Example:
13
+ * ```r
14
+ * list(a = 1, b = 2)
15
+ * ```
16
+ */
17
+ function processList(name, args, rootId, data) {
18
+ if (!(0, config_1.getConfig)().solver.pointerTracking) {
19
+ return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
20
+ }
21
+ const namedArguments = [];
22
+ for (const arg of args) {
23
+ // Skip non named arguments
24
+ if (arg === r_function_call_1.EmptyArgument || arg.type !== type_1.RType.Argument || arg.name === undefined) {
25
+ continue;
26
+ }
27
+ let newIndex = {
28
+ lexeme: arg.name.content,
29
+ nodeId: arg.info.id,
30
+ };
31
+ // Check whether argument value is non-primitive
32
+ if (arg.value?.type === type_1.RType.Symbol) {
33
+ const defs = (0, resolve_by_name_1.resolveByName)(arg.value.lexeme, data.environment);
34
+ const indices = defs?.flatMap(index => index.indicesCollection ?? []);
35
+ if (indices) {
36
+ newIndex = {
37
+ ...newIndex,
38
+ subIndices: indices,
39
+ };
40
+ }
41
+ }
42
+ namedArguments.push(newIndex);
43
+ }
44
+ const indices = {
45
+ indices: namedArguments,
46
+ isContainer: true,
47
+ };
48
+ return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }, [indices]).information;
49
+ }
50
+ //# sourceMappingURL=built-in-list.js.map
@@ -3,7 +3,7 @@ import type { DataflowInformation } from '../../../../../info';
3
3
  import type { ForceArguments } from '../common';
4
4
  import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
5
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
6
- import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
6
+ import { type RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
7
7
  import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
8
8
  export declare function processReplacementFunction<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>,
9
9
  /** The last one has to be the value */
@@ -7,11 +7,14 @@ const log_1 = require("../../../../../../util/log");
7
7
  const built_in_assignment_1 = require("./built-in-assignment");
8
8
  const common_1 = require("../common");
9
9
  const assert_1 = require("../../../../../../util/assert");
10
+ const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
10
11
  const logger_1 = require("../../../../../logger");
11
12
  const vertex_1 = require("../../../../../graph/vertex");
12
13
  const graph_1 = require("../../../../../graph/graph");
13
14
  const edge_1 = require("../../../../../graph/edge");
14
15
  const dfg_1 = require("../../../../../../util/mermaid/dfg");
16
+ const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
17
+ const list_access_1 = require("../../../../../../util/list-access");
15
18
  function processReplacementFunction(name,
16
19
  /** The last one has to be the value */
17
20
  args, rootId, data, config) {
@@ -21,8 +24,33 @@ args, rootId, data, config) {
21
24
  }
22
25
  /* we only get here if <-, <<-, ... or whatever is part of the replacement is not overwritten */
23
26
  (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Replacement ${name.content} with ${JSON.stringify(args)}, processing`);
27
+ let indices = undefined;
28
+ if (name.content === '$<-') {
29
+ const nonEmptyArgs = args.filter(arg => arg !== r_function_call_1.EmptyArgument);
30
+ const accessedArg = nonEmptyArgs.find(arg => arg.info.role === "accessed" /* RoleInParent.Accessed */);
31
+ const accessArg = nonEmptyArgs.find(arg => arg.info.role === "index-access" /* RoleInParent.IndexAccess */);
32
+ if (accessArg !== undefined && accessedArg != undefined) {
33
+ const leafIndex = { lexeme: accessArg.lexeme, nodeId: accessedArg.info.parent ?? '' };
34
+ const accessIndices = {
35
+ indices: [leafIndex],
36
+ isContainer: false
37
+ };
38
+ // Check for nested access
39
+ if (accessedArg.value?.type === type_1.RType.Access) {
40
+ indices = (0, list_access_1.constructNestedAccess)(accessedArg.value, accessIndices);
41
+ }
42
+ else {
43
+ // use access node as reference to get complete line in slice
44
+ indices = [accessIndices];
45
+ }
46
+ }
47
+ }
24
48
  /* 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 */
25
- const res = (0, built_in_assignment_1.processAssignment)(name, [args[0], args[args.length - 1]], rootId, data, { superAssignment: config.assignmentOperator === '<<-', makeMaybe: config.makeMaybe });
49
+ const res = (0, built_in_assignment_1.processAssignment)(name, [args[0], args[args.length - 1]], rootId, data, {
50
+ superAssignment: config.assignmentOperator === '<<-',
51
+ makeMaybe: indices !== undefined ? false : config.makeMaybe,
52
+ indicesCollection: indices
53
+ });
26
54
  /* now, we soft-inject other arguments, so that calls like `x[y] <- 3` are linked correctly */
27
55
  const { callArgs } = (0, common_1.processAllArguments)({
28
56
  functionName: (0, info_1.initializeCleanDataflowInformation)(rootId, data),
@@ -77,7 +77,12 @@ function processAllArguments({ functionName, args, data, finalGraph, functionRoo
77
77
  if ((0, info_1.happensInEveryBranch)(resolved.controlDependencies)) {
78
78
  assumeItMayHaveAHigherTarget = false;
79
79
  }
80
- finalGraph.addEdge(ingoing.nodeId, resolved.nodeId, edge_1.EdgeType.Reads);
80
+ // When only a single index is referenced, we don't need to reference the whole object
81
+ const resolvedInGraphDef = resolved;
82
+ const isContainer = checkForContainer(resolvedInGraphDef?.indicesCollection);
83
+ if (isContainer || isContainer === undefined) {
84
+ finalGraph.addEdge(ingoing.nodeId, resolved.nodeId, edge_1.EdgeType.Reads);
85
+ }
81
86
  }
82
87
  if (assumeItMayHaveAHigherTarget) {
83
88
  remainingReadInArgs.push(ingoing);
@@ -113,4 +118,13 @@ function patchFunctionCall({ nextGraph, rootId, name, data, argumentProcessResul
113
118
  }
114
119
  }
115
120
  }
121
+ /**
122
+ * Check whether passed {@link indices} are containers or whether their sub-indices are containers.
123
+ */
124
+ function checkForContainer(indices) {
125
+ return indices?.every((indices) => {
126
+ const areSubIndicesContainers = indices.indices.every(index => 'subIndices' in index && checkForContainer(index.subIndices));
127
+ return indices.isContainer || areSubIndicesContainers;
128
+ });
129
+ }
116
130
  //# sourceMappingURL=common.js.map
@@ -8,6 +8,7 @@ import type { NodeId } from '../../../../../r-bridge/lang-4.x/ast/model/processi
8
8
  import type { RNode } from '../../../../../r-bridge/lang-4.x/ast/model/model';
9
9
  import type { IdentifierReference } from '../../../../environments/identifier';
10
10
  import { DataflowGraph } from '../../../../graph/graph';
11
+ import type { ContainerIndicesCollection } from '../../../../graph/vertex';
11
12
  export interface ProcessKnownFunctionCallInput<OtherInfo> extends ForceArguments {
12
13
  readonly name: RSymbol<OtherInfo & ParentInformation>;
13
14
  readonly args: readonly (RNode<OtherInfo & ParentInformation> | RFunctionArgument<OtherInfo & ParentInformation>)[];
@@ -28,4 +29,4 @@ export interface ProcessKnownFunctionCallResult {
28
29
  readonly fnRef: IdentifierReference;
29
30
  }
30
31
  export declare function markNonStandardEvaluationEdges(markAsNSE: readonly number[], callArgs: readonly (DataflowInformation | undefined)[], finalGraph: DataflowGraph, rootId: NodeId): void;
31
- export declare function processKnownFunctionCall<OtherInfo>({ name, args, rootId, data, reverseOrder, markAsNSE, forceArgs, patchData, hasUnknownSideEffect }: ProcessKnownFunctionCallInput<OtherInfo>): ProcessKnownFunctionCallResult;
32
+ export declare function processKnownFunctionCall<OtherInfo>({ name, args, rootId, data, reverseOrder, markAsNSE, forceArgs, patchData, hasUnknownSideEffect }: ProcessKnownFunctionCallInput<OtherInfo>, indicesCollection?: ContainerIndicesCollection): ProcessKnownFunctionCallResult;
@@ -23,7 +23,7 @@ function markNonStandardEvaluationEdges(markAsNSE, callArgs, finalGraph, rootId)
23
23
  }
24
24
  }
25
25
  }
26
- function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = false, markAsNSE = undefined, forceArgs, patchData = d => d, hasUnknownSideEffect }) {
26
+ function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = false, markAsNSE = undefined, forceArgs, patchData = d => d, hasUnknownSideEffect }, indicesCollection = undefined) {
27
27
  const functionName = (0, processor_1.processDataflowFor)(name, data);
28
28
  const finalGraph = new graph_1.DataflowGraph(data.completeAst.idMap);
29
29
  const functionCallName = name.content;
@@ -41,7 +41,8 @@ function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = fal
41
41
  /* will be overwritten accordingly */
42
42
  onlyBuiltin: false,
43
43
  controlDependencies: data.controlDependencies,
44
- args: reverseOrder ? [...callArgs].reverse() : callArgs
44
+ args: reverseOrder ? [...callArgs].reverse() : callArgs,
45
+ indicesCollection: indicesCollection,
45
46
  });
46
47
  if (hasUnknownSideEffect) {
47
48
  finalGraph.markIdForUnknownSideEffects(rootId);
@@ -171,6 +171,7 @@ The following summarizes the configuration options:
171
171
  - \`semantics\`: allows to configure the way _flowR_ handles R, although we currently only support \`semantics/environment/overwriteBuiltIns\`.
172
172
  You may use this to overwrite _flowR_'s handling of built-in function and even completely clear the preset definitions shipped with flowR.
173
173
  See [Configure BuiltIn Semantics](#configure-builtin-semantics) for more information.
174
+ - \`solver\`: allows to configure how _flowR_ resolves variables and their values (currently we support: ${Object.values(config_1.VariableResolve).map(v => `\`${v}\``).join(', ')}), as well as if pointer analysis should be active.
174
175
 
175
176
  So you can configure _flowR_ by adding a file like the following:
176
177
 
@@ -189,6 +190,10 @@ ${(0, doc_code_1.codeBlock)('json', JSON.stringify({
189
190
  ]
190
191
  }
191
192
  }
193
+ },
194
+ solver: {
195
+ variables: config_1.VariableResolve.Alias,
196
+ pointerTracking: true
192
197
  }
193
198
  }, null, 2))}
194
199
 
@@ -233,7 +238,7 @@ _flowR_ can be used as a [module](${doc_files_1.FlowrNpmRef}) and offers several
233
238
  ### Using the \`${shell_1.RShell.name}\` to Interact with R
234
239
 
235
240
  The \`${shell_1.RShell.name}\` class allows to interface with the \`R\`&nbsp;ecosystem installed on the host system.
236
- For now there are no (real) alternatives, although we plan on providing more flexible drop-in replacements.
241
+ For now, there are no (real) alternatives, although we plan on providing more flexible drop-in replacements.
237
242
 
238
243
  > [!IMPORTANT]
239
244
  > Each \`${shell_1.RShell.name}\` controls a new instance of the R&nbsp;interpreter, make sure to call \`${shell_1.RShell.name}::${shell.close.name}()\` when you’re done.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
4
4
  "description": "Static Dataflow Analyzer and Program Slicer for the R Programming Language",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "repository": {
@@ -0,0 +1,48 @@
1
+ import type { REnvironmentInformation } from '../dataflow/environments/environment';
2
+ import type { ContainerIndices, ContainerIndicesCollection } from '../dataflow/graph/vertex';
3
+ import type { RAccess } from '../r-bridge/lang-4.x/ast/model/nodes/r-access';
4
+ import type { ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate';
5
+ /**
6
+ * Resolves {@link accessedArg} in the {@link environment} and filters its indices according to {@link accessArg}.
7
+ *
8
+ * @param accessedArg - The argument to resolve
9
+ * @param accessArg - The argument which is used to filter the indices
10
+ * @param environment - The environment in which {@link accessedArg} is resolved
11
+ * @returns The filtered {@link ContainerIndicesCollection} of the resolved {@link accessedArg}
12
+ */
13
+ export declare function resolveSingleIndex(accessedArg: {
14
+ lexeme: string;
15
+ }, accessArg: {
16
+ lexeme: string;
17
+ }, environment: REnvironmentInformation): ContainerIndicesCollection;
18
+ /**
19
+ * Filters the single indices of the {@link indicesCollection} according to the lexeme of the {@link accessArg}.
20
+ *
21
+ * @param indicesCollection - The {@link ContainerIndicesCollection} to filter
22
+ * @param accessArg - The argument which is used to filter {@link indicesCollection}
23
+ * @returns The filtered copy of {@link indicesCollection}
24
+ */
25
+ export declare function filterIndices(indicesCollection: ContainerIndicesCollection, accessArg: {
26
+ lexeme: string;
27
+ }): ContainerIndicesCollection;
28
+ /**
29
+ * Constructs the definition of a nested access.
30
+ *
31
+ * Example:
32
+ * ```r
33
+ * person$credentials$username
34
+ * ```
35
+ * would result in a list with the index `credentials`, which has the subIndex `username`.
36
+ *
37
+ * @param accessedArg - The top level argument that is accessed
38
+ * @param leafIndices - The index at the end of the nested access i.e. `c` in `a$b$c`.
39
+ * @returns The constructed nested access
40
+ */
41
+ export declare function constructNestedAccess<OtherInfo>(accessedArg: RAccess<OtherInfo & ParentInformation>, leafIndices: ContainerIndices): ContainerIndices[];
42
+ /**
43
+ * Adds the passed list of {@link leafSubIndices} to the leaf (sub-)indices of {@link indicesCollection}.
44
+ *
45
+ * @param indicesCollection - Indices where to add the sub indices.
46
+ * @param leafSubIndices - Indices that are added to the leaf indices.
47
+ */
48
+ export declare function addSubIndicesToLeafIndices(indicesCollection: ContainerIndices[], leafSubIndices: ContainerIndices[]): ContainerIndices[];