Skip to content

Commit ed14d4a

Browse files
authored
Flowr Search (#1175)
2 parents f1f1076 + 4e53819 commit ed14d4a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2431
-242
lines changed

.github/workflows/broken-links-and-wiki.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ jobs:
8484
update_wiki_page "Capabilities" capabilities-markdown
8585
update_wiki_page "Dataflow Graph" wiki:df-graph
8686
update_wiki_page "Query API" wiki:query-api
87+
update_wiki_page "Search API" wiki:search-api
8788
update_wiki_page "Interface" wiki:interface
8889
update_wiki_page "Normalized AST" wiki:normalized-ast
8990
update_wiki_page "Linting and Testing" wiki:linting-and-testing

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,4 @@ We welcome every contribution! Please check out the [contributing guidelines](ht
7373
[GPLv3 License](LICENSE).
7474

7575
----
76+
# abstract-interpretation-ltx

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"wiki:df-graph": "ts-node src/documentation/print-dataflow-graph-wiki.ts",
2929
"wiki:normalized-ast": "ts-node src/documentation/print-normalized-ast-wiki.ts",
3030
"wiki:query-api": "ts-node src/documentation/print-query-wiki.ts",
31+
"wiki:search-api": "ts-node src/documentation/print-search-wiki.ts",
3132
"wiki:linting-and-testing": "ts-node src/documentation/print-linting-and-testing-wiki.ts",
3233
"wiki:interface": "ts-node src/documentation/print-interface-wiki.ts",
3334
"build": "tsc --project .",

src/cli/repl/commands/repl-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ async function processQueryArgs(line: string, shell: RShell, output: ReplOutput)
6868

6969
const processed = await getDataflow(shell, args.join(' '));
7070
return {
71-
query: executeQueries({ graph: processed.dataflow.graph, ast: processed.normalize }, parsedQuery),
71+
query: executeQueries({ dataflow: processed.dataflow, ast: processed.normalize }, parsedQuery),
7272
processed
7373
};
7474
}

src/cli/repl/server/connection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ export class FlowRServerConnection {
354354
const { dataflow: dfg, normalize: ast } = fileInformation.pipeline.getResults(true);
355355
guard(dfg !== undefined, `Dataflow graph must be present (request: ${request.filetoken})`);
356356
guard(ast !== undefined, `AST must be present (request: ${request.filetoken})`);
357-
const results = executeQueries({ graph: dfg.graph, ast }, request.query);
357+
const results = executeQueries({ dataflow: dfg, ast }, request.query);
358358
sendMessage<QueryResponseMessage>(this.socket, {
359359
type: 'response-query',
360360
id: request.id,

src/core/steps/pipeline/default-pipelines.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ export const DEFAULT_SLICING_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP,
1212
export const DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE = DEFAULT_SLICING_PIPELINE;
1313
export const DEFAULT_SLICE_WITHOUT_RECONSTRUCT_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE, STATIC_DATAFLOW, STATIC_SLICE);
1414

15+
/**
16+
* The default pipeline for working with flowr, including the dataflow step,
17+
* see the {@link DEFAULT_NORMALIZE_PIPELINE} for the pipeline without the dataflow step,
18+
* and the {@link DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE} for the pipeline with slicing and reconstructing steps
19+
*/
1520
export const DEFAULT_DATAFLOW_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE, STATIC_DATAFLOW);
1621

22+
/** The pipeline to use when you want to parse and normalize your R file, see {@link DEFAULT_DATAFLOW_PIPELINE} for the additional `dataflow` step */
1723
export const DEFAULT_NORMALIZE_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE);
1824
export const DEFAULT_PARSE_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP);

src/dataflow/graph/vertex.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export enum VertexType {
1313
FunctionDefinition = 'function-definition'
1414
}
1515

