@eagleoutice/flowr 2.0.14 → 2.0.15

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 (34) hide show
  1. package/benchmark/slicer.d.ts +2 -1
  2. package/benchmark/slicer.js +3 -2
  3. package/cli/common/options.js +1 -0
  4. package/cli/flowr.js +1 -2
  5. package/cli/repl/commands/commands.js +1 -1
  6. package/cli/repl/core.js +1 -1
  7. package/cli/repl/server/connection.js +6 -1
  8. package/cli/repl/server/messages/slice.d.ts +4 -0
  9. package/cli/repl/server/messages/slice.js +1 -1
  10. package/cli/slicer-app.d.ts +1 -0
  11. package/cli/slicer-app.js +5 -1
  12. package/core/steps/all/static-slicing/10-reconstruct.d.ts +1 -1
  13. package/core/steps/pipeline/default-pipelines.d.ts +1 -1
  14. package/core/steps/pipeline/default-pipelines.js +2 -2
  15. package/dataflow/graph/diff.d.ts +4 -3
  16. package/dataflow/graph/diff.js +58 -26
  17. package/dataflow/internal/linker.d.ts +6 -1
  18. package/dataflow/internal/linker.js +30 -21
  19. package/dataflow/internal/process/functions/process-argument.js +28 -0
  20. package/package.json +1 -1
  21. package/r-bridge/lang-4.x/ast/parser/json/format.js +5 -5
  22. package/reconstruct/auto-select/auto-select-defaults.d.ts +21 -0
  23. package/reconstruct/auto-select/auto-select-defaults.js +23 -0
  24. package/reconstruct/auto-select/magic-comments.d.ts +17 -0
  25. package/reconstruct/auto-select/magic-comments.js +86 -0
  26. package/reconstruct/reconstruct.d.ts +3 -7
  27. package/reconstruct/reconstruct.js +15 -22
  28. package/slicing/static/static-slicer.d.ts +6 -1
  29. package/slicing/static/static-slicer.js +10 -5
  30. package/util/diff.d.ts +12 -0
  31. package/util/diff.js +6 -3
  32. package/util/mermaid/dfg.d.ts +3 -0
  33. package/util/mermaid/dfg.js +3 -0
  34. package/util/version.js +1 -1
@@ -11,6 +11,7 @@ import type { NormalizedAst } from '../r-bridge/lang-4.x/ast/model/processing/de
11
11
  import type { SlicingCriteria } from '../slicing/criterion/parse';
12
12
  import type { RParseRequestFromFile, RParseRequestFromText } from '../r-bridge/retriever';
13
13
  import type { SlicingCriteriaFilter } from '../slicing/criterion/collect-all';
14
+ import type { AutoSelectPredicate } from '../reconstruct/auto-select/auto-select-defaults';
14
15
  /**
15
16
  * The logger to be used for benchmarking as a global object.
16
17
  */
