diff --git a/.github/workflows/check-broken-links.yaml b/.github/workflows/broken-links-and-wiki.yaml similarity index 77% rename from .github/workflows/check-broken-links.yaml rename to .github/workflows/broken-links-and-wiki.yaml index cd3f4ee810..9feb759017 100644 --- a/.github/workflows/check-broken-links.yaml +++ b/.github/workflows/broken-links-and-wiki.yaml @@ -1,4 +1,4 @@ -name: Check for Broken Links +name: Check for Broken Links and Publish Wiki 'on': push: @@ -17,10 +17,19 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: "Setup the Environment" + uses: Code-Inspect/flowr/.github/actions/setup@main + + - name: "Checkout Repository" + uses: actions/checkout@v4 with: lfs: true + - name: Update the Capabilities Wiki Page + run: | + npm ci + npm run capabilities-markdown --silent > wiki/Capabilities-New.md + - name: Check the README for broken links uses: Wandalen/wretry.action@v1.3.0 with: diff --git a/package.json b/package.json index dbf26cc404..711974cdfa 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "benchmark": "ts-node src/cli/benchmark-app.ts", "summarizer": "ts-node src/cli/summarizer-app.ts", "export-quads": "ts-node src/cli/export-quads-app.ts", + "capabilities-markdown": "ts-node src/r-bridge/data/print.ts", "build": "tsc --project .", "lint-local": "eslint src/ test/ --rule \"no-warning-comments: off\"", "lint": "npm run license-compat -- --summary && eslint src/ test/", diff --git a/src/r-bridge/data/data.ts b/src/r-bridge/data/data.ts new file mode 100644 index 0000000000..c315ee34b2 --- /dev/null +++ b/src/r-bridge/data/data.ts @@ -0,0 +1,701 @@ +import { FlowrCapabilities } from './types' + +export const flowrCapabilities = { + name: 'Capabilities of flowR', + description: 'This is an evolving representation of what started with #636 to formulate capabilities in a structured format.', + version: '0.0.1', + capabilities: [ + { + name: 'Names and Identifiers', + id: 'names-and-identifiers', + capabilities: [ + { + name: 'Form', + id: 'form', + capabilities: [ + { + name: 'Normal', + id: 'name-normal', + supported: 'fully', + description: '_Recognize constructs like `a`, `plot`, ..._' + }, + { + name: 'Quoted', + id: 'name-quoted', + supported: 'fully', + description: "_Recognize `\"a\"`, `'plot'`, ..._" + }, + { + name: 'Escaped', + id: 'name-escaped', + supported: 'fully', + description: '_Recognize `` `a` ``, `` `plot` ``, ..._' + } + ] + }, + { + name: 'Resolution', + id: 'resolution', + capabilities: [ + { + name: 'Global Scope', + id: 'global-scope', + supported: 'fully', + description: '_For example, tracking a big table of current identifier bindings_' + }, + { + name: 'Lexicographic Scope', + id: 'lexicographic-scope', + supported: 'fully', + description: '_For example, support function definition scopes_' + }, + { + name: 'Closures', + id: 'closures', + supported: 'partially', + description: '_Handling [function factories](https://adv-r.hadley.nz/function-factories.html) and friends._ Currently, we do not have enough tests to be sure.' + }, + { + name: 'Dynamic Environment Resolution', + id: 'dynamic-environment-resolution', + supported: 'not', + description: '_For example, using `new.env` and friends_' + }, + { + name: 'Environment Sharing', + id: 'environment-sharing', + supported: 'not', + description: '_Handling side-effects by environments which are not copied when modified_' + }, + { + name: 'Search Path', + id: 'search-path', + supported: 'partially', + description: "_Handling [R's search path](https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Search-path) as explained in [Advanced R](https://adv-r.hadley.nz/environments.html#search-path)._ Currently, _flowR_ does not support dynamic modifications with `attach`, `search`, or `fn_env` and tests are definitely missing. Yet, theoretically, the tooling is all there." + }, + { + name: 'Namespaces', + id: 'namespaces', + supported: 'not', + description: "_Handling R's namespaces as explained in [Advanced R](https://adv-r.hadley.nz/environments.html#namespaces)_" + }, + { + name: 'Accessing Exported Names', + id: 'accessing-exported-names', + supported: 'not', + description: '_Resolving calls with `::` to their origin._ Accessing external files is currently work in progress.' + }, + { + name: 'Accessing Internal Names', + id: 'accessing-internal-names', + supported: 'not', + description: '_Similar to `::` but for internal names._' + }, + { + name: 'Library Loading', + id: 'library-loading', + supported: 'not', + description: '_Resolve libraries identified with `library`, `require`, `attachNamespace`, ... and attach them to the search path_' + } + ] + } + ] + }, + { + name: 'Expressions', + id: 'expressions', + capabilities: [ + { + name: 'Function Calls', + id: 'function-calls', + capabilities: [ + { + name: 'Grouping', + id: 'grouping', + supported: 'fully', + description: '_Recognize groups done with `(`, `{`, ... (more precisely, their default mapping to the primitive implementations)._' + }, + { + name: 'Normal', + id: 'normal', + supported: 'fully', + description: '_Recognize and resolve calls like `f(x)`, `foo::bar(x, y)`, ..._', + capabilities: [ + { + name: 'Unnamed Arguments', + id: 'unnamed-arguments', + supported: 'fully', + description: '_Recognize and resolve calls like `f(3)`, `foo::bar(3, c(1,2))`, ..._' + }, + { + name: 'Named Arguments', + id: 'named-arguments', + supported: 'fully', + description: '_Recognize and resolve calls like `f(x = 3)`, `foo::bar(x = 3, y = 4)`, ..._' + }, + { + name: 'Resolve Arguments', + id: 'resolve-arguments', + supported: 'partially', + description: '_Correctly bind arguments (including [`pmatch`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/pmatch))._ Currently, we do not have a correct implementation for `pmatch`. Furthermore, more tests would be nice.' + }, + { + name: 'Side-Effects in Argument', + id: 'side-effects-in-argument', + supported: 'partially', + description: '_Handle side-effects of arguments (e.g., `f(x <- 3)`, `f(x = y <- 3)`, ...)._ We have not enough tests to be sure' + }, + { + name: 'Side-Effects in Function Call', + id: 'side-effects-in-function-call', + supported: 'partially', + description: '_Handle side-effects of function calls (e.g., `setXTo(3)`, ...) for example achieved with the super assignment._ We need more tests and handlings. Furthermore, we do not detect side effects with external files, network, logging, etc.' + } + ] + }, + { + name: 'Anonymous Calls', + id: 'anonymous-calls', + supported: 'fully', + description: '_Recognize and resolve calls like `(function(x) x)(3)`, `factory(0)()`, ..._' + }, + { + name: 'Infix Calls', + id: 'infix-calls', + supported: 'fully', + description: '_Recognize and resolve calls like `x + y`, `x %>% f(y)`, ..._' + }, + { + name: 'Redefinition of Built-In Functions/primitives', + id: 'redefinition-of-built-in-functions-primitives', + supported: 'partially', + description: '_Handle cases like `print <- function(x) x`, `` `for` <- function(a,b,c) a``, ..._ Currently, we can not handle all of them there are no tests. Still wip as part of desugaring' + }, + { + name: 'Index Access', + id: 'index-access', + capabilities: [ + { + name: 'Single Bracket Access', + id: 'single-bracket-access', + supported: 'fully', + description: '_Detect calls like `x[i]`, `x[i, b]` `x[[i]]`, ... This does not include the real separation of cells, which is handled extra._' + }, + { + name: 'Double Bracket Access', + id: 'double-bracket-access', + supported: 'fully', + description: '_Detect calls like `x[[i]]`, `x[[i, b]]`, ... Similar to single bracket._' + }, + { + name: 'Dollar Access', + id: 'dollar-access', + supported: 'fully', + description: '_Detect calls like `x$y`, `x$"y"`, `x$y$z`, ..._' + }, + { + name: 'Slotted Access', + id: 'slotted-access', + supported: 'fully', + description: '_Detect calls like `x@y`, `x@y@z`, ..._' + }, + { + name: 'Access with Argument-Names', + id: 'access-with-argument-names', + supported: 'fully', + description: '_Detect calls like `x[i = 3]`, `x[[i=]]`, ..._' + }, + { + name: 'Subsetting', + id: 'subsetting', + supported: 'fully', + description: '_Detect calls like `x[i > 3]`, `x[c(1,3)]`, ..._' + } + ] + }, + { + name: 'Operators', + id: 'operators', + capabilities: [ + { + name: 'Unary Operator', + id: 'unary-operator', + supported: 'fully', + description: '_Recognize and resolve calls like `+3`, `-3`, ..._' + }, + { + name: 'Binary Operator', + id: 'binary-operator', + supported: 'fully', + description: '_Recognize and resolve calls like `3 + 4`, `3 * 4`, ..._', + capabilities: [ + { + name: 'Special Operator', + id: 'special-operator', + supported: 'fully', + description: '_Recognize and resolve calls like `3 %in% 4`, `3 %*% 4`, ..._' + }, + { + name: 'Model Formula', + id: 'model-formula', + supported: 'partially', + description: '_Recognize and resolve calls like `y ~ x`, `y ~ x + z`, ... including their implicit redefinitions of some functions._ Currently, we do not handle their redefinition and only treat model formulas as normal binary operators' + }, + { + name: 'Assignments and Bindings', + id: 'assignments-and-bindings', + capabilities: [ + { + name: 'Local Left Assignment', + id: 'local-left-assignment', + supported: 'fully', + description: '_Handle `x <- 3`, `x$y <- 3`, ..._' + }, + { + name: 'Local Right Assignment', + id: 'local-right-assignment', + supported: 'fully', + description: '_Handle `3 -> x`, `3 -> x$y`, ..._' + }, + { + name: 'Local Equal Assignment', + id: 'local-equal-assignment', + supported: 'fully', + description: '_Handle `x = 3`, `x$y := 3`, ..._' + }, + { + name: 'Super Left Assignment', + id: 'super-left-assignment', + supported: 'fully', + description: '_Handle `x <<- 42`, `x$y <<- 42`, ..._' + }, + { + name: 'Super Right Assignment', + id: 'super-right-assignment', + supported: 'fully', + description: '_Handle `42 ->> x`, `42 ->> x$y`, ..._' + }, + { + name: 'Return Value of Assignments', + id: 'return-value-of-assignments', + supported: 'fully', + description: '_Handle `x <- 3` returning `3`, e.g., in `x <- y <- 3`_' + }, + { + name: 'Assignment Functions', + id: 'assignment-functions', + supported: 'partially', + description: '_Handle `assign(x, 3)`, `delayedAssign(x, 3)`, ..._ Currently we can not handle all of them and tests are rare.' + }, + { + name: 'Range Assignment', + id: 'range-assignment', + supported: 'fully', + description: '_Handle `x[1:3] <- 3`, `x$y[1:3] <- 3`, ..._' + }, + { + name: 'Replacement Functions', + id: 'replacement-functions', + supported: 'partially', + description: '_Handle `x[i] <- 3`, `x$y <- 3`, ... as `` `[<-`(x, 3) ``, ..._ Currently work in progress as part of the desugaring but still untested.' + }, + { + name: 'Locked Bindings', + id: 'locked-bindings', + supported: 'not', + description: '_Handle `lockBinding(x, 3)`, ..._' + } + ] + } + ] + } + ] + }, + { + name: 'Control-Flow', + id: 'control-flow', + capabilities: [ + { + name: 'if', + id: 'if', + supported: 'fully', + description: '_Handle `if (x) y else z`, `if (x) y`, ..._' + }, + { + name: 'for loop', + id: 'for-loop', + supported: 'fully', + description: '_Handle `for (i in 1:3) print(i)`, ..._' + }, + { + name: 'while loop', + id: 'while-loop', + supported: 'fully', + description: '_Handle `while (x) b`, ..._' + }, + { + name: 'repeat loop', + id: 'repeat-loop', + supported: 'fully', + description: '_Handle `repeat {b; if (x) break}`, ..._' + }, + { + name: 'break', + id: 'break', + supported: 'fully', + description: '_Handle `break` (including `break()`) ..._' + }, + { + name: 'next', + id: 'next', + supported: 'fully', + description: '_Handle `next` (including `next()`) ..._' + }, + { + name: 'switch', + id: 'switch', + supported: 'fully', + description: '_Handle `switch(3, "a", "b", "c")`, ..._' + }, + { + name: 'return', + id: 'return', + supported: 'fully', + description: '_Handle `return(3)`, ... in function definitions_' + }, + { + name: 'exceptions', + id: 'exceptions', + supported: 'not', + description: '_Handle `try`, `stop`, ..._' + } + ] + }, + { + name: 'Function Definitions', + id: 'function-definitions', + capabilities: [ + { + name: 'Normal', + id: 'normal', + supported: 'fully', + description: '_Handle `function() 3`, ..._' + }, + { + name: 'Formals', + id: 'formals', + capabilities: [ + { + name: 'Named', + id: 'formals-named', + supported: 'fully', + description: '_Handle `function(x) x`, ..._' + }, + { + name: 'Default', + id: 'formals-default', + supported: 'fully', + description: '_Handle `function(x = 3) x`, ..._' + }, + { + name: 'Dot-Dot-Dot', + id: 'formals-dot-dot-dot', + supported: 'fully', + description: '_Handle `function(...) 3`, ..._' + }, + { + name: 'Promises', + id: 'formals-promises', + supported: 'partially', + description: '_Handle `function(x = y) { y <- 3; x }`, `function(x = { x <- 3; x}) { x * x }`, ..._ We _try_ to identify promises correctly but this is really rudimentary.' + } + ] + }, + { + name: 'Implicit Return', + id: 'implicit-return', + supported: 'fully', + description: '_Handle the return of `function() 3`, ..._' + }, + { + name: 'Lambda Syntax', + id: 'lambda-syntax', + supported: 'fully', + description: '_Support `\\(x) x`, ..._' + } + ] + }, + { + name: 'Important Built-Ins', + id: 'important-built-ins', + capabilities: [ + { + name: 'Pipe and Pipe-Bind', + id: 'built-in-pipe-and-pipe-bind', + supported: 'partially', + description: '_Handle the [new (4.1) pipe and pipe-bind syntax](https://www.r-bloggers.com/2021/05/the-new-r-pipe/): `|>`, and `=>`._ We have not enough tests and do not support pipe-bind.' + }, + { + name: 'Sequencing', + id: 'built-in-sequencing', + supported: 'not', + description: '_Handle `:`, `seq`, ... as they are used often._' + }, + { + name: 'Internal and Primitive Functions', + id: 'built-in-internal-and-primitive-functions', + supported: 'partially', + description: '_Handle `.Internal`, `.Primitive`, ..._ In general we can not handle them as they refer to non-R code. Yet we support some (like control-flow) as defined above.' + }, + { + name: 'Options', + id: 'built-in-options', + supported: 'not', + description: '_Handle `options`, `getOption`, ..._ Currently, we do not support the function at all.' + }, + { + name: 'Help', + id: 'built-in-help', + supported: 'partially', + description: '_Handle `help`, `?`, ..._ We do not support the function in a sensible way but just ignore it (although this does not happen resolved).' + }, + { + name: 'Reflection / "Computing on the Language"', + id: 'reflection-"computing-on-the-language"', + capabilities: [ + { + name: 'Get Function Structure', + id: 'get-function-structure', + supported: 'not', + description: '_Handle `body`, `formals`, `environment` to access the respective parts of a function._ We do not support the functions at all.' + }, + { + name: 'Modify Function Structure', + id: 'modify-function-structure', + supported: 'not', + description: '_Handle `body<-`, `formals<-`, `environment<-` to modify the respective parts of a function._ We do not support the functions at all.' + }, + { + name: 'Quoting', + id: 'built-in-quoting', + supported: 'partially', + description: '_Handle `quote`, `substitute`, `bquote`, ..._ We partially ignore some of them but most likely not all.' + }, + { + name: 'Evaluation', + id: 'built-in-evaluation', + supported: 'not', + description: '_Handle `eval`, `evalq`, `eval.parent`, ..._ We do not handle them at all.' + }, + { + name: 'Parsing', + id: 'built-in-parsing', + supported: 'partially', + description: '_Handle `parse`, `deparse`, ..._ We handle them as unknown function calls, which, in a sense, is already doing something.' + } + ] + } + ] + } + ] + }, + { + name: 'Literal Values', + id: 'literal-values', + capabilities: [ + { + name: 'Numbers', + id: 'numbers', + supported: 'fully', + description: '_Recognize numbers like `3`, `3.14`, `NA`, float-hex, ..._' + }, + { + name: 'Strings', + id: 'strings', + supported: 'fully', + description: "_Recognize strings like `\"a\"`, `'b'`, ..._" + }, + { + name: 'Logical', + id: 'logical', + supported: 'fully', + description: '_Recognize the logicals `TRUE` and `FALSE`, ..._' + }, + { + name: 'NULL', + id: 'null', + supported: 'fully', + description: '_Recognize `NULL`_' + }, + { + name: 'Inf and NaN', + id: 'inf-and-nan', + supported: 'fully', + description: '_Recognize `Inf` and `NaN`_' + } + ] + } + ] + }, + { + name: 'Non-Standard Evaluations/Semantics', + id: 'non-standard-evaluations-semantics', + capabilities: [ + { + name: 'Recycling', + id: 'recycling', + supported: 'not', + description: '_Handle recycling of vectors as explained in [Advanced R](https://adv-r.hadley.nz/vectors-chap.html)._ We do not support recycling.' + }, + { + name: 'Vectorized Operator or Functions', + id: 'vectorized-operator-or-functions', + supported: 'not', + description: '_Handle vectorized operations as explained in [Advanced R](https://adv-r.hadley.nz/perf-improve.html?q=vectorised#vectorise)._ We do not support vectorized operations.' + }, + { + name: 'Hooks', + id: 'hooks', + supported: 'not', + description: '_Handle hooks like [`userhooks`](https://stat.ethz.ch/R-manual/R-devel/library/base/html/userhooks.html) and [`on.exit`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/on.exit)._ We do not support hooks.' + }, + { + name: 'Precedence', + id: 'precedence', + supported: 'fully', + description: '_Handle the precedence of operators as explained in the [Documentation](https://rdrr.io/r/base/Syntax.html)._ We handle the precedence of operators (implicitly with the parser).' + }, + { + name: 'Attributes', + id: 'attributes', + capabilities: [ + { + name: 'User-Defined', + id: 'user-defined', + supported: 'not', + description: '_Handle [attributes](https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Attributes) like `attr`, `attributes`, ..._ We do not support attributes.' + }, + { + name: 'Built-In', + id: 'built-in', + supported: 'not', + description: '_Handle built-in attributes like `dim`, ..._ We do not support them.' + } + ] + } + ] + }, + { + name: 'Types', + id: 'types', + capabilities: [ + { + name: 'Primitive', + id: 'types-primitive', + supported: 'not', + description: '_Recognize and resolve primitive types like `numeric`, `character`, ..._ We do not support typing currently.' + }, + { + name: 'Non-Primitive', + id: 'types-non-primitive', + supported: 'not', + description: '_Recognize and resolve non-primitive/composite types._ We do not support typing currently.' + }, + { + name: 'Inference', + id: 'types-inference', + supported: 'not', + description: '_Infer types from the code._ We do not support typing currently.' + }, + { + name: 'Coercion', + id: 'types-coercion', + supported: 'not', + description: '_Handle coercion of types._ We do not support typing currently.' + }, + { + name: 'Object-Oriented Programming', + id: 'object-oriented-programming', + capabilities: [ + { + name: 'S3', + id: 'oop-s3', + note: 'https://adv-r.hadley.nz/s3.html', + supported: 'not', + description: '_Handle S3 classes and methods as one unit (with attributes etc.). Including Dispatch and Inheritance._ We do not support typing currently and do not handle objects of these classes "as units."' + }, + { + name: 'S4', + id: 'oop-s4', + note: 'https://adv-r.hadley.nz/s4.html', + supported: 'not', + description: '_Handle S4 classes and methods as one unit. Including Dispatch and Inheritance_ We do not support typing currently and do not handle objects of these classes "as units."' + }, + { + name: 'R6', + id: 'oop-r6', + note: 'https://adv-r.hadley.nz/r6.html', + supported: 'not', + description: '_Handle R6 classes and methods as one unit. Including Dispatch and Inheritance, as well as its Reference Semantics, Access Control, Finalizers, and Introspection._ We do not support typing currently and do not handle objects of these classes "as units."' + }, + { + name: 'R7/S7', + id: 'r7-s7', + note: 'https://www.r-bloggers.com/2022/12/what-is-r7-a-new-oop-system-for-r/, https://cran.r-project.org/web/packages/S7/index.html', + supported: 'not', + description: '_Handle R7 classes and methods as one unit. Including Dispatch and Inheritance, as well as its Reference Semantics, Validators, ..._ We do not support typing currently and do not handle objects of these classes "as units."' + } + ] + } + ] + }, + { + name: 'Comments', + id: 'comments', + supported: 'fully', + description: '_Recognize comments like `# this is a comment`, ... and line-directives_' + }, + { + name: 'System, I/O, FFI, and Other Files', + id: 'system-i-o-ffi-and-other-files', + capabilities: [ + { + name: 'Sourcing External Files', + id: 'sourcing-external-files', + supported: 'partially', + description: '_Handle `source`, `sys.source`, ..._ We are currently working on supporting the inclusion of external files.' + }, + { + name: 'Handling Binary Riles', + id: 'handling-binary-riles', + supported: 'not', + description: '_Handle files dumped with, e.g., [`save`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/save), ... due to their frequent usage._ We do not support binary files.' + }, + { + name: 'I/O', + id: 'i-o', + supported: 'not', + description: '_Handle `read.csv`, `write.csv`, ..._ We do not support I/O for the time being but treat them as unknown function calls.' + }, + { + name: 'Foreign Function Interface', + id: 'foreign-function-interface', + supported: 'not', + description: '_Handle `.Fortran`, `C`,..._ We do not support FFI but treat them as unknown function calls.' + }, + { + name: 'System Calls', + id: 'system-calls', + supported: 'not', + description: '_Handle [`system`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/system), `system.*`, ..._ We do not support system calls but treat them as unknown function calls.' + } + ] + }, + { + name: 'Pre-Processors/external Tooling', + id: 'pre-processors-external-tooling', + supported: 'not', + description: '_Handle pre-processors like `knitr`, `rmarkdown`, `roxygen2` ..._ We do not support pre-processors for the time being (being unable to handle things like `@importFrom`)' + } + ] +} as const satisfies FlowrCapabilities diff --git a/src/r-bridge/data/get.ts b/src/r-bridge/data/get.ts new file mode 100644 index 0000000000..8a3b47f5cf --- /dev/null +++ b/src/r-bridge/data/get.ts @@ -0,0 +1,52 @@ +import { guard } from '../../util/assert' +import { FlowrCapability } from './types' +import { flowrCapabilities } from './data' + + +/** Recursively extract all valid identifiers */ +type ExtractAllIds = + T extends { readonly capabilities: infer U } + ? U extends readonly FlowrCapability[] + ? (T['id'] | ExtractAllIds) + : T['id'] + : T['id'] + +type Capabilities = (typeof flowrCapabilities)['capabilities'][number] +export type FlowrCapabilityId = ExtractAllIds + +type PathToCapability = number[] + +export interface FlowrCapabilityWithPath extends FlowrCapability{ + path: PathToCapability +} + +function search(id: FlowrCapabilityId, capabilities: readonly FlowrCapability[], path: number[] = []): FlowrCapabilityWithPath | undefined { + let idx = 0 + for(const capability of capabilities) { + idx++ // index by one :) + if(capability.id === id) { + return { ...capability, path: [...path, idx] } + } + if(capability.capabilities) { + const found = search(id, capability.capabilities, [...path, idx]) + if(found) { + return found + } + } + } + return undefined +} + +const capabilityCache = new Map + +export function getCapabilityById(id: FlowrCapabilityId): FlowrCapabilityWithPath { + const cached = capabilityCache.get(id) + if(cached) { + return cached + } + const value = search(id, flowrCapabilities.capabilities) + guard(value !== undefined, () => `Could not find capability with id ${id}`) + capabilityCache.set(id, value) + return value +} + diff --git a/src/r-bridge/data/index.ts b/src/r-bridge/data/index.ts new file mode 100644 index 0000000000..cd7457bd55 --- /dev/null +++ b/src/r-bridge/data/index.ts @@ -0,0 +1,4 @@ +export * from './types' +export * from './get' +export * from './data' +export * from './print' diff --git a/src/r-bridge/data/print.ts b/src/r-bridge/data/print.ts new file mode 100644 index 0000000000..2c35f6d594 --- /dev/null +++ b/src/r-bridge/data/print.ts @@ -0,0 +1,65 @@ +import { FlowrCapability } from './types' +import { flowrCapabilities } from './data' + +const supportedSymbolMap: Map = new Map([ + ['not', ':red_circle:'], + ['partially', ':large_orange_diamond:'], + ['fully', ':green_square:'] +]) + +function printSingleCapability(depth: number, index: number, capability: FlowrCapability) { + const indent = ' '.repeat(depth) + const indexStr = index.toString().padStart(2, ' ') + const nextLineIndent = ' '.repeat(depth + indexStr.length) + const mainLine = `${indent}${indexStr}. **${capability.name}** (\`${capability.id}\`)` + let nextLine = '' + + if(capability.supported) { + nextLine += `${supportedSymbolMap.get(capability.supported)} ` + } + if(capability.description) { + nextLine += capability.description + } + if(capability.note) { + nextLine += `\\\n${nextLineIndent}_${capability.note}_` + } + return nextLine ? `${mainLine}\\\n${nextLineIndent}${nextLine}` : mainLine +} + +function printAsMarkdown(capabilities: readonly FlowrCapability[], depth = 0, lines: string[] = []): string { + for(let i = 0; i < capabilities.length; i++) { + const capability = capabilities[i] + const result = printSingleCapability(depth, i + 1, capability) + lines.push(result) + if(capability.capabilities) { + printAsMarkdown(capability.capabilities, depth + 1, lines) + } + } + return lines.join('\n') +} + +function getPreamble(): string { + const currentDateAndTime = new Date().toISOString().replace('T', ', ').replace(/\.\d+Z$/, ' UTC') + return ` +# Flowr Capabilities + +_This document was generated automatically from '${module.filename}' on ${currentDateAndTime}_ + +The code-font behind each capability name is a link to the capability's id. This id can be used to reference the capability in a labeled test within flowR. +Besides we use colored bullets like this: + +| | | +| ---------------------- | ----------------------------------------------------- | +| :green_square: | _flowR_ is capable of handling this feature fully | +| :large_orange_diamond: | _flowR_ is capable of handling this feature partially | +| :red_circle: | _flowR_ is not capable of handling this feature | + +:cloud: This could be a feature diagram... :cloud: + +` +} + +/** if we run this script, we want a markdown representation of the capabilities */ +if(require.main === module) { + console.log(getPreamble() + printAsMarkdown(flowrCapabilities.capabilities)) +} diff --git a/src/r-bridge/data/types.ts b/src/r-bridge/data/types.ts new file mode 100644 index 0000000000..922b4f4d1f --- /dev/null +++ b/src/r-bridge/data/types.ts @@ -0,0 +1,34 @@ +const enum RequiredFeature { + /** https://github.com/Code-Inspect/flowr/labels/typing */ + Typing, + /** https://github.com/Code-Inspect/flowr/labels/abstract%20interpretation */ + AbstractInterpretation, +} + +export interface FlowrCapability { + /** The human-readable name of the capability */ + readonly name: string + /** + * The unique identifier of the capability, used to refer to it independent of the location. + * We could use a key-value mapping. However, this way, an id is self-contained and can be moved around as one object. + */ + readonly id: string + /** A list of features that are required for the capability, extend at need. */ + readonly needs?: RequiredFeature[] + readonly description?: string + readonly note?: string + /** The level of support for the capability, undefined if it is a meta-capability that does not need such an attribute */ + readonly supported?: 'not' | 'partially' | 'fully' + readonly capabilities?: readonly FlowrCapability[] +} + +export interface FlowrCapabilities { + /** The human-readable name of the capabilities */ + readonly name: string + /** A description of the capabilities */ + readonly description: string + /** The version of the capabilities */ + readonly version: string + /** A list of the capabilities */ + readonly capabilities: readonly FlowrCapability[] +} diff --git a/test/functionality/_helper/label.ts b/test/functionality/_helper/label.ts new file mode 100644 index 0000000000..45d4c01552 --- /dev/null +++ b/test/functionality/_helper/label.ts @@ -0,0 +1,67 @@ +/** + * Labels can be used whenever a test name is expected, to wrap around the original + * string and link it to functionality it refers to. As this is currently work in + * progress, no automated linkage or validation is performed. + * @module + */ + + +import { DefaultMap } from '../../../src/util/defaultmap' +import { guard } from '../../../src/util/assert' +import { FlowrCapabilityWithPath, FlowrCapabilityId, getCapabilityById } from '../../../src/r-bridge/data' + +const TheGlobalLabelMap: DefaultMap = new DefaultMap(() => []) + +const uniqueTestId = (() => { + let id = 0 + return () => `${id++}` +})() + +/** + * Wraps a test name with a unique identifier and label it with the given ids. + * @param testname - the name of the test (`it`) to be labeled + * @param ids - the capability ids to attach to the test + */ +export function label(testname: string, ...ids: FlowrCapabilityId[]): string { + // assert ids in array are unique + guard(new Set(ids).size === ids.length, () => `Labels must be unique, but are not for ${JSON.stringify(ids)}`) + + const id = uniqueTestId() + const fullName = `[${id}, ${ids.join(', ')}] ${testname}` + const idName = `#${id} (${testname})` + + for(const l of ids) { + const capability = getCapabilityById(l) + TheGlobalLabelMap.get(capability).push(idName) + } + + return fullName +} + +function getSortedByPath(): [FlowrCapabilityWithPath, string[]][] { + // sort entries by path + return Array.from(TheGlobalLabelMap.entries()).sort(([{ path: a }], [{ path: b }]) => { + for(let i = 0; i < Math.min(a.length, b.length); i++) { + if(a[i] < b[i]) { + return -1 + } + if(a[i] > b[i]) { + return 1 + } + } + return a.length - b.length + }) +} + +after(() => { + console.log('='.repeat(80)) + const entries = getSortedByPath() + const maxTestLength = Math.max(...entries.map(([, tests]) => tests.length.toString().length)) + + for(const [label, testNames] of entries) { + const paddedLabel = `[${label.path.join('/')}] ${label.name}` + const paddedTestLength = testNames.length.toString().padStart(maxTestLength, ' ') + const tests = testNames.length > 1 ? 'tests:' : 'test: ' + console.log(`\x1b[1m${paddedLabel}\x1b[0m is covered by ${paddedTestLength} ${tests}\n \x1b[36m${testNames.join('\x1b[m, \x1b[36m')}\x1b[m`) + } +}) diff --git a/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts b/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts index f1ffbc4495..ae4b203595 100644 --- a/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts +++ b/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts @@ -10,22 +10,35 @@ import { appendEnvironments, define } from '../../../../../src/dataflow/common/e import { UnnamedArgumentPrefix } from '../../../../../src/dataflow/v1/internal/process/functions/argument' import { GlobalScope, LocalScope } from '../../../../../src/dataflow/common/environments/scopes' import { MIN_VERSION_PIPE } from '../../../../../src/r-bridge/lang-4.x/ast/model/versions' +import { label } from '../../../_helper/label' +import { FlowrCapabilityId } from '../../../../../src/r-bridge/data' describe('Atomic (dataflow information)', withShell((shell) => { - describe('uninteresting leafs', () => { - for(const input of ['42', '"test"', 'TRUE', 'NA', 'NULL']) { - assertDataflow(input, shell, input, new DataflowGraph()) + describe('Uninteresting Leafs', () => { + for(const [input, id] of [ + ['42', 'numbers'], + ['-3.14', 'numbers'], + ['"test"', 'strings'], + ['\'test\'', 'strings'], + ['TRUE', 'logical'], + ['FALSE', 'logical'], + ['NA', 'numbers'], + ['NULL', 'null'], + ['Inf', 'inf-and-nan'], + ['NaN', 'inf-and-nan'] + ] as [string, FlowrCapabilityId][]) { + assertDataflow(label(input, id), shell, input, new DataflowGraph()) } }) - assertDataflow('simple variable', shell, + assertDataflow(label('simple variable', 'name-normal'), shell, 'xylophone', new DataflowGraph().addVertex({ tag: 'use', id: '0', name: 'xylophone' }) ) describe('access', () => { describe('const access', () => { - assertDataflow('single constant', shell, + assertDataflow(label('single constant', 'name-normal', 'numbers', 'single-bracket-access'), shell, 'a[2]', new DataflowGraph().addVertex({ tag: 'use', id: '0', name: 'a', when: 'maybe' }) .addVertex({ tag: 'use', id: '2', name: `${UnnamedArgumentPrefix}2` }) diff --git a/wiki/Linting and Testing.md b/wiki/Linting and Testing.md index b9f12cab02..6d68ea9a43 100644 --- a/wiki/Linting and Testing.md +++ b/wiki/Linting and Testing.md @@ -99,7 +99,7 @@ We explain the most important workflows in the following: - running the [linter](#linting) and reporting its results - deploying the documentation to [GitHub Pages](https://code-inspect.github.io/flowr/doc/) - [release.yaml](../.github/workflows/release.yaml) is responsible for creating a new release, only to be run by repository owners. Furthermore, it adds the new docker image to [docker hub](https://hub.docker.com/r/eagleoutice/flowr). -- [check-broken-links.yaml](../.github/workflows/check-broken-links.yaml) repeatedly tests that all links are not dead! +- [broken-links-and-wiki.yaml](../.github/workflows/broken-links-and-wiki.yaml) repeatedly tests that all links are not dead! ## Linting