From f11067766255d83f9b795debe32b8b82f0795118 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 11:56:58 +0100 Subject: [PATCH 01/10] feat(tests): basic test-name label semantic --- test/functionality/_helper/label.ts | 61 +++++++++++++++++++ .../atomic/atomic-tests.ts | 7 ++- 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 test/functionality/_helper/label.ts diff --git a/test/functionality/_helper/label.ts b/test/functionality/_helper/label.ts new file mode 100644 index 0000000000..9500a96adf --- /dev/null +++ b/test/functionality/_helper/label.ts @@ -0,0 +1,61 @@ +/** + * 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' + +/** + * This type allows to specify a label identifier of *up to* the given depth (+1) + * in the form of '1', '3/2', '1/4/2', ... + */ +type NumericLabelFormat = + HelperArray['length'] extends DeepestDepth ? `${number}` : + (`${number}` | `${number}/${NumericLabelFormat}`) + + +const TheGlobalLabelMap: DefaultMap = new DefaultMap(() => []) + +const uniqueTestId = (() => { + let id = 0 + return () => `${id++}` +})() + +/** + * Wraps a test name with a unique identifier and labels it with the given labels. + * @param testname - the name of the test (`it`) to be labeled + * @param labels - the labels to attach to the test + */ +export function label(testname: string, ...labels: NumericLabelFormat[]): string { + // assert labels in array are unique + guard(new Set(labels).size === labels.length, () => `Labels must be unique, but are not for ${JSON.stringify(labels)}`) + + const id = uniqueTestId() + const fullName = `[${id}, ${labels.join(', ')}] ${testname}` + const idName = `#${id} (${testname})` + + for(const l of labels) { + TheGlobalLabelMap.get(l).push(idName) + } + + return fullName +} + +after(() => { + console.log('='.repeat(80)) + // sort entries alpha-numerically + const entries = Array.from(TheGlobalLabelMap.entries()).sort(([a], [b]) => a.localeCompare(b)) + const maxTestLength = Math.max(...entries.map(([, tests]) => tests.length)) - 1 + const maxLabelLength = Math.max(...entries.map(([label]) => label.length)) - 1 + + for(const [label, testNames] of entries) { + const paddedLabel = label.padEnd(maxLabelLength, ' ') + 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} \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..3df038a1fb 100644 --- a/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts +++ b/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts @@ -10,11 +10,12 @@ 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' 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', '2/2/1'], ['"test"', '2/2/2'], ['TRUE', '2/2/3'], ['NA', '2/2/1'], ['NULL', '2/2/4']] as const) { + assertDataflow(label(input, id), shell, input, new DataflowGraph()) } }) From d2e5a7967f3cc2bb9d0e2d6aa29ffd7576092ab9 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 13:20:24 +0100 Subject: [PATCH 02/10] feat: name based id mapping support --- test/functionality/_helper/label.ts | 4 +- test/functionality/capabilities.ts | 96 +++++++++++++++++++ .../atomic/atomic-tests.ts | 17 +++- 3 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 test/functionality/capabilities.ts diff --git a/test/functionality/_helper/label.ts b/test/functionality/_helper/label.ts index 9500a96adf..58023cf493 100644 --- a/test/functionality/_helper/label.ts +++ b/test/functionality/_helper/label.ts @@ -49,8 +49,8 @@ after(() => { console.log('='.repeat(80)) // sort entries alpha-numerically const entries = Array.from(TheGlobalLabelMap.entries()).sort(([a], [b]) => a.localeCompare(b)) - const maxTestLength = Math.max(...entries.map(([, tests]) => tests.length)) - 1 - const maxLabelLength = Math.max(...entries.map(([label]) => label.length)) - 1 + const maxTestLength = Math.max(...entries.map(([, tests]) => tests.length.toString().length)) + const maxLabelLength = Math.max(...entries.map(([label]) => label.length)) for(const [label, testNames] of entries) { const paddedLabel = label.padEnd(maxLabelLength, ' ') diff --git a/test/functionality/capabilities.ts b/test/functionality/capabilities.ts new file mode 100644 index 0000000000..fa8fb13f5a --- /dev/null +++ b/test/functionality/capabilities.ts @@ -0,0 +1,96 @@ +import { guard } from '../../src/util/assert' + +const enum RequiredFeature { + /** https://github.com/Code-Inspect/flowr/labels/typing */ + Typing, + /** https://github.com/Code-Inspect/flowr/labels/abstract%20interpretation */ + AbstractInterpretation, +} + +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: FlowrCapability[] +} + +// TODO automatically produce wiki page from this! + +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: 'name-form', + capabilities: [ + { + name: 'Normal', + id: 'name-form-normal', + description: 'Recognize constructs like `a`, `plot`, ...', + supported: 'fully', + needs: [RequiredFeature.Typing], + capabilities: [] + } + ] + } + ] + } + ] +} as const satisfies FlowrCapabilities + +/** 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 FlowrCapabilitiesId = ExtractAllIds + +function search(id: FlowrCapabilitiesId, capabilities: readonly FlowrCapability[]): FlowrCapability | undefined { + for(const capability of capabilities) { + if(capability.id === id) { + return capability + } + const found = search(id, capability.capabilities) + if(found) { + return found + } + } + return undefined +} + +export function getCapabilityById(id: FlowrCapabilitiesId): FlowrCapability { + const value = search(id, flowrCapabilities.capabilities) + guard(value !== undefined, () => `Could not find capability with id ${id}`) + return value +} 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 3df038a1fb..bdcec63fec 100644 --- a/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts +++ b/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts @@ -14,19 +14,30 @@ import { label } from '../../../_helper/label' describe('Atomic (dataflow information)', withShell((shell) => { describe('Uninteresting Leafs', () => { - for(const [input, id] of [['42', '2/2/1'], ['"test"', '2/2/2'], ['TRUE', '2/2/3'], ['NA', '2/2/1'], ['NULL', '2/2/4']] as const) { + for(const [input, id] of [ + ['42', '2/2/1'], + ['-3.14', '2/2/1'], + ['"test"', '2/2/2'], + ['\'test\'', '2/2/2'], + ['TRUE', '2/2/3'], + ['FALSE', '2/2/3'], + ['NA', '2/2/1'], + ['NULL', '2/2/4'], + ['Inf', '2/2/5'], + ['NaN', '2/2/5'] + ] as const) { assertDataflow(label(input, id), shell, input, new DataflowGraph()) } }) - assertDataflow('simple variable', shell, + assertDataflow(label('simple variable', '1/1/1'), shell, 'xylophone', new DataflowGraph().addVertex({ tag: 'use', id: '0', name: 'xylophone' }) ) describe('access', () => { describe('const access', () => { - assertDataflow('single constant', shell, + assertDataflow(label('single constant', '1/1/1', '2/2/1', '2/1/6/1'), shell, 'a[2]', new DataflowGraph().addVertex({ tag: 'use', id: '0', name: 'a', when: 'maybe' }) .addVertex({ tag: 'use', id: '2', name: `${UnnamedArgumentPrefix}2` }) From f6ae4b02d6bc99166de32dd4ac8ee8e07f6b178a Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 15:04:36 +0100 Subject: [PATCH 03/10] feat: a basic capabilities list --- test/functionality/capabilities.ts | 757 +++++++++++++++++++++++++++-- 1 file changed, 716 insertions(+), 41 deletions(-) diff --git a/test/functionality/capabilities.ts b/test/functionality/capabilities.ts index fa8fb13f5a..d8eccdf139 100644 --- a/test/functionality/capabilities.ts +++ b/test/functionality/capabilities.ts @@ -9,19 +9,19 @@ const enum RequiredFeature { interface FlowrCapability { /** The human-readable name of the capability */ - readonly name: string + 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 + 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 + 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[] + readonly supported?: 'not' | 'partially' | 'fully' + readonly capabilities?: readonly FlowrCapability[] } export interface FlowrCapabilities { @@ -35,6 +35,38 @@ export interface FlowrCapabilities { readonly capabilities: FlowrCapability[] } +/** 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 + +function search(id: FlowrCapabilityId, capabilities: readonly FlowrCapability[]): FlowrCapability | undefined { + for(const capability of capabilities) { + if(capability.id === id) { + return capability + } + if(capability.capabilities) { + const found = search(id, capability.capabilities) + if(found) { + return found + } + } + } + return undefined +} + +export function getCapabilityById(id: FlowrCapabilityId): FlowrCapability { + const value = search(id, flowrCapabilities.capabilities) + guard(value !== undefined, () => `Could not find capability with id ${id}`) + return value +} + // TODO automatically produce wiki page from this! export const flowrCapabilities = { @@ -48,49 +80,692 @@ export const flowrCapabilities = { capabilities: [ { name: 'Form', - id: 'name-form', + id: 'form', + capabilities: [ + { + name: 'Normal', + id: 'normal', + supported: 'fully', + description: '_Recognize constructs like `a`, `plot`, ..._' + }, + { + name: 'Quoted', + id: 'quoted', + supported: 'fully', + description: "_Recognize `\"a\"`, `'plot'`, ..._" + }, + { + name: 'Escaped', + id: '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: 'name-form-normal', - description: 'Recognize constructs like `a`, `plot`, ...', + id: 'normal', supported: 'fully', - needs: [RequiredFeature.Typing], - capabilities: [] + 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: 'named', + supported: 'fully', + description: '_Handle `function(x) x`, ..._' + }, + { + name: 'Default', + id: 'default', + supported: 'fully', + description: '_Handle `function(x = 3) x`, ..._' + }, + { + name: 'Dot-Dot-Dot', + id: 'dot-dot-dot', + supported: 'fully', + description: '_Handle `function(...) 3`, ..._' + }, + { + name: 'Promises', + id: '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: '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: 'sequencing', + supported: 'not', + description: '_Handle `:`, `seq`, ... as they are used often._' + }, + { + name: 'Internal and Primitive Functions', + id: '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: 'options', + supported: 'not', + description: '_Handle `options`, `getOption`, ..._ Currently, we do not support the function at all.' + }, + { + name: 'Help', + id: '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: 'quoting', + supported: 'partially', + description: '_Handle `quote`, `substitute`, `bquote`, ..._ We partially ignore some of them but most likely not all.' + }, + { + name: 'Evaluation', + id: 'evaluation', + supported: 'not', + description: '_Handle `eval`, `evalq`, `eval.parent`, ..._ We do not handle them at all.' + }, + { + name: 'Parsing', + id: '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: 'primitive', + supported: 'not', + description: '_Recognize and resolve primitive types like `numeric`, `character`, ..._ We do not support typing currently.' + }, + { + name: 'Non-Primitive', + id: 'non-primitive', + supported: 'not', + description: '_Recognize and resolve non-primitive/composite types._ We do not support typing currently.' + }, + { + name: 'Inference', + id: 'inference', + supported: 'not', + description: '_Infer types from the code._ We do not support typing currently.' + }, + { + name: 'Coercion', + id: '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: '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: '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: '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 -/** 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 FlowrCapabilitiesId = ExtractAllIds - -function search(id: FlowrCapabilitiesId, capabilities: readonly FlowrCapability[]): FlowrCapability | undefined { - for(const capability of capabilities) { - if(capability.id === id) { - return capability - } - const found = search(id, capability.capabilities) - if(found) { - return found - } - } - return undefined -} - -export function getCapabilityById(id: FlowrCapabilitiesId): FlowrCapability { - const value = search(id, flowrCapabilities.capabilities) - guard(value !== undefined, () => `Could not find capability with id ${id}`) - return value -} From e142cf9351d2bc7b30473ba7454579c095200be7 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 15:05:32 +0100 Subject: [PATCH 04/10] refactor: move capabilities to the main sources --- {test/functionality => src/r-bridge}/capabilities.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename {test/functionality => src/r-bridge}/capabilities.ts (99%) diff --git a/test/functionality/capabilities.ts b/src/r-bridge/capabilities.ts similarity index 99% rename from test/functionality/capabilities.ts rename to src/r-bridge/capabilities.ts index d8eccdf139..fdeb262f27 100644 --- a/test/functionality/capabilities.ts +++ b/src/r-bridge/capabilities.ts @@ -1,4 +1,4 @@ -import { guard } from '../../src/util/assert' +import { guard } from '../util/assert' const enum RequiredFeature { /** https://github.com/Code-Inspect/flowr/labels/typing */ @@ -768,4 +768,3 @@ export const flowrCapabilities = { } ] } as const satisfies FlowrCapabilities - From f8d3a774f667e190acf74ec4c318326e5ccd59e1 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 15:31:32 +0100 Subject: [PATCH 05/10] feat: infrastructure to auto-print markdown lists from capabilities --- package.json | 1 + .../{capabilities.ts => data/data.ts} | 71 +------------------ src/r-bridge/data/get.ts | 36 ++++++++++ src/r-bridge/data/index.ts | 4 ++ src/r-bridge/data/print.ts | 61 ++++++++++++++++ src/r-bridge/data/types.ts | 34 +++++++++ 6 files changed, 137 insertions(+), 70 deletions(-) rename src/r-bridge/{capabilities.ts => data/data.ts} (91%) create mode 100644 src/r-bridge/data/get.ts create mode 100644 src/r-bridge/data/index.ts create mode 100644 src/r-bridge/data/print.ts create mode 100644 src/r-bridge/data/types.ts 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/capabilities.ts b/src/r-bridge/data/data.ts similarity index 91% rename from src/r-bridge/capabilities.ts rename to src/r-bridge/data/data.ts index fdeb262f27..ec406985c7 100644 --- a/src/r-bridge/capabilities.ts +++ b/src/r-bridge/data/data.ts @@ -1,73 +1,4 @@ -import { guard } from '../util/assert' - -const enum RequiredFeature { - /** https://github.com/Code-Inspect/flowr/labels/typing */ - Typing, - /** https://github.com/Code-Inspect/flowr/labels/abstract%20interpretation */ - AbstractInterpretation, -} - -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: FlowrCapability[] -} - -/** 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 - -function search(id: FlowrCapabilityId, capabilities: readonly FlowrCapability[]): FlowrCapability | undefined { - for(const capability of capabilities) { - if(capability.id === id) { - return capability - } - if(capability.capabilities) { - const found = search(id, capability.capabilities) - if(found) { - return found - } - } - } - return undefined -} - -export function getCapabilityById(id: FlowrCapabilityId): FlowrCapability { - const value = search(id, flowrCapabilities.capabilities) - guard(value !== undefined, () => `Could not find capability with id ${id}`) - return value -} - -// TODO automatically produce wiki page from this! +import { FlowrCapabilities } from './types' export const flowrCapabilities = { name: 'Capabilities of flowR', diff --git a/src/r-bridge/data/get.ts b/src/r-bridge/data/get.ts new file mode 100644 index 0000000000..43bfe255a5 --- /dev/null +++ b/src/r-bridge/data/get.ts @@ -0,0 +1,36 @@ +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 + +function search(id: FlowrCapabilityId, capabilities: readonly FlowrCapability[]): FlowrCapability | undefined { + for(const capability of capabilities) { + if(capability.id === id) { + return capability + } + if(capability.capabilities) { + const found = search(id, capability.capabilities) + if(found) { + return found + } + } + } + return undefined +} + +export function getCapabilityById(id: FlowrCapabilityId): FlowrCapability { + const value = search(id, flowrCapabilities.capabilities) + guard(value !== undefined, () => `Could not find capability with id ${id}`) + 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..4ebc791ce7 --- /dev/null +++ b/src/r-bridge/data/print.ts @@ -0,0 +1,61 @@ +// TODO automatically produce wiki page from this! +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') +} + +export const preamble = ` +# Flowr Capabilities +This is to be moved into the wiki afterward, as part of the capabilities. + +Ticks indicate that a corresponding test exists. 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(preamble + 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[] +} From 3160242604a416e4f53ebf15de4d689928fc395b Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 15:36:42 +0100 Subject: [PATCH 06/10] refactor: improved automatic generation with stamp --- src/r-bridge/data/print.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/r-bridge/data/print.ts b/src/r-bridge/data/print.ts index 4ebc791ce7..c358325236 100644 --- a/src/r-bridge/data/print.ts +++ b/src/r-bridge/data/print.ts @@ -39,9 +39,12 @@ function printAsMarkdown(capabilities: readonly FlowrCapability[], depth = 0, li return lines.join('\n') } -export const preamble = ` -# Flowr Capabilities -This is to be moved into the wiki afterward, as part of the capabilities. +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}_ Ticks indicate that a corresponding test exists. Besides we use colored bullets like this: @@ -54,8 +57,9 @@ Ticks indicate that a corresponding test exists. Besides we use colored bullets :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(preamble + printAsMarkdown(flowrCapabilities.capabilities)) + console.log(getPreamble() + printAsMarkdown(flowrCapabilities.capabilities)) } From 7864b16e669bea8b7c6e88e42f02d4b28cc8722f Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 15:43:55 +0100 Subject: [PATCH 07/10] feat(ci): integrate wiki generation into the broken links ci check --- ...broken-links.yaml => broken-links-and-wiki.yaml} | 13 +++++++++++-- src/r-bridge/data/print.ts | 1 - wiki/Linting and Testing.md | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) rename .github/workflows/{check-broken-links.yaml => broken-links-and-wiki.yaml} (77%) 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/src/r-bridge/data/print.ts b/src/r-bridge/data/print.ts index c358325236..73c86fba83 100644 --- a/src/r-bridge/data/print.ts +++ b/src/r-bridge/data/print.ts @@ -1,4 +1,3 @@ -// TODO automatically produce wiki page from this! import { FlowrCapability } from './types' import { flowrCapabilities } from './data' 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 From c167534de6c744802ad6b2695cf7aea4b9705552 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 16:04:36 +0100 Subject: [PATCH 08/10] refactor: update test usage of labels and paths --- src/r-bridge/data/data.ts | 46 ++++++++-------- src/r-bridge/data/get.ts | 24 +++++++-- src/r-bridge/data/print.ts | 3 +- test/functionality/_helper/label.ts | 53 +++++++++++-------- .../atomic/atomic-tests.ts | 27 +++++----- 5 files changed, 89 insertions(+), 64 deletions(-) diff --git a/src/r-bridge/data/data.ts b/src/r-bridge/data/data.ts index ec406985c7..8bcacdf740 100644 --- a/src/r-bridge/data/data.ts +++ b/src/r-bridge/data/data.ts @@ -15,19 +15,19 @@ export const flowrCapabilities = { capabilities: [ { name: 'Normal', - id: 'normal', + id: 'name-normal', supported: 'fully', description: '_Recognize constructs like `a`, `plot`, ..._' }, { name: 'Quoted', - id: 'quoted', + id: 'name-quoted', supported: 'fully', description: "_Recognize `\"a\"`, `'plot'`, ..._" }, { name: 'Escaped', - id: 'escaped', + id: 'name-escaped', supported: 'fully', description: '_Recognize `` `a` ``, `` `plot` ``, ..._' } @@ -387,25 +387,25 @@ export const flowrCapabilities = { capabilities: [ { name: 'Named', - id: 'named', + id: 'formals-named', supported: 'fully', description: '_Handle `function(x) x`, ..._' }, { name: 'Default', - id: 'default', + id: 'formals-default', supported: 'fully', description: '_Handle `function(x = 3) x`, ..._' }, { name: 'Dot-Dot-Dot', - id: 'dot-dot-dot', + id: 'formals-dot-dot-dot', supported: 'fully', description: '_Handle `function(...) 3`, ..._' }, { name: 'Promises', - id: '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.' } @@ -431,31 +431,31 @@ export const flowrCapabilities = { capabilities: [ { name: 'Pipe and Pipe-Bind', - id: '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: 'sequencing', + name: 'Sequencing', + id: 'built-in-sequencing', supported: 'not', description: '_Handle `:`, `seq`, ... as they are used often._' }, { name: 'Internal and Primitive Functions', - id: '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: 'options', + id: 'built-in-options', supported: 'not', description: '_Handle `options`, `getOption`, ..._ Currently, we do not support the function at all.' }, { name: 'Help', - id: '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).' }, @@ -477,19 +477,19 @@ export const flowrCapabilities = { }, { name: 'Quoting', - id: '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: 'evaluation', + id: 'built-in-evaluation', supported: 'not', description: '_Handle `eval`, `evalq`, `eval.parent`, ..._ We do not handle them at all.' }, { name: 'Parsing', - id: '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.' } @@ -591,25 +591,25 @@ export const flowrCapabilities = { capabilities: [ { name: 'Primitive', - id: '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: '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: 'inference', + id: 'types-inference', supported: 'not', description: '_Infer types from the code._ We do not support typing currently.' }, { name: 'Coercion', - id: 'coercion', + id: 'types-coercion', supported: 'not', description: '_Handle coercion of types._ We do not support typing currently.' }, @@ -619,21 +619,21 @@ export const flowrCapabilities = { capabilities: [ { name: 'S3', - id: '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: '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: '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."' diff --git a/src/r-bridge/data/get.ts b/src/r-bridge/data/get.ts index 43bfe255a5..8a3b47f5cf 100644 --- a/src/r-bridge/data/get.ts +++ b/src/r-bridge/data/get.ts @@ -14,13 +14,21 @@ type ExtractAllIds = type Capabilities = (typeof flowrCapabilities)['capabilities'][number] export type FlowrCapabilityId = ExtractAllIds -function search(id: FlowrCapabilityId, capabilities: readonly FlowrCapability[]): FlowrCapability | undefined { +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 + return { ...capability, path: [...path, idx] } } if(capability.capabilities) { - const found = search(id, capability.capabilities) + const found = search(id, capability.capabilities, [...path, idx]) if(found) { return found } @@ -29,8 +37,16 @@ function search(id: FlowrCapabilityId, capabilities: readonly FlowrCapability[]) return undefined } -export function getCapabilityById(id: FlowrCapabilityId): FlowrCapability { +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/print.ts b/src/r-bridge/data/print.ts index 73c86fba83..2c35f6d594 100644 --- a/src/r-bridge/data/print.ts +++ b/src/r-bridge/data/print.ts @@ -45,7 +45,8 @@ function getPreamble(): string { _This document was generated automatically from '${module.filename}' on ${currentDateAndTime}_ -Ticks indicate that a corresponding test exists. Besides we use colored bullets like this: +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: | | | | ---------------------- | ----------------------------------------------------- | diff --git a/test/functionality/_helper/label.ts b/test/functionality/_helper/label.ts index 58023cf493..eb1233e61e 100644 --- a/test/functionality/_helper/label.ts +++ b/test/functionality/_helper/label.ts @@ -8,17 +8,9 @@ import { DefaultMap } from '../../../src/util/defaultmap' import { guard } from '../../../src/util/assert' +import { FlowrCapabilityWithPath, FlowrCapabilityId, getCapabilityById } from '../../../src/r-bridge/data' -/** - * This type allows to specify a label identifier of *up to* the given depth (+1) - * in the form of '1', '3/2', '1/4/2', ... - */ -type NumericLabelFormat = - HelperArray['length'] extends DeepestDepth ? `${number}` : - (`${number}` | `${number}/${NumericLabelFormat}`) - - -const TheGlobalLabelMap: DefaultMap = new DefaultMap(() => []) +const TheGlobalLabelMap: DefaultMap = new DefaultMap(() => []) const uniqueTestId = (() => { let id = 0 @@ -26,36 +18,51 @@ const uniqueTestId = (() => { })() /** - * Wraps a test name with a unique identifier and labels it with the given labels. + * 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 labels - the labels to attach to the test + * @param ids - the capability ids to attach to the test */ -export function label(testname: string, ...labels: NumericLabelFormat[]): string { - // assert labels in array are unique - guard(new Set(labels).size === labels.length, () => `Labels must be unique, but are not for ${JSON.stringify(labels)}`) +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}, ${labels.join(', ')}] ${testname}` + const fullName = `[${id}, ${ids.join(', ')}] ${testname}` const idName = `#${id} (${testname})` - for(const l of labels) { - TheGlobalLabelMap.get(l).push(idName) + for(const l of ids) { + const capability = getCapabilityById(l) + TheGlobalLabelMap.get(capability).push(idName) } return fullName } +function getSortedByPath(): [FlowrCapabilityWithPath, string[]][] { + // sort entries alpha-numerically + const entries = 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 + }) + return entries +} + after(() => { console.log('='.repeat(80)) - // sort entries alpha-numerically - const entries = Array.from(TheGlobalLabelMap.entries()).sort(([a], [b]) => a.localeCompare(b)) + const entries = getSortedByPath() const maxTestLength = Math.max(...entries.map(([, tests]) => tests.length.toString().length)) - const maxLabelLength = Math.max(...entries.map(([label]) => label.length)) for(const [label, testNames] of entries) { - const paddedLabel = label.padEnd(maxLabelLength, ' ') + 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} \x1b[36m${testNames.join('\x1b[m, \x1b[36m')}\x1b[m`) + 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 bdcec63fec..ae4b203595 100644 --- a/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts +++ b/test/functionality/dataflow/processing-of-elements/atomic/atomic-tests.ts @@ -11,33 +11,34 @@ import { UnnamedArgumentPrefix } from '../../../../../src/dataflow/v1/internal/p 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, id] of [ - ['42', '2/2/1'], - ['-3.14', '2/2/1'], - ['"test"', '2/2/2'], - ['\'test\'', '2/2/2'], - ['TRUE', '2/2/3'], - ['FALSE', '2/2/3'], - ['NA', '2/2/1'], - ['NULL', '2/2/4'], - ['Inf', '2/2/5'], - ['NaN', '2/2/5'] - ] as const) { + ['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(label('simple variable', '1/1/1'), shell, + assertDataflow(label('simple variable', 'name-normal'), shell, 'xylophone', new DataflowGraph().addVertex({ tag: 'use', id: '0', name: 'xylophone' }) ) describe('access', () => { describe('const access', () => { - assertDataflow(label('single constant', '1/1/1', '2/2/1', '2/1/6/1'), 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` }) From 3cffc33a406097afb73876e7a96086b43355821d Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 16:07:03 +0100 Subject: [PATCH 09/10] refactor: refine array sort --- test/functionality/_helper/label.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/functionality/_helper/label.ts b/test/functionality/_helper/label.ts index eb1233e61e..45d4c01552 100644 --- a/test/functionality/_helper/label.ts +++ b/test/functionality/_helper/label.ts @@ -39,8 +39,8 @@ export function label(testname: string, ...ids: FlowrCapabilityId[]): string { } function getSortedByPath(): [FlowrCapabilityWithPath, string[]][] { - // sort entries alpha-numerically - const entries = Array.from(TheGlobalLabelMap.entries()).sort(([{ path: a }], [{ path: b }]) => { + // 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 @@ -51,7 +51,6 @@ function getSortedByPath(): [FlowrCapabilityWithPath, string[]][] { } return a.length - b.length }) - return entries } after(() => { From 3b43c19a735b2ed627934b774d74bec93dd0a4e1 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Sun, 4 Feb 2024 16:07:40 +0100 Subject: [PATCH 10/10] refactor: remove brackets from r6 --- src/r-bridge/data/data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r-bridge/data/data.ts b/src/r-bridge/data/data.ts index 8bcacdf740..c315ee34b2 100644 --- a/src/r-bridge/data/data.ts +++ b/src/r-bridge/data/data.ts @@ -632,7 +632,7 @@ export const flowrCapabilities = { 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]', + name: 'R6', id: 'oop-r6', note: 'https://adv-r.hadley.nz/r6.html', supported: 'not',