@eagleoutice/flowr 2.1.6 → 2.1.8

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 (35) hide show
  1. package/README.md +2 -1
  2. package/abstract-interpretation/normalized-ast-fold.d.ts +124 -0
  3. package/abstract-interpretation/normalized-ast-fold.js +178 -0
  4. package/cli/slicer-app.js +1 -1
  5. package/core/steps/pipeline/pipeline.d.ts +63 -0
  6. package/dataflow/environments/default-builtin-config.js +1 -1
  7. package/dataflow/environments/resolve-by-name.d.ts +5 -0
  8. package/dataflow/environments/resolve-by-name.js +14 -0
  9. package/dataflow/internal/process/functions/call/built-in/built-in-expression-list.js +1 -1
  10. package/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.js +17 -7
  11. package/documentation/doc-util/doc-dfg.d.ts +2 -1
  12. package/documentation/doc-util/doc-dfg.js +10 -2
  13. package/documentation/doc-util/doc-normalized-ast.js +1 -1
  14. package/documentation/doc-util/doc-types.d.ts +1 -1
  15. package/documentation/doc-util/doc-types.js +21 -0
  16. package/documentation/print-dataflow-graph-wiki.js +18 -0
  17. package/documentation/print-normalized-ast-wiki.js +107 -5
  18. package/documentation/print-query-wiki.js +8 -1
  19. package/package.json +2 -2
  20. package/queries/catalog/call-context-query/call-context-query-executor.js +22 -1
  21. package/queries/catalog/call-context-query/call-context-query-format.d.ts +16 -2
  22. package/queries/catalog/call-context-query/call-context-query-format.js +4 -0
  23. package/queries/catalog/static-slice-query/static-slice-query-executor.d.ts +1 -1
  24. package/queries/catalog/static-slice-query/static-slice-query-executor.js +2 -2
  25. package/queries/catalog/static-slice-query/static-slice-query-format.d.ts +2 -2
  26. package/queries/catalog/static-slice-query/static-slice-query-format.js +1 -1
  27. package/queries/query.d.ts +1 -1
  28. package/r-bridge/lang-4.x/ast/model/processing/visitor.d.ts +1 -1
  29. package/r-bridge/lang-4.x/ast/model/processing/visitor.js +1 -1
  30. package/r-bridge/lang-4.x/ast/parser/json/format.js +2 -2
  31. package/reconstruct/reconstruct.js +1 -1
  32. package/slicing/static/slice-call.js +2 -1
  33. package/util/assert.d.ts +1 -1
  34. package/util/assert.js +3 -2
  35. package/util/version.js +1 -1
package/README.md CHANGED
@@ -34,7 +34,6 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
34
34
  ### Contributors
35
35
 
36
36
  <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
37
- <!--suppress ALL -->
38
37
  <!-- prettier-ignore-start -->
39
38
  <!-- markdownlint-disable -->
40
39
  <table>
@@ -42,6 +41,7 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
42
41
  <tr>
43
42
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/EagleoutIce"><img src="https://avatars.githubusercontent.com/u/9303573?v=4?s=100" width="100px;" alt="Florian Sihler"/><br /><sub><b>Florian Sihler</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=EagleoutIce" title="Code">💻</a> <a href="#ideas-EagleoutIce" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-EagleoutIce" title="Maintenance">🚧</a> <a href="#projectManagement-EagleoutIce" title="Project Management">📆</a> <a href="#research-EagleoutIce" title="Research">🔬</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=EagleoutIce" title="Tests">⚠️</a> <a href="#talk-EagleoutIce" title="Talks">📢</a></td>
44
43
  <td align="center" valign="top" width="14.28%"><a href="https://ellpeck.de/"><img src="https://avatars.githubusercontent.com/u/5741138?v=4?s=100" width="100px;" alt="Ell"/><br /><sub><b>Ell</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=Ellpeck" title="Code">💻</a> <a href="#maintenance-Ellpeck" title="Maintenance">🚧</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=Ellpeck" title="Tests">⚠️</a> <a href="#plugin-Ellpeck" title="Plugin/utility libraries">🔌</a></td>
44
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/gigalasr"><img src="https://avatars.githubusercontent.com/u/25102989?v=4?s=100" width="100px;" alt="Lars"/><br /><sub><b>Lars</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=gigalasr" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=gigalasr" title="Tests">⚠️</a></td>
45
45
  <td align="center" valign="top" width="14.28%"><a href="https://lukas.pietzschmann.org/"><img src="https://avatars.githubusercontent.com/u/49213919?v=4?s=100" width="100px;" alt="Lukas Pietzschmann"/><br /><sub><b>Lukas Pietzschmann</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=LukasPietzschmann" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=LukasPietzschmann" title="Tests">⚠️</a></td>
46
46
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/bjthehun"><img src="https://avatars.githubusercontent.com/u/38729215?v=4?s=100" width="100px;" alt="Benedikt Jutz"/><br /><sub><b>Benedikt Jutz</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=bjthehun" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=bjthehun" title="Tests">⚠️</a></td>
47
47
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Core5563"><img src="https://avatars.githubusercontent.com/u/140061253?v=4?s=100" width="100px;" alt="Core5563"/><br /><sub><b>Core5563</b></sub></a><br /><a href="https://github.com/flowr-analysis/flowr/commits?author=Core5563" title="Code">💻</a> <a href="https://github.com/flowr-analysis/flowr/commits?author=Core5563" title="Tests">⚠️</a></td>
@@ -61,6 +61,7 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
61
61
 
62
62
  <!-- markdownlint-restore -->
63
63
  <!-- prettier-ignore-end -->
64
+
64
65
  <!-- ALL-CONTRIBUTORS-LIST:END -->
65
66
 
66
67
  ----
