diff --git a/src/documentation/data/server/doc-data-server-messages.ts b/src/documentation/data/server/doc-data-server-messages.ts index 5c6b8d5f343..4b8150da238 100644 --- a/src/documentation/data/server/doc-data-server-messages.ts +++ b/src/documentation/data/server/doc-data-server-messages.ts @@ -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'; @@ -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'; @@ -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', diff --git a/src/documentation/doc-util/doc-escape.ts b/src/documentation/doc-util/doc-escape.ts index f83b42863c8..0e5086e3d43 100644 --- a/src/documentation/doc-util/doc-escape.ts +++ b/src/documentation/doc-util/doc-escape.ts @@ -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'; + }); +} diff --git a/src/documentation/doc-util/doc-query.ts b/src/documentation/doc-util/doc-query.ts index 695235bc800..e589ef27dce 100644 --- a/src/documentation/doc-util/doc-query.ts +++ b/src/documentation/doc-util/doc-query.ts @@ -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; } /** @@ -25,7 +27,11 @@ export interface ShowQueryOptions { export async function showQuery< Base extends SupportedQueryTypes, VirtualArguments extends VirtualCompoundConstraint = VirtualCompoundConstraint ->(shell: RShell, code: string, queries: Queries, { showCode, collapseResult, collapseQuery }: ShowQueryOptions = {}): Promise { +>( + shell: RShell, code: string, + queries: Queries, + { showCode, collapseResult, collapseQuery, shorthand }: ShowQueryOptions = {} +): Promise { const now = performance.now(); const analyzer = await new FlowrAnalyzerBuilder(requestFromInput(code)).setParser(shell).build(); const results = await analyzer.query(queries); @@ -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 ''; } @@ -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, '-'); } diff --git a/src/documentation/doc-util/doc-search.ts b/src/documentation/doc-util/doc-search.ts index 8358cd2528f..acdc131a6ca 100644 --- a/src/documentation/doc-util/doc-search.ts +++ b/src/documentation/doc-util/doc-search.ts @@ -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'; @@ -72,90 +69,4 @@ ${await printDfGraphForCode(shell, code, { showCode: false, switchCodeAndGraph: ${collapseResult ? '' : ''} `; - -} - -export interface QueryDocumentation { - readonly name: string; - readonly type: 'virtual' | 'active'; - readonly shortDescription: string; - readonly functionName: string; - readonly functionFile: string; - readonly buildExplanation: (shell: RShell) => Promise; -} - -export const RegisteredQueries = { - 'active': new Map(), - 'virtual': new Map() -}; - - -/** - * - */ -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)} - -
- -Implementation Details - -Responsible for the execution of the ${name} query is \`${functionName}\` in ${getFilePathMd(functionFile)}. - -
- -`; -} - - -/** - * - */ -export async function explainQueries(shell: RShell, type: 'active' | 'virtual'): Promise { - 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`); } diff --git a/src/documentation/print-interface-wiki.ts b/src/documentation/print-interface-wiki.ts index a5df6bb1ca1..fa847908f8a 100644 --- a/src/documentation/print-interface-wiki.ts +++ b/src/documentation/print-interface-wiki.ts @@ -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 (REPL) works relatively simple. -You can submit an expression (using enter), +You can submit an expression (using Enter), which is interpreted as an R 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 CTRL+C twice. +Besides, you can leave the REPL either with the command ${getReplCommand('quit')} or by pressing Ctrl+C twice. +When writing a *command*, you may press Tab 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. diff --git a/src/documentation/print-query-wiki.ts b/src/documentation/print-query-wiki.ts index 9ced4e76098..2b3ae5c0f56 100644 --- a/src/documentation/print-query-wiki.ts +++ b/src/documentation/print-query-wiki.ts @@ -9,6 +9,7 @@ import { linkToQueryOfName, registerQueryDocumentation, showQuery, + sliceQueryShorthand, tocForQueryType } from './doc-util/doc-query'; import { describeSchema } from '../util/schema'; @@ -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', { @@ -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)) }) } `; } @@ -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. @@ -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)) }) } `; } @@ -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, [ @@ -397,6 +402,7 @@ 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: ${ @@ -404,6 +410,14 @@ ${ 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)) }) +} `; } }); @@ -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). @@ -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, @@ -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 }) ) @@ -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. @@ -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})). `; } @@ -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. @@ -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]]\`. `; diff --git a/src/queries/catalog/df-shape-query/df-shape-query-format.ts b/src/queries/catalog/df-shape-query/df-shape-query-format.ts index 7b6d0654283..bb1fbb95b87 100644 --- a/src/queries/catalog/df-shape-query/df-shape-query-format.ts +++ b/src/queries/catalog/df-shape-query/df-shape-query-format.ts @@ -20,7 +20,7 @@ export interface DfShapeQueryResult extends BaseQueryResult { domains: DataFrameStateDomain | Map } -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 { diff --git a/src/queries/catalog/location-map-query/location-map-query-format.ts b/src/queries/catalog/location-map-query/location-map-query-format.ts index 1398bc40709..ca42c03eba5 100644 --- a/src/queries/catalog/location-map-query/location-map-query-format.ts +++ b/src/queries/catalog/location-map-query/location-map-query-format.ts @@ -28,7 +28,7 @@ export interface LocationMapQueryResult extends BaseQueryResult { } } -function locationMapLineParser(output: ReplOutput, line: readonly string[], _config: FlowrConfigOptions): ParsedQueryLine<'location-map'> { +function locationMapLineParser(_output: ReplOutput, line: readonly string[], _config: FlowrConfigOptions): ParsedQueryLine<'location-map'> { const criteria = sliceCriteriaParser(line[0]); return { query: { diff --git a/test/testfiles/df-shape.R b/test/testfiles/df-shape.R new file mode 100644 index 00000000000..4053eb94f44 --- /dev/null +++ b/test/testfiles/df-shape.R @@ -0,0 +1,22 @@ +# Example R script for demonstrating the flowR dataframe shape inference +# Try it out in the REPL: +#R> :query @df-shape file://test/testfiles/df-shape.R + +library(dplyr) + +df1 <- data.frame( + id = 1:4, + age = c(25, 32, 35, 40), + score = c(90, 85, 88, 92) +) +df2 <- data.frame( + id = c(1, 2, 4), + category = c("A", "B", "A") +) +df3 <- df1 %>% + filter(age > 30) %>% + mutate(level = score^2) %>% + left_join(df2, by = "id") %>% + select(-age) + +print(df3$level) \ No newline at end of file diff --git a/test/testfiles/linter.R b/test/testfiles/linter.R new file mode 100644 index 00000000000..e092c00c80d --- /dev/null +++ b/test/testfiles/linter.R @@ -0,0 +1,8 @@ +# Example R script for demonstrating the flowR linter +# Try it out in the REPL: +# R> :query @linter rules:absolute-file-paths,dead-code file://test/testfiles/linter.R + +my_data <- read.csv("C:/Users/Researcher/R/Reproducible.csv") +if (FALSE) { + dplyr::sample_n(my_data, 5) +} \ No newline at end of file diff --git a/wiki/Overview.md b/wiki/Overview.md index b67b15c0c99..4b45f170701 100644 --- a/wiki/Overview.md +++ b/wiki/Overview.md @@ -3,7 +3,7 @@ If you have never used _flowR_ before, please refer to the [setup](https://githu - [_flowR_'s Modules](#flowrs-modules) -- [Using _flowR_](#using-flowr) +- [Using _flowR_ from the outside](#using-flowr-from-the-outside) - [The Read-Eval-Print Loop (REPL)](#the-read-eval-print-loop-repl) - [The Server](#the-server) - [Calling the Scripts Directly](#calling-the-scripts-directly) @@ -43,8 +43,7 @@ with the important components directly related to the analysis highlighted accor Primarily, _flowR_ provides a dataflow analysis framework for the [*R*](https://www.r-project.org/) programming language. Its subcomponents (like the custom [R bridge](https://github.com/flowr-analysis/flowr/tree/main/src/r-bridge) or the internals of the static [dataflow analysis]("https://github.com/flowr-analysis/flowr/tree/main/src/dataflow)) are not important if you simply wish to use _flowR_. -If you wish to use flowR, check out one of its extensions (e.g., the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=code-inspect.vscode-flowr)) -or its coding API with the [`FlowrAnalyzer`](https://github.com/flowr-analysis/flowr/wiki/Core). +If you wish to use flowR, check out one of its extensions (e.g., the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=code-inspect.vscode-flowr)), the [REPL and server interfaces](#using-_flowr_-from-the-outside) or its coding API with the [`FlowrAnalyzer`](https://github.com/flowr-analysis/flowr/wiki/Core). The benchmark module is only of interest if you want to benchmark/measure the runtime performance and reduction of the slicer. It is available with the [`benchmark`](#benchmark-the-slicer) script. @@ -56,25 +55,27 @@ The [utility](https://github.com/flowr-analysis/flowr/tree/main/src/util) module The following sections explain how to use these features. -## Using _flowR_ +## Using _flowR_ from the outside _flowR_ itself has two main ways to operate: -- as a **server** which processes analysis and slicing requests (`--server` option) -- as a **read-eval-print loop** (REPL) that can be accessed directly from the command line (default option) +- as a [**server**](#the-server) which processes analysis and slicing requests (`--server` option) +- as a [**read-eval-print loop** (REPL)](#the-read-eval-print-loop-repl) that can be accessed directly from the command line (default option) Besides these two ways, there is a [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=code-inspect.vscode-flowr) that allows you to use _flowR_ directly from within the editor (it is available on [open-vsx](https://open-vsx.org/extension/code-inspect/vscode-flowr) as well). Similarly, we offer an [Addin for RStudio](https://github.com/flowr-analysis/rstudio-addin-flowr), as well as an [R package](https://github.com/flowr-analysis/flowr-r-adapter). 🐳️ If you use the docker-version, simply starting the docker container in interactive mode drops you right into the REPL (`docker run -it --rm eagleoutice/flowr:latest`), while launching with the `--server` argument starts the server (`docker run -it --rm eagleoutice/flowr:latest --server`).\ -⚒️ If you compile the _flowR_ sources yourself, you can access _flowR_ by first building the sources (`npm run build`) and executing then the root script (`node dist/src/flowr.js`). +⚒️ If you compile the _flowR_ sources yourself, you can access _flowR_ by first building the sources (`npm run build`) and executing then the root script (`node dist/src/cli/flowr.js`). -Independent of your way of launching *flowr*, we will write simply `flowr` for either (🐳️) `docker run -it --rm eagleoutice/flowr:latest` or (⚒️) `node dist/src/flowr.js`. See the [setup](https://github.com/flowr-analysis/flowr/wiki/Setup) wiki page for more information on how to get _flowR_ running. +Independent of your way of launching *flowr*, we will write simply `flowr` for either (🐳️) `docker run -it --rm eagleoutice/flowr:latest` or (⚒️) `node dist/src/cli/flowr.js`. See the [setup](https://github.com/flowr-analysis/flowr/wiki/Setup) wiki page for more information on how to get _flowR_ running. ### The Read-Eval-Print Loop (REPL) Once you launched _flowR_, you should see a small `R>` prompt. Use `:help` to receive instructions on how to use the REPL and what features are available (most prominently, you can access all [scripts](#calling-the-scripts-directly) simply by adding a colon before them). -In general, all commands start with a colon (`:`), everything else is interpreted as a R expression which is directly evaluated by the underlying R shell (however, due to security concerns, you need to start _flowR_ with `--r-session-access` and use the `r-shell` engine to allow this). The following GIF showcases a simple example session: +In general, all commands start with a colon (`:`), everything else is interpreted as a R expression which is directly evaluated by the underlying R shell (however, due to security concerns, you need to start _flowR_ with `--r-session-access` and use the `r-shell` engine to allow this). +See the [Interface](https://github.com/flowr-analysis/flowr/wiki/Interface) wiki page for more information on usage and the available commands. +The following GIF showcases a simple example session: ![Example of a simple REPL session](gif/repl-demo-opt.gif)