Skip to content

Commit

Permalink
Make Traverse API async (#295)
Browse files Browse the repository at this point in the history
* initial commit

* more impl, all tests pass

* alpha sort

* consistent async fn naming
  • Loading branch information
JordanBoltonMN authored Mar 2, 2022
1 parent 627750a commit b434e09
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ module.exports = {
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/consistent-type-assertions": ["warn", { assertionStyle: "as" }],
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/prefer-namespace-keyword": "error",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@microsoft/powerquery-parser",
"version": "0.6.1",
"version": "0.6.2",
"description": "A parser for the Power Query/M formula language.",
"author": "Microsoft",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion src/powerquery-parser/common/arrayUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function assertNonZeroLength<T>(
);
}

export async function asyncMap<T, U>(
export async function mapAsync<T, U>(
collection: ReadonlyArray<T>,
mapFn: (value: T) => Promise<U>,
): Promise<ReadonlyArray<U>> {
Expand Down
2 changes: 1 addition & 1 deletion src/powerquery-parser/common/result/resultUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function ensureResult<T>(locale: string, callbackFn: () => T): Result<T,
}
}

export async function ensureAsyncResult<T>(
export async function ensureResultAsync<T>(
locale: string,
callbackFn: () => Promise<T>,
): Promise<Result<T, CommonError.CommonError>> {
Expand Down
69 changes: 39 additions & 30 deletions src/powerquery-parser/common/traversal.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { Assert, CommonError, Result } from ".";
import { ArrayUtils, Assert, CommonError, Result } from ".";
import { NodeIdMap, NodeIdMapUtils, ParseContext, TXorNode, XorNodeKind, XorNodeUtils } from "../parser";
import { Ast } from "../language";
import { ResultUtils } from "./result";
import { Settings } from "..";
import { Trace } from "./trace";

export type TriedTraverse<ResultType> = Result<ResultType, CommonError.CommonError>;

export type TVisitNodeFn<State extends ITraversalState<ResultType>, ResultType, Node, Return> = (
state: State,
node: Node,
) => Return;

export type TVisitChildNodeFn<State extends ITraversalState<ResultType>, ResultType, Node, Return> = (
state: State,
parent: Node,
node: Node,
) => Return;
) => Promise<Return>;

export type TEarlyExitFn<State extends ITraversalState<ResultType>, ResultType, Node> = TVisitNodeFn<
State,
Expand All @@ -30,15 +26,14 @@ export type TExpandNodesFn<State extends ITraversalState<ResultType>, ResultType
state: State,
node: Node,
collection: NodesById,
) => ReadonlyArray<Node>;
) => Promise<ReadonlyArray<Node>>;

export const enum VisitNodeStrategy {
BreadthFirst = "BreadthFirst",
DepthFirst = "DepthFirst",
}