@@ -0,0 +1,124 @@
1
+ import type { NoInfo, RNode } from '../r-bridge/lang-4.x/ast/model/model';
2
+ import type { RExpressionList } from '../r-bridge/lang-4.x/ast/model/nodes/r-expression-list';
3
+ import type { RFunctionCall } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
4
+ import { EmptyArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
5
+ import type { RFunctionDefinition } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-definition';
6
+ import { RType } from '../r-bridge/lang-4.x/ast/model/type';
7
+ import type { RForLoop } from '../r-bridge/lang-4.x/ast/model/nodes/r-for-loop';
8
+ import type { RWhileLoop } from '../r-bridge/lang-4.x/ast/model/nodes/r-while-loop';
9
+ import type { RRepeatLoop } from '../r-bridge/lang-4.x/ast/model/nodes/r-repeat-loop';
10
+ import type { RIfThenElse } from '../r-bridge/lang-4.x/ast/model/nodes/r-if-then-else';
11
+ import type { RBinaryOp } from '../r-bridge/lang-4.x/ast/model/nodes/r-binary-op';
12
+ import type { RPipe } from '../r-bridge/lang-4.x/ast/model/nodes/r-pipe';
13
+ import type { RUnaryOp } from '../r-bridge/lang-4.x/ast/model/nodes/r-unary-op';
14
+ import type { RParameter } from '../r-bridge/lang-4.x/ast/model/nodes/r-parameter';
15
+ import type { RArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-argument';
16
+ import type { RAccess } from '../r-bridge/lang-4.x/ast/model/nodes/r-access';
17
+ import type { RLogical } from '../r-bridge/lang-4.x/ast/model/nodes/r-logical';
18
+ import type { RBreak } from '../r-bridge/lang-4.x/ast/model/nodes/r-break';
19
+ import type { RComment } from '../r-bridge/lang-4.x/ast/model/nodes/r-comment';
20
+ import type { RNext } from '../r-bridge/lang-4.x/ast/model/nodes/r-next';
21
+ import type { RNumber } from '../r-bridge/lang-4.x/ast/model/nodes/r-number';
22
+ import type { RLineDirective } from '../r-bridge/lang-4.x/ast/model/nodes/r-line-directive';
23
+ import type { RString } from '../r-bridge/lang-4.x/ast/model/nodes/r-string';
24
+ import type { RSymbol } from '../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
25
+ type FoldOfType<T extends RType, Returns = void, Info = NoInfo> = (node: Extract<RNode<Info>, {
26
+ type: T;
27
+ }>) => Returns;
28
+ /** explicitly excludes types that are not visitable */
29
+ export type FoldableRType = Exclude<RType, RType.Delimiter>;
30
+ /**
31
+ * Describes the fold functions for each node type.
32
+ */
33
+ export type NormalizedAstFold<Returns = void, Info = NoInfo> = {
34
+ [K in FoldableRType as `fold${Capitalize<K>}`]: FoldOfType<K, Returns, Info>;
35
+ };
36
+ /**
37
+ * Describes the type of a mapping object,
38
+ * which maps the type of the normalized AST node to the corresponding fold function.
39
+ */
40
+ export type FittingNormalizedAstFold<Returns = void, Info = NoInfo> = Readonly<{
41
+ [K in FoldableRType]: FoldOfType<K, Returns, Info>;
42
+ }>;
43
+ export type SingleOrArrayOrNothing<T> = T | readonly (T | null | undefined)[] | null | undefined;
44
+ export type EntryExitVisitor<Info> = ((node: RNode<Info>) => void) | undefined;
45
+ /**
46
+ * Default implementation of a fold over the normalized AST (using the classic fold traversal).
47
+ * To modify the behavior, please extend this class and overwrite the methods of interest.
48
+ * You can control the value passing (`Returns` generic)
49
+ * by providing sensible Monoid behavior overwriting the {@link DefaultNormalizedAstFold#concat|concat} method
50
+ * and supplying the empty value in the constructor.
51
+ *
52
+ * @note By providing `entry` and `exit` you can use this as an extension to the simpler {@link visitAst} function but without
53
+ * the early termination within the visitors (for this, you can overwrite the respective `fold*` methods).
54
+ *
55
+ * @example First you want to create your own fold:
56
+ *
57
+ * ```ts
58
+ * let marker = false;
59
+ * class MyNumberFold<Info> extends DefaultNormalizedAstFold<void, Info> {
60
+ * override foldRNumber(node: RNumber<Info>) {
61
+ * super.foldRNumber(node);
62
+ * marker = true;
63
+ * }
64
+ * }
65
+ * ```
66
+ * This one does explicitly not use the return functionality (and hence acts more as a conventional visitor).
67
+ * Now let us suppose we have a normalized AST as an {@link RNode} in the variable `ast`
68
+ * and want to check if the AST contains a number:
69
+ *
70
+ * ```ts
71
+ * const result = new MyNumberFold().fold(ast);
72
+ * ```
73
+ *
74
+ * Please take a look at the corresponding tests or the wiki pages for more information on how to use this fold.
75
+ */
76
+ export declare class DefaultNormalizedAstFold<Returns = void, Info = NoInfo> implements NormalizedAstFold<Returns, Info> {
77
+ protected readonly enter: EntryExitVisitor<Info>;
78
+ protected readonly exit: EntryExitVisitor<Info>;
79
+ protected readonly empty: Returns;
80
+ /**
81
+ * Empty must provide a sensible default whenever you want to have `Returns` as non-`void`
82
+ * (e.g., whenever you want your visitors to be able to return a value).
83
+ */
84
+ constructor(empty: Returns, enter?: EntryExitVisitor<Info>, exit?: EntryExitVisitor<Info>);
85
+ /**
86
+ * Monoid::concat
87
+ *
88
+ *
89
+ * @see {@link https://en.wikipedia.org/wiki/Monoid}
90
+ * @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
91
+ */
92
+ protected concat(_a: Returns, _b: Returns): Returns;
93
+ /**
94
+ * overwrite this method, if you have a faster way to concat multiple nodes
95
+ *
96
+ * @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
97
+ */
98
+ protected concatAll(nodes: readonly Returns[]): Returns;
99
+ fold(nodes: SingleOrArrayOrNothing<RNode<Info> | typeof EmptyArgument>): Returns;
100
+ protected foldSingle(node: RNode<Info>): Returns;
101
+ foldRAccess(access: RAccess<Info>): Returns;
102
+ foldRArgument(argument: RArgument<Info>): Returns;
103
+ foldRBinaryOp(binaryOp: RBinaryOp<Info>): Returns;
104
+ foldRExpressionList(exprList: RExpressionList<Info>): Returns;
105
+ foldRForLoop(loop: RForLoop<Info>): Returns;
106
+ foldRFunctionCall(call: RFunctionCall<Info>): Returns;
107
+ foldRFunctionDefinition(definition: RFunctionDefinition<Info>): Returns;
108
+ foldRIfThenElse(ite: RIfThenElse<Info>): Returns;
109
+ foldRParameter(parameter: RParameter<Info>): Returns;
110
+ foldRPipe(pipe: RPipe<Info>): Returns;
111
+ foldRRepeatLoop(loop: RRepeatLoop<Info>): Returns;
112
+ foldRUnaryOp(unaryOp: RUnaryOp<Info>): Returns;
113
+ foldRWhileLoop(loop: RWhileLoop<Info>): Returns;
114
+ foldRBreak(_node: RBreak<Info>): Returns;
115
+ foldRComment(_node: RComment<Info>): Returns;
116
+ foldRLineDirective(_node: RLineDirective<Info>): Returns;
117
+ foldRLogical(_node: RLogical<Info>): Returns;
118
+ foldRNext(_node: RNext<Info>): Returns;
119
+ foldRNumber(_node: RNumber<Info>): Returns;
120
+ foldRString(_node: RString<Info>): Returns;
121
+ foldRSymbol(_node: RSymbol<Info>): Returns;
122
+ protected readonly folds: FittingNormalizedAstFold<Returns, Info>;
123
+ }
124
+ export {};
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultNormalizedAstFold = void 0;
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
+ /**
7
+ * Default implementation of a fold over the normalized AST (using the classic fold traversal).
8
+ * To modify the behavior, please extend this class and overwrite the methods of interest.
9
+ * You can control the value passing (`Returns` generic)
10
+ * by providing sensible Monoid behavior overwriting the {@link DefaultNormalizedAstFold#concat|concat} method
11
+ * and supplying the empty value in the constructor.
12
+ *
13
+ * @note By providing `entry` and `exit` you can use this as an extension to the simpler {@link visitAst} function but without
14
+ * the early termination within the visitors (for this, you can overwrite the respective `fold*` methods).
15
+ *
16
+ * @example First you want to create your own fold:
17
+ *
18
+ * ```ts
19
+ * let marker = false;
20
+ * class MyNumberFold<Info> extends DefaultNormalizedAstFold<void, Info> {
21
+ * override foldRNumber(node: RNumber<Info>) {
22
+ * super.foldRNumber(node);
23
+ * marker = true;
24
+ * }
25
+ * }
26
+ * ```
27
+ * This one does explicitly not use the return functionality (and hence acts more as a conventional visitor).
28
+ * Now let us suppose we have a normalized AST as an {@link RNode} in the variable `ast`
29
+ * and want to check if the AST contains a number:
30
+ *
31
+ * ```ts
32
+ * const result = new MyNumberFold().fold(ast);
33
+ * ```
34
+ *
35
+ * Please take a look at the corresponding tests or the wiki pages for more information on how to use this fold.
36
+ */
37
+ class DefaultNormalizedAstFold {
38
+ enter;
39
+ exit;
40
+ empty;
41
+ /**
42
+ * Empty must provide a sensible default whenever you want to have `Returns` as non-`void`
43
+ * (e.g., whenever you want your visitors to be able to return a value).
44
+ */
45
+ constructor(empty, enter, exit) {
46
+ this.empty = empty;
47
+ this.enter = enter;
48
+ this.exit = exit;
49
+ }
50
+ /**
51
+ * Monoid::concat
52
+ *
53
+ *
54
+ * @see {@link https://en.wikipedia.org/wiki/Monoid}
55
+ * @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
56
+ */
57
+ concat(_a, _b) {
58
+ return this.empty;
59
+ }
60
+ /**
61
+ * overwrite this method, if you have a faster way to concat multiple nodes
62
+ *
63
+ * @see {@link DefaultNormalizedAstFold#concatAll|concatAll}
64
+ */
65
+ concatAll(nodes) {
66
+ return nodes.reduce((acc, n) => this.concat(acc, n), this.empty);
67
+ }
68
+ fold(nodes) {
69
+ if (Array.isArray(nodes)) {
70
+ const n = nodes;
71
+ return this.concatAll(n.filter(n => n && n !== r_function_call_1.EmptyArgument).map(node => this.foldSingle(node)));
72
+ }
73
+ else if (nodes) {
74
+ return this.foldSingle(nodes);
75
+ }
76
+ return this.empty;
77
+ }
78
+ foldSingle(node) {
79
+ this.enter?.(node);
80
+ const type = node.type;
81
+ // @ts-expect-error -- ts may be unable to infer that the type is correct
82
+ const result = this.folds[type]?.(node);
83
+ this.exit?.(node);
84
+ return result;
85
+ }
86
+ foldRAccess(access) {
87
+ let accessed = this.foldSingle(access.accessed);
88
+ if (access.operator === '[' || access.operator === '[[') {
89
+ accessed = this.concat(accessed, this.fold(access.access));
90
+ }
91
+ return accessed;
92
+ }
93
+ foldRArgument(argument) {
94
+ return this.concat(this.fold(argument.name), this.fold(argument.value));
95
+ }
96
+ foldRBinaryOp(binaryOp) {
97
+ return this.concat(this.foldSingle(binaryOp.lhs), this.foldSingle(binaryOp.rhs));
98
+ }
99
+ foldRExpressionList(exprList) {
100
+ return this.concat(this.fold(exprList.grouping), this.fold(exprList.children));
101
+ }
102
+ foldRForLoop(loop) {
103
+ return this.concatAll([this.foldSingle(loop.variable), this.foldSingle(loop.vector), this.foldSingle(loop.body)]);
104
+ }
105
+ foldRFunctionCall(call) {
106
+ return this.concat(this.foldSingle(call.named ? call.functionName : call.calledFunction), this.fold(call.arguments));
107
+ }
108
+ foldRFunctionDefinition(definition) {
109
+ return this.concat(this.fold(definition.parameters), this.foldSingle(definition.body));
110
+ }
111
+ foldRIfThenElse(ite) {
112
+ return this.concatAll([this.foldSingle(ite.condition), this.foldSingle(ite.then), this.fold(ite.otherwise)]);
113
+ }
114
+ foldRParameter(parameter) {
115
+ return this.concat(this.foldSingle(parameter.name), this.fold(parameter.defaultValue));
116
+ }
117
+ foldRPipe(pipe) {
118
+ return this.concat(this.foldSingle(pipe.lhs), this.foldSingle(pipe.rhs));
119
+ }
120
+ foldRRepeatLoop(loop) {
121
+ return this.foldSingle(loop.body);
122
+ }
123
+ foldRUnaryOp(unaryOp) {
124
+ return this.foldSingle(unaryOp.operand);
125
+ }
126
+ foldRWhileLoop(loop) {
127
+ return this.concat(this.foldSingle(loop.condition), this.foldSingle(loop.body));
128
+ }
129
+ foldRBreak(_node) {
130
+ return this.empty;
131
+ }
132
+ foldRComment(_node) {
133
+ return this.empty;
134
+ }
135
+ foldRLineDirective(_node) {
136
+ return this.empty;
137
+ }
138
+ foldRLogical(_node) {
139
+ return this.empty;
140
+ }
141
+ foldRNext(_node) {
142
+ return this.empty;
143
+ }
144
+ foldRNumber(_node) {
145
+ return this.empty;
146
+ }
147
+ foldRString(_node) {
148
+ return this.empty;
149
+ }
150
+ foldRSymbol(_node) {
151
+ return this.empty;
152
+ }
153
+ folds = {
154
+ [type_1.RType.Access]: n => this.foldRAccess(n),
155
+ [type_1.RType.Argument]: n => this.foldRArgument(n),
156
+ [type_1.RType.BinaryOp]: n => this.foldRBinaryOp(n),
157
+ [type_1.RType.Break]: n => this.foldRBreak(n),
158
+ [type_1.RType.Comment]: n => this.foldRComment(n),
159
+ [type_1.RType.ExpressionList]: n => this.foldRExpressionList(n),
160
+ [type_1.RType.ForLoop]: n => this.foldRForLoop(n),
161
+ [type_1.RType.FunctionCall]: n => this.foldRFunctionCall(n),
162
+ [type_1.RType.FunctionDefinition]: n => this.foldRFunctionDefinition(n),
163
+ [type_1.RType.IfThenElse]: n => this.foldRIfThenElse(n),
164
+ [type_1.RType.LineDirective]: n => this.foldRLineDirective(n),
165
+ [type_1.RType.Logical]: n => this.foldRLogical(n),
166
+ [type_1.RType.Next]: n => this.foldRNext(n),
167
+ [type_1.RType.Number]: n => this.foldRNumber(n),
168
+ [type_1.RType.Parameter]: n => this.foldRParameter(n),
169
+ [type_1.RType.Pipe]: n => this.foldRPipe(n),
170
+ [type_1.RType.RepeatLoop]: n => this.foldRRepeatLoop(n),
171
+ [type_1.RType.String]: n => this.foldRString(n),
172
+ [type_1.RType.Symbol]: n => this.foldRSymbol(n),
173
+ [type_1.RType.UnaryOp]: n => this.foldRUnaryOp(n),
174
+ [type_1.RType.WhileLoop]: n => this.foldRWhileLoop(n),
175
+ };
176
+ }
177
+ exports.DefaultNormalizedAstFold = DefaultNormalizedAstFold;
178
+ //# sourceMappingURL=normalized-ast-fold.js.map
package/cli/slicer-app.js CHANGED
@@ -29,7 +29,7 @@ async function getSlice() {
29
29
  (0, assert_1.guard)(options.input !== undefined, 'input must be given');
30
30
  (0, assert_1.guard)(options.criterion !== undefined, 'a slicing criterion must be given');
31
31
  await slicer.init(options['input-is-text']
32
- ? { request: 'text', content: options.input }
32
+ ? { request: 'text', content: options.input.replaceAll('\\n', '\n') }
33
33
  : { request: 'file', content: options.input }, options['no-magic-comments'] ? auto_select_defaults_1.doNotAutoSelect : (0, magic_comments_1.makeMagicCommentHandler)(auto_select_defaults_1.doNotAutoSelect));
34
34
  let mappedSlices = [];
35
35
  let reconstruct = undefined;
@@ -22,6 +22,16 @@ export interface Pipeline<T extends IPipelineStep = IPipelineStep> {
22
22
  * @see Pipeline for details
23
23
  */
24
24
  export type PipelineStepNames<P extends Pipeline> = PipelineStep<P>['name'];
25
+ /**
26
+ * Returns the steps included in the given pipeline.
27
+ * @example
28
+ * ```ts
29
+ * type Pipeline = typeof DEFAULT_DATAFLOW_PIPELINE
30
+ * // Pipeline is now Pipeline<step1 | step2 | ...>
31
+ * type Steps = PipelineStep<typeof DEFAULT_DATAFLOW_PIPELINE>
32
+ * // Steps is now just step1 | step2 | ...
33
+ * ```
34
+ */
25
35
  export type PipelineStep<P extends Pipeline> = P extends Pipeline<infer U> ? U : never;
26
36
  /**
27
37
  * Meta-information attached to every step result
@@ -35,10 +45,51 @@ export interface PipelinePerStepMetaInformation {
35
45
  readonly timing: number;
36
46
  };
37
47
  }
48
+ /**
49
+ * Returns the step with the given name from the given pipeline.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * type Foo = PipelineStepWithName<typeof DEFAULT_DATAFLOW_PIPELINE, 'parse'>
54
+ * // Foo is now only the "parse" step from the DEFAULT_DATAFLOW_PIPELINE
55
+ * ```
56
+ */
38
57
  export type PipelineStepWithName<P extends Pipeline, Name extends PipelineStepName> = P extends Pipeline<infer U> ? U extends IPipelineStep<Name> ? U : never : never;
58
+ /**
59
+ * Returns the processor function of the step with the given name from the given pipeline.
60
+ * @see {@link PipelineStepWithName}
61
+ */
39
62
  export type PipelineStepProcessorWithName<P extends Pipeline, Name extends PipelineStepName> = PipelineStepWithName<P, Name>['processor'];
63
+ /**
64
+ * Returns the printer function of the step with the given name from the given pipeline.
65
+ * @see {@link PipelineStepWithName}
66
+ */
40
67
  export type PipelineStepPrintersWithName<P extends Pipeline, Name extends PipelineStepName> = PipelineStepWithName<P, Name>['printer'];
68
+ /**
69
+ * Returns the output type of the step with the given name from the given pipeline.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * type Foo = PipelineStepOutputWithName<typeof DEFAULT_DATAFLOW_PIPELINE, 'parse'>
74
+ * // Foo contains the ParseStepOutput & PipelinePerStepMetaInformation type (ie the parse output and meta information)
75
+ * @see {@link PipelineStepWithName}
76
+ * ```
77
+ */
41
78
  export type PipelineStepOutputWithName<P extends Pipeline, Name extends PipelineStepName> = Awaited<ReturnType<PipelineStepProcessorWithName<P, Name>>> & PipelinePerStepMetaInformation;
79
+ /**
80
+ * Returns a union type that represents the required inputs to be passed to the given pipeline.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * type Foo = PipelineInput<typeof DEFAULT_DATAFLOW_PIPELINE>
85
+ * // Foo contains ParseRequiredInput & NormalizeRequiredInput
86
+ * ```
87
+ *
88
+ * In short, this can be useful whenever you want to describe _all_ inputs a complete
89
+ * pipeline needs to run through (i.e., the union of all inputs required by the individual steps).
90
+ *
91
+ * @see {@link PipelineOutput}
92
+ */
42
93
  export type PipelineInput<P extends Pipeline> = UnionToIntersection<PipelineStep<P>['requiredInput']>;
43
94
  /**
44
95
  * Only gets the union of 'requiredInput' of those PipelineSteps which have a 'execute' field of type 'OncePerRequest'.
@@ -47,6 +98,18 @@ export type PipelineInput<P extends Pipeline> = UnionToIntersection<PipelineStep
47
98
  export type PipelinePerRequestInput<P extends Pipeline> = {
48
99
  [K in PipelineStepNames<P>]: PipelineStepWithName<P, K>['executed'] extends PipelineStepStage.OncePerFile ? never : PipelineStepWithName<P, K>['requiredInput'];
49
100
  }[PipelineStepNames<P>];
101
+ /**
102
+ * Returns an object type that represents the types of the outputs that will result from running the given pipeline, each as the step's name mapped to its PipelineStepOutputWithName.
103
+ * @example
104
+ * ```ts
105
+ * type Foo = PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>
106
+ * // Foo contains {
107
+ * // parse: ParseStepOutput & PipelinePerStepMetaInformation,
108
+ * // normalize: NormalizeStepOutput & PipelinePerStepMetaInformation,
109
+ * // ...
110
+ * // }
111
+ * ```
112
+ */
50
113
  export type PipelineOutput<P extends Pipeline> = {
51
114
  [K in PipelineStepNames<P>]: PipelineStepOutputWithName<P, K>;
52
115
  };
@@ -83,7 +83,7 @@ exports.DefaultBuiltinConfig = [
83
83
  /* downloader and installer functions (R, devtools, BiocManager) */
84
84
  'library.dynam', 'install.packages', 'install', 'install_github', 'install_gitlab', 'install_bitbucket', 'install_url', 'install_git', 'install_svn', 'install_local', 'install_version', 'update_packages',
85
85
  /* weird env attachments */
86
- 'attach', 'detach', 'unname', 'rm', 'remove'
86
+ 'attach', 'unname'
87
87
  ],
88
88
  processor: 'builtin:default',
89
89
  config: { hasUnknownSideEffects: true },
@@ -13,3 +13,8 @@ import { ReferenceType } from './identifier';
13
13
  */
14
14
  export declare function resolveByName(name: Identifier, environment: REnvironmentInformation, target?: ReferenceType): IdentifierDefinition[] | undefined;
15
15
  export declare function resolvesToBuiltInConstant(name: Identifier | undefined, environment: REnvironmentInformation, wantedValue: unknown): Ternary;
16
+ export interface ResolveResult<T = unknown> {
17
+ value: T;
18
+ from: ReferenceType;
19
+ }
20
+ export declare function resolveToConstants(name: Identifier | undefined, environment: REnvironmentInformation): ResolveResult[] | undefined;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveByName = resolveByName;
4
4
  exports.resolvesToBuiltInConstant = resolvesToBuiltInConstant;
5
+ exports.resolveToConstants = resolveToConstants;
5
6
  const environment_1 = require("./environment");
6
7
  const identifier_1 = require("./identifier");
7
8
  const info_1 = require("../info");
@@ -80,4 +81,17 @@ function resolvesToBuiltInConstant(name, environment, wantedValue) {
80
81
  return some ? 1 /* Ternary.Maybe */ : 2 /* Ternary.Never */;
81
82
  }
82
83
  }
84
+ function resolveToConstants(name, environment) {
85
+ if (name === undefined) {
86
+ return undefined;
87
+ }
88
+ const definitions = resolveByName(name, environment, identifier_1.ReferenceType.Constant);
89
+ if (definitions === undefined) {
90
+ return undefined;
91
+ }
92
+ return definitions.map(def => ({
93
+ value: def.value,
94
+ from: def.type
95
+ }));
96
+ }
83
97
  //# sourceMappingURL=resolve-by-name.js.map
@@ -53,7 +53,7 @@ function updateSideEffectsForCalledFunctions(calledEnvs, inputEnvironment, nextG
53
53
  for (const { functionCall, called } of calledEnvs) {
54
54
  const callDependencies = nextGraph.getVertex(functionCall, true)?.controlDependencies;
55
55
  for (const calledFn of called) {
56
- (0, assert_1.guard)(calledFn.tag === vertex_1.VertexType.FunctionDefinition, 'called function must call a function definition');
56
+ (0, assert_1.guard)(calledFn.tag === vertex_1.VertexType.FunctionDefinition, 'called function must be a function definition');
57
57
  // only merge the environments they have in common
58
58
  let environment = calledFn.environment;
59
59
  while (environment.level > inputEnvironment.level) {
@@ -33,32 +33,42 @@ 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 conditionIsFalse = (0, resolve_by_name_1.resolvesToBuiltInConstant)(condArg?.lexeme, data.environment, false);
37
- const conditionIsTrue = (0, resolve_by_name_1.resolvesToBuiltInConstant)(condArg?.lexeme, data.environment, true);
38
- if (conditionIsFalse !== 0 /* Ternary.Always */) {
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;
39
+ if (!conditionIsAlwaysFalse) {
39
40
  then = (0, processor_1.processDataflowFor)(thenArg, data);
40
41
  if (then.entryPoint) {
41
42
  then.graph.addEdge(name.info.id, then.entryPoint, edge_1.EdgeType.Returns);
42
43
  }
43
- if (conditionIsTrue !== 0 /* Ternary.Always */) {
44
+ if (!conditionIsAlwaysTrue) {
44
45
  makeThenMaybe = true;
45
46
  }
46
47
  }
47
48
  let otherwise;
48
49
  let makeOtherwiseMaybe = false;
49
- if (otherwiseArg !== undefined && conditionIsTrue !== 0 /* Ternary.Always */) {
50
+ if (otherwiseArg !== undefined && !conditionIsAlwaysTrue) {
50
51
  otherwise = (0, processor_1.processDataflowFor)(otherwiseArg, data);
51
52
  if (otherwise.entryPoint) {
52
53
  otherwise.graph.addEdge(name.info.id, otherwise.entryPoint, edge_1.EdgeType.Returns);
53
54
  }
54
- if (conditionIsFalse !== 0 /* Ternary.Always */) {
55
+ if (!conditionIsAlwaysFalse) {
55
56
  makeOtherwiseMaybe = true;
56
57
  }
57
58
  }
58
59
  const nextGraph = cond.graph.mergeWith(then?.graph).mergeWith(otherwise?.graph);
59
60
  const thenEnvironment = then?.environment ?? cond.environment;
60
61
  // if there is no "else" case, we have to recover whatever we had before as it may be not executed
61
- const finalEnvironment = (0, append_1.appendEnvironment)(thenEnvironment, otherwise ? otherwise.environment : cond.environment);
62
+ let finalEnvironment;
63
+ if (conditionIsAlwaysFalse) {
64
+ finalEnvironment = otherwise ? otherwise.environment : cond.environment;
65
+ }
66
+ else if (conditionIsAlwaysTrue) {
67
+ finalEnvironment = thenEnvironment;
68
+ }
69
+ else {
70
+ finalEnvironment = (0, append_1.appendEnvironment)(thenEnvironment, otherwise ? otherwise.environment : cond.environment);
71
+ }
62
72
  const cdTrue = { id: rootId, when: true };
63
73
  const cdFalse = { id: rootId, when: false };
64
74
  // again within an if-then-else we consider all actives to be read
@@ -1,4 +1,4 @@
1
- import type { DataflowGraph } from '../../dataflow/graph/graph';
1
+ import type { DataflowGraph, UnknownSidEffect } from '../../dataflow/graph/graph';
2
2
  import type { RShell } from '../../r-bridge/shell';
3
3
  import type { MermaidMarkdownMark } from '../../util/mermaid/dfg';
4
4
  import { DEFAULT_DATAFLOW_PIPELINE } from '../../core/steps/pipeline/default-pipelines';
@@ -12,6 +12,7 @@ export interface PrintDataflowGraphOptions {
12
12
  readonly switchCodeAndGraph?: boolean;
13
13
  readonly hideEnvInMermaid?: boolean;
14
14
  }
15
+ export declare function formatSideEffect(ef: UnknownSidEffect): string;
15
16
  export declare function printDfGraphForCode(shell: RShell, code: string, options: PrintDataflowGraphOptions & {
16
17
  exposeResult: true;
17
18
  }): Promise<[string, PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>]>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.printDfGraph = printDfGraph;
4
+ exports.formatSideEffect = formatSideEffect;
4
5
  exports.printDfGraphForCode = printDfGraphForCode;
5
6
  exports.verifyExpectedSubgraph = verifyExpectedSubgraph;
6
7
  const dfg_1 = require("../../util/mermaid/dfg");
@@ -11,7 +12,6 @@ const decorate_1 = require("../../r-bridge/lang-4.x/ast/model/processing/decorat
11
12
  const resolve_graph_1 = require("../../dataflow/graph/resolve-graph");
12
13
  const diff_1 = require("../../dataflow/graph/diff");
13
14
  const assert_1 = require("../../util/assert");
14
- const json_1 = require("../../util/json");
15
15
  const time_1 = require("../../util/time");
16
16
  function printDfGraph(graph, mark) {
17
17
  return `
@@ -24,6 +24,14 @@ ${(0, dfg_1.graphToMermaid)({
24
24
  \`\`\`
25
25
  `;
26
26
  }
27
+ function formatSideEffect(ef) {
28
+ if (typeof ef === 'object') {
29
+ return `${ef.id} (linked)`;
30
+ }
31
+ else {
32
+ return `${ef}`;
33
+ }
34
+ }
27
35
  async function printDfGraphForCode(shell, code, { mark, showCode = true, codeOpen = false, exposeResult, switchCodeAndGraph = false, hideEnvInMermaid = false } = {}) {
28
36
  const now = performance.now();
29
37
  const result = await new pipeline_executor_1.PipelineExecutor(default_pipelines_1.DEFAULT_DATAFLOW_PIPELINE, {
@@ -48,7 +56,7 @@ ${code}
48
56
  <summary style="color:gray">${switchCodeAndGraph ? 'Dataflow Graph of the R Code' : 'R Code of the Dataflow Graph'}</summary>
49
57
 
50
58
  ${metaInfo} ${mark ? `The following marks are used in the graph to highlight sub-parts (uses ids): {${[...mark].join(', ')}}.` : ''}
51
- We encountered ${result.dataflow.graph.unknownSideEffects.size > 0 ? 'unknown side effects (with ids: ' + JSON.stringify(result.dataflow.graph.unknownSideEffects, json_1.jsonReplacer) + ')' : 'no unknown side effects'} during the analysis.
59
+ We encountered ${result.dataflow.graph.unknownSideEffects.size > 0 ? 'unknown side effects (with ids: ' + [...result.dataflow.graph.unknownSideEffects].map(formatSideEffect).join(', ') + ')' : 'no unknown side effects'} during the analysis.
52
60
 
53
61
  ${switchCodeAndGraph ? dfGraph : codeText}
54
62
 
@@ -26,7 +26,7 @@ async function printNormalizedAstForCode(shell, code, { showCode = true, prefix
26
26
  request: (0, retriever_1.requestFromInput)(code)
27
27
  }).allRemainingSteps();
28
28
  const duration = performance.now() - now;
29
- const metaInfo = `The analysis required _${(0, time_1.printAsMs)(duration)}_ (including parsing) within the generation environment.`;
29
+ const metaInfo = `The analysis required _${(0, time_1.printAsMs)(duration)}_ (including parsing with the R&nbsp;shell) within the generation environment.`;
30
30
  return '\n\n' + printNormalizedAst(result.normalize.ast, prefix) + (showCode ? `
31
31
  <details>
32
32
 
@@ -2,7 +2,7 @@ import ts from 'typescript';
2
2
  export interface TypeElementInSource {
3
3
  name: string;
4
4
  node: ts.Node;
5
- kind: 'interface' | 'type' | 'enum';
5
+ kind: 'interface' | 'type' | 'enum' | 'class';
6
6
  extends: string[];
7
7
  generics: string[];
8
8
  filePath: string;
@@ -150,6 +150,27 @@ function collectHierarchyInformation(sourceFiles, options) {
150
150
  })
151
151
  });
152
152
  }
153
+ else if (typescript_1.default.isClassDeclaration(node)) {
154
+ const className = node.name?.getText(sourceFile) ?? '';
155
+ const baseTypes = node.heritageClauses?.flatMap(clause => clause.types
156
+ .map(type => type.getText(sourceFile) ?? '')
157
+ .map(dropGenericsFromType)) ?? [];
158
+ const generics = node.typeParameters?.map(param => param.getText(sourceFile) ?? '') ?? [];
159
+ hierarchyList.push({
160
+ name: dropGenericsFromType(className),
161
+ node,
162
+ kind: 'class',
163
+ extends: baseTypes,
164
+ comments: getTextualComments(node),
165
+ generics,
166
+ filePath: sourceFile.fileName,
167
+ lineNumber: getStartLine(node, sourceFile),
168
+ properties: node.members.map(member => {
169
+ const name = member.name?.getText(sourceFile) ?? '';
170
+ return `${name}${(0, mermaid_1.escapeMarkdown)(': ' + getType(member, typeChecker))}`;
171
+ }),
172
+ });
173
+ }
153
174
  typescript_1.default.forEachChild(node, child => visit(child, sourceFile));
154
175
  };
155
176
  sourceFiles.forEach(sourceFile => {
@@ -841,6 +841,24 @@ ${await (0, doc_dfg_1.printDfGraphForCode)(shell, 'load("file")\nprint(x + y)')}
841
841
 
842
842
  In general, as we cannot handle these correctly, we leave it up to other analyses (and [queries](${doc_files_1.FlowrWikiBaseRef}/Query%20API)) to handle these cases
843
843
  as they see fit.
844
+
845
+ #### Linked Unknown Side Effects
846
+
847
+ Not all side effects are created equal in the sense that they stem from a specific function call.
848
+ Consider R's basic [\`graphics\`](https://www.rdocumentation.org/packages/graphics/) which
849
+ implicitly draws on the current device and does not explicitly link a function like \`points\` to the last call opening a new graphic device. In such a scenario, we use a linked side effect to mark the relation:
850
+
851
+ ${await (async () => {
852
+ const [result, df] = await (0, doc_dfg_1.printDfGraphForCode)(shell, 'plot(data)\npoints(data2)', { exposeResult: true });
853
+ return `
854
+ ${result}
855
+
856
+ Such side effects are not marked explicitly (with a big edge) but they are part of the unknown side effects: [${[...df.dataflow.graph.unknownSideEffects].map(doc_dfg_1.formatSideEffect).join(',')}].
857
+ Additionally, we express this by a ${linkEdgeName(edge_1.EdgeType.Reads)} edge.
858
+ `;
859
+ })()}
860
+
861
+
844
862
  `;
845
863
  })()}
846
864
 
@@ -13,11 +13,18 @@ const path_1 = __importDefault(require("path"));
13
13
  const doc_files_1 = require("./doc-util/doc-files");
14
14
  const doc_cli_option_1 = require("./doc-util/doc-cli-option");
15
15
  const time_1 = require("../util/time");
16
+ const doc_structure_1 = require("./doc-util/doc-structure");
17
+ const pipeline_executor_1 = require("../core/pipeline-executor");
18
+ const retriever_1 = require("../r-bridge/retriever");
19
+ const visitor_1 = require("../r-bridge/lang-4.x/ast/model/processing/visitor");
20
+ const collect_1 = require("../r-bridge/lang-4.x/ast/model/collect");
21
+ const normalized_ast_fold_1 = require("../abstract-interpretation/normalized-ast-fold");
16
22
  async function getText(shell) {
17
23
  const rversion = (await shell.usedRVersion())?.format() ?? 'unknown';
18
24
  const now = performance.now();
19
25
  const types = (0, doc_types_1.getTypesFromFolderAsMermaid)({
20
26
  rootFolder: path_1.default.resolve('./src/r-bridge/lang-4.x/ast/model/'),
27
+ files: [path_1.default.resolve('./src/abstract-interpretation/normalized-ast-fold.ts')],
21
28
  typeName: 'RNode',
22
29
  inlineTypes: doc_types_1.mermaidHide
23
30
  });
@@ -40,7 +47,7 @@ ${(0, doc_code_1.codeBlock)('r', 'x <- 2 * 3 + 1')}
40
47
  Each node in the AST contains the type, the id, and the lexeme (if applicable).
41
48
  Each edge is labeled with the type of the parent-child relationship (the "role").
42
49
 
43
- ${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, 'x <- 2 * 3 + 1')}
50
+ ${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, 'x <- 2 * 3 + 1', { showCode: false, prefix: 'flowchart LR\n' })}
44
51
 
45
52
  &nbsp;
46
53
 
@@ -49,7 +56,7 @@ ${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, 'x <- 2 * 3 +
49
56
  > you can either use the [Visual Studio Code extension](${doc_files_1.FlowrGithubBaseRef}/vscode-flowr) or the ${(0, doc_cli_option_1.getReplCommand)('normalize*')}
50
57
  > command in the REPL (see the [Interface wiki page](${doc_files_1.FlowrWikiBaseRef}/Interface) for more information).
51
58
 
52
- Indicative is the root expression list node, which is present in every normalized AST.
59
+ Indicative of the normalization is the root expression list node, which is present in every normalized AST.
53
60
  In general, we provide node types for:
54
61
 
55
62
  1. literals (e.g., numbers and strings)
@@ -77,11 +84,106 @@ Most notably, the \`info\` field holds the \`id\` of the node, which is used to
77
84
 
78
85
  In summary, we have the following types:
79
86
 
80
- ${(0, doc_types_1.printHierarchy)({ program: types.program, hierarchy: types.info, root: 'RNode', collapseFromNesting: Number.MAX_VALUE })}
87
+ ${(0, doc_structure_1.details)('Normalized AST Node Types', (0, doc_types_1.printHierarchy)({ program: types.program, hierarchy: types.info, root: 'RNode', collapseFromNesting: Number.MAX_VALUE }))}
81
88
 
82
- With this, the example file produced the following AST (shown from left to right for space reasons):
89
+ The following segments intend to give you an overview of how to work with the normalized AST:
83
90
 
84
- ${await (0, doc_normalized_ast_1.printNormalizedAstForCode)(shell, (0, doc_files_1.getFileContentFromRoot)('test/testfiles/example.R'), { prefix: 'flowchart LR\n' })}
91
+ * [How to get a normalized AST](#how-get-a-normalized-ast)
92
+ * [Visitors and Folds](#visitors-and-folds)
93
+
94
+ ## How Get a Normalized AST
95
+
96
+ As explained alongside the [Interface](${doc_files_1.FlowrWikiBaseRef}/Interface#the-pipeline-executor) wiki page, you can use the
97
+ \`${pipeline_executor_1.PipelineExecutor.name}\` to get the normalized AST. If you are only interested in the normalization,
98
+ a pipeline like the \`DEFAULT_NORMALIZE_PIPELINE\` suffices:
99
+
100
+ ${(0, doc_code_1.codeBlock)('ts', `
101
+ async function getAst(code: string): Promise<RNode> {
102
+ const result = await new ${pipeline_executor_1.PipelineExecutor.name}(DEFAULT_NORMALIZE_PIPELINE, {
103
+ shell: new ${shell_1.RShell.name}(),
104
+ request: ${retriever_1.requestFromInput.name}(code.trim())
105
+ }).allRemainingSteps();
106
+ return result.normalize.ast;
107
+ }`)}
108
+
109
+ From the REPL, you can use the ${(0, doc_cli_option_1.getReplCommand)('normalize')} command.
110
+
111
+ ## Traversing the Normalized AST
112
+
113
+ We provide two ways to traverse the normalized AST: [Visitors](#visitors) and [Folds](#folds).
114
+
115
+ ### Visitors
116
+
117
+ If you want a simple visitor which traverses the AST, the \`${visitor_1.visitAst.name}\` function from
118
+ ${(0, doc_files_1.getFilePathMd)('../r-bridge/lang-4.x/ast/model/processing/visitor.ts')} is a good starting point.
119
+ You may specify functions to be called whenever you enter and exit a node during the traversal, and any
120
+ computation is to be done by side effects.
121
+ For example, if you want to collect all the \`id\`s present within a normalized (sub-)ast,
122
+ as it is done by the ${collect_1.collectAllIds.name} function, you can use the following visitor:
123
+
124
+ ${(0, doc_code_1.codeBlock)('ts', `
125
+ const ids = new Set<NodeId>();
126
+ visitAst(nodes, node => {
127
+ ids.add(node.info.id);
128
+ });
129
+ return ids;
130
+ `)}
131
+
132
+ ### Folds
133
+
134
+ We formulate a fold with the base class \`${normalized_ast_fold_1.DefaultNormalizedAstFold.name}\` in ${(0, doc_files_1.getFilePathMd)('../abstract-interpretation/normalized-ast-fold.ts')}.
135
+ Using this class, you can create your own fold behavior by overwriting the default methods.
136
+ By default, the class provides a monoid abstraction using the _empty_ from the constructor and the _concat_ method.
137
+
138
+
139
+ ${(0, doc_types_1.printHierarchy)({ program: types.program, hierarchy: types.info, root: 'DefaultNormalizedAstFold' })}
140
+
141
+ Now, of course, we could provide hundreds of examples here, but we use tests to verify that the fold behaves as expected
142
+ and happily point to them at ${(0, doc_files_1.getFilePathMd)('../../test/functionality/r-bridge/normalize-ast-fold.test.ts')}.
143
+
144
+ As a simple showcase, we want to use the fold to evaluate numeric expressions containing numbers, \`+\`, and \`*\` operators.
145
+
146
+ ${(0, doc_code_1.codeBlock)('ts', `
147
+ class MyMathFold<Info> extends ${normalized_ast_fold_1.DefaultNormalizedAstFold.name}<number, Info> {
148
+ constructor() {
149
+ /* use \`0\` as a placeholder empty for the monoid */
150
+ super(0);
151
+ }
152
+
153
+ protected override concat(a: number, b: number): number {
154
+ /* for this example, we ignore cases that we cannot handle */
155
+ return b;
156
+ }
157
+
158
+ override foldRNumber(node: RNumber<Info>) {
159
+ /* return the value of the number */
160
+ return node.content.num;
161
+ }
162
+
163
+ override foldRBinaryOp(node: RBinaryOp<Info>) {
164
+ if(node.operator === '+') {
165
+ return this.fold(node.lhs) + this.fold(node.rhs);
166
+ } else if(node.operator === '*') {
167
+ return this.fold(node.lhs) * this.fold(node.rhs);
168
+ } else {
169
+ /* in case we cannot handle the operator we could throw an error, or just use the default behavior: */
170
+ return super.foldRBinaryOp(node);
171
+ }
172
+ }
173
+ }
174
+ `)}
175
+
176
+ Now, we can use the \`${pipeline_executor_1.PipelineExecutor.name}\` to get the normalized AST and apply the fold:
177
+
178
+ ${(0, doc_code_1.codeBlock)('ts', `
179
+ const shell = new ${shell_1.RShell.name}();
180
+ const ast = (await new ${pipeline_executor_1.PipelineExecutor.name}(DEFAULT_NORMALIZE_PIPELINE, {
181
+ shell, request: retrieveNormalizedAst(${shell_1.RShell.name}, '1 + 3 * 2')
182
+ }).allRemainingSteps()).normalize.ast;
183
+
184
+ const result = new MyMathFold().fold(ast);
185
+ console.log(result); // -> 7
186
+ `)}
85
187
 
86
188
  `;
87
189
  }
@@ -47,6 +47,13 @@ Besides this, we provide the following ways to automatically categorize and link
47
47
  For now, we _only_ offer support for linking to the last call, as the current flow dependency over-approximation is not stable.
48
48
  4. **Aliases** (\`includeAliases\`): Consider a case like \`f <- function_of_interest\`, do you want calls to \`f\` to be included in the results? There is probably no need to combine this with a global call target!
49
49
 
50
+ It's also possible to filter the results based on the following properties:
51
+
52
+ 1. **File** (\`fileFilter\`): This allows you to filter the results based on the file in which the call is located. This can be useful if you are only interested in calls in, e.g., specific folders.
53
+ The \`fileFilter\` property is an object made up of two properties:
54
+ - **Filter** (\`filter\`): A regular expression that a node's file attribute must match to be considered.
55
+ - **Include Undefined Files** (\`includeUndefinedFiles\`): If \`fileFilter\` is set, but a node's file attribute is not present, should we include it in the results? Defaults to \`true\`.
56
+
50
57
  Re-using the example code from above, the following query attaches all calls to \`mean\` to the kind \`visualize\` and the subkind \`text\`,
51
58
  all calls that start with \`read_\` to the kind \`input\` but only if they are not locally overwritten, and the subkind \`csv-file\`, and links all calls to \`points\` to the last call to \`plot\`:
52
59
 
@@ -259,7 +266,7 @@ Now, the results no longer contain calls to \`plot\` that are not defined locall
259
266
  name: 'Static Slice Query',
260
267
  type: 'active',
261
268
  shortDescription: 'Slice the dataflow graph reducing the code to just the parts relevant for the given criteria.',
262
- functionName: static_slice_query_executor_1.executeStaticSliceClusterQuery.name,
269
+ functionName: static_slice_query_executor_1.executeStaticSliceQuery.name,
263
270
  functionFile: '../queries/catalog/static-slice-query/static-slice-query-executor.ts',
264
271
  buildExplanation: async (shell) => {
265
272
  const exampleCode = 'x <- 1\ny <- 2\nx';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.1.6",
3
+ "version": "2.1.8",
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": {
@@ -41,7 +41,7 @@
41
41
  "performance-test": "func() { cd test/performance/ && bash run-all-suites.sh $1 $2 $3; cd ../../; }; func",
42
42
  "test-full": "npm run test:coverage -- --no-watch -- --make-summary --test-installation",
43
43
  "detect-circular-deps": "npx madge --extensions ts,tsx --circular src/",
44
- "checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --forbid-only && docker build -t test-flowr -f scripts/Dockerfile . && npm run doc && npm-run-all wiki:*"
44
+ "checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --allowOnly=false && docker build -t test-flowr -f scripts/Dockerfile . && npm run doc && npm-run-all wiki:*"
45
45
  },
46
46
  "keywords": [
47
47
  "static code analysis",
@@ -51,6 +51,10 @@ function promoteQueryCallNames(queries) {
51
51
  ...q,
52
52
  callName: q.callNameExact ? exactCallNameRegex(q.callName)
53
53
  : new RegExp(q.callName),
54
+ fileFilter: q.fileFilter && {
55
+ ...q.fileFilter,
56
+ filter: new RegExp(q.fileFilter.filter)
57
+ },
54
58
  linkTo: {
55
59
  ...q.linkTo,
56
60
  /* we have to add another promotion layer whenever we add something without this call name */
@@ -62,7 +66,11 @@ function promoteQueryCallNames(queries) {
62
66
  return {
63
67
  ...q,
64
68
  callName: q.callNameExact ? exactCallNameRegex(q.callName)
65
- : new RegExp(q.callName)
69
+ : new RegExp(q.callName),
70
+ fileFilter: q.fileFilter && {
71
+ ...q.fileFilter,
72
+ filter: new RegExp(q.fileFilter.filter)
73
+ }
66
74
  };
67
75
  }
68
76
  });
@@ -132,6 +140,15 @@ function removeIdenticalDuplicates(collector) {
132
140
  }
133
141
  }
134
142
  }
143
+ function doesFilepathMatch(file, filter) {
144
+ if (filter === undefined) {
145
+ return true;
146
+ }
147
+ if (file === undefined) {
148
+ return filter.includeUndefinedFiles ?? true;
149
+ }
150
+ return filter.filter.test(file);
151
+ }
135
152
  /**
136
153
  * Multi-stage call context query resolve.
137
154
  *
@@ -174,6 +191,10 @@ function executeCallContextQueries({ graph, ast }, queries) {
174
191
  }
175
192
  }
176
193
  for (const query of promotedQueries.filter(q => q.callName.test(info.name))) {
194
+ const file = ast.idMap.get(nodeId)?.info.file;
195
+ if (!doesFilepathMatch(file, query.fileFilter)) {
196
+ continue;
197
+ }
177
198
  let targets = undefined;
178
199
  if (query.callTargets) {
179
200
  targets = (0, identify_link_to_last_call_relation_1.satisfiesCallTargets)(nodeId, graph, query.callTargets);
@@ -6,10 +6,20 @@ import Joi from 'joi';
6
6
  import type { PipelineOutput } from '../../../core/steps/pipeline/pipeline';
7
7
  import type { DEFAULT_DATAFLOW_PIPELINE } from '../../../core/steps/pipeline/default-pipelines';
8
8
  import { CallTargets } from './identify-link-to-last-call-relation';
9
- export interface DefaultCallContextQueryFormat<CallName extends RegExp | string> extends BaseQueryFormat {
9
+ export interface FileFilter<FilterType> {
10
+ /**
11
+ * Regex that a node's file attribute must match to be considered
12
+ */
13
+ readonly filter: FilterType;
14
+ /**
15
+ * If `fileFilter` is set, but a nodes `file` attribute is `undefined`, should we include it in the results? Defaults to `true`.
16
+ */
17
+ readonly includeUndefinedFiles?: boolean;
18
+ }
19
+ export interface DefaultCallContextQueryFormat<RegexType extends RegExp | string> extends BaseQueryFormat {
10
20
  readonly type: 'call-context';
11
21
  /** Regex regarding the function name, please note that strings will be interpreted as regular expressions too! */
12
- readonly callName: CallName;
22
+ readonly callName: RegexType;
13
23
  /**
14
24
  * Should we automatically add the `^` and `$` anchors to the regex to make it an exact match?
15
25
  */
@@ -27,6 +37,10 @@ export interface DefaultCallContextQueryFormat<CallName extends RegExp | string>
27
37
  * Consider a case like `f <- function_of_interest`, do you want uses of `f` to be included in the results?
28
38
  */
29
39
  readonly includeAliases?: boolean;
40
+ /**
41
+ * Filter that, when set, a node's file attribute must match to be considered
42
+ */
43
+ readonly fileFilter?: FileFilter<RegexType>;
30
44
  }
31
45
  /**
32
46
  * Links the current call to the last call of the given kind.
@@ -26,6 +26,10 @@ exports.CallContextQueryDefinition = {
26
26
  subkind: joi_1.default.string().optional().description('The subkind of the call, this can be used to uniquely identify the respective call type when grouping the output (e.g., the normalized name, linking `ggplot` to `plot`). Defaults to `.`'),
27
27
  callTargets: joi_1.default.string().valid(...Object.values(identify_link_to_last_call_relation_1.CallTargets)).optional().description('Call targets the function may have. This defaults to `any`. Request this specifically to gain all call targets we can resolve.'),
28
28
  includeAliases: joi_1.default.boolean().optional().description('Consider a case like `f <- function_of_interest`, do you want uses of `f` to be included in the results?'),
29
+ fileFilter: joi_1.default.object({
30
+ fileFilter: joi_1.default.string().required().description('Regex that a node\'s file attribute must match to be considered'),
31
+ includeUndefinedFiles: joi_1.default.boolean().optional().description('If `fileFilter` is set, but a nodes `file` attribute is `undefined`, should we include it in the results? Defaults to `true`.')
32
+ }).optional().description('Filter that, when set, a node\'s file attribute must match to be considered'),
29
33
  linkTo: joi_1.default.object({
30
34
  type: joi_1.default.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
31
35
  callName: joi_1.default.string().required().description('Regex regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression.')
@@ -1,4 +1,4 @@
1
1
  import type { StaticSliceQuery, StaticSliceQueryResult } from './static-slice-query-format';
2
2
  import type { BasicQueryData } from '../../base-query-format';
3
3
  export declare function fingerPrintOfQuery(query: StaticSliceQuery): string;
4
- export declare function executeStaticSliceClusterQuery({ graph, ast }: BasicQueryData, queries: readonly StaticSliceQuery[]): StaticSliceQueryResult;
4
+ export declare function executeStaticSliceQuery({ graph, ast }: BasicQueryData, queries: readonly StaticSliceQuery[]): StaticSliceQueryResult;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fingerPrintOfQuery = fingerPrintOfQuery;
4
- exports.executeStaticSliceClusterQuery = executeStaticSliceClusterQuery;
4
+ exports.executeStaticSliceQuery = executeStaticSliceQuery;
5
5
  const static_slicer_1 = require("../../../slicing/static/static-slicer");
6
6
  const reconstruct_1 = require("../../../reconstruct/reconstruct");
7
7
  const auto_select_defaults_1 = require("../../../reconstruct/auto-select/auto-select-defaults");
@@ -10,7 +10,7 @@ const log_1 = require("../../../util/log");
10
10
  function fingerPrintOfQuery(query) {
11
11
  return JSON.stringify(query);
12
12
  }
13
- function executeStaticSliceClusterQuery({ graph, ast }, queries) {
13
+ function executeStaticSliceQuery({ graph, ast }, queries) {
14
14
  const start = Date.now();
15
15
  const results = {};
16
16
  for (const query of queries) {
@@ -3,7 +3,7 @@ import type { PipelineOutput } from '../../../core/steps/pipeline/pipeline';
3
3
  import type { DEFAULT_DATAFLOW_PIPELINE, DEFAULT_SLICE_WITHOUT_RECONSTRUCT_PIPELINE, DEFAULT_SLICING_PIPELINE } from '../../../core/steps/pipeline/default-pipelines';
4
4
  import type { SlicingCriteria } from '../../../slicing/criterion/parse';
5
5
  import Joi from 'joi';
6
- import { executeStaticSliceClusterQuery } from './static-slice-query-executor';
6
+ import { executeStaticSliceQuery } from './static-slice-query-executor';
7
7
  /** Calculates and returns all clusters encountered in the dataflow graph. */
8
8
  export interface StaticSliceQuery extends BaseQueryFormat {
9
9
  readonly type: 'static-slice';
@@ -25,7 +25,7 @@ export interface StaticSliceQueryResult extends BaseQueryResult {
25
25
  results: Record<string, Omit<PipelineOutput<typeof DEFAULT_SLICING_PIPELINE>, keyof PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>> | Omit<PipelineOutput<typeof DEFAULT_SLICE_WITHOUT_RECONSTRUCT_PIPELINE>, keyof PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>>>;
26
26
  }
27
27
  export declare const StaticSliceQueryDefinition: {
28
- readonly executor: typeof executeStaticSliceClusterQuery;
28
+ readonly executor: typeof executeStaticSliceQuery;
29
29
  readonly asciiSummarizer: (formatter: import("../../../util/ansi").OutputFormatter, _processed: PipelineOutput<import("../../../core/steps/pipeline/pipeline").Pipeline<{
30
30
  readonly name: "parse";
31
31
  readonly humanReadableName: "parse with R shell";
@@ -10,7 +10,7 @@ const joi_1 = __importDefault(require("joi"));
10
10
  const static_slice_query_executor_1 = require("./static-slice-query-executor");
11
11
  const query_print_1 = require("../../query-print");
12
12
  exports.StaticSliceQueryDefinition = {
13
- executor: static_slice_query_executor_1.executeStaticSliceClusterQuery,
13
+ executor: static_slice_query_executor_1.executeStaticSliceQuery,
14
14
  asciiSummarizer: (formatter, _processed, queryResults, result) => {
15
15
  const out = queryResults;
16
16
  result.push(`Query: ${(0, ansi_1.bold)('static-slice', formatter)} (${(0, time_1.printAsMs)(out['.meta'].timing, 0)})`);
@@ -262,7 +262,7 @@ export declare const SupportedQueries: {
262
262
  readonly schema: Joi.ObjectSchema<any>;
263
263
  };
264
264
  readonly 'static-slice': {
265
- readonly executor: typeof import("./catalog/static-slice-query/static-slice-query-executor").executeStaticSliceClusterQuery;
265
+ readonly executor: typeof import("./catalog/static-slice-query/static-slice-query-executor").executeStaticSliceQuery;
266
266
  readonly asciiSummarizer: (formatter: OutputFormatter, _processed: PipelineOutput<import("../core/steps/pipeline/pipeline").Pipeline<{
267
267
  readonly name: "parse";
268
268
  readonly humanReadableName: "parse with R shell";
@@ -4,7 +4,7 @@ export type OnEnter<OtherInfo> = (node: RNode<OtherInfo>) => (boolean | void);
4
4
  /** Similar to {@link OnEnter} but called when leaving a node. Can't stop exploration as the subtree is already visited! */
5
5
  export type OnExit<OtherInfo> = (node: RNode<OtherInfo>) => void;
6
6
  /**
7
- * Collects all node ids within a tree given by a respective root node
7
+ * Visits all node ids within a tree given by a respective root node using a depth-first search with prefix order.
8
8
  *
9
9
  * @param nodes - The root id nodes to start collecting from
10
10
  * @param onVisit - Called before visiting the subtree of each node. Can be used to stop visiting the subtree starting with this node (return `true` stop)
@@ -100,7 +100,7 @@ class NodeVisitor {
100
100
  }
101
101
  }
102
102
  /**
103
- * Collects all node ids within a tree given by a respective root node
103
+ * Visits all node ids within a tree given by a respective root node using a depth-first search with prefix order.
104
104
  *
105
105
  * @param nodes - The root id nodes to start collecting from
106
106
  * @param onVisit - Called before visiting the subtree of each node. Can be used to stop visiting the subtree starting with this node (return `true` stop)
@@ -10,10 +10,10 @@ exports.RootId = 0;
10
10
  function prepareParsedData(data) {
11
11
  let json;
12
12
  try {
13
- json = JSON.parse(`[${data}]`);
13
+ json = JSON.parse(`[${data.trim()}]`);
14
14
  }
15
15
  catch (e) {
16
- throw new Error(`Failed to parse data ${data}: ${e?.message}`);
16
+ throw new Error(`Failed to parse data [${data}]: ${e?.message}`);
17
17
  }
18
18
  (0, assert_1.guard)(Array.isArray(json), () => `Expected ${data} to be an array but was not`);
19
19
  const ret = new Map(json.map(([line1, col1, line2, col2, id, parent, token, terminal, text]) => {
@@ -277,7 +277,7 @@ function reconstructParameter(parameter, name, defaultValue, configuration) {
277
277
  return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`);
278
278
  }
279
279
  else if (parameter.defaultValue !== undefined && name.length === 0) {
280
- return plain(getLexeme(parameter.defaultValue));
280
+ return defaultValue ?? [];
281
281
  }
282
282
  else {
283
283
  return name;
@@ -15,13 +15,14 @@ const edge_1 = require("../../dataflow/graph/edge");
15
15
  const identifier_1 = require("../../dataflow/environments/identifier");
16
16
  function retrieveActiveEnvironment(callerInfo, baseEnvironment) {
17
17
  let callerEnvironment = callerInfo.environment;
18
- const level = callerEnvironment?.level ?? 0;
18
+ let level = callerEnvironment?.level ?? 0;
19
19
  if (baseEnvironment.level !== level) {
20
20
  while (baseEnvironment.level < level) {
21
21
  baseEnvironment = (0, scoping_1.pushLocalEnvironment)(baseEnvironment);
22
22
  }
23
23
  while (baseEnvironment.level > level) {
24
24
  callerEnvironment = (0, scoping_1.pushLocalEnvironment)(callerEnvironment ?? (0, environment_1.initializeCleanEnvironments)(true));
25
+ level = callerEnvironment.level;
25
26
  }
26
27
  }
27
28
  return (0, overwrite_1.overwriteEnvironment)(baseEnvironment, callerEnvironment);
package/util/assert.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare function isNotNull<T>(x: T | null): x is T;
6
6
  export type GuardMessage = string | (() => string);
7
7
  /**
8
8
  * @param assertion - will be asserted
9
- * @param message - if a string, will be used as error message, if a function, will be called to produce the error message (can be used to avoid costly message generations)
9
+ * @param message - if a string, we will use it as the error message, if it is a function, we will call it to produce the error message (can be used to avoid costly message generations)
10
10
  * @throws GuardError - if the assertion fails
11
11
  */
12
12
  export declare function guard(assertion: boolean | undefined, message?: GuardMessage): asserts assertion;
package/util/assert.js CHANGED
@@ -6,7 +6,7 @@ exports.isNotUndefined = isNotUndefined;
6
6
  exports.isUndefined = isUndefined;
7
7
  exports.isNotNull = isNotNull;
8
8
  exports.guard = guard;
9
- /* istanbul ignore next */
9
+ /* v8 ignore next */
10
10
  function assertUnreachable(x) {
11
11
  throw new Error(`Unexpected object: ${JSON.stringify(x)}`);
12
12
  }
@@ -27,10 +27,11 @@ class GuardError extends Error {
27
27
  }
28
28
  /**
29
29
  * @param assertion - will be asserted
30
- * @param message - if a string, will be used as error message, if a function, will be called to produce the error message (can be used to avoid costly message generations)
30
+ * @param message - if a string, we will use it as the error message, if it is a function, we will call it to produce the error message (can be used to avoid costly message generations)
31
31
  * @throws GuardError - if the assertion fails
32
32
  */
33
33
  function guard(assertion, message = 'Assertion failed') {
34
+ /* v8 ignore next 3 */
34
35
  if (!assertion) {
35
36
  throw new GuardError(typeof message === 'string' ? message : message());
36
37
  }
package/util/version.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.flowrVersion = flowrVersion;
4
4
  const semver_1 = require("semver");
5
5
  // this is automatically replaced with the current version by release-it
6
- const version = '2.1.6';
6
+ const version = '2.1.8';
7
7
  function flowrVersion() {
8
8
  return new semver_1.SemVer(version);
9
9
  }