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());
+ });
+});