export interface ITraversalState<T> {
readonly locale: string;
export interface ITraversalState<T> extends Pick<Settings, "locale" | "maybeCancellationToken" | "traceManager"> {
result: T;
}

Expand All @@ -51,7 +46,7 @@ export function tryTraverseAst<State extends ITraversalState<ResultType>, Result
visitNodeFn: TVisitNodeFn<State, ResultType, Ast.TNode, void>,
expandNodesFn: TExpandNodesFn<State, ResultType, Ast.TNode, NodeIdMap.Collection>,
maybeEarlyExitFn: TEarlyExitFn<State, ResultType, Ast.TNode> | undefined,
): TriedTraverse<ResultType> {
): Promise<TriedTraverse<ResultType>> {
return tryTraverse<State, ResultType, Ast.TNode, NodeIdMap.Collection>(
state,
nodeIdMapCollection,
Expand All @@ -72,7 +67,7 @@ export function tryTraverseXor<State extends ITraversalState<ResultType>, Result
visitNodeFn: TVisitNodeFn<State, ResultType, TXorNode, void>,
expandNodesFn: TExpandNodesFn<State, ResultType, TXorNode, NodeIdMap.Collection>,
maybeEarlyExitFn: TEarlyExitFn<State, ResultType, TXorNode> | undefined,
): TriedTraverse<ResultType> {
): Promise<TriedTraverse<ResultType>> {
return tryTraverse<State, ResultType, TXorNode, NodeIdMap.Collection>(
state,
nodeIdMapCollection,
Expand All @@ -92,9 +87,9 @@ export function tryTraverse<State extends ITraversalState<ResultType>, ResultTyp
visitNodeFn: TVisitNodeFn<State, ResultType, Node, void>,
expandNodesFn: TExpandNodesFn<State, ResultType, Node, NodesById>,
maybeEarlyExitFn: TEarlyExitFn<State, ResultType, Node> | undefined,
): TriedTraverse<ResultType> {
return ResultUtils.ensureResult(state.locale, () => {
traverseRecursion<State, ResultType, Node, NodesById>(
): Promise<TriedTraverse<ResultType>> {
return ResultUtils.ensureResultAsync(state.locale, async () => {
await traverseRecursion<State, ResultType, Node, NodesById>(
state,
nodesById,
root,
Expand All @@ -109,11 +104,12 @@ export function tryTraverse<State extends ITraversalState<ResultType>, ResultTyp
}

// a TExpandNodesFn usable by tryTraverseAst which visits all nodes.
export function assertGetAllAstChildren<State extends ITraversalState<ResultType>, ResultType>(
// eslint-disable-next-line require-await
export async function assertGetAllAstChildren<State extends ITraversalState<ResultType>, ResultType>(
_state: State,
astNode: Ast.TNode,
nodeIdMapCollection: NodeIdMap.Collection,
): ReadonlyArray<Ast.TNode> {
): Promise<ReadonlyArray<Ast.TNode>> {
const maybeChildIds: ReadonlyArray<number> | undefined = nodeIdMapCollection.childIdsById.get(astNode.id);

if (maybeChildIds) {
Expand All @@ -126,16 +122,23 @@ export function assertGetAllAstChildren<State extends ITraversalState<ResultType
}

// a TExpandNodesFn usable by tryTraverseXor which visits all nodes.
export function assertGetAllXorChildren<State extends ITraversalState<ResultType>, ResultType>(
// eslint-disable-next-line require-await
export async function assertGetAllXorChildren<State extends ITraversalState<ResultType>, ResultType>(
_state: State,
xorNode: TXorNode,
nodeIdMapCollection: NodeIdMap.Collection,
): ReadonlyArray<TXorNode> {
): Promise<ReadonlyArray<TXorNode>> {
switch (xorNode.kind) {
case XorNodeKind.Ast: {
const astNode: Ast.TNode = xorNode.node;

return assertGetAllAstChildren(_state, astNode, nodeIdMapCollection).map(XorNodeUtils.boxAst);
const children: ReadonlyArray<Ast.TNode> = await assertGetAllAstChildren(
_state,
astNode,
nodeIdMapCollection,
);

return ArrayUtils.mapAsync(children, (value: Ast.TNode) => Promise.resolve(XorNodeUtils.boxAst(value)));
}

case XorNodeKind.Context: {
Expand Down Expand Up @@ -167,32 +170,38 @@ export function maybeExpandXorParent<T>(
_state: T,
xorNode: TXorNode,
nodeIdMapCollection: NodeIdMap.Collection,
): ReadonlyArray<TXorNode> {
): Promise<ReadonlyArray<TXorNode>> {
const maybeParent: TXorNode | undefined = NodeIdMapUtils.maybeParentXor(nodeIdMapCollection, xorNode.node.id);

return maybeParent !== undefined ? [maybeParent] : [];
return Promise.resolve(maybeParent !== undefined ? [maybeParent] : []);
}

function traverseRecursion<State extends ITraversalState<ResultType>, ResultType, Node, NodesById>(
async function traverseRecursion<State extends ITraversalState<ResultType>, ResultType, Node, NodesById>(
state: State,
nodesById: NodesById,
node: Node,
strategy: VisitNodeStrategy,
visitNodeFn: TVisitNodeFn<State, ResultType, Node, void>,
expandNodesFn: TExpandNodesFn<State, ResultType, Node, NodesById>,
maybeEarlyExitFn: TEarlyExitFn<State, ResultType, Node> | undefined,
): void {
if (maybeEarlyExitFn && maybeEarlyExitFn(state, node)) {
): Promise<void> {
const trace: Trace = state.traceManager.entry("Traversal", traverseRecursion.name);
state.maybeCancellationToken?.throwIfCancelled();

if (maybeEarlyExitFn && (await maybeEarlyExitFn(state, node))) {
return;
} else if (strategy === VisitNodeStrategy.BreadthFirst) {
visitNodeFn(state, node);
await visitNodeFn(state, node);
}

for (const child of expandNodesFn(state, node, nodesById)) {
traverseRecursion(state, nodesById, child, strategy, visitNodeFn, expandNodesFn, maybeEarlyExitFn);
for (const child of await expandNodesFn(state, node, nodesById)) {
// eslint-disable-next-line no-await-in-loop
await traverseRecursion(state, nodesById, child, strategy, visitNodeFn, expandNodesFn, maybeEarlyExitFn);
}

if (strategy === VisitNodeStrategy.DepthFirst) {
visitNodeFn(state, node);
await visitNodeFn(state, node);
}

trace.exit();
}
2 changes: 1 addition & 1 deletion src/test/libraryTest/parser/children.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function createActual(lexParseOk: Task.ParseTaskOk): ChildIdsByIdEntry[] {
}

describe("Parser.Children", () => {
it(`WIP () as number => 1`, async () => {
it(`() as number => 1`, async () => {
const text: string = `() as number => 1`;

const expected: ReadonlyArray<ChildIdsByIdEntry> = [
Expand Down
21 changes: 14 additions & 7 deletions src/test/libraryTest/parser/idUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Assert, Language, TaskUtils, Traverse } from "../../../powerquery-parse
import { ChildIdsById, IdsByNodeKind, ParentIdById } from "../../../powerquery-parser/parser/nodeIdMap/nodeIdMap";
import { DefaultSettings, Task } from "../../..";
import { NodeIdMap, TXorNode, XorNodeUtils } from "../../../powerquery-parser/parser";
import { NoOpTraceManager } from "../../../powerquery-parser/common/trace";

type TraverseState = Traverse.ITraversalState<undefined> &
Pick<NodeIdMap.Collection, "leafIds" | "idsByNodeKind"> & { astIds: number[]; contextIds: number[] };
Expand Down Expand Up @@ -48,7 +49,10 @@ function createSimplifiedParentIdById(parentIdById: ParentIdById): ReadonlyArray
return [...parentIdById.entries()].sort();
}

function expectLinksMatch(triedLexParse: Task.TriedLexParseTask, expected: AbridgedNodeIdMapCollection): void {
async function expectLinksMatch(
triedLexParse: Task.TriedLexParseTask,
expected: AbridgedNodeIdMapCollection,
): Promise<void> {
let nodeIdMapCollection: NodeIdMap.Collection;
let xorNode: TXorNode;

Expand Down Expand Up @@ -78,9 +82,11 @@ function expectLinksMatch(triedLexParse: Task.TriedLexParseTask, expected: Abrid
contextIds: [],
leafIds: new Set<number>(),
idsByNodeKind: new Map(),
maybeCancellationToken: undefined,
traceManager: new NoOpTraceManager(),
};

const triedTraverse: Traverse.TriedTraverse<undefined> = Traverse.tryTraverseXor(
const triedTraverse: Traverse.TriedTraverse<undefined> = await Traverse.tryTraverseXor(
traverseState,
triedLexParse.nodeIdMapCollection,
xorNode,
Expand Down Expand Up @@ -185,7 +191,8 @@ function assertTraverseMatchesState(traverseState: TraverseState, nodeIdMapColle
);
}

function traverseVisitNode(state: TraverseState, xorNode: TXorNode): void {
// eslint-disable-next-line require-await
async function traverseVisitNode(state: TraverseState, xorNode: TXorNode): Promise<void> {
if (XorNodeUtils.isAstXor(xorNode)) {
state.astIds.push(xorNode.node.id);

Expand Down Expand Up @@ -219,7 +226,7 @@ describe("idUtils", () => {
};

const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text);
expectLinksMatch(triedLexParse, expected);
await expectLinksMatch(triedLexParse, expected);
});

it(`-1`, async () => {
Expand Down Expand Up @@ -247,7 +254,7 @@ describe("idUtils", () => {
};

const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text);
expectLinksMatch(triedLexParse, expected);
await expectLinksMatch(triedLexParse, expected);
});

it(`1 + 2`, async () => {
Expand All @@ -271,7 +278,7 @@ describe("idUtils", () => {
};

const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text);
expectLinksMatch(triedLexParse, expected);
await expectLinksMatch(triedLexParse, expected);
});

it(`foo()`, async () => {
Expand Down Expand Up @@ -307,6 +314,6 @@ describe("idUtils", () => {
};

const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text);
expectLinksMatch(triedLexParse, expected);
await expectLinksMatch(triedLexParse, expected);
});
});
18 changes: 13 additions & 5 deletions src/test/libraryTest/parser/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TaskUtils,
Traverse,
} from "../../..";
import { NoOpTraceManager } from "../../../powerquery-parser/common/trace";
import { TestAssertUtils } from "../../testUtils";

type AbridgedNode = [Language.Ast.NodeKind, number | undefined];
Expand All @@ -33,9 +34,11 @@ async function collectAbridgeNodeFromAst(text: string): Promise<ReadonlyArray<Ab
const state: CollectAbridgeNodeState = {
locale: DefaultLocale,
result: [],
maybeCancellationToken: undefined,
traceManager: new NoOpTraceManager(),
};

const triedTraverse: Traverse.TriedTraverse<AbridgedNode[]> = Traverse.tryTraverseAst<
const triedTraverse: Traverse.TriedTraverse<AbridgedNode[]> = await Traverse.tryTraverseAst<
CollectAbridgeNodeState,
AbridgedNode[]
>(
Expand Down Expand Up @@ -66,9 +69,11 @@ async function assertGetNthNodeOfKind<N extends Language.Ast.TNode>(
nodeKind,
nthCounter: 0,
nthRequired,
maybeCancellationToken: undefined,
traceManager: new NoOpTraceManager(),
};

const triedTraverse: Traverse.TriedTraverse<Language.Ast.TNode | undefined> = Traverse.tryTraverseAst<
const triedTraverse: Traverse.TriedTraverse<Language.Ast.TNode | undefined> = await Traverse.tryTraverseAst<
NthNodeOfKindState,
Language.Ast.TNode | undefined
>(
Expand All @@ -86,11 +91,13 @@ async function assertGetNthNodeOfKind<N extends Language.Ast.TNode>(
return Assert.asDefined(triedTraverse.value) as N;
}

function collectAbridgeNodeVisit(state: CollectAbridgeNodeState, node: Language.Ast.TNode): void {
// eslint-disable-next-line require-await
async function collectAbridgeNodeVisit(state: CollectAbridgeNodeState, node: Language.Ast.TNode): Promise<void> {
state.result.push([node.kind, node.maybeAttributeIndex]);
}

function nthNodeVisit(state: NthNodeOfKindState, node: Language.Ast.TNode): void {
// eslint-disable-next-line require-await
async function nthNodeVisit(state: NthNodeOfKindState, node: Language.Ast.TNode): Promise<void> {
if (node.kind === state.nodeKind) {
state.nthCounter += 1;

Expand All @@ -100,7 +107,8 @@ function nthNodeVisit(state: NthNodeOfKindState, node: Language.Ast.TNode): void
}
}

function nthNodeEarlyExit(state: NthNodeOfKindState, _: Language.Ast.TNode): boolean {
// eslint-disable-next-line require-await
async function nthNodeEarlyExit(state: NthNodeOfKindState, _: Language.Ast.TNode): Promise<boolean> {
return state.nthCounter === state.nthRequired;
}

Expand Down

0 comments on commit b434e09

Please sign in to comment.