Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions src/documentation/data/server/doc-data-server-messages.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
documentServerMessage,
documentServerMessageResponse,
inServerContext
} from '../../doc-util/doc-server-message';
import { documentServerMessage, documentServerMessageResponse, inServerContext } from '../../doc-util/doc-server-message';
import { helloMessageDefinition } from '../../../cli/repl/server/messages/message-hello';
import { RShell } from '../../../r-bridge/shell';
import { DockerName } from '../../doc-util/doc-docker';
Expand All @@ -19,10 +15,7 @@ import { type ExecuteIntermediateResponseMessage ,
responseExecuteReplEndMessage,
responseExecuteReplIntermediateMessage
} from '../../../cli/repl/server/messages/message-repl';
import {
requestQueryMessage,
responseQueryMessage
} from '../../../cli/repl/server/messages/message-query';
import { requestQueryMessage, responseQueryMessage } from '../../../cli/repl/server/messages/message-query';
import { exampleQueryCode } from '../query/example-query-code';
import { CallTargets } from '../../../queries/catalog/call-context-query/identify-link-to-last-call-relation';

Expand Down Expand Up @@ -100,7 +93,7 @@ ${
messages: [
{
type: 'request',
description: `Let' suppose you simply want to analyze the following script:\n ${codeBlock('r', 'x <- 1\nx + 1')}\n For this, you can send the following request:`,
description: `Let's suppose you simply want to analyze the following script:\n ${codeBlock('r', 'x <- 1\nx + 1')}\n For this, you can send the following request:`,
message: {
type: 'request-file-analysis',
id: '1',
Expand Down
11 changes: 11 additions & 0 deletions src/documentation/doc-util/doc-escape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ export function escapeHTML(str: string | undefined): string | undefined {
}[tag] ?? tag)
);
}

/**
* Escapes newline characters in a string (Supports Windows and Unix newlines).
* @param str - The string to escape
* @returns The escaped string
*/
export function escapeNewline(str: string): string {
return str.replace(/([\n\r])/g, (match) => {
return match == '\n' ? '\\n' : '\\r';
});
}
19 changes: 16 additions & 3 deletions src/documentation/doc-util/doc-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import { printAsMs } from '../../util/text/time';
import { asciiSummaryOfQueryResult } from '../../queries/query-print';
import { FlowrAnalyzerBuilder } from '../../project/flowr-analyzer-builder';
import { getReplCommand } from './doc-cli-option';
import type { SlicingCriteria } from '../../slicing/criterion/parse';

export interface ShowQueryOptions {
readonly showCode?: boolean;
readonly collapseResult?: boolean;
readonly collapseQuery?: boolean;
readonly shorthand?: string;
}

/**
Expand All @@ -25,7 +27,11 @@ export interface ShowQueryOptions {
export async function showQuery<
Base extends SupportedQueryTypes,
VirtualArguments extends VirtualCompoundConstraint<Base> = VirtualCompoundConstraint<Base>
>(shell: RShell, code: string, queries: Queries<Base, VirtualArguments>, { showCode, collapseResult, collapseQuery }: ShowQueryOptions = {}): Promise<string> {
>(
shell: RShell, code: string,
queries: Queries<Base, VirtualArguments>,
{ showCode, collapseResult, collapseQuery, shorthand }: ShowQueryOptions = {}
): Promise<string> {
const now = performance.now();
const analyzer = await new FlowrAnalyzerBuilder(requestFromInput(code)).setParser(shell).build();
const results = await analyzer.query(queries);
Expand All @@ -41,8 +47,8 @@ The analysis required _${printAsMs(duration)}_ (including parsing and normalizat
${codeBlock('json', collapseQuery ? str.split('\n').join(' ').replace(/([{[])\s{2,}/g,'$1 ').replace(/\s{2,}([\]}])/g,' $1') : str)}

${(function() {
if(queries.length === 1 && Object.keys(queries[0]).length === 1) {
return `(This query can be shortened to \`@${queries[0].type}\` when used within the REPL command ${getReplCommand('query')}).`;
if((queries.length === 1 && Object.keys(queries[0]).length === 1) || shorthand) {
return `(This query can be shortened to \`@${queries[0].type}${shorthand ? ' ' + shorthand : ''}\` when used within the REPL command ${getReplCommand('query')}).`;
} else {
return '';
}
Expand Down Expand Up @@ -109,6 +115,13 @@ export function registerQueryDocumentation(query: SupportedQueryTypes | Supporte
map.set(query, doc);
}

/**
* Creates a REPL shorthand for the given slicing criteria and R code.
*/
export function sliceQueryShorthand(criteria: SlicingCriteria, code: string, forward?: boolean) {
return `(${(criteria.join(';'))})${forward ? 'f' : ''} "${code}"`;
}

function linkify(name: string) {
return name.toLowerCase().replace(/ /g, '-');
}
Expand Down
89 changes: 0 additions & 89 deletions src/documentation/doc-util/doc-search.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import type { RShell } from '../../r-bridge/shell';
import type { SupportedQueryTypes } from '../../queries/query';
import { requestFromInput } from '../../r-bridge/retriever';
import { getFilePathMd } from './doc-files';
import type { SupportedVirtualQueryTypes } from '../../queries/virtual-query/virtual-queries';
import { printDfGraphForCode } from './doc-dfg';
import { codeBlock } from './doc-code';
import { printAsMs } from '../../util/text/time';
Expand Down Expand Up @@ -72,90 +69,4 @@ ${await printDfGraphForCode(shell, code, { showCode: false, switchCodeAndGraph:
${collapseResult ? '</details>' : ''}

`;

}

export interface QueryDocumentation {
readonly name: string;
readonly type: 'virtual' | 'active';
readonly shortDescription: string;
readonly functionName: string;
readonly functionFile: string;
readonly buildExplanation: (shell: RShell) => Promise<string>;
}

export const RegisteredQueries = {
'active': new Map<string, QueryDocumentation>(),
'virtual': new Map<string, QueryDocumentation>()
};


/**
*
*/
export function registerQueryDocumentation(query: SupportedQueryTypes | SupportedVirtualQueryTypes, doc: QueryDocumentation) {
const map = RegisteredQueries[doc.type];
if(map.has(query)) {
throw new Error(`Query ${query} already registered`);
}
map.set(query, doc);
}

function linkify(name: string) {
return name.toLowerCase().replace(/ /g, '-');
}


/**
*
*/
export function linkToQueryOfName(id: SupportedQueryTypes | SupportedVirtualQueryTypes) {
const query = RegisteredQueries.active.get(id) ?? RegisteredQueries.virtual.get(id);
if(!query) {
throw new Error(`Query ${id} not found`);
}
return `[${query.name}](#${linkify(query.name)})`;
}


/**
*
*/
export function tocForQueryType(type: 'active' | 'virtual') {
const queries = [...RegisteredQueries[type].entries()].sort(([,{ name: a }], [, { name: b }]) => a.localeCompare(b));
const result: string[] = [];
for(const [id, { name, shortDescription }] of queries) {
result.push(`1. [${name}](#${linkify(name)}) (\`${id}\`):\\\n ${shortDescription}`);
}
return result.join('\n');
}

async function explainQuery(shell: RShell, { name, functionName, functionFile, buildExplanation }: QueryDocumentation) {
return `
### ${name}

${await buildExplanation(shell)}

<details>

<summary style="color:gray">Implementation Details</summary>

Responsible for the execution of the ${name} query is \`${functionName}\` in ${getFilePathMd(functionFile)}.

</details>

`;
}


/**
*
*/
export async function explainQueries(shell: RShell, type: 'active' | 'virtual'): Promise<string> {
const queries = [...RegisteredQueries[type].entries()].sort(([,{ name: a }], [, { name: b }]) => a.localeCompare(b));
const result: string[] = [];
for(const [,doc] of queries) {
result.push(await explainQuery(shell, doc));
}
return result.join(`\n${'-'.repeat(5)}\n\n`);
}
8 changes: 6 additions & 2 deletions src/documentation/print-interface-wiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,14 @@ for communication (although you can access the REPL using the server as well,
with the [REPL Request](#message-request-repl-execution) message).

The read-eval-print loop&nbsp;(REPL) works relatively simple.
You can submit an expression (using enter),
You can submit an expression (using <kbd>Enter</kbd>),
which is interpreted as an R&nbsp;expression by default but interpreted as a *command* if it starts with a colon (\`:\`).
The best command to get started with the REPL is ${getReplCommand('help')}.
Besides, you can leave the REPL either with the command ${getReplCommand('quit')} or by pressing <kbd>CTRL</kbd>+<kbd>C</kbd> twice.
Besides, you can leave the REPL either with the command ${getReplCommand('quit')} or by pressing <kbd>Ctrl</kbd>+<kbd>C</kbd> twice.
When writing a *command*, you may press <kbd>Tab</kbd> to get a list of completions, if available.
Multiple commands can be entered in a single line by separating them with a semicolon (\`;\`), e.g. \`:parse "x<-2"; :df*\`.
If a command is given without R code, the REPL will re-use R code given in a previous command.
The prior example will hence return the dataflow graph for \`"x <- 2"\`.

> [!NOTE]
> If you develop flowR, you may want to launch the repl using the \`npm run main-dev\` command, this way, you get a non-minified version of flowR with debug information and hot-reloading of source files.
Expand Down
62 changes: 49 additions & 13 deletions src/documentation/print-query-wiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
linkToQueryOfName,
registerQueryDocumentation,
showQuery,
sliceQueryShorthand,
tocForQueryType
} from './doc-util/doc-query';
import { describeSchema } from '../util/schema';
Expand Down Expand Up @@ -43,6 +44,8 @@ import { documentReplSession } from './doc-util/doc-repl';
import {
executeHigherOrderQuery
} from '../queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor';
import type { SingleSlicingCriterion, SlicingCriteria } from '../slicing/criterion/parse';
import { escapeNewline } from './doc-util/doc-escape';


registerQueryDocumentation('call-context', {
Expand Down Expand Up @@ -224,17 +227,18 @@ registerQueryDocumentation('resolve-value', {
functionName: executeSearch.name,
functionFile: '../queries/catalog/resolve-value-query/resolve-value-query-executor.ts',
buildExplanation: async(shell: RShell) => {
const exampleCode = 'x <- 1\nprint(x)';
const exampleCode = 'x <- 1\ny <-2\nprint(x)\nprint(y)';
const criteria = ['3@x', '4@y'] as SlicingCriteria;
return `
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).
The extent to which flowR traces values (e.g., built-ins vs. constants) can be configured in flowR's Configuration file (see the [Interface](${FlowrWikiBaseRef}/Interface) wiki page for more information).

Using the example code \`${exampleCode}\` (with the \`print(x)\` in the second line), the following query returns all values of \`x\` in the code:
Using the example code \`${exampleCode}\` (with newlines), the following query returns all values of \`x\` in the code:
${
await showQuery(shell, exampleCode, [{
type: 'resolve-value',
criteria: ['2@x']
}], { showCode: true })
criteria: criteria
}], { showCode: true, shorthand: sliceQueryShorthand(criteria, escapeNewline(exampleCode)) })
}
`;
}
Expand Down Expand Up @@ -270,6 +274,7 @@ registerQueryDocumentation('origin', {
functionFile: '../queries/catalog/origin-query/origin-query-executor.ts',
buildExplanation: async(shell: RShell) => {
const exampleCode = 'x <- 1\nprint(x)';
const criterion = '2@x';
return `
With this query you can use flowR's origin tracking to find out the read origins of a variable,
the functions called by a call, and more.
Expand All @@ -278,8 +283,8 @@ Using the example code \`${exampleCode}\` (with the \`print(x)\` in the second l
${
await showQuery(shell, exampleCode, [{
type: 'origin',
criterion: '2@x'
}], { showCode: true })
criterion: criterion
}], { showCode: true, shorthand: sliceQueryShorthand([criterion], escapeNewline(exampleCode)) })
}
`;
}
Expand Down Expand Up @@ -375,7 +380,7 @@ ${
}], { showCode: false, collapseQuery: true, collapseResult: true })
}

Please note that, in the repl, a special syntax starting with \`+\` (which should be autocompleted) can be used to update the configuration on the fly:
Please note that, in the REPL, a special syntax starting with \`+\` (which should be autocompleted) can be used to update the configuration on the fly:

${
await documentReplSession(shell, [
Expand All @@ -397,13 +402,22 @@ registerQueryDocumentation('df-shape', {
functionFile: '../queries/catalog/df-shape-query/df-shape-query-format.ts',
buildExplanation: async(shell: RShell) => {
const exampleCode = 'x <- data.frame(a=1:3)\nfilter(x, FALSE)';
const criterion = '2@x' as SingleSlicingCriterion;
return `
This query infers all shapes of dataframes within the code. For example, you can use:
${
await showQuery(shell, exampleCode, [{
type: 'df-shape'
}], { showCode: true, collapseQuery: true })
}

The query also accepts an optional slice criterion to narrow the results to a specific node. For example:
${
await showQuery(shell, exampleCode, [{
type: 'df-shape',
criterion: criterion
}], { showCode: true, collapseQuery: true, shorthand: sliceQueryShorthand([criterion], escapeNewline(exampleCode)) })
}
`;
}
});
Expand Down Expand Up @@ -476,7 +490,8 @@ registerQueryDocumentation('static-slice', {
functionName: executeStaticSliceQuery.name,
functionFile: '../queries/catalog/static-slice-query/static-slice-query-executor.ts',
buildExplanation: async(shell: RShell) => {
const exampleCode = 'x <- 1\ny <- 2\nx';
const exampleCode = 'x <- 1\ny <- 2\nz <- 3\nx';
const criteria = ['3@z','4@x'] as SlicingCriteria;
return `
To slice, _flowR_ needs one thing from you: a variable or a list of variables (function calls are supported to, referring to the anonymous
return of the call) that you want to slice the dataflow graph for (additionally, you have to tell flowR if you want to have a forward slice).
Expand All @@ -490,8 +505,8 @@ If you are interested in the parts required for the use of \`x\` in the last lin
${
await showQuery(shell, exampleCode, [{
type: 'static-slice',
criteria: ['3@x']
}], { showCode: false })
criteria: criteria
}], { showCode: false, shorthand: sliceQueryShorthand(criteria, escapeNewline(exampleCode)) })
}

In general, you may be uninterested in seeing the reconstructed version and want to save some computation time, for this,
Expand All @@ -501,7 +516,7 @@ ${
details('No Reconstruction Example',
await showQuery(shell, exampleCode, [{
type: 'static-slice',
criteria: ['3@x'],
criteria: ['4@x'],
noReconstruction: true
}], { showCode: false })
)
Expand All @@ -514,7 +529,7 @@ ${
type: 'static-slice',
criteria: ['1@x'],
direction: SliceDirection.Forward
}], { showCode: false })
}], { showCode: false, shorthand: sliceQueryShorthand(['1@x'], escapeNewline(exampleCode), true) })
}

You can disable [magic comments](${FlowrWikiBaseRef}/Interface#slice-magic-comments) using the \`noMagicComments\` flag.
Expand Down Expand Up @@ -601,7 +616,18 @@ ${
}], { showCode: false, collapseQuery: true })
}

You can also configure which rules to apply and what settings to use for these rules.
You can also configure which rules to apply and what settings to use for these rules:
${
await showQuery(shell, exampleCode, [{
type: 'linter',
rules: ['file-path-validity'],
}], {
showCode: false,
collapseQuery: true,
shorthand: `rules:file-path-validity "${exampleCode}"`
})
}

We welcome any feedback and suggestions for new rules on this (consider opening a [new issue](${NewIssueUrl})).
`;
}
Expand Down Expand Up @@ -685,6 +711,7 @@ registerQueryDocumentation('location-map', {
files: [path.resolve('./src/util/range.ts')],
});
const exampleCode = 'x + 1\nx * 2';
const criteria = ['1@x','2@x'] as SlicingCriteria;
return `
A query like the ${linkToQueryOfName('id-map')} query can return a huge result, especially for larger scripts.
If you are not interested in all of the information contained within the full map, you can use the location map query to get a simple mapping of ids to their location in the source file.
Expand All @@ -701,6 +728,15 @@ ${
}], { showCode: false, collapseQuery: true })
}

The query also accepts a list of slice criteria to filter the results to only include the locations of specific nodes. For example:

${
await showQuery(shell, exampleCode, [{
type: 'location-map',
ids: criteria
}], { showCode: false, collapseQuery: true, shorthand: sliceQueryShorthand(criteria, escapeNewline(exampleCode)) })
}

All locations are given as a ${shortLink('SourceRange', types.info)} paired with the file id in the format \`[file-id, [start-line, start-column, end-line, end-column]]\`.

`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface DfShapeQueryResult extends BaseQueryResult {
domains: DataFrameStateDomain | Map<SingleSlicingCriterion, DataFrameDomain | undefined>
}

function dfShapeQueryLineParser(output: ReplOutput, line: readonly string[], _config: FlowrConfigOptions): ParsedQueryLine<'df-shape'> {
function dfShapeQueryLineParser(_output: ReplOutput, line: readonly string[], _config: FlowrConfigOptions): ParsedQueryLine<'df-shape'> {
const criterion = sliceCriterionParser(line[0]);

return {
Expand Down
Loading