@eagleoutice/flowr 2.2.4 → 2.2.6
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.
- package/README.md +9 -9
- package/cli/repl/commands/repl-cfg.js +4 -3
- package/cli/repl/commands/repl-dataflow.js +4 -3
- package/cli/repl/commands/repl-normalize.js +4 -3
- package/cli/repl/core.d.ts +1 -1
- package/cli/repl/core.js +23 -13
- package/config.d.ts +2 -2
- package/config.js +7 -1
- package/dataflow/environments/resolve-by-name.d.ts +24 -2
- package/dataflow/environments/resolve-by-name.js +85 -4
- package/documentation/print-query-wiki.js +6 -4
- package/documentation/print-readme.js +6 -6
- package/package.json +1 -1
- package/queries/catalog/call-context-query/call-context-query-executor.js +20 -6
- package/queries/catalog/call-context-query/call-context-query-format.d.ts +12 -6
- package/queries/catalog/call-context-query/call-context-query-format.js +8 -6
- package/queries/catalog/dependencies-query/dependencies-query-executor.js +157 -53
- package/queries/catalog/dependencies-query/dependencies-query-format.d.ts +16 -1
- package/queries/catalog/dependencies-query/dependencies-query-format.js +121 -112
- package/queries/catalog/resolve-value-query/resolve-value-query-executor.d.ts +1 -1
- package/queries/catalog/resolve-value-query/resolve-value-query-executor.js +3 -4
- package/queries/query-print.js +7 -4
- package/util/version.js +1 -1
package/README.md
CHANGED
|
@@ -41,14 +41,14 @@ It offers a wide variety of features, for example:
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
Let's suppose we are interested only in the sum which is printed in line 11.
|
|
44
|
+
Let's suppose we are interested only in the `sum` which is printed in line 11.
|
|
45
45
|
To get a slice for this, you can use the following command:
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
```shell
|
|
50
50
|
$ docker run -it --rm eagleoutice/flowr # or npm run flowr
|
|
51
|
-
flowR repl using flowR v2.2.
|
|
51
|
+
flowR repl using flowR v2.2.5, R v4.4.0 (r-shell engine)
|
|
52
52
|
R> :slicer test/testfiles/example.R --criterion "11@sum"
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -94,8 +94,8 @@ It offers a wide variety of features, for example:
|
|
|
94
94
|
</details>
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
* 🚀 **fast data and control-flow graphs**\
|
|
98
|
-
Within just <i><span title="This measurement is automatically fetched from the latest
|
|
97
|
+
* 🚀 **fast data- and control-flow graphs**\
|
|
98
|
+
Within just <i><span title="This measurement is automatically fetched from the latest benchmark!">117.7 ms</i></span> (as of Feb 17, 2025),
|
|
99
99
|
_flowR_ can analyze the data- and control-flow of the average real-world R script. See the [benchmarks](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark) for more information,
|
|
100
100
|
and consult the [wiki pages](https://github.com/flowr-analysis/flowr/wiki/Dataflow-Graph) for more details on the dataflow graph.
|
|
101
101
|
|
|
@@ -131,7 +131,7 @@ It offers a wide variety of features, for example:
|
|
|
131
131
|
|
|
132
132
|
```shell
|
|
133
133
|
$ docker run -it --rm eagleoutice/flowr # or npm run flowr
|
|
134
|
-
flowR repl using flowR v2.2.
|
|
134
|
+
flowR repl using flowR v2.2.5, R v4.4.0 (r-shell engine)
|
|
135
135
|
R> :dataflow* test/testfiles/example.R
|
|
136
136
|
```
|
|
137
137
|
|
|
@@ -375,7 +375,7 @@ It offers a wide variety of features, for example:
|
|
|
375
375
|
52 -->|"argument"| 50
|
|
376
376
|
```
|
|
377
377
|
|
|
378
|
-
(The analysis required
|
|
378
|
+
(The analysis required _24.34 ms_ (including parse and normalize, using the [r-shell](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
|
|
379
379
|
|
|
380
380
|
|
|
381
381
|
|
|
@@ -390,11 +390,11 @@ It offers a wide variety of features, for example:
|
|
|
390
390
|
|
|
391
391
|
If you want to use flowR and the features it provides, feel free to check out the:
|
|
392
392
|
|
|
393
|
-
- [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=code-inspect.vscode-flowr): provides access to flowR
|
|
393
|
+
- [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=code-inspect.vscode-flowr): provides access to flowR directly in VS Code (or [vscode.dev](https://vscode.dev/))
|
|
394
394
|
- [RStudio Addin](https://github.com/flowr-analysis/rstudio-addin-flowr): integrates flowR into [RStudio](https://posit.co/downloads/)
|
|
395
|
-
- [R package](https://github.com/flowr-analysis/flowr-r-adapter):
|
|
395
|
+
- [R package](https://github.com/flowr-analysis/flowr-r-adapter): use flowR in your R scripts
|
|
396
396
|
- [Docker image](https://hub.docker.com/r/eagleoutice/flowr): run flowR in a container, this also includes [flowR's server](https://github.com/flowr-analysis/flowr/wiki/Interface#communicating-with-the-server)
|
|
397
|
-
- [NPM package](https://www.npmjs.com/package/@eagleoutice/flowr): include flowR in your TypeScript and JavaScript projects
|
|
397
|
+
- [NPM package](https://www.npmjs.com/package/@eagleoutice/flowr): include flowR in your TypeScript and JavaScript projects
|
|
398
398
|
|
|
399
399
|
## ⭐ Getting Started
|
|
400
400
|
|
|
@@ -39,7 +39,6 @@ const default_pipelines_1 = require("../../../core/steps/pipeline/default-pipeli
|
|
|
39
39
|
const retriever_1 = require("../../../r-bridge/retriever");
|
|
40
40
|
const cfg_2 = require("../../../util/mermaid/cfg");
|
|
41
41
|
const ansi_1 = require("../../../util/ansi");
|
|
42
|
-
const clipboard = Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
43
42
|
async function controlflow(parser, remainingLine) {
|
|
44
43
|
return await (0, default_pipelines_1.createDataflowPipeline)(parser, {
|
|
45
44
|
request: (0, retriever_1.requestFromInput)(remainingLine.trim())
|
|
@@ -62,7 +61,8 @@ exports.controlflowCommand = {
|
|
|
62
61
|
const mermaid = (0, cfg_2.cfgToMermaid)(cfg, result.normalize);
|
|
63
62
|
output.stdout(mermaid);
|
|
64
63
|
try {
|
|
65
|
-
|
|
64
|
+
const clipboard = await Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
65
|
+
clipboard.default.writeSync(mermaid);
|
|
66
66
|
output.stdout(formatInfo(output, 'mermaid code'));
|
|
67
67
|
}
|
|
68
68
|
catch { /* do nothing this is a service thing */ }
|
|
@@ -79,7 +79,8 @@ exports.controlflowStarCommand = {
|
|
|
79
79
|
const mermaid = (0, cfg_2.cfgToMermaidUrl)(cfg, result.normalize);
|
|
80
80
|
output.stdout(mermaid);
|
|
81
81
|
try {
|
|
82
|
-
|
|
82
|
+
const clipboard = await Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
83
|
+
clipboard.default.writeSync(mermaid);
|
|
83
84
|
output.stdout(formatInfo(output, 'mermaid url'));
|
|
84
85
|
}
|
|
85
86
|
catch { /* do nothing this is a service thing */ }
|
|
@@ -37,7 +37,6 @@ exports.dataflowStarCommand = exports.dataflowCommand = void 0;
|
|
|
37
37
|
const default_pipelines_1 = require("../../../core/steps/pipeline/default-pipelines");
|
|
38
38
|
const retriever_1 = require("../../../r-bridge/retriever");
|
|
39
39
|
const dfg_1 = require("../../../util/mermaid/dfg");
|
|
40
|
-
const clipboard = Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
41
40
|
const ansi_1 = require("../../../util/ansi");
|
|
42
41
|
/**
|
|
43
42
|
* Obtain the dataflow graph using a known parser (such as the {@link RShell} or {@link TreeSitterExecutor}).
|
|
@@ -63,7 +62,8 @@ exports.dataflowCommand = {
|
|
|
63
62
|
const mermaid = (0, dfg_1.graphToMermaid)({ graph: result.dataflow.graph, includeEnvironments: false }).string;
|
|
64
63
|
output.stdout(mermaid);
|
|
65
64
|
try {
|
|
66
|
-
|
|
65
|
+
const clipboard = await Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
66
|
+
clipboard.default.writeSync(mermaid);
|
|
67
67
|
output.stdout(formatInfo(output, 'mermaid code', result.dataflow['.meta'].timing));
|
|
68
68
|
}
|
|
69
69
|
catch { /* do nothing this is a service thing */ }
|
|
@@ -79,7 +79,8 @@ exports.dataflowStarCommand = {
|
|
|
79
79
|
const mermaid = (0, dfg_1.graphToMermaidUrl)(result.dataflow.graph, false);
|
|
80
80
|
output.stdout(mermaid);
|
|
81
81
|
try {
|
|
82
|
-
|
|
82
|
+
const clipboard = await Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
83
|
+
clipboard.default.writeSync(mermaid);
|
|
83
84
|
output.stdout(formatInfo(output, 'mermaid url', result.dataflow['.meta'].timing));
|
|
84
85
|
}
|
|
85
86
|
catch { /* do nothing this is a service thing */ }
|
|
@@ -37,7 +37,6 @@ exports.normalizeStarCommand = exports.normalizeCommand = void 0;
|
|
|
37
37
|
const default_pipelines_1 = require("../../../core/steps/pipeline/default-pipelines");
|
|
38
38
|
const retriever_1 = require("../../../r-bridge/retriever");
|
|
39
39
|
const ast_1 = require("../../../util/mermaid/ast");
|
|
40
|
-
const clipboard = Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
41
40
|
const ansi_1 = require("../../../util/ansi");
|
|
42
41
|
async function normalize(parser, remainingLine) {
|
|
43
42
|
return await (0, default_pipelines_1.createNormalizePipeline)(parser, {
|
|
@@ -60,7 +59,8 @@ exports.normalizeCommand = {
|
|
|
60
59
|
const mermaid = (0, ast_1.normalizedAstToMermaid)(result.normalize.ast);
|
|
61
60
|
output.stdout(mermaid);
|
|
62
61
|
try {
|
|
63
|
-
|
|
62
|
+
const clipboard = await Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
63
|
+
clipboard.default.writeSync(mermaid);
|
|
64
64
|
output.stdout(formatInfo(output, 'mermaid url', result.normalize['.meta'].timing));
|
|
65
65
|
}
|
|
66
66
|
catch { /* do nothing this is a service thing */ }
|
|
@@ -76,7 +76,8 @@ exports.normalizeStarCommand = {
|
|
|
76
76
|
const mermaid = (0, ast_1.normalizedAstToMermaidUrl)(result.normalize.ast);
|
|
77
77
|
output.stdout(mermaid);
|
|
78
78
|
try {
|
|
79
|
-
|
|
79
|
+
const clipboard = await Promise.resolve().then(() => __importStar(require('clipboardy')));
|
|
80
|
+
clipboard.default.writeSync(mermaid);
|
|
80
81
|
output.stdout(formatInfo(output, 'mermaid url', result.normalize['.meta'].timing));
|
|
81
82
|
}
|
|
82
83
|
catch { /* do nothing this is a service thing */ }
|
package/cli/repl/core.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { KnownParser } from '../../r-bridge/parser';
|
|
|
6
6
|
* Used by the repl to provide automatic completions for a given (partial) input line
|
|
7
7
|
*/
|
|
8
8
|
export declare function replCompleter(line: string): [string[], string];
|
|
9
|
-
export declare
|
|
9
|
+
export declare function makeDefaultReplReadline(): readline.ReadLineOptions;
|
|
10
10
|
/**
|
|
11
11
|
* This function interprets the given `expr` as a REPL command (see {@link repl} for more on the semantics).
|
|
12
12
|
*
|
package/cli/repl/core.js
CHANGED
|
@@ -36,8 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.DEFAULT_REPL_READLINE_CONFIGURATION = void 0;
|
|
40
39
|
exports.replCompleter = replCompleter;
|
|
40
|
+
exports.makeDefaultReplReadline = makeDefaultReplReadline;
|
|
41
41
|
exports.replProcessAnswer = replProcessAnswer;
|
|
42
42
|
exports.repl = repl;
|
|
43
43
|
exports.loadReplHistory = loadReplHistory;
|
|
@@ -98,15 +98,18 @@ function replCompleter(line) {
|
|
|
98
98
|
// if no command is already typed, just return all commands that match
|
|
99
99
|
return [replCompleterKeywords().filter(k => k.startsWith(line)).map(k => `${k} `), line];
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
function makeDefaultReplReadline() {
|
|
102
|
+
return {
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
output: process.stdout,
|
|
105
|
+
tabSize: 4,
|
|
106
|
+
terminal: true,
|
|
107
|
+
history: loadReplHistory(defaultHistoryFile),
|
|
108
|
+
removeHistoryDuplicates: true,
|
|
109
|
+
completer: replCompleter
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
;
|
|
110
113
|
async function replProcessStatement(output, statement, parser, allowRSessionAccess) {
|
|
111
114
|
if (statement.startsWith(':')) {
|
|
112
115
|
const command = statement.slice(1).split(' ')[0].toLowerCase();
|
|
@@ -157,7 +160,7 @@ async function replProcessAnswer(output, expr, parser, allowRSessionAccess) {
|
|
|
157
160
|
* For the execution, this function makes use of {@link replProcessAnswer}.
|
|
158
161
|
*
|
|
159
162
|
*/
|
|
160
|
-
async function repl({ parser = new shell_1.RShell({ revive: 2 /* RShellReviveOptions.Always */ }), rl = readline.createInterface(
|
|
163
|
+
async function repl({ parser = new shell_1.RShell({ revive: 2 /* RShellReviveOptions.Always */ }), rl = readline.createInterface(makeDefaultReplReadline()), output = repl_main_1.standardReplOutput, historyFile = defaultHistoryFile, allowRSessionAccess = false }) {
|
|
161
164
|
if (historyFile) {
|
|
162
165
|
rl.on('history', h => fs_1.default.writeFileSync(historyFile, h.join('\n'), { encoding: 'utf-8' }));
|
|
163
166
|
}
|
|
@@ -175,9 +178,16 @@ async function repl({ parser = new shell_1.RShell({ revive: 2 /* RShellReviveOpt
|
|
|
175
178
|
}
|
|
176
179
|
}
|
|
177
180
|
function loadReplHistory(historyFile) {
|
|
178
|
-
|
|
181
|
+
try {
|
|
182
|
+
if (!fs_1.default.existsSync(historyFile)) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return fs_1.default.readFileSync(historyFile, { encoding: 'utf-8' }).split('\n');
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
log_1.log.error(`Failed to load repl history from ${historyFile}: ${e?.message}`);
|
|
189
|
+
log_1.log.error(e?.stack);
|
|
179
190
|
return undefined;
|
|
180
191
|
}
|
|
181
|
-
return fs_1.default.readFileSync(historyFile, { encoding: 'utf-8' }).split('\n');
|
|
182
192
|
}
|
|
183
193
|
//# sourceMappingURL=core.js.map
|
package/config.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface FlowrConfigOptions extends MergeableRecord {
|
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
|
-
* The engines to use for interacting with R code. Currently supports {@link TreeSitterEngineConfig} and {@link RShellEngineConfig}.
|
|
32
|
+
* The engines to use for interacting with R code. Currently, supports {@link TreeSitterEngineConfig} and {@link RShellEngineConfig}.
|
|
33
33
|
* An empty array means all available engines will be used.
|
|
34
34
|
*/
|
|
35
35
|
readonly engines: readonly EngineConfig[];
|
|
@@ -37,7 +37,7 @@ export interface FlowrConfigOptions extends MergeableRecord {
|
|
|
37
37
|
* The default engine to use for interacting with R code. If this is undefined, an arbitrary engine from {@link engines} will be used.
|
|
38
38
|
*/
|
|
39
39
|
readonly defaultEngine?: EngineConfig['type'];
|
|
40
|
-
/** How to resolve constants, constraints, cells,
|
|
40
|
+
/** How to resolve constants, constraints, cells, … */
|
|
41
41
|
readonly solver: {
|
|
42
42
|
/**
|
|
43
43
|
* How to resolve variables and their values
|
package/config.js
CHANGED
|
@@ -110,7 +110,13 @@ function amendConfig(amendment) {
|
|
|
110
110
|
function getConfig() {
|
|
111
111
|
// lazy-load the config based on the current settings
|
|
112
112
|
if (currentConfig === undefined) {
|
|
113
|
-
|
|
113
|
+
try {
|
|
114
|
+
setConfig(loadConfigFromFile(configFile, configWorkingDirectory));
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
log_1.log.error(`Failed to load config: ${e.message}`);
|
|
118
|
+
setConfig(exports.defaultConfigOptions);
|
|
119
|
+
}
|
|
114
120
|
}
|
|
115
121
|
return currentConfig;
|
|
116
122
|
}
|
|
@@ -4,7 +4,7 @@ import type { Identifier, IdentifierDefinition } from './identifier';
|
|
|
4
4
|
import { ReferenceType } from './identifier';
|
|
5
5
|
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
6
6
|
import type { DataflowGraph } from '../graph/graph';
|
|
7
|
-
import type { AstIdMap } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
|
|
7
|
+
import type { AstIdMap, RNodeWithParent } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
|
|
8
8
|
/**
|
|
9
9
|
* Resolves a given identifier name to a list of its possible definition location using R scoping and resolving rules.
|
|
10
10
|
*
|
|
@@ -21,9 +21,31 @@ export declare function resolvesToBuiltInConstant(name: Identifier | undefined,
|
|
|
21
21
|
export declare function resolveToConstants(name: Identifier | undefined, environment: REnvironmentInformation): unknown[] | undefined;
|
|
22
22
|
export declare function getAliases(sourceIds: readonly NodeId[], dataflow: DataflowGraph, environment: REnvironmentInformation): NodeId[] | undefined;
|
|
23
23
|
/** Please use {@link resolveValueOfVariable} */
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function trackAliasInEnvironments(identifier: Identifier | undefined, use: REnvironmentInformation, idMap?: AstIdMap): unknown[] | undefined;
|
|
25
|
+
export declare function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, idMap?: AstIdMap): unknown[] | undefined;
|
|
25
26
|
/**
|
|
26
27
|
* Convenience function using the variable resolver as specified within the configuration file
|
|
27
28
|
* In the future we may want to have this set once at the start of the analysis
|
|
29
|
+
*
|
|
30
|
+
* @see {@link resolve} - for a more general approach which "evaluates" a node based on value resolve
|
|
28
31
|
*/
|
|
29
32
|
export declare function resolveValueOfVariable(identifier: Identifier | undefined, environment: REnvironmentInformation, idMap?: AstIdMap): unknown[] | undefined;
|
|
33
|
+
export interface ResolveInfo {
|
|
34
|
+
/** The current environment used for name resolution */
|
|
35
|
+
environment?: REnvironmentInformation;
|
|
36
|
+
/** The id map to resolve the node if given as an id */
|
|
37
|
+
idMap?: AstIdMap;
|
|
38
|
+
/** The graph to resolve in */
|
|
39
|
+
graph?: DataflowGraph;
|
|
40
|
+
/** Whether to track variables */
|
|
41
|
+
full?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generalized {@link resolveValueOfVariable} function which evaluates a node based on the value resolve
|
|
45
|
+
*
|
|
46
|
+
* @param id - The node id or node to resolve
|
|
47
|
+
* @param environment - The current environment used for name resolution
|
|
48
|
+
* @param idMap - The id map to resolve the node if given as an id
|
|
49
|
+
* @param full - Whether to track variables
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolve(id: NodeId | RNodeWithParent, { environment, graph, idMap, full }: ResolveInfo): unknown[] | undefined;
|
|
@@ -4,8 +4,10 @@ exports.resolveByName = resolveByName;
|
|
|
4
4
|
exports.resolvesToBuiltInConstant = resolvesToBuiltInConstant;
|
|
5
5
|
exports.resolveToConstants = resolveToConstants;
|
|
6
6
|
exports.getAliases = getAliases;
|
|
7
|
-
exports.
|
|
7
|
+
exports.trackAliasInEnvironments = trackAliasInEnvironments;
|
|
8
|
+
exports.trackAliasesInGraph = trackAliasesInGraph;
|
|
8
9
|
exports.resolveValueOfVariable = resolveValueOfVariable;
|
|
10
|
+
exports.resolve = resolve;
|
|
9
11
|
const environment_1 = require("./environment");
|
|
10
12
|
const logic_1 = require("../../util/logic");
|
|
11
13
|
const identifier_1 = require("./identifier");
|
|
@@ -14,6 +16,10 @@ const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"
|
|
|
14
16
|
const vertex_1 = require("../graph/vertex");
|
|
15
17
|
const config_1 = require("../../config");
|
|
16
18
|
const assert_1 = require("../../util/assert");
|
|
19
|
+
const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
|
|
20
|
+
const visiting_queue_1 = require("../../slicing/static/visiting-queue");
|
|
21
|
+
const fingerprint_1 = require("../../slicing/static/fingerprint");
|
|
22
|
+
const edge_1 = require("../graph/edge");
|
|
17
23
|
const FunctionTargetTypes = identifier_1.ReferenceType.Function | identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Parameter;
|
|
18
24
|
const VariableTargetTypes = identifier_1.ReferenceType.Variable | identifier_1.ReferenceType.Parameter | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Unknown;
|
|
19
25
|
const ConstantTargetTypes = identifier_1.ReferenceType.Constant | identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown;
|
|
@@ -149,11 +155,11 @@ function getAliases(sourceIds, dataflow, environment) {
|
|
|
149
155
|
return [...definitions];
|
|
150
156
|
}
|
|
151
157
|
/** Please use {@link resolveValueOfVariable} */
|
|
152
|
-
function
|
|
158
|
+
function trackAliasInEnvironments(identifier, use, idMap) {
|
|
153
159
|
if (identifier === undefined) {
|
|
154
160
|
return undefined;
|
|
155
161
|
}
|
|
156
|
-
const defs = resolveByName(identifier,
|
|
162
|
+
const defs = resolveByName(identifier, use);
|
|
157
163
|
if (defs === undefined) {
|
|
158
164
|
return undefined;
|
|
159
165
|
}
|
|
@@ -183,17 +189,92 @@ function resolveToValues(identifier, environment, idMap) {
|
|
|
183
189
|
}
|
|
184
190
|
return values;
|
|
185
191
|
}
|
|
192
|
+
function trackAliasesInGraph(id, graph, idMap) {
|
|
193
|
+
idMap ??= graph.idMap;
|
|
194
|
+
(0, assert_1.guard)(idMap !== undefined, 'The ID map is required to get the lineage of a node');
|
|
195
|
+
const start = graph.getVertex(id);
|
|
196
|
+
(0, assert_1.guard)(start !== undefined, 'Unable to find start for alias tracking');
|
|
197
|
+
const queue = new visiting_queue_1.VisitingQueue(25);
|
|
198
|
+
const clean = (0, environment_1.initializeCleanEnvironments)();
|
|
199
|
+
const cleanFingerprint = (0, fingerprint_1.envFingerprint)(clean);
|
|
200
|
+
queue.add(id, clean, cleanFingerprint, false);
|
|
201
|
+
const resultIds = [];
|
|
202
|
+
while (queue.nonEmpty()) {
|
|
203
|
+
const { id, baseEnvironment } = queue.next();
|
|
204
|
+
const res = graph.get(id);
|
|
205
|
+
if (!res) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const [vertex, outgoingEdges] = res;
|
|
209
|
+
if (vertex.tag === vertex_1.VertexType.Value) {
|
|
210
|
+
resultIds.push(id);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// travel all read and defined-by edges
|
|
214
|
+
for (const [targetId, edge] of outgoingEdges) {
|
|
215
|
+
if ((0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall)) {
|
|
216
|
+
queue.add(targetId, baseEnvironment, cleanFingerprint, false);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (resultIds.length === 0) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
const values = [];
|
|
224
|
+
for (const id of resultIds) {
|
|
225
|
+
const node = idMap.get(id);
|
|
226
|
+
if (node !== undefined) {
|
|
227
|
+
values.push(node.content);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return values;
|
|
231
|
+
}
|
|
186
232
|
/**
|
|
187
233
|
* Convenience function using the variable resolver as specified within the configuration file
|
|
188
234
|
* In the future we may want to have this set once at the start of the analysis
|
|
235
|
+
*
|
|
236
|
+
* @see {@link resolve} - for a more general approach which "evaluates" a node based on value resolve
|
|
189
237
|
*/
|
|
190
238
|
function resolveValueOfVariable(identifier, environment, idMap) {
|
|
191
239
|
const resolve = (0, config_1.getConfig)().solver.variables;
|
|
192
240
|
switch (resolve) {
|
|
193
|
-
case config_1.VariableResolve.Alias: return
|
|
241
|
+
case config_1.VariableResolve.Alias: return trackAliasInEnvironments(identifier, environment, idMap);
|
|
194
242
|
case config_1.VariableResolve.Builtin: return resolveToConstants(identifier, environment);
|
|
195
243
|
case config_1.VariableResolve.Disabled: return [];
|
|
196
244
|
default: (0, assert_1.assertUnreachable)(resolve);
|
|
197
245
|
}
|
|
198
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Generalized {@link resolveValueOfVariable} function which evaluates a node based on the value resolve
|
|
249
|
+
*
|
|
250
|
+
* @param id - The node id or node to resolve
|
|
251
|
+
* @param environment - The current environment used for name resolution
|
|
252
|
+
* @param idMap - The id map to resolve the node if given as an id
|
|
253
|
+
* @param full - Whether to track variables
|
|
254
|
+
*/
|
|
255
|
+
function resolve(id, { environment, graph, idMap, full }) {
|
|
256
|
+
idMap ??= graph?.idMap;
|
|
257
|
+
const node = typeof id === 'object' ? id : idMap?.get(id);
|
|
258
|
+
if (node === undefined) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
switch (node.type) {
|
|
262
|
+
case type_1.RType.Symbol:
|
|
263
|
+
if (environment) {
|
|
264
|
+
return full ? resolveValueOfVariable(node.lexeme, environment, idMap) : undefined;
|
|
265
|
+
}
|
|
266
|
+
else if (graph && (0, config_1.getConfig)().solver.variables === config_1.VariableResolve.Alias) {
|
|
267
|
+
return full ? trackAliasesInGraph(node.info.id, graph, idMap) : undefined;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
case type_1.RType.String:
|
|
273
|
+
case type_1.RType.Number:
|
|
274
|
+
case type_1.RType.Logical:
|
|
275
|
+
return [node.content];
|
|
276
|
+
default:
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
199
280
|
//# sourceMappingURL=resolve-by-name.js.map
|
|
@@ -208,9 +208,10 @@ ${await (0, doc_query_1.showQuery)(shell, example_query_code_1.exampleQueryCode,
|
|
|
208
208
|
buildExplanation: async (shell) => {
|
|
209
209
|
const exampleCode = 'x <- 1\nprint(x)';
|
|
210
210
|
return `
|
|
211
|
-
With this query you can use flowR's value-tracking capabilities to resolve identifiers to all potential values they may have at runtime (if possible).
|
|
211
|
+
With this query you can use flowR's value-tracking capabilities to resolve identifiers to all potential values they may have at runtime (if possible).
|
|
212
|
+
The extent to which flowR traces values (e.g., built-ins vs. constants) can be configured in flowR's Configuration file (see the [Interface](${doc_files_1.FlowrWikiBaseRef}/Interface) wiki page for more information).
|
|
212
213
|
|
|
213
|
-
Using the example code \`${exampleCode}
|
|
214
|
+
Using the example code \`${exampleCode}\` (with the \`print(x)\` in the second line), the following query returns all values of \`x\` in the code:
|
|
214
215
|
${await (0, doc_query_1.showQuery)(shell, exampleCode, [{
|
|
215
216
|
type: 'resolve-value',
|
|
216
217
|
criteria: ['2@x']
|
|
@@ -419,7 +420,7 @@ ${await (0, doc_query_1.showQuery)(shell, longerCode, [{
|
|
|
419
420
|
type: 'dependencies'
|
|
420
421
|
}], { showCode: false, collapseQuery: true, collapseResult: true })}
|
|
421
422
|
|
|
422
|
-
Currently the dependency extraction may fail as it is essentially a set of heuristics guessing the dependencies.
|
|
423
|
+
Currently, the dependency extraction may fail as it is essentially a set of heuristics guessing the dependencies.
|
|
423
424
|
We welcome any feedback on this (consider opening a [new issue](${doc_issue_1.NewIssueUrl})).
|
|
424
425
|
|
|
425
426
|
In the meantime we offer several properties to overwrite the default behavior (e.g., function names that should be collected)
|
|
@@ -427,12 +428,13 @@ In the meantime we offer several properties to overwrite the default behavior (e
|
|
|
427
428
|
${await (0, doc_query_1.showQuery)(shell, longerCode, [{
|
|
428
429
|
type: 'dependencies',
|
|
429
430
|
ignoreDefaultFunctions: true,
|
|
430
|
-
libraryFunctions: [{ name: 'print', argIdx: 0, argName: 'library' }],
|
|
431
|
+
libraryFunctions: [{ name: 'print', argIdx: 0, argName: 'library', resolveValue: true }],
|
|
431
432
|
sourceFunctions: [],
|
|
432
433
|
readFunctions: [],
|
|
433
434
|
writeFunctions: []
|
|
434
435
|
}], { showCode: false, collapseQuery: false, collapseResult: true })}
|
|
435
436
|
|
|
437
|
+
Here, \`resolveValue\` tells the dependency query to resolve the value of this argument in case it is not a constant.
|
|
436
438
|
`;
|
|
437
439
|
}
|
|
438
440
|
});
|
|
@@ -42,7 +42,7 @@ For this, let's have a look at the example file, located at ${(0, doc_files_1.li
|
|
|
42
42
|
|
|
43
43
|
${(0, doc_code_1.codeBlock)('r', (0, doc_files_1.getFileContentFromRoot)('test/testfiles/example.R'))}
|
|
44
44
|
|
|
45
|
-
Let's suppose we are interested only in the sum which is printed in line 11.
|
|
45
|
+
Let's suppose we are interested only in the \`sum\` which is printed in line 11.
|
|
46
46
|
To get a slice for this, you can use the following command:
|
|
47
47
|
|
|
48
48
|
${await (0, doc_repl_1.documentReplSession)(shell, [{
|
|
@@ -64,8 +64,8 @@ The following showcases the dependency view of the [Visual Studio Code extension
|
|
|
64
64
|
|
|
65
65
|
`), ' ')}
|
|
66
66
|
|
|
67
|
-
* 🚀 **fast data and control-flow graphs**\\
|
|
68
|
-
Within just ${'<i>' + (0, html_hover_over_1.textWithTooltip)((0, numbers_1.roundToDecimals)(await (0, doc_benchmarks_1.getLatestDfAnalysisTime)('"social-science" Benchmark Suite (tree-sitter)'), 1) + ' ms</i>', 'This measurement is automatically fetched from the latest
|
|
67
|
+
* 🚀 **fast data- and control-flow graphs**\\
|
|
68
|
+
Within just ${'<i>' + (0, html_hover_over_1.textWithTooltip)((0, numbers_1.roundToDecimals)(await (0, doc_benchmarks_1.getLatestDfAnalysisTime)('"social-science" Benchmark Suite (tree-sitter)'), 1) + ' ms</i>', 'This measurement is automatically fetched from the latest benchmark!')} (as of ${new Date(await (0, doc_benchmarks_1.getLastBenchmarkUpdate)()).toLocaleDateString('en-US', dateOptions)}),
|
|
69
69
|
_flowR_ can analyze the data- and control-flow of the average real-world R script. See the [benchmarks](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark) for more information,
|
|
70
70
|
and consult the [wiki pages](${doc_files_1.FlowrWikiBaseRef}/Dataflow-Graph) for more details on the dataflow graph.
|
|
71
71
|
|
|
@@ -90,11 +90,11 @@ ${await (0, doc_dfg_1.printDfGraphForCode)(shell, (0, doc_files_1.getFileContent
|
|
|
90
90
|
|
|
91
91
|
If you want to use flowR and the features it provides, feel free to check out the:
|
|
92
92
|
|
|
93
|
-
- [Visual Studio Code extension](${doc_files_1.FlowrVsCode}): provides access to flowR
|
|
93
|
+
- [Visual Studio Code extension](${doc_files_1.FlowrVsCode}): provides access to flowR directly in VS Code (or [vscode.dev](https://vscode.dev/))
|
|
94
94
|
- [RStudio Addin](${doc_files_1.FlowrGithubBaseRef}/rstudio-addin-flowr): integrates flowR into [RStudio](https://posit.co/downloads/)
|
|
95
|
-
- [R package](${doc_files_1.FlowrGithubBaseRef}/flowr-r-adapter):
|
|
95
|
+
- [R package](${doc_files_1.FlowrGithubBaseRef}/flowr-r-adapter): use flowR in your R scripts
|
|
96
96
|
- [Docker image](${doc_files_1.FlowrDockerRef}): run flowR in a container, this also includes [flowR's server](${doc_files_1.FlowrWikiBaseRef}/Interface#communicating-with-the-server)
|
|
97
|
-
- [NPM package](${doc_files_1.FlowrNpmRef}): include flowR in your TypeScript and JavaScript projects
|
|
97
|
+
- [NPM package](${doc_files_1.FlowrNpmRef}): include flowR in your TypeScript and JavaScript projects
|
|
98
98
|
|
|
99
99
|
## ⭐ Getting Started
|
|
100
100
|
|
package/package.json
CHANGED
|
@@ -55,7 +55,10 @@ function promoteQueryCallNames(queries) {
|
|
|
55
55
|
...q.fileFilter,
|
|
56
56
|
filter: new RegExp(q.fileFilter.filter)
|
|
57
57
|
},
|
|
58
|
-
linkTo: {
|
|
58
|
+
linkTo: Array.isArray(q.linkTo) ? q.linkTo.map(l => ({
|
|
59
|
+
...l,
|
|
60
|
+
callName: new RegExp(l.callName)
|
|
61
|
+
})) : {
|
|
59
62
|
...q.linkTo,
|
|
60
63
|
/* we have to add another promotion layer whenever we add something without this call name */
|
|
61
64
|
callName: new RegExp(q.linkTo.callName)
|
|
@@ -208,13 +211,24 @@ function executeCallContextQueries({ dataflow: { graph }, ast }, queries) {
|
|
|
208
211
|
}
|
|
209
212
|
let linkedIds = undefined;
|
|
210
213
|
if (cfg && isSubCallQuery(query)) {
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
const linked = Array.isArray(query.linkTo) ? query.linkTo : [query.linkTo];
|
|
215
|
+
for (const link of linked) {
|
|
216
|
+
/* if we have a linkTo query, we have to find the last call */
|
|
217
|
+
const lastCall = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelation)(nodeId, cfg.graph, graph, link);
|
|
218
|
+
if (lastCall) {
|
|
219
|
+
linkedIds ??= new Set();
|
|
220
|
+
for (const l of lastCall) {
|
|
221
|
+
if (link.attachLinkInfo) {
|
|
222
|
+
linkedIds.add({ id: l, info: link.attachLinkInfo });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
linkedIds.add(l);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
215
229
|
}
|
|
216
230
|
}
|
|
217
|
-
initialIdCollector.add(query.kind ?? '.', query.subkind ?? '.', (0, objects_1.compactRecord)({ id: nodeId, name: info.name, calls: targets, linkedIds }));
|
|
231
|
+
initialIdCollector.add(query.kind ?? '.', query.subkind ?? '.', (0, objects_1.compactRecord)({ id: nodeId, name: info.name, calls: targets, linkedIds: linkedIds ? [...linkedIds] : undefined }));
|
|
218
232
|
}
|
|
219
233
|
}
|
|
220
234
|
removeIdenticalDuplicates(initialIdCollector);
|
|
@@ -9,6 +9,7 @@ import { CallTargets } from './identify-link-to-last-call-relation';
|
|
|
9
9
|
import type { DataflowGraph } from '../../../dataflow/graph/graph';
|
|
10
10
|
import type { DataflowGraphVertexInfo } from '../../../dataflow/graph/vertex';
|
|
11
11
|
import type { CascadeAction } from './cascade-action';
|
|
12
|
+
import type { NoInfo } from '../../../r-bridge/lang-4.x/ast/model/model';
|
|
12
13
|
export interface FileFilter<FilterType> {
|
|
13
14
|
/**
|
|
14
15
|
* Regex that a node's file attribute must match to be considered
|
|
@@ -66,9 +67,11 @@ interface LinkToLastCall<CallName extends RegExp | string = RegExp | string> ext
|
|
|
66
67
|
*/
|
|
67
68
|
readonly cascadeIf?: (target: DataflowGraphVertexInfo, from: NodeId, graph: DataflowGraph) => CascadeAction;
|
|
68
69
|
}
|
|
69
|
-
export type LinkTo<CallName extends RegExp | string> = LinkToLastCall<CallName
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
export type LinkTo<CallName extends RegExp | string = RegExp | string, AttachLinkInfo = NoInfo> = (LinkToLastCall<CallName>) & {
|
|
71
|
+
attachLinkInfo?: AttachLinkInfo;
|
|
72
|
+
};
|
|
73
|
+
export interface SubCallContextQueryFormat<CallName extends RegExp | string = RegExp | string, AttachLinkInfo = NoInfo> extends DefaultCallContextQueryFormat<CallName> {
|
|
74
|
+
readonly linkTo: LinkTo<CallName, AttachLinkInfo> | LinkTo<CallName, AttachLinkInfo>[];
|
|
72
75
|
}
|
|
73
76
|
export interface CallContextQuerySubKindResult {
|
|
74
77
|
/** The id of the call vertex identified within the supplied dataflow graph */
|
|
@@ -81,8 +84,11 @@ export interface CallContextQuerySubKindResult {
|
|
|
81
84
|
* An empty array means that the call targets only non-local functions.
|
|
82
85
|
*/
|
|
83
86
|
readonly calls?: readonly NodeId[];
|
|
84
|
-
/** ids attached by the linkTo query */
|
|
85
|
-
readonly linkedIds?: readonly NodeId
|
|
87
|
+
/** ids attached by the linkTo query, if you attached information with the `attachLinkInfo` field you can find it here */
|
|
88
|
+
readonly linkedIds?: readonly (NodeId | {
|
|
89
|
+
id: NodeId;
|
|
90
|
+
info: object;
|
|
91
|
+
})[];
|
|
86
92
|
/**
|
|
87
93
|
* (Direct) alias locations this call stems from
|
|
88
94
|
*/
|
|
@@ -95,7 +101,7 @@ export type CallContextQueryKindResult = Record<string, {
|
|
|
95
101
|
export interface CallContextQueryResult extends BaseQueryResult {
|
|
96
102
|
readonly kinds: CallContextQueryKindResult;
|
|
97
103
|
}
|
|
98
|
-
export type CallContextQuery<CallName extends RegExp | string = RegExp | string> = DefaultCallContextQueryFormat<CallName> | SubCallContextQueryFormat<CallName>;
|
|
104
|
+
export type CallContextQuery<CallName extends RegExp | string = RegExp | string, AttachLinkInfo = NoInfo> = DefaultCallContextQueryFormat<CallName> | SubCallContextQueryFormat<CallName, AttachLinkInfo>;
|
|
99
105
|
export declare const CallContextQueryDefinition: {
|
|
100
106
|
readonly executor: typeof executeCallContextQueries;
|
|
101
107
|
readonly asciiSummarizer: (formatter: OutputFormatter, processed: PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>, queryResults: BaseQueryResult, result: string[]) => boolean;
|
|
@@ -10,6 +10,13 @@ const time_1 = require("../../../util/time");
|
|
|
10
10
|
const joi_1 = __importDefault(require("joi"));
|
|
11
11
|
const query_print_1 = require("../../query-print");
|
|
12
12
|
const identify_link_to_last_call_relation_1 = require("./identify-link-to-last-call-relation");
|
|
13
|
+
const CallContextQueryLinkTo = joi_1.default.object({
|
|
14
|
+
type: joi_1.default.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
|
|
15
|
+
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.'),
|
|
16
|
+
ignoreIf: joi_1.default.function().optional().description('Should we ignore this (source) call? Currently, there is no well working serialization for this.'),
|
|
17
|
+
cascadeIf: joi_1.default.function().optional().description('Should we continue searching after the link was created? Currently, there is no well working serialization for this.'),
|
|
18
|
+
attachLinkInfo: joi_1.default.object().optional().description('Additional information to attach to the link.')
|
|
19
|
+
});
|
|
13
20
|
exports.CallContextQueryDefinition = {
|
|
14
21
|
executor: call_context_query_executor_1.executeCallContextQueries,
|
|
15
22
|
asciiSummarizer: (formatter, processed, queryResults, result) => {
|
|
@@ -30,12 +37,7 @@ exports.CallContextQueryDefinition = {
|
|
|
30
37
|
fileFilter: joi_1.default.string().required().description('Regex that a node\'s file attribute must match to be considered'),
|
|
31
38
|
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
39
|
}).optional().description('Filter that, when set, a node\'s file attribute must match to be considered'),
|
|
33
|
-
linkTo: joi_1.default.
|
|
34
|
-
type: joi_1.default.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
|
|
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.'),
|
|
36
|
-
ignoreIf: joi_1.default.function().optional().description('Should we ignore this (source) call? Currently, there is no well working serialization for this.'),
|
|
37
|
-
cascadeIf: joi_1.default.function().optional().description('Should we continue searching after the link was created? Currently, there is no well working serialization for this.')
|
|
38
|
-
}).optional().description('Links the current call to the last call of the given kind. This way, you can link a call like `points` to the latest graphics plot etc.')
|
|
40
|
+
linkTo: joi_1.default.alternatives(CallContextQueryLinkTo, joi_1.default.array().items(CallContextQueryLinkTo)).optional().description('Links the current call to the last call of the given kind. This way, you can link a call like `points` to the latest graphics plot etc.')
|
|
39
41
|
}).description('Call context query used to find calls in the dataflow graph')
|
|
40
42
|
};
|
|
41
43
|
//# sourceMappingURL=call-context-query-format.js.map
|