@@ -68,7 +69,7 @@ export declare class BenchmarkSlicer {
68
69
  * Initialize the slicer on the given request.
69
70
  * Can only be called once for each instance.
70
71
  */
71
- init(request: RParseRequestFromFile | RParseRequestFromText): Promise<void>;
72
+ init(request: RParseRequestFromFile | RParseRequestFromText, autoSelectIf?: AutoSelectPredicate): Promise<void>;
72
73
  private calculateStatsAfterInit;
73
74
  /**
74
75
  * Slice for the given {@link SlicingCriteria}.
@@ -55,12 +55,13 @@ class BenchmarkSlicer {
55
55
  * Initialize the slicer on the given request.
56
56
  * Can only be called once for each instance.
57
57
  */
58
- async init(request) {
58
+ async init(request, autoSelectIf) {
59
59
  (0, assert_1.guard)(this.stats === undefined, 'cannot initialize the slicer twice');
60
60
  this.pipeline = new pipeline_executor_1.PipelineExecutor(default_pipelines_1.DEFAULT_SLICING_PIPELINE, {
61
61
  shell: this.shell,
62
62
  request: { ...request },
63
- criterion: []
63
+ criterion: [],
64
+ autoSelectIf
64
65
  });
65
66
  this.loadedXml = await this.measureCommonStep('parse', 'retrieve AST from R code');
66
67
  this.normalizedAst = await this.measureCommonStep('normalize', 'normalize R AST');
@@ -46,6 +46,7 @@ exports.slicerOptions = [
46
46
  { name: 'criterion', alias: 'c', type: String, description: '(Required) Slicing criterion either in the form {underline line:col} or {underline line@variable}, multiple can be separated by \'{bold ;}\'. If you do not want to slice but only process the file, pass an empty string.', multiple: false },
47
47
  { name: 'stats', alias: 's', type: Boolean, description: 'Print stats and write them to {italic <output>.stats} (runtimes etc.)', multiple: false },
48
48
  { name: 'output', alias: 'o', type: String, description: 'File to write all the generated quads to (defaults to the commandline)', typeLabel: '{underline file}' },
49
+ { name: 'no-magic-comments', alias: 'm', type: Boolean, description: 'Disable the effects of magic comments which force lines to be included.' },
49
50
  { name: 'api', type: Boolean, description: 'Instead of human-readable output, dump a lot of json with the results of all intermediate steps.' },
50
51
  ];
51
52
  const featureNameList = [...feature_1.allFeatureNames].map(s => `"${s}"`).join(', ');
package/cli/flowr.js CHANGED
@@ -87,10 +87,9 @@ function retrieveShell() {
87
87
  }
88
88
  async function mainRepl() {
89
89
  if (options.script) {
90
- let target = scripts_info_1.scripts[options.script].target;
90
+ const target = scripts_info_1.scripts[options.script].target;
91
91
  (0, assert_1.guard)(target !== undefined, `Unknown script ${options.script}, pick one of ${getScriptsText()}.`);
92
92
  console.log(`Running script '${ansi_1.formatter.format(options.script, { style: 1 /* FontStyles.Bold */ })}'`);
93
- target = `cli/${target}`;
94
93
  log_1.log.debug(`Script maps to "${target}"`);
95
94
  await (0, execute_1.waitOnScript)(`${__dirname}/${target}`, process.argv.slice(3), undefined, true);
96
95
  process.exit(0);
@@ -30,7 +30,7 @@ exports.helpCommand = {
30
30
  fn: output => {
31
31
  initCommandMapping();
32
32
  output.stdout(`
33
- You can always just enter R expressions which get evaluated right away:
33
+ If enabled, you can just enter R expressions which get evaluated right away:
34
34
  ${prompt_1.rawPrompt} ${(0, ansi_1.bold)('1 + 1', output.formatter)}
35
35
  ${(0, ansi_1.italic)('[1] 2', output.formatter)}
36
36
 
package/cli/repl/core.js CHANGED
@@ -115,7 +115,7 @@ async function replProcessStatement(output, statement, shell, allowRSessionAcces
115
115
  await (0, execute_1.executeRShellCommand)(output, shell, statement);
116
116
  }
117
117
  else {
118
- output.stderr(`${output.formatter.format('You are not allowed to execute arbitrary R code.', { style: 1 /* FontStyles.Bold */, color: 1 /* Colors.Red */, effect: ansi_1.ColorEffect.Foreground })}\nIf you want to do so, please restart flowR with the ${output.formatter.format('--r-session-access', { style: 1 /* FontStyles.Bold */ })} flag. Please be careful of the security implications of this action.`);
118
+ output.stderr(`${output.formatter.format('You are not allowed to execute arbitrary R code.', { style: 1 /* FontStyles.Bold */, color: 1 /* Colors.Red */, effect: ansi_1.ColorEffect.Foreground })}\nIf you want to do so, please restart flowR with the ${output.formatter.format('--r-session-access', { style: 1 /* FontStyles.Bold */ })} flag. Please be careful of the security implications of this action.`);
119
119
  }
120
120
  }
121
121
  /**
@@ -46,6 +46,8 @@ const default_pipelines_1 = require("../../../core/steps/pipeline/default-pipeli
46
46
  const graph_1 = require("../../../dataflow/graph/graph");
47
47
  const tmp = __importStar(require("tmp"));
48
48
  const fs_1 = __importDefault(require("fs"));
49
+ const auto_select_defaults_1 = require("../../../reconstruct/auto-select/auto-select-defaults");
50
+ const magic_comments_1 = require("../../../reconstruct/auto-select/magic-comments");
49
51
  /**
50
52
  * Each connection handles a single client, answering to its requests.
51
53
  * There is no need to construct this class manually, {@link FlowRServer} will do it for you.
@@ -211,7 +213,10 @@ class FlowRServerConnection {
211
213
  });
212
214
  return;
213
215
  }
214
- fileInformation.pipeline.updateRequest({ criterion: request.criterion });
216
+ fileInformation.pipeline.updateRequest({
217
+ criterion: request.criterion,
218
+ autoSelectIf: request.noMagicComments ? auto_select_defaults_1.autoSelectLibrary : (0, magic_comments_1.makeMagicCommentHandler)(auto_select_defaults_1.autoSelectLibrary)
219
+ });
215
220
  void fileInformation.pipeline.allRemainingSteps(true).then(results => {
216
221
  (0, send_1.sendMessage)(this.socket, {
217
222
  type: 'response-slice',
@@ -13,6 +13,10 @@ export interface SliceRequestMessage extends IdMessageBase {
13
13
  filetoken: string;
14
14
  /** The slicing criteria to use */
15
15
  criterion: SlicingCriteria;
16
+ /**
17
+ * Should the magic comments (force-including lines within the slice) be ignord?
18
+ */
19
+ noMagicComments?: boolean;
16
20
  }
17
21
  export declare const requestSliceMessage: MessageDefinition<SliceRequestMessage>;
18
22
  /**
@@ -31,7 +31,7 @@ exports.requestSliceMessage = {
31
31
  type: Joi.string().valid('request-slice').required(),
32
32
  id: Joi.string().optional(),
33
33
  filetoken: Joi.string().required(),
34
- criterion: Joi.array().items(Joi.string().regex(/\d+:\d+|\d+@.*|\$\d+/)).min(0).required()
34
+ criterion: Joi.array().items(Joi.string()).min(0).required()
35
35
  })
36
36
  };
37
37
  //# sourceMappingURL=slice.js.map
@@ -8,4 +8,5 @@ export interface SlicerCliOptions {
8
8
  'input-is-text': boolean;
9
9
  stats: boolean;
10
10
  api: boolean;
11
+ 'no-magic-comments': boolean;
11
12
  }
package/cli/slicer-app.js CHANGED
@@ -12,6 +12,8 @@ const json_1 = require("../util/json");
12
12
  const script_1 = require("./common/script");
13
13
  const slicer_1 = require("../benchmark/slicer");
14
14
  const print_1 = require("../benchmark/stats/print");
15
+ const auto_select_defaults_1 = require("../reconstruct/auto-select/auto-select-defaults");
16
+ const magic_comments_1 = require("../reconstruct/auto-select/magic-comments");
15
17
  const options = (0, script_1.processCommandLineArgs)('slicer', ['input', 'criterion'], {
16
18
  subtitle: 'Slice R code based on a given slicing criterion',
17
19
  examples: [
@@ -26,7 +28,9 @@ async function getSlice() {
26
28
  const slicer = new slicer_1.BenchmarkSlicer();
27
29
  (0, assert_1.guard)(options.input !== undefined, 'input must be given');
28
30
  (0, assert_1.guard)(options.criterion !== undefined, 'a slicing criterion must be given');
29
- await slicer.init(options['input-is-text'] ? { request: 'text', content: options.input } : { request: 'file', content: options.input });
31
+ await slicer.init(options['input-is-text']
32
+ ? { request: 'text', content: options.input }
33
+ : { request: 'file', content: options.input }, options['no-magic-comments'] ? auto_select_defaults_1.autoSelectLibrary : (0, magic_comments_1.makeMagicCommentHandler)(auto_select_defaults_1.autoSelectLibrary));
30
34
  let mappedSlices = [];
31
35
  let reconstruct = undefined;
32
36
  const doSlicing = options.criterion.trim() !== '';
@@ -1,8 +1,8 @@
1
1
  import { internalPrinter } from '../../../print/print';
2
2
  import { PipelineStepStage } from '../../pipeline-step';
3
3
  import type { SliceResult } from '../../../../slicing/static/slicer-types';
4
- import type { AutoSelectPredicate } from '../../../../reconstruct/reconstruct';
5
4
  import type { NormalizedAst } from '../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
5
+ import type { AutoSelectPredicate } from '../../../../reconstruct/auto-select/auto-select-defaults';
6
6
  export interface ReconstructRequiredInput {
7
7
  autoSelectIf?: AutoSelectPredicate;
8
8
  }
@@ -76,7 +76,7 @@ export declare const DEFAULT_SLICING_PIPELINE: import("./pipeline").Pipeline<{
76
76
  readonly dependencies: readonly ["slice"];
77
77
  readonly requiredInput: import("../all/static-slicing/10-reconstruct").ReconstructRequiredInput;
78
78
  }>;
79
- export declare const DEFAULT_RECONSTRUCT_PIPELINE: import("./pipeline").Pipeline<{
79
+ export declare const DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE: import("./pipeline").Pipeline<{
80
80
  readonly name: "parse";
81
81
  readonly humanReadableName: "parse with R shell";
82
82
  readonly description: "Parse the given R code into an AST";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_PARSE_PIPELINE = exports.DEFAULT_NORMALIZE_PIPELINE = exports.DEFAULT_DATAFLOW_PIPELINE = exports.DEFAULT_RECONSTRUCT_PIPELINE = exports.DEFAULT_SLICING_PIPELINE = void 0;
3
+ exports.DEFAULT_PARSE_PIPELINE = exports.DEFAULT_NORMALIZE_PIPELINE = exports.DEFAULT_DATAFLOW_PIPELINE = exports.DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE = exports.DEFAULT_SLICING_PIPELINE = void 0;
4
4
  /**
5
5
  * Contains the default pipeline for working with flowr
6
6
  */
@@ -11,7 +11,7 @@ const _20_dataflow_1 = require("../all/core/20-dataflow");
11
11
  const _00_slice_1 = require("../all/static-slicing/00-slice");
12
12
  const _10_reconstruct_1 = require("../all/static-slicing/10-reconstruct");
13
13
  exports.DEFAULT_SLICING_PIPELINE = (0, pipeline_1.createPipeline)(_00_parse_1.PARSE_WITH_R_SHELL_STEP, _10_normalize_1.NORMALIZE, _20_dataflow_1.STATIC_DATAFLOW, _00_slice_1.STATIC_SLICE, _10_reconstruct_1.NAIVE_RECONSTRUCT);
14
- exports.DEFAULT_RECONSTRUCT_PIPELINE = exports.DEFAULT_SLICING_PIPELINE;
14
+ exports.DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE = exports.DEFAULT_SLICING_PIPELINE;
15
15
  exports.DEFAULT_DATAFLOW_PIPELINE = (0, pipeline_1.createPipeline)(_00_parse_1.PARSE_WITH_R_SHELL_STEP, _10_normalize_1.NORMALIZE, _20_dataflow_1.STATIC_DATAFLOW);
16
16
  exports.DEFAULT_NORMALIZE_PIPELINE = (0, pipeline_1.createPipeline)(_00_parse_1.PARSE_WITH_R_SHELL_STEP, _10_normalize_1.NORMALIZE);
17
17
  exports.DEFAULT_PARSE_PIPELINE = (0, pipeline_1.createPipeline)(_00_parse_1.PARSE_WITH_R_SHELL_STEP);
@@ -1,5 +1,5 @@
1
1
  import type { DataflowGraph, FunctionArgument, OutgoingEdges } from './graph';
2
- import type { GenericDifferenceInformation, WriteableDifferenceReport } from '../../util/diff';
2
+ import type { GenericDiffConfiguration, GenericDifferenceInformation, WriteableDifferenceReport } from '../../util/diff';
3
3
  import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
4
4
  interface ProblematicVertex {
5
5
  tag: 'vertex';
@@ -14,7 +14,7 @@ export type ProblematicDiffInfo = ProblematicVertex | ProblematicEdge;
14
14
  export declare class DataflowDifferenceReport implements WriteableDifferenceReport {
15
15
  _comments: string[] | undefined;
16
16
  _problematic: ProblematicDiffInfo[] | undefined;
17
- addComment(comment: string, ...related: ProblematicDiffInfo[]): void;
17
+ addComment(comment: string, ...related: readonly ProblematicDiffInfo[]): void;
18
18
  comments(): readonly string[] | undefined;
19
19
  problematic(): readonly ProblematicDiffInfo[] | undefined;
20
20
  isEqual(): boolean;
@@ -26,8 +26,9 @@ export interface NamedGraph {
26
26
  interface DataflowDiffContext extends GenericDifferenceInformation<DataflowDifferenceReport> {
27
27
  left: DataflowGraph;
28
28
  right: DataflowGraph;
29
+ config: GenericDiffConfiguration;
29
30
  }
30
- export declare function diffOfDataflowGraphs(left: NamedGraph, right: NamedGraph): DataflowDifferenceReport;
31
+ export declare function diffOfDataflowGraphs(left: NamedGraph, right: NamedGraph, config?: Partial<GenericDiffConfiguration>): DataflowDifferenceReport;
31
32
  export declare function equalFunctionArguments(fn: NodeId, a: false | readonly FunctionArgument[], b: false | readonly FunctionArgument[]): boolean;
32
33
  export declare function diffFunctionArguments(fn: NodeId, a: false | readonly FunctionArgument[], b: false | readonly FunctionArgument[], ctx: GenericDifferenceInformation<DataflowDifferenceReport>): void;
33
34
  export declare function diffVertices(ctx: DataflowDiffContext): void;
@@ -40,31 +40,40 @@ class DataflowDifferenceReport {
40
40
  }
41
41
  }
42
42
  exports.DataflowDifferenceReport = DataflowDifferenceReport;
43
- function initDiffContext(left, right) {
43
+ function initDiffContext(left, right, config) {
44
44
  return {
45
45
  left: left.graph,
46
46
  leftname: left.name,
47
47
  right: right.graph,
48
48
  rightname: right.name,
49
49
  report: new DataflowDifferenceReport(),
50
- position: ''
50
+ position: '',
51
+ config: {
52
+ rightIsSubgraph: false,
53
+ leftIsSubgraph: false,
54
+ ...config
55
+ }
51
56
  };
52
57
  }
53
58
  function diff(ctx) {
54
59
  diffRootVertices(ctx);
55
60
  diffVertices(ctx);
56
61
  diffOutgoingEdges(ctx);
57
- return true;
58
62
  }
59
63
  function diffOutgoingEdges(ctx) {
60
64
  const lEdges = new Map([...ctx.left.edges()]);
61
65
  const rEdges = new Map([...ctx.right.edges()]);
62
- if (lEdges.size !== rEdges.size) {
66
+ if (lEdges.size < rEdges.size && !ctx.config.leftIsSubgraph || lEdges.size > rEdges.size && !ctx.config.rightIsSubgraph) {
63
67
  ctx.report.addComment(`Detected different number of edges! ${ctx.leftname} has ${lEdges.size} (${JSON.stringify(lEdges, json_1.jsonReplacer)}). ${ctx.rightname} has ${rEdges.size} ${JSON.stringify(rEdges, json_1.jsonReplacer)}`);
64
68
  }
65
69
  for (const [id, edge] of lEdges) {
70
+ /* This has nothing to do with the subset relation as we verify this in the same graph.
71
+ * Yet we still do the check as a subgraph may not have to have all source vertices for edges.
72
+ */
66
73
  if (!ctx.left.hasVertex(id)) {
67
- ctx.report.addComment(`The source ${id} of edges ${JSON.stringify(edge, json_1.jsonReplacer)} is not present in ${ctx.leftname}. This means that the graph contains an edge but not the corresponding vertex.`);
74
+ if (!ctx.config.leftIsSubgraph) {
75
+ ctx.report.addComment(`The source ${id} of edges ${JSON.stringify(edge, json_1.jsonReplacer)} is not present in ${ctx.leftname}. This means that the graph contains an edge but not the corresponding vertex.`);
76
+ }
68
77
  continue;
69
78
  }
70
79
  diffEdges(ctx, id, edge, rEdges.get(id));
@@ -72,22 +81,25 @@ function diffOutgoingEdges(ctx) {
72
81
  // just to make it both ways in case the length differs
73
82
  for (const [id, edge] of rEdges) {
74
83
  if (!ctx.right.hasVertex(id)) {
75
- ctx.report.addComment(`The source ${id} of edges ${JSON.stringify(edge, json_1.jsonReplacer)} is not present in ${ctx.rightname}. This means that the graph contains an edge but not the corresponding vertex.`);
84
+ if (!ctx.config.rightIsSubgraph) {
85
+ ctx.report.addComment(`The source ${id} of edges ${JSON.stringify(edge, json_1.jsonReplacer)} is not present in ${ctx.rightname}. This means that the graph contains an edge but not the corresponding vertex.`);
86
+ }
76
87
  continue;
77
88
  }
78
- if (!lEdges.has(id)) {
89
+ if (!ctx.config.leftIsSubgraph && !lEdges.has(id)) {
79
90
  diffEdges(ctx, id, undefined, edge);
80
91
  }
92
+ /* otherwise, we already cover the edge above */
81
93
  }
82
94
  }
83
95
  function diffRootVertices(ctx) {
84
96
  (0, diff_1.setDifference)(ctx.left.rootIds(), ctx.right.rootIds(), { ...ctx, position: `${ctx.position}Root vertices differ in graphs. ` });
85
97
  }
86
- function diffOfDataflowGraphs(left, right) {
98
+ function diffOfDataflowGraphs(left, right, config) {
87
99
  if (left.graph === right.graph) {
88
100
  return new DataflowDifferenceReport();
89
101
  }
90
- const ctx = initDiffContext(left, right);
102
+ const ctx = initDiffContext(left, right, config);
91
103
  diff(ctx);
92
104
  return ctx.report;
93
105
  }
@@ -106,7 +118,8 @@ function equalFunctionArguments(fn, a, b) {
106
118
  report: new DataflowDifferenceReport(),
107
119
  leftname: 'left',
108
120
  rightname: 'right',
109
- position: ''
121
+ position: '',
122
+ config: {}
110
123
  };
111
124
  diffFunctionArguments(fn, a, b, ctx);
112
125
  return ctx.report.isEqual();
@@ -155,13 +168,16 @@ function diffVertices(ctx) {
155
168
  // collect vertices from both sides
156
169
  const lVert = [...ctx.left.vertices(true)].map(([id, info]) => [id, info]);
157
170
  const rVert = [...ctx.right.vertices(true)].map(([id, info]) => [id, info]);
158
- if (lVert.length !== rVert.length) {
171
+ if (lVert.length < rVert.length && !ctx.config.leftIsSubgraph
172
+ || lVert.length > rVert.length && !ctx.config.rightIsSubgraph) {
159
173
  ctx.report.addComment(`Detected different number of vertices! ${ctx.leftname} has ${lVert.length}, ${ctx.rightname} has ${rVert.length}`);
160
174
  }
161
175
  for (const [id, lInfo] of lVert) {
162
176
  const rInfoMay = ctx.right.get(id);
163
177
  if (rInfoMay === undefined) {
164
- ctx.report.addComment(`Vertex ${id} is not present in ${ctx.rightname}`, { tag: 'vertex', id });
178
+ if (!ctx.config.rightIsSubgraph) {
179
+ ctx.report.addComment(`Vertex ${id} is not present in ${ctx.rightname}`, { tag: 'vertex', id });
180
+ }
165
181
  continue;
166
182
  }
167
183
  const [rInfo] = rInfoMay;
@@ -173,15 +189,21 @@ function diffVertices(ctx) {
173
189
  const lname = lInfo.name ?? (0, node_id_1.recoverName)(id, ctx.left.idMap) ?? '??';
174
190
  const rname = rInfo.name ?? (0, node_id_1.recoverName)(id, ctx.right.idMap) ?? '??';
175
191
  if (lname !== rname) {
176
- // eslint-disable-next-line @typescript-eslint/no-base-to-string,@typescript-eslint/restrict-template-expressions
177
- ctx.report.addComment(`Vertex ${id} differs in names. ${ctx.leftname}: ${lname} vs ${ctx.rightname}: ${rname}`, {
192
+ ctx.report.addComment(`Vertex ${id} differs in names. ${ctx.leftname}: ${String(lname)} vs ${ctx.rightname}: ${String(rname)}`, {
178
193
  tag: 'vertex',
179
194
  id
180
195
  });
181
196
  }
182
197
  }
183
198
  (0, info_1.diffControlDependencies)(lInfo.controlDependencies, rInfo.controlDependencies, { ...ctx, position: `Vertex ${id} differs in controlDependencies. ` });
184
- (0, diff_2.diffEnvironmentInformation)(lInfo.environment, rInfo.environment, { ...ctx, position: `${ctx.position}Vertex ${id} differs in environment. ` });
199
+ if ((lInfo.environment === undefined && rInfo.environment !== undefined && !ctx.config.leftIsSubgraph)
200
+ || (lInfo.environment !== undefined && rInfo.environment === undefined && !ctx.config.rightIsSubgraph)) {
201
+ /* only diff them if specified at all */
202
+ (0, diff_2.diffEnvironmentInformation)(lInfo.environment, rInfo.environment, {
203
+ ...ctx,
204
+ position: `${ctx.position}Vertex ${id} differs in environment. `
205
+ });
206
+ }
185
207
  if (lInfo.tag === "function-call" /* VertexType.FunctionCall */) {
186
208
  if (rInfo.tag !== "function-call" /* VertexType.FunctionCall */) {
187
209
  ctx.report.addComment(`Vertex ${id} differs in tags. ${ctx.leftname}: ${lInfo.tag} vs. ${ctx.rightname}: ${rInfo.tag}`);
@@ -190,10 +212,13 @@ function diffVertices(ctx) {
190
212
  if (lInfo.onlyBuiltin !== rInfo.onlyBuiltin) {
191
213
  ctx.report.addComment(`Vertex ${id} differs in onlyBuiltin. ${ctx.leftname}: ${lInfo.onlyBuiltin} vs ${ctx.rightname}: ${rInfo.onlyBuiltin}`, { tag: 'vertex', id });
192
214
  }
193
- diffFunctionArguments(lInfo.id, lInfo.args, rInfo.args, {
194
- ...ctx,
195
- position: `${ctx.position}Vertex ${id} (function call) differs in arguments. `
196
- });
215
+ if ((lInfo.args.length === 0 && rInfo.args.length !== 0 && !ctx.config.leftIsSubgraph)
216
+ || (lInfo.args.length !== 0 && rInfo.args.length === 0 && !ctx.config.rightIsSubgraph)) {
217
+ diffFunctionArguments(lInfo.id, lInfo.args, rInfo.args, {
218
+ ...ctx,
219
+ position: `${ctx.position}Vertex ${id} (function call) differs in arguments. `
220
+ });
221
+ }
197
222
  }
198
223
  }
199
224
  if (lInfo.tag === 'function-definition') {
@@ -204,10 +229,13 @@ function diffVertices(ctx) {
204
229
  if (!(0, arrays_1.arrayEqual)(lInfo.exitPoints, rInfo.exitPoints)) {
205
230
  ctx.report.addComment(`Vertex ${id} differs in exit points. ${ctx.leftname}: ${JSON.stringify(lInfo.exitPoints, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(rInfo.exitPoints, json_1.jsonReplacer)}`, { tag: 'vertex', id });
206
231
  }
207
- (0, diff_2.diffEnvironmentInformation)(lInfo.subflow.environment, rInfo.subflow.environment, {
208
- ...ctx,
209
- position: `${ctx.position}Vertex ${id} (function definition) differs in subflow environments. `
210
- });
232
+ if ((lInfo.subflow.environment === undefined && rInfo.subflow.environment !== undefined && !ctx.config.leftIsSubgraph)
233
+ || (lInfo.subflow.environment !== undefined && rInfo.subflow.environment === undefined && !ctx.config.rightIsSubgraph)) {
234
+ (0, diff_2.diffEnvironmentInformation)(lInfo.subflow.environment, rInfo.subflow.environment, {
235
+ ...ctx,
236
+ position: `${ctx.position}Vertex ${id} (function definition) differs in subflow environments. `
237
+ });
238
+ }
211
239
  (0, diff_1.setDifference)(lInfo.subflow.graph, rInfo.subflow.graph, {
212
240
  ...ctx,
213
241
  position: `${ctx.position}Vertex ${id} differs in subflow graph. `
@@ -229,19 +257,23 @@ function diffEdge(edge, otherEdge, ctx, id, target) {
229
257
  }
230
258
  function diffEdges(ctx, id, lEdges, rEdges) {
231
259
  if (lEdges === undefined || rEdges === undefined) {
232
- if (lEdges !== rEdges) {
260
+ if ((lEdges === undefined && !ctx.config.leftIsSubgraph)
261
+ || (rEdges === undefined && !ctx.config.rightIsSubgraph)) {
233
262
  ctx.report.addComment(`Vertex ${id} has undefined outgoing edges. ${ctx.leftname}: ${JSON.stringify(lEdges, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(rEdges, json_1.jsonReplacer)}`, { tag: 'vertex', id });
234
263
  }
235
264
  return;
236
265
  }
237
- if (lEdges.size !== rEdges.size) {
266
+ if (lEdges.size < rEdges.size && !ctx.config.leftIsSubgraph
267
+ || lEdges.size > rEdges.size && !ctx.config.rightIsSubgraph) {
238
268
  ctx.report.addComment(`Vertex ${id} differs in number of outgoing edges. ${ctx.leftname}: [${[...lEdges.keys()].join(',')}] vs ${ctx.rightname}: [${[...rEdges.keys()].join(',')}] `, { tag: 'vertex', id });
239
269
  }
240
270
  // order independent compare
241
271
  for (const [target, edge] of lEdges) {
242
272
  const otherEdge = rEdges.get(target);
243
273
  if (otherEdge === undefined) {
244
- ctx.report.addComment(`Target of ${id}->${target} in ${ctx.leftname} is not present in ${ctx.rightname}`, { tag: 'edge', from: id, to: target });
274
+ if (!ctx.config.rightIsSubgraph) {
275
+ ctx.report.addComment(`Target of ${id}->${target} in ${ctx.leftname} is not present in ${ctx.rightname}`, { tag: 'edge', from: id, to: target });
276
+ }
245
277
  continue;
246
278
  }
247
279
  diffEdge(edge, otherEdge, ctx, id, target);
@@ -4,14 +4,19 @@ import type { IdentifierReference } from '../environments/identifier';
4
4
  import type { DataflowGraph, FunctionArgument } from '../graph/graph';
5
5
  import type { RParameter } from '../../r-bridge/lang-4.x/ast/model/nodes/r-parameter';
6
6
  import type { AstIdMap, ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
7
- import type { DataflowGraphVertexInfo } from '../graph/vertex';
7
+ import type { DataflowGraphVertexFunctionCall, DataflowGraphVertexFunctionDefinition, DataflowGraphVertexInfo } from '../graph/vertex';
8
8
  import type { REnvironmentInformation } from '../environments/environment';
9
9
  export type NameIdMap = DefaultMap<string, IdentifierReference[]>;
10
10
  export declare function produceNameSharedIdMap(references: IdentifierReference[]): NameIdMap;
11
11
  export declare function linkArgumentsOnCall(args: FunctionArgument[], params: RParameter<ParentInformation>[], graph: DataflowGraph): void;
12
+ export declare function linkFunctionCallWithSingleTarget(graph: DataflowGraph, def: DataflowGraphVertexFunctionDefinition, info: DataflowGraphVertexFunctionCall, idMap: AstIdMap): void;
12
13
  /**
13
14
  * Returns the called functions within the current graph, which can be used to merge the environments with the call.
14
15
  * Furthermore, it links the corresponding arguments.
16
+ *
17
+ * @param graph - The graph to use for search and resolution traversals (ideally a superset of the `thisGraph`)
18
+ * @param idMap - The map to resolve ids to names
19
+ * @param thisGraph - The graph to search for function calls in
15
20
  */
16
21
  export declare function linkFunctionCalls(graph: DataflowGraph, idMap: AstIdMap, thisGraph: DataflowGraph): {
17
22
  functionCall: NodeId;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.linkCircularRedefinitionsWithinALoop = exports.linkInputs = exports.getAllLinkedFunctionDefinitions = exports.linkFunctionCalls = exports.linkArgumentsOnCall = exports.produceNameSharedIdMap = void 0;
3
+ exports.linkCircularRedefinitionsWithinALoop = exports.linkInputs = exports.getAllLinkedFunctionDefinitions = exports.linkFunctionCalls = exports.linkFunctionCallWithSingleTarget = exports.linkArgumentsOnCall = exports.produceNameSharedIdMap = void 0;
4
4
  const defaultmap_1 = require("../../util/defaultmap");
5
5
  const assert_1 = require("../../util/assert");
6
6
  const log_1 = require("../../util/log");
@@ -79,6 +79,30 @@ function linkFunctionCallArguments(targetId, idMap, functionCallName, functionRo
79
79
  (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `linking arguments for ${functionCallName} (${functionRootId}) to ${JSON.stringify(linkedFunction.location)}`);
80
80
  linkArgumentsOnCall(callArgs, linkedFunction.parameters, finalGraph);
81
81
  }
82
+ function linkFunctionCallWithSingleTarget(graph, def, info, idMap) {
83
+ const id = info.id;
84
+ if (info.environment !== undefined) {
85
+ // for each open ingoing reference, try to resolve it here, and if so, add a read edge from the call to signal that it reads it
86
+ for (const ingoing of def.subflow.in) {
87
+ const defs = ingoing.name ? (0, resolve_by_name_1.resolveByName)(ingoing.name, info.environment) : undefined;
88
+ if (defs === undefined) {
89
+ continue;
90
+ }
91
+ for (const def of defs) {
92
+ graph.addEdge(id, def, { type: 1 /* EdgeType.Reads */ });
93
+ }
94
+ }
95
+ }
96
+ const exitPoints = def.exitPoints;
97
+ for (const exitPoint of exitPoints) {
98
+ graph.addEdge(id, exitPoint, { type: 8 /* EdgeType.Returns */ });
99
+ }
100
+ const defName = (0, node_id_1.recoverName)(def.id, idMap);
101
+ (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `recording expression-list-level call from ${(0, node_id_1.recoverName)(info.id, idMap)} to ${defName}`);
102
+ graph.addEdge(id, def.id, { type: 4 /* EdgeType.Calls */ });
103
+ linkFunctionCallArguments(def.id, idMap, defName, id, info.args, graph);
104
+ }
105
+ exports.linkFunctionCallWithSingleTarget = linkFunctionCallWithSingleTarget;
82
106
  function linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefinitions) {
83
107
  const edges = graph.outgoingEdges(id);
84
108
  if (edges === undefined) {
@@ -91,26 +115,7 @@ function linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefin
91
115
  const functionDefs = getAllLinkedFunctionDefinitions(new Set(functionDefinitionReadIds), graph);
92
116
  for (const def of functionDefs.values()) {
93
117
  (0, assert_1.guard)(def.tag === "function-definition" /* VertexType.FunctionDefinition */, () => `expected function definition, but got ${def.tag}`);
94
- if (info.environment !== undefined) {
95
- // for each open ingoing reference, try to resolve it here, and if so, add a read edge from the call to signal that it reads it
96
- for (const ingoing of def.subflow.in) {
97
- const defs = ingoing.name ? (0, resolve_by_name_1.resolveByName)(ingoing.name, info.environment) : undefined;
98
- if (defs === undefined) {
99
- continue;
100
- }
101
- for (const def of defs) {
102
- graph.addEdge(id, def, { type: 1 /* EdgeType.Reads */ });
103
- }
104
- }
105
- }
106
- const exitPoints = def.exitPoints;
107
- for (const exitPoint of exitPoints) {
108
- graph.addEdge(id, exitPoint, { type: 8 /* EdgeType.Returns */ });
109
- }
110
- const defName = (0, node_id_1.recoverName)(def.id, idMap);
111
- (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `recording expression-list-level call from ${(0, node_id_1.recoverName)(info.id, idMap)} to ${defName}`);
112
- graph.addEdge(id, def.id, { type: 4 /* EdgeType.Calls */ });
113
- linkFunctionCallArguments(def.id, idMap, defName, id, info.args, graph);
118
+ linkFunctionCallWithSingleTarget(graph, def, info, idMap);
114
119
  }
115
120
  if (thisGraph.isRoot(id)) {
116
121
  calledFunctionDefinitions.push({ functionCall: id, called: [...functionDefs.values()] });
@@ -119,6 +124,10 @@ function linkFunctionCall(graph, id, info, idMap, thisGraph, calledFunctionDefin
119
124
  /**
120
125
  * Returns the called functions within the current graph, which can be used to merge the environments with the call.
121
126
  * Furthermore, it links the corresponding arguments.
127
+ *
128
+ * @param graph - The graph to use for search and resolution traversals (ideally a superset of the `thisGraph`)
129
+ * @param idMap - The map to resolve ids to names
130
+ * @param thisGraph - The graph to search for function calls in
122
131
  */
123
132
  function linkFunctionCalls(graph, idMap, thisGraph) {
124
133
  const functionCalls = [...thisGraph.vertices(true)]
@@ -4,6 +4,8 @@ exports.processFunctionArgument = exports.linkReadsForArgument = void 0;
4
4
  const processor_1 = require("../../../processor");
5
5
  const collect_1 = require("../../../../r-bridge/lang-4.x/ast/model/collect");
6
6
  const graph_1 = require("../../../graph/graph");
7
+ const edge_1 = require("../../../graph/edge");
8
+ const resolve_by_name_1 = require("../../../environments/resolve-by-name");
7
9
  function linkReadsForArgument(root, ingoingRefs, graph) {
8
10
  const allIdsBeforeArguments = new Set((0, collect_1.collectAllIds)(root, n => n.type === "RArgument" /* RType.Argument */ && n.info.id !== root.info.id));
9
11
  const ingoingBeforeArgs = ingoingRefs.filter(r => allIdsBeforeArguments.has(r.nodeId));
@@ -13,6 +15,18 @@ function linkReadsForArgument(root, ingoingRefs, graph) {
13
15
  }
14
16
  }
15
17
  exports.linkReadsForArgument = linkReadsForArgument;
18
+ function hasNoOutgoingCallEdge(graph, id) {
19
+ const outgoings = graph.outgoingEdges(id);
20
+ if (outgoings === undefined) {
21
+ return true;
22
+ }
23
+ for (const [_, edge] of outgoings) {
24
+ if ((0, edge_1.edgeIncludesType)(edge.types, 4 /* EdgeType.Calls */)) {
25
+ return false;
26
+ }
27
+ }
28
+ return true;
29
+ }
16
30
  function processFunctionArgument(argument, data) {
17
31
  const name = argument.name === undefined ? undefined : (0, processor_1.processDataflowFor)(argument.name, data);
18
32
  const value = argument.value === undefined ? undefined : (0, processor_1.processDataflowFor)(argument.value, data);
@@ -29,6 +43,20 @@ function processFunctionArgument(argument, data) {
29
43
  entryPoint = argument.info.id;
30
44
  }
31
45
  const ingoingRefs = [...value?.unknownReferences ?? [], ...value?.in ?? [], ...(name === undefined ? [] : [...name.in])];
46
+ /* potentially link all function calls here (as maybes with a maybe cd) as the called function may employ calling-env-semantics if unknown */
47
+ if (value) {
48
+ const functionCalls = [...graph.vertices(true)]
49
+ .filter(([_, info]) => info.tag === "function-call" /* VertexType.FunctionCall */)
50
+ .filter(([id]) => hasNoOutgoingCallEdge(graph, id));
51
+ // try to resolve them against the current environment
52
+ for (const [id, info] of functionCalls) {
53
+ const resolved = (0, resolve_by_name_1.resolveByName)(info.name, data.environment) ?? [];
54
+ /* first, only link a read ref */
55
+ for (const resolve of resolved) {
56
+ graph.addEdge(id, resolve.nodeId, { type: 1 /* EdgeType.Reads */ });
57
+ }
58
+ }
59
+ }
32
60
  if (entryPoint && argument.value?.type === "RFunctionDefinition" /* RType.FunctionDefinition */) {
33
61
  graph.addEdge(entryPoint, argument.value.info.id, { type: 1 /* EdgeType.Reads */ });
34
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
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": {
@@ -23,11 +23,11 @@ function prepareParsedData(data) {
23
23
  // iterate a second time to set parent-child relations (since they may be out of order in the csv)
24
24
  for (const entry of ret.values()) {
25
25
  if (entry.parent != exports.RootId) {
26
- const parent = ret.get(entry.parent);
27
- if (parent) {
28
- parent.children ??= [];
29
- parent.children.push(entry);
30
- }
26
+ /** it turns out that comments may return a negative id pair to their parent */
27
+ const parent = ret.get(Math.abs(entry.parent));
28
+ (0, assert_1.guard)(parent !== undefined, () => `Could not find parent ${entry.parent} for entry ${entry.id}`);
29
+ parent.children ??= [];
30
+ parent.children.push(entry);
31
31
  }
32
32
  else {
33
33
  roots.push(entry);
@@ -0,0 +1,21 @@
1
+ import type { NoInfo, RNode } from '../../r-bridge/lang-4.x/ast/model/model';
2
+ import type { ParentInformation, NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
3
+ /**
4
+ * The structure of the predicate that should be used to determine
5
+ * if a given normalized node should be included in the reconstructed code,
6
+ * independent of if it is selected by the slice or not.
7
+ *
8
+ * @see reconstructToCode
9
+ * @see doNotAutoSelect
10
+ * @see autoSelectLibrary
11
+ */
12
+ export type AutoSelectPredicate = (node: RNode<ParentInformation>, fullAst: NormalizedAst) => boolean;
13
+ /**
14
+ * A variant of the {@link AutoSelectPredicate} which does not select any additional statements (~&gt; false)
15
+ */
16
+ export declare function doNotAutoSelect(_node: RNode): boolean;
17
+ /**
18
+ * A variant of the {@link AutoSelectPredicate} which does its best
19
+ * to select any kind of library import automatically.
20
+ */
21
+ export declare function autoSelectLibrary<Info = NoInfo>(node: RNode<Info>): boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.autoSelectLibrary = exports.doNotAutoSelect = void 0;
4
+ /**
5
+ * A variant of the {@link AutoSelectPredicate} which does not select any additional statements (~&gt; false)
6
+ */
7
+ function doNotAutoSelect(_node) {
8
+ return false;
9
+ }
10
+ exports.doNotAutoSelect = doNotAutoSelect;
11
+ const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/;
12
+ /**
13
+ * A variant of the {@link AutoSelectPredicate} which does its best
14
+ * to select any kind of library import automatically.
15
+ */
16
+ function autoSelectLibrary(node) {
17
+ if (node.type !== "RFunctionCall" /* RType.FunctionCall */ || !node.named) {
18
+ return false;
19
+ }
20
+ return libraryFunctionCall.test(node.functionName.content);
21
+ }
22
+ exports.autoSelectLibrary = autoSelectLibrary;
23
+ //# sourceMappingURL=auto-select-defaults.js.map
@@ -0,0 +1,17 @@
1
+ import type { AutoSelectPredicate } from './auto-select-defaults';
2
+ /**
3
+ * This takes an {@link NormalizedAst} and returns an auto-select predicate for {@link reconstructToCode},
4
+ * which will automatically include lines marked by these special comments!
5
+ * Please make sure to create one per source as it will use it to cache.
6
+ *
7
+ * We support two formats:
8
+ * - Line comments in the form of `# flowr@include_next_line` or `# flowr@include_this_line`.
9
+ * - Block comments which start with `# flowr@include_start` and end with `# flowr@include_end`.
10
+ * This supports nesting, but they have to appear on a single line.
11
+ *
12
+ * Please note that these comments have to start exactly with this content to work.
13
+ *
14
+ * @param and - Predicate to composite this one with, If you do not pass a predicate, you may assume composition with
15
+ * {@link doNotAutoSelect}.
16
+ */
17
+ export declare function makeMagicCommentHandler(and?: AutoSelectPredicate): AutoSelectPredicate;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeMagicCommentHandler = void 0;
4
+ const visitor_1 = require("../../r-bridge/lang-4.x/ast/model/processing/visitor");
5
+ const assert_1 = require("../../util/assert");
6
+ function getLoc({ location, info: { fullRange } }) {
7
+ const loc = location ?? fullRange;
8
+ (0, assert_1.guard)(loc !== undefined, 'TODO: support location-less nodes!');
9
+ return loc;
10
+ }
11
+ const magicCommentIdMapper = {
12
+ 'include_next_line': (n) => {
13
+ return [getLoc(n)[0] + 1];
14
+ },
15
+ 'include_this_line': (n) => {
16
+ return [getLoc(n)[0]];
17
+ },
18
+ 'include_start': (n, stack) => {
19
+ stack.push(getLoc(n)[0] + 1);
20
+ return undefined;
21
+ },
22
+ 'include_end': (n, stack) => {
23
+ const to = getLoc(n)[0];
24
+ (0, assert_1.guard)(stack.length >= 1, `mismatched magic start and end at ${to}`);
25
+ const from = stack.pop();
26
+ const ret = new Array(to - from - 1);
27
+ for (let i = from; i < to; i++) {
28
+ ret[i - from] = i;
29
+ }
30
+ return ret;
31
+ }
32
+ };
33
+ const commentTriggerRegex = / flowr@(\w+)/;
34
+ /**
35
+ * This takes an {@link NormalizedAst} and returns an auto-select predicate for {@link reconstructToCode},
36
+ * which will automatically include lines marked by these special comments!
37
+ * Please make sure to create one per source as it will use it to cache.
38
+ *
39
+ * We support two formats:
40
+ * - Line comments in the form of `# flowr@include_next_line` or `# flowr@include_this_line`.
41
+ * - Block comments which start with `# flowr@include_start` and end with `# flowr@include_end`.
42
+ * This supports nesting, but they have to appear on a single line.
43
+ *
44
+ * Please note that these comments have to start exactly with this content to work.
45
+ *
46
+ * @param and - Predicate to composite this one with, If you do not pass a predicate, you may assume composition with
47
+ * {@link doNotAutoSelect}.
48
+ */
49
+ function makeMagicCommentHandler(and) {
50
+ let lines = undefined;
51
+ return (node, normalizedAst) => {
52
+ if (!lines) {
53
+ lines = new Set();
54
+ const startLineStack = [];
55
+ (0, visitor_1.visitAst)(normalizedAst.ast, n => {
56
+ const comments = n.info.additionalTokens;
57
+ if (!comments) {
58
+ return;
59
+ }
60
+ for (const c of comments) {
61
+ if (c.type !== "RComment" /* RType.Comment */ || !c.content.startsWith(' flowr@')) {
62
+ continue;
63
+ }
64
+ const match = commentTriggerRegex.exec(c.content);
65
+ (0, assert_1.guard)(match !== null, `invalid magic comment: ${c.content}`);
66
+ const idMapper = magicCommentIdMapper[match[1]];
67
+ (0, assert_1.guard)(idMapper !== undefined, `unknown magic comment: ${match[1]}`);
68
+ const ls = idMapper(c, startLineStack);
69
+ if (ls !== undefined) {
70
+ for (const l of ls) {
71
+ lines.add(l);
72
+ }
73
+ }
74
+ }
75
+ });
76
+ (0, assert_1.guard)(startLineStack.length === 0, `mismatched magic start and end at end of file (${JSON.stringify(startLineStack)})`);
77
+ }
78
+ const loc = node.location ?? node.info.fullRange;
79
+ if (loc && lines.has(loc[0])) {
80
+ return true;
81
+ }
82
+ return and?.(node, normalizedAst) ?? false;
83
+ };
84
+ }
85
+ exports.makeMagicCommentHandler = makeMagicCommentHandler;
86
+ //# sourceMappingURL=magic-comments.js.map
@@ -3,15 +3,11 @@
3
3
  * as the file itself is way too long). See {@link reconstructToCode}.
4
4
  * @module
5
5
  */
6
- import type { NormalizedAst, ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate';
7
- import type { RNode } from '../r-bridge/lang-4.x/ast/model/model';
6
+ import type { NormalizedAst } from '../r-bridge/lang-4.x/ast/model/processing/decorate';
8
7
  import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
8
+ import type { AutoSelectPredicate } from './auto-select/auto-select-defaults';
9
9
  type Selection = ReadonlySet<NodeId>;
10
10
  export declare const reconstructLogger: import("tslog").Logger<import("tslog").ILogObj>;
11
- /** The structure of the predicate that should be used to determine if a given normalized node should be included in the reconstructed code independent of if it is selected by the slice or not */
12
- export type AutoSelectPredicate = (node: RNode<ParentInformation>) => boolean;
13
- export declare function doNotAutoSelect(_node: RNode<ParentInformation>): boolean;
14
- export declare function autoSelectLibrary(node: RNode<ParentInformation>): boolean;
15
11
  export interface ReconstructionResult {
16
12
  code: string;
17
13
  /** number of lines that contain nodes that triggered the `autoSelectIf` predicate {@link reconstructToCode} */
@@ -26,5 +22,5 @@ export interface ReconstructionResult {
26
22
  *
27
23
  * @returns The number of lines for which `autoSelectIf` triggered, as well as the reconstructed code itself.
28
24
  */
29
- export declare function reconstructToCode<Info>(ast: NormalizedAst<Info>, selection: Selection, autoSelectIf?: AutoSelectPredicate): ReconstructionResult;
25
+ export declare function reconstructToCode(ast: NormalizedAst, selection: Selection, autoSelectIf?: AutoSelectPredicate): ReconstructionResult;
30
26
  export {};
@@ -5,21 +5,26 @@
5
5
  * @module
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.reconstructToCode = exports.autoSelectLibrary = exports.doNotAutoSelect = exports.reconstructLogger = void 0;
8
+ exports.reconstructToCode = exports.reconstructLogger = void 0;
9
9
  const log_1 = require("../util/log");
10
10
  const assert_1 = require("../util/assert");
11
11
  const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
12
12
  const stateful_fold_1 = require("../r-bridge/lang-4.x/ast/model/processing/stateful-fold");
13
+ const auto_select_defaults_1 = require("./auto-select/auto-select-defaults");
13
14
  function plain(text) {
14
15
  return [{ line: text, indent: 0 }];
15
16
  }
16
17
  exports.reconstructLogger = log_1.log.getSubLogger({ name: 'reconstruct' });
17
- const getLexeme = (n) => n.info.fullLexeme ?? n.lexeme ?? '';
18
- const reconstructAsLeaf = (leaf, configuration) => {
19
- const selectionHasLeaf = configuration.selection.has(leaf.info.id) || configuration.autoSelectIf(leaf);
18
+ function getLexeme(n) {
19
+ return n.info.fullLexeme ?? n.lexeme ?? '';
20
+ }
21
+ function reconstructAsLeaf(leaf, configuration) {
22
+ const selectionHasLeaf = configuration.selection.has(leaf.info.id) || configuration.autoSelectIf(leaf, configuration.fullAst);
20
23
  return selectionHasLeaf ? foldToConst(leaf) : [];
21
- };
22
- const foldToConst = (n) => plain(getLexeme(n));
24
+ }
25
+ function foldToConst(n) {
26
+ return plain(getLexeme(n));
27
+ }
23
28
  function indentBy(lines, indent) {
24
29
  return lines.map(({ line, indent: i }) => ({ line, indent: i + indent }));
25
30
  }
@@ -57,7 +62,7 @@ function reconstructExpressionList(exprList, _grouping, expressions, config) {
57
62
  }
58
63
  }
59
64
  function isSelected(configuration, n) {
60
- return configuration.selection.has(n.info.id) || configuration.autoSelectIf(n);
65
+ return configuration.selection.has(n.info.id) || configuration.autoSelectIf(n, configuration.fullAst);
61
66
  }
62
67
  function reconstructRawBinaryOperator(lhs, n, rhs) {
63
68
  return [
@@ -360,18 +365,6 @@ function reconstructFunctionCall(call, functionName, args, configuration) {
360
365
  return plain(getLexeme(call));
361
366
  }
362
367
  }
363
- function doNotAutoSelect(_node) {
364
- return false;
365
- }
366
- exports.doNotAutoSelect = doNotAutoSelect;
367
- const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/;
368
- function autoSelectLibrary(node) {
369
- if (node.type !== "RFunctionCall" /* RType.FunctionCall */ || !node.named) {
370
- return false;
371
- }
372
- return libraryFunctionCall.test(node.functionName.content);
373
- }
374
- exports.autoSelectLibrary = autoSelectLibrary;
375
368
  /**
376
369
  * The fold functions used to reconstruct the ast in {@link reconstructToCode}.
377
370
  */
@@ -431,14 +424,14 @@ function removeOuterExpressionListIfApplicable(result, linesWithAutoSelected) {
431
424
  *
432
425
  * @returns The number of lines for which `autoSelectIf` triggered, as well as the reconstructed code itself.
433
426
  */
434
- function reconstructToCode(ast, selection, autoSelectIf = autoSelectLibrary) {
427
+ function reconstructToCode(ast, selection, autoSelectIf = auto_select_defaults_1.autoSelectLibrary) {
435
428
  if (exports.reconstructLogger.settings.minLevel <= 1 /* LogLevel.Trace */) {
436
429
  exports.reconstructLogger.trace(`reconstruct ast with ids: ${JSON.stringify([...selection])}`);
437
430
  }
438
431
  // we use a wrapper to count the number of lines for which the autoSelectIf predicate triggered
439
432
  const linesWithAutoSelected = new Set();
440
433
  const autoSelectIfWrapper = (node) => {
441
- const result = autoSelectIf(node);
434
+ const result = autoSelectIf(node, ast);
442
435
  if (result && node.location) {
443
436
  for (let i = node.location[0]; i <= node.location[2]; i++) {
444
437
  linesWithAutoSelected.add(i);
@@ -447,7 +440,7 @@ function reconstructToCode(ast, selection, autoSelectIf = autoSelectLibrary) {
447
440
  return result;
448
441
  };
449
442
  // fold of the normalized ast
450
- const result = (0, stateful_fold_1.foldAstStateful)(ast.ast, { selection, autoSelectIf: autoSelectIfWrapper }, reconstructAstFolds);
443
+ const result = (0, stateful_fold_1.foldAstStateful)(ast.ast, { selection, autoSelectIf: autoSelectIfWrapper, fullAst: ast }, reconstructAstFolds);
451
444
  (0, log_1.expensiveTrace)(exports.reconstructLogger, () => `reconstructed ast before string conversion: ${JSON.stringify(result)}`);
452
445
  return removeOuterExpressionListIfApplicable(result, linesWithAutoSelected.size);
453
446
  }
@@ -4,8 +4,13 @@ import type { NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing
4
4
  import type { SlicingCriteria } from '../criterion/parse';
5
5
  export declare const slicerLogger: import("tslog").Logger<import("tslog").ILogObj>;
6
6
  /**
7
- * This returns the ids to include in the slice, when slicing with the given seed id's (must be at least one).
7
+ * This returns the ids to include in the static backward slice, when slicing with the given seed id's (must be at least one).
8
8
  * <p>
9
9
  * The returned ids can be used to {@link reconstructToCode|reconstruct the slice to R code}.
10
+ *
11
+ * @param graph - The dataflow graph to conduct the slicing on.
12
+ * @param ast - The normalized AST of the code (used to get static depth information of the lexemes in case of control flow dependencies that may have no effect on the slicing scope).
13
+ * @param criteria - The criteras to slice on.
14
+ * @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts.
10
15
  */
11
16
  export declare function staticSlicing(graph: DataflowGraph, ast: NormalizedAst, criteria: SlicingCriteria, threshold?: number): Readonly<SliceResult>;
@@ -11,9 +11,14 @@ const environment_1 = require("../../dataflow/environments/environment");
11
11
  const edge_1 = require("../../dataflow/graph/edge");
12
12
  exports.slicerLogger = log_1.log.getSubLogger({ name: 'slicer' });
13
13
  /**
14
- * This returns the ids to include in the slice, when slicing with the given seed id's (must be at least one).
14
+ * This returns the ids to include in the static backward slice, when slicing with the given seed id's (must be at least one).
15
15
  * <p>
16
16
  * The returned ids can be used to {@link reconstructToCode|reconstruct the slice to R code}.
17
+ *
18
+ * @param graph - The dataflow graph to conduct the slicing on.
19
+ * @param ast - The normalized AST of the code (used to get static depth information of the lexemes in case of control flow dependencies that may have no effect on the slicing scope).
20
+ * @param criteria - The criteras to slice on.
21
+ * @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts.
17
22
  */
18
23
  function staticSlicing(graph, ast, criteria, threshold = 75) {
19
24
  (0, assert_1.guard)(criteria.length > 0, 'must have at least one seed id to calculate slice');
@@ -26,11 +31,11 @@ function staticSlicing(graph, ast, criteria, threshold = 75) {
26
31
  {
27
32
  const emptyEnv = (0, environment_1.initializeCleanEnvironments)();
28
33
  const basePrint = (0, fingerprint_1.envFingerprint)(emptyEnv);
29
- for (const startId of decodedCriteria) {
30
- queue.add(startId.id, emptyEnv, basePrint, false);
34
+ for (const { id: startId } of decodedCriteria) {
35
+ queue.add(startId, emptyEnv, basePrint, false);
31
36
  // retrieve the minimum depth of all nodes to only add control dependencies if they are "part" of the current execution
32
- minDepth = Math.min(minDepth, ast.idMap.get(startId.id)?.info.depth ?? minDepth);
33
- sliceSeedIds.add(startId.id);
37
+ minDepth = Math.min(minDepth, ast.idMap.get(startId)?.info.depth ?? minDepth);
38
+ sliceSeedIds.add(startId);
34
39
  }
35
40
  }
36
41
  while (queue.nonEmpty()) {
package/util/diff.d.ts CHANGED
@@ -39,5 +39,17 @@ export interface GenericDifferenceInformation<Report extends WriteableDifference
39
39
  readonly report: Report;
40
40
  /** A human-readable indication of where we are (the prefix of the information if the structures differ) */
41
41
  readonly position: string;
42
+ readonly config: GenericDiffConfiguration;
43
+ }
44
+ export interface GenericDiffConfiguration {
45
+ /**
46
+ * The left graph may contain more vertices and or edges than the right graph.
47
+ * However, those which are the same (based on their ids) have to be equal
48
+ */
49
+ readonly rightIsSubgraph?: boolean;
50
+ /**
51
+ * Similar to {@link rightIsSubgraph}, but for the left graph.
52
+ */
53
+ readonly leftIsSubgraph?: boolean;
42
54
  }
43
55
  export declare function setDifference<T, Report extends WriteableDifferenceReport = WriteableDifferenceReport>(left: ReadonlySet<T>, right: ReadonlySet<T>, info: GenericDifferenceInformation<Report>): void;
package/util/diff.js CHANGED
@@ -15,14 +15,17 @@ function setDifference(left, right, info) {
15
15
  return;
16
16
  }
17
17
  let message = info.position;
18
- if (lWithoutR.size > 0) {
18
+ if (lWithoutR.size > 0 && !info.config.rightIsSubgraph) {
19
19
  message += ` More elements in ${info.leftname}: ${JSON.stringify([...lWithoutR])}`;
20
20
  }
21
- if (rWithoutL.size > 0) {
21
+ if (rWithoutL.size > 0 && !info.config.leftIsSubgraph) {
22
22
  message += lWithoutR.size > 0 ? ' and m' : 'M';
23
23
  message += `ore in ${info.rightname}: ${JSON.stringify([...rWithoutL])}`;
24
24
  }
25
- info.report.addComment(message);
25
+ if ((rWithoutL.size > 0 && !info.config.leftIsSubgraph)
26
+ || (lWithoutR.size > 0 && !info.config.rightIsSubgraph)) {
27
+ info.report.addComment(message);
28
+ }
26
29
  }
27
30
  exports.setDifference = setDifference;
28
31
  //# sourceMappingURL=diff.js.map
@@ -13,6 +13,9 @@ interface MermaidGraph {
13
13
  presentEdges: Set<string>;
14
14
  rootGraph: DataflowGraph;
15
15
  }
16
+ /**
17
+ * Prints a {@link SourceRange|range} as a human readable string.
18
+ */
16
19
  export declare function formatRange(range: SourceRange | undefined): string;
17
20
  interface MermaidGraphConfiguration {
18
21
  graph: DataflowGraph;
@@ -7,6 +7,9 @@ const graph_1 = require("../../dataflow/graph/graph");
7
7
  const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
8
8
  const edge_1 = require("../../dataflow/graph/edge");
9
9
  const environment_1 = require("../../dataflow/environments/environment");
10
+ /**
11
+ * Prints a {@link SourceRange|range} as a human readable string.
12
+ */
10
13
  function formatRange(range) {
11
14
  if (range === undefined) {
12
15
  return '??-??';
package/util/version.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.flowrVersion = void 0;
4
4
  const semver_1 = require("semver");
5
5
  // this is automatically replaced with the current version by release-it
6
- const version = '2.0.14';
6
+ const version = '2.0.15';
7
7
  function flowrVersion() {
8
8
  return new semver_1.SemVer(version);
9
9
  }