16+
export const ValidVertexTypes: Set<string> = new Set(Object.values(VertexType));
17+
export const ValidVertexTypeReverse = Object.fromEntries(Object.entries(VertexType).map(([k, v]) => [v, k]));
18+
1619
/**
1720
* A single index of a container, which is not a container itself.
1821
*

src/documentation/doc-util/doc-dfg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export async function printDfGraphForCode(shell: RShell, code: string, { mark, s
5555
guard(showCode, 'can not switch code and graph if code is not shown');
5656
}
5757

58-
const metaInfo = `The analysis required _${printAsMs(duration)}_ (incl. parse and normalize) within the generation environment.`;
58+
const metaInfo = `The analysis required _${printAsMs(duration)}_ (including parse and normalize) within the generation environment.`;
5959
const dfGraph = printDfGraph(result.dataflow.graph, mark);
6060
let resultText = '\n\n';
6161

src/documentation/doc-util/doc-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export async function showQuery<
2929
shell,
3030
request: requestFromInput(code)
3131
}).allRemainingSteps();
32-
const results = executeQueries({ graph: analysis.dataflow.graph, ast: analysis.normalize }, queries);
32+
const results = executeQueries({ dataflow: analysis.dataflow, ast: analysis.normalize }, queries);
3333
const duration = performance.now() - now;
3434

3535
const metaInfo = `
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import type { RShell } from '../../r-bridge/shell';
2+
import type { SupportedQueryTypes } from '../../queries/query';
3+
import { PipelineExecutor } from '../../core/pipeline-executor';
4+
import { DEFAULT_DATAFLOW_PIPELINE } from '../../core/steps/pipeline/default-pipelines';
5+
import { requestFromInput } from '../../r-bridge/retriever';
6+
import { getFilePathMd } from './doc-files';
7+
import type { SupportedVirtualQueryTypes } from '../../queries/virtual-query/virtual-queries';
8+
import { printDfGraphForCode } from './doc-dfg';
9+
import { codeBlock } from './doc-code';
10+
import { printAsMs } from '../../util/time';
11+
import type { FlowrSearchLike } from '../../search/flowr-search-builder';
12+
import { runSearch } from '../../search/flowr-search-executor';
13+
import { flowrSearchToCode, flowrSearchToMermaid } from '../../search/flowr-search-printer';
14+
import { recoverContent } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
15+
import { formatRange } from '../../util/mermaid/dfg';
16+
17+
export interface ShowSearchOptions {
18+
readonly showCode?: boolean;
19+
readonly collapseResult?: boolean;
20+
}
21+
22+
export async function showSearch(shell: RShell, code: string, search: FlowrSearchLike, { collapseResult = true }: ShowSearchOptions = {}): Promise<string> {
23+
const now = performance.now();
24+
const analysis = await new PipelineExecutor(DEFAULT_DATAFLOW_PIPELINE, {
25+
shell,
26+
request: requestFromInput(code)
27+
}).allRemainingSteps();
28+
const result = runSearch(search, analysis);
29+
const duration = performance.now() - now;
30+
31+
const metaInfo = `
32+
The search required _${printAsMs(duration)}_ (including parsing and normalization and the query) within the generation environment.
33+
`.trim();
34+
35+
return `
36+
37+
${codeBlock('ts', flowrSearchToCode(search))}
38+
39+
<details style="color:gray"> <summary>Search Visualization</summary>
40+
41+
${codeBlock('mermaid', flowrSearchToMermaid(search))}
42+
43+
In the code:
44+
45+
${codeBlock('r', code)}
46+
47+
<details style="color:gray"> <summary>JSON Representation</summary>
48+
49+
${codeBlock('json', JSON.stringify(search, null, 2))}
50+
51+
</details>
52+
53+
</details>
54+
55+
56+
${collapseResult ? ' <details> <summary style="color:gray">Show Results</summary>' : ''}
57+
58+
The query returns the following vetices (all references to \`x\` in the code):
59+
${
60+
result.map(({ node }) => `<b>${node.info.id} ('${recoverContent(node.info.id, analysis.dataflow.graph)}')</b> at L${formatRange(node.location)}`).join(', ')
61+
}
62+
63+
${metaInfo}
64+
65+
The returned results are highlighted thick and blue within the dataflow graph:
66+
67+
${await printDfGraphForCode(shell, code, { showCode: false, switchCodeAndGraph: false, mark: new Set(result.map(({ node }) => node.info.id )) } )}
68+
69+
70+
${collapseResult ? '</details>' : ''}
71+
72+
`;
73+
74+
}
75+
76+
export interface QueryDocumentation {
77+
readonly name: string;
78+
readonly type: 'virtual' | 'active';
79+
readonly shortDescription: string;
80+
readonly functionName: string;
81+
readonly functionFile: string;
82+
readonly buildExplanation: (shell: RShell) => Promise<string>;
83+
}
84+
85+
export const RegisteredQueries = {
86+
'active': new Map<string, QueryDocumentation>(),
87+
'virtual': new Map<string, QueryDocumentation>()
88+
};
89+
90+
export function registerQueryDocumentation(query: SupportedQueryTypes | SupportedVirtualQueryTypes, doc: QueryDocumentation) {
91+
const map = RegisteredQueries[doc.type];
92+
if(map.has(query)) {
93+
throw new Error(`Query ${query} already registered`);
94+
}
95+
map.set(query, doc);
96+
}
97+
98+
function linkify(name: string) {
99+
return name.toLowerCase().replace(/ /g, '-');
100+
}
101+
102+
export function linkToQueryOfName(id: SupportedQueryTypes | SupportedVirtualQueryTypes) {
103+
const query = RegisteredQueries.active.get(id) ?? RegisteredQueries.virtual.get(id);
104+
if(!query) {
105+
throw new Error(`Query ${id} not found`);
106+
}
107+
return `[${query.name}](#${linkify(query.name)})`;
108+
}
109+
110+
export function tocForQueryType(type: 'active' | 'virtual') {
111+
const queries = [...RegisteredQueries[type].entries()].sort(([,{ name: a }], [, { name: b }]) => a.localeCompare(b));
112+
const result: string[] = [];
113+
for(const [id, { name, shortDescription }] of queries) {
114+
result.push(`1. [${name}](#${linkify(name)}) (\`${id}\`):\\\n ${shortDescription}`);
115+
}
116+
return result.join('\n');
117+
}
118+
119+
async function explainQuery(shell: RShell, { name, functionName, functionFile, buildExplanation }: QueryDocumentation) {
120+
return `
121+
### ${name}
122+
123+
${await buildExplanation(shell)}
124+
125+
<details>
126+
127+
<summary style="color:gray">Implementation Details</summary>
128+
129+
Responsible for the execution of the ${name} query is \`${functionName}\` in ${getFilePathMd(functionFile)}.
130+
131+
</details>
132+
133+
`;
134+
}
135+
136+
export async function explainQueries(shell: RShell, type: 'active' | 'virtual'): Promise<string> {
137+
const queries = [...RegisteredQueries[type].entries()].sort(([,{ name: a }], [, { name: b }]) => a.localeCompare(b));
138+
const result: string[] = [];
139+
for(const [,doc] of queries) {
140+
result.push(await explainQuery(shell, doc));
141+
}
142+
return result.join(`\n${'-'.repeat(5)}\n\n`);
143+
}

0 commit comments

Comments
 (0)