diff --git a/.changeset/tidy-bananas-collect.md b/.changeset/tidy-bananas-collect.md new file mode 100644 index 000000000..93d5ab173 --- /dev/null +++ b/.changeset/tidy-bananas-collect.md @@ -0,0 +1,5 @@ +--- +'@codama/visitors-core': minor +--- + +Introduces the `NodePath` type diff --git a/packages/visitors-core/src/NodePath.ts b/packages/visitors-core/src/NodePath.ts new file mode 100644 index 000000000..ed6d3b294 --- /dev/null +++ b/packages/visitors-core/src/NodePath.ts @@ -0,0 +1,7 @@ +import { Node } from '@codama/nodes'; + +export type NodePath = readonly [...Node[], TNode]; + +export function getLastNodeFromPath(path: NodePath): TNode { + return path[path.length - 1] as TNode; +} diff --git a/packages/visitors-core/src/NodeStack.ts b/packages/visitors-core/src/NodeStack.ts index ba76f3f92..973274e80 100644 --- a/packages/visitors-core/src/NodeStack.ts +++ b/packages/visitors-core/src/NodeStack.ts @@ -1,10 +1,35 @@ -import { GetNodeFromKind, InstructionNode, isNode, Node, NodeKind, ProgramNode } from '@codama/nodes'; +import { + assertIsNode, + GetNodeFromKind, + InstructionNode, + isNode, + Node, + NodeKind, + ProgramNode, + REGISTERED_NODE_KINDS, +} from '@codama/nodes'; + +import { NodePath } from './NodePath'; export class NodeStack { - private readonly stack: Node[]; + /** + * Contains all the node stacks saved during the traversal. + * + * - The very last stack is the current stack which is being + * used during the traversal. + * - The other stacks can be used to save and restore the + * current stack when jumping to different parts of the tree. + * + * There must at least be one stack in the heap at all times. + */ + private readonly heap: [...Node[][], Node[]]; + + constructor(...heap: readonly [...(readonly (readonly Node[])[]), readonly Node[]] | readonly []) { + this.heap = heap.length === 0 ? [[]] : ([...heap.map(nodes => [...nodes])] as [...Node[][], Node[]]); + } - constructor(stack: Node[] = []) { - this.stack = [...stack]; + public get stack(): Node[] { + return this.heap[this.heap.length - 1]; } public push(node: Node): void { @@ -19,6 +44,19 @@ export class NodeStack { return this.isEmpty() ? undefined : this.stack[this.stack.length - 1]; } + public pushStack(newStack: readonly Node[] = []): void { + this.heap.push([...newStack]); + } + + public popStack(): readonly Node[] { + const oldStack = this.heap.pop() as Node[]; + if (this.heap.length === 0) { + // TODO: Coded error + throw new Error('The heap of stacks can never be empty.'); + } + return [...oldStack] as readonly Node[]; + } + public find(kind: TKind | TKind[]): GetNodeFromKind | undefined { for (let index = this.stack.length - 1; index >= 0; index--) { const node = this.stack[index]; @@ -39,12 +77,18 @@ export class NodeStack { return [...this.stack]; } + public getPath(kind?: TKind | TKind[]): NodePath> { + const node = this.peek(); + assertIsNode(node, kind ?? REGISTERED_NODE_KINDS); + return [...this.stack] as unknown as NodePath>; + } + public isEmpty(): boolean { return this.stack.length === 0; } public clone(): NodeStack { - return new NodeStack(this.stack); + return new NodeStack(...this.heap); } public toString(): string { diff --git a/packages/visitors-core/src/index.ts b/packages/visitors-core/src/index.ts index 85fa51a2b..65e9029a5 100644 --- a/packages/visitors-core/src/index.ts +++ b/packages/visitors-core/src/index.ts @@ -12,6 +12,7 @@ export * from './interceptVisitor'; export * from './LinkableDictionary'; export * from './mapVisitor'; export * from './mergeVisitor'; +export * from './NodePath'; export * from './NodeSelector'; export * from './NodeStack'; export * from './nonNullableIdentityVisitor';