diff --git a/src/search/flowr-search-builder.ts b/src/search/flowr-search-builder.ts new file mode 100644 index 0000000000..65b74c8c73 --- /dev/null +++ b/src/search/flowr-search-builder.ts @@ -0,0 +1,114 @@ +import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id'; +import type { + FlowrSearchElement, + FlowrSearchElements, + FlowrSearchGeneratorNode, FlowrSearchGetFilters, + FlowrSearchInput, FlowrSearchTransformerNode +} from './flowr-search'; +import type { Pipeline } from '../core/steps/pipeline/pipeline'; +import type { FlowrFilterExpression } from './flowr-search-filters'; +import type { NoInfo } from '../r-bridge/lang-4.x/ast/model/model'; + + +export type FlowrGenerator

= Record) => FlowrSearchElements> + +export const FlowrSearchGenerator = { + all(): FlowrSearchBuilder { + return new FlowrSearchBuilder({ type: 'generator', name: 'all', args: undefined }); + }, + /** + * TODO TODO TODO + */ + get(filter: FlowrSearchGetFilters): FlowrSearchBuilder { + return new FlowrSearchBuilder({ type: 'generator', name: 'get', args: filter }); + }, + /** + * Short form of {@link get} with only the + * {@link FlowrSearchGetFilters#line|line} and {@link FlowrSearchGetFilters#column|column} filters: + * `get({line, column})`. + */ + loc(line?: number, column?: number) { + return FlowrSearchGenerator.get({ line, column }); + }, + /** + * Short form of {@link get} with only the {@link FlowrSearchGetFilters#name|name} filter: + * `get({name})`. + */ + var(name: string) { + return FlowrSearchGenerator.get({ name }); + }, + /** + * Short form of {@link get} with only the {@link FlowrSearchGetFilters#id|id} filter: + * `get({id})`. + */ + id(id: NodeId) { + return FlowrSearchGenerator.get({ id }); + } +} as const; + +export type FlowrSearchBuilderType[]>> = FlowrSearchBuilder; + +class FlowrSearchBuilder[]>> { + private generator: FlowrSearchGeneratorNode; + private search: FlowrSearchTransformerNode[] = []; + + constructor(generator: FlowrSearchGeneratorNode) { + this.generator = generator; + } + + /** + * TODO + * + * As filter does not change the type of any contained elements, we can return the same type for type safety checks. + */ + filter(filter: FlowrFilterExpression): this { + this.search.push({ type: 'transformer', name: 'filter', args: { filter: filter } }); + return this; + } + + /** + * first either returns the first element of the search or nothing, if no elements are present. + */ + first(): FlowrSearchBuilder] | []> { + this.search.push({ type: 'transformer', name: 'first', args: undefined }); + return this as unknown as FlowrSearchBuilder] | []>; + } + + /** + * last either returns the last element of the search or nothing, if no elements are present. + */ + last(): FlowrSearchBuilder] | []> { + this.search.push({ type: 'transformer', name: 'last', args: undefined }); + return this as unknown as FlowrSearchBuilder] | []>; + } + /** + * index returns the element at the given index if it exists + */ + index(index: number): FlowrSearchBuilder] | []> { + this.search.push({ type: 'transformer', name: 'index', args: { index } }); + return this as unknown as FlowrSearchBuilder] | []>; + } + /** + * tail returns all elements of the search except the first one. + */ + tail(): this { + this.search.push({ type: 'transformer', name: 'tail', args: undefined }); + return this; + } + + /** + * take returns the first `count` elements of the search. + */ + take(count: number): this { + this.search.push({ type: 'transformer', name: 'take', args: { count } }); + return this; + } + + /** + * skip returns all elements of the search except the first `count` ones. + */ + skip(count: number): this { + this.search.push({ type: 'transformer', name: 'skip', args: { count } }); + return this; + } +} diff --git a/src/search/flowr-search-executor.ts b/src/search/flowr-search-executor.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/search/flowr-search-filters.ts b/src/search/flowr-search-filters.ts new file mode 100644 index 0000000000..0cf1461ed5 --- /dev/null +++ b/src/search/flowr-search-filters.ts @@ -0,0 +1,91 @@ + + +import type { RType } from '../r-bridge/lang-4.x/ast/model/type'; +import type { VertexType } from '../dataflow/graph/vertex'; + +export type FlowrFilterName = keyof typeof FlowrFilters; + +export enum FlowrFilter { + +} + +export const FlowrFilters = { + +} as const; + + +type ValidFilterTypes = FlowrFilterName | RType | VertexType; +/** + * By default, we provide filter for every {@link RType} and {@link VertexType}. + */ +export type FlowrFilterExpression = FlowrFilterCombinator | ValidFilterTypes; + +interface BooleanBinaryNode { + readonly type: 'and' | 'or' | 'xor'; + readonly left: Composite; + readonly right: Composite; +} +interface BooleanUnaryNode { + readonly type: 'not'; + readonly operand: Composite; +} + +type Leaf = ValidFilterTypes; + +type BooleanNode = BooleanBinaryNode + | BooleanUnaryNode + | Leaf; + + +type BooleanNodeOrCombinator = BooleanNode | FlowrFilterCombinator + +export class FlowrFilterCombinator { + private tree: BooleanNode; + + protected constructor(init: BooleanNodeOrCombinator) { + this.tree = this.unpack(init); + } + + public static is(value: BooleanNodeOrCombinator): FlowrFilterCombinator { + return new this(value); + } + + public and(right: BooleanNodeOrCombinator): this { + this.tree = { + type: 'and', + left: this.tree, + right: this.unpack(right) + }; + return this; + } + + public or(right: BooleanNodeOrCombinator): this { + this.tree = { + type: 'or', + left: this.tree, + right: this.unpack(right) + }; + return this; + } + + public xor(right: BooleanNodeOrCombinator): this { + this.tree = { + type: 'xor', + left: this.tree, + right: this.unpack(right) + }; + return this; + } + + public not(): this { + this.tree = { + type: 'not', + operand: this.tree + }; + return this; + } + + private unpack(val: BooleanNodeOrCombinator): BooleanNode { + return val instanceof FlowrFilterCombinator ? val.tree : val; + } +} diff --git a/src/search/flowr-search.ts b/src/search/flowr-search.ts new file mode 100644 index 0000000000..ec2fcc47f1 --- /dev/null +++ b/src/search/flowr-search.ts @@ -0,0 +1,70 @@ +import type { NoInfo, RNode } from '../r-bridge/lang-4.x/ast/model/model'; +import type { Pipeline, PipelineOutput, PipelineStepOutputWithName } from '../core/steps/pipeline/pipeline'; +import type { NormalizedAst } from '../r-bridge/lang-4.x/ast/model/processing/decorate'; +import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id'; +import type { FlowrFilterExpression } from './flowr-search-filters'; +import type { DataflowGraph } from '../dataflow/graph/graph'; + +export interface FlowrSearchElement { + readonly node: RNode; +} + +export interface FlowrSearchNodeBase | undefined> { + readonly type: Type; + readonly name: Name; + readonly args: Args; +} + +/* Input extends FlowrSearchElements, Output extends FlowrSearchElements = Input */ +export type FlowrSearchGeneratorNodeBase | undefined> = FlowrSearchNodeBase<'generator', Name, Args>; +export type FlowrSearchTransformerNodeBase | undefined> = FlowrSearchNodeBase<'transformer', Name, Args>; + +export interface FlowrSearchGetFilters extends Record { + readonly line?: number; + readonly column?: number; + readonly name?: string; + readonly id?: NodeId; +} + +export type FlowrSearchGeneratorNode = FlowrSearchGeneratorNodeBase<'all', undefined> + | FlowrSearchGeneratorNodeBase<'get', FlowrSearchGetFilters> + +export type FlowrSearchTransformerNode = FlowrSearchTransformerNodeBase<'first', undefined> + | FlowrSearchTransformerNodeBase<'last', undefined> + | FlowrSearchTransformerNodeBase<'index', { index: number }> + | FlowrSearchTransformerNodeBase<'tail', undefined> + | FlowrSearchTransformerNodeBase<'take', { count: number }> + | FlowrSearchTransformerNodeBase<'skip', { count: number }> + | FlowrSearchTransformerNodeBase<'filter', { + filter: FlowrFilterExpression; + }> + +type MinimumInputForFlowrSearch

= + PipelineStepOutputWithName extends NormalizedAst ? ( + PipelineStepOutputWithName extends DataflowGraph ? PipelineOutput

+ : never + ): never + +/** we allow any pipeline, which provides us with a 'normalize' and 'dataflow' step */ +export type FlowrSearchInput< + P extends Pipeline +> = MinimumInputForFlowrSearch

+ +/** Intentionally, we abstract away from an array to avoid the use of conventional typescript operations */ +export class FlowrSearchElements[] = FlowrSearchElement[]> { + private readonly elements: Elements = [] as unknown as Elements; + + public add(this: FlowrSearchElements, element: FlowrSearchElement): FlowrSearchElements[]> { + this.elements.push(element); + return this; + } + + public getElements(): readonly FlowrSearchElement[] { + return this.elements; + } + /* TODO: conventional operations */ +} + +/* TODO: differentiate generators, transformer, and terminators */ + + diff --git a/test/functionality/search/playground.test.ts b/test/functionality/search/playground.test.ts new file mode 100644 index 0000000000..5b945df936 --- /dev/null +++ b/test/functionality/search/playground.test.ts @@ -0,0 +1,18 @@ +import { describe, test } from 'vitest'; +import type { FlowrSearchBuilderType } from '../../../src/search/flowr-search-builder'; +import { FlowrSearchGenerator as Q } from '../../../src/search/flowr-search-builder'; +import { RType } from '../../../src/r-bridge/lang-4.x/ast/model/type'; +import { VertexType } from '../../../src/dataflow/graph/vertex'; +import { FlowrFilterCombinator as F } from '../../../src/search/flowr-search-filters'; + +describe('flowR Search (playground)', () => { + function print(search: FlowrSearchBuilderType) { + console.log(JSON.stringify(search, null, 2)); + } + test('poor mans testing', () => { + print(Q.all().filter(RType.Comment)); + print(Q.get({ line: 3, name: 'x' }).filter( + F.is(VertexType.Use).or(RType.Number) + ).first()); + }); +});