in src/powerquery-parser/parser/parsers/naive.ts [864:972]
export function readRecursivePrimaryExpression(
state: ParseState,
parser: Parser,
head: Ast.TPrimaryExpression,
): Ast.RecursivePrimaryExpression {
const nodeKind: Ast.NodeKind.RecursivePrimaryExpression = Ast.NodeKind.RecursivePrimaryExpression;
const trace: Trace = state.traceManager.entry(NaiveTraceConstant.Parse, readRecursivePrimaryExpression.name, {
[NaiveTraceConstant.TokenIndex]: state.tokenIndex,
});
state.maybeCancellationToken?.throwIfCancelled();
ParseStateUtils.startContext(state, nodeKind);
const nodeIdMapCollection: NodeIdMap.Collection = state.contextState.nodeIdMapCollection;
const currentContextNode: ParseContext.TNode = Assert.asDefined(
state.maybeCurrentContextNode,
"state.maybeCurrentContextNode",
);
// Update parent attributes.
const parentOfHeadId: number = MapUtils.assertGet(nodeIdMapCollection.parentIdById, head.id);
nodeIdMapCollection.childIdsById.set(
parentOfHeadId,
ArrayUtils.removeFirstInstance(MapUtils.assertGet(nodeIdMapCollection.childIdsById, parentOfHeadId), head.id),
);
nodeIdMapCollection.childIdsById.set(currentContextNode.id, [head.id]);
nodeIdMapCollection.parentIdById.set(head.id, currentContextNode.id);
const newTokenIndexStart: number = head.tokenRange.tokenIndexStart;
const mutableContext: TypeScriptUtils.StripReadonly<ParseContext.TNode> = currentContextNode;
const mutableHead: TypeScriptUtils.StripReadonly<Ast.TPrimaryExpression> = head;
// Update token start to match the first parsed node under it, aka the head.
mutableContext.maybeTokenStart = state.lexerSnapshot.tokens[newTokenIndexStart];
mutableContext.tokenIndexStart = newTokenIndexStart;
// Update attribute counters.
mutableContext.attributeCounter = 1;
mutableHead.maybeAttributeIndex = 0;
// Recalculate ids after shuffling things around.
const newNodeIdByOldNodeId: Map<number, number> = NodeIdMapUtils.recalculateIds(
nodeIdMapCollection,
NodeIdMapUtils.assertGetXor(
nodeIdMapCollection,
MapUtils.assertGet(nodeIdMapCollection.parentIdById, currentContextNode.id),
),
);
NodeIdMapUtils.updateNodeIds(nodeIdMapCollection, newNodeIdByOldNodeId);
// Begin normal parsing.
const recursiveArrayNodeKind: Ast.NodeKind.ArrayWrapper = Ast.NodeKind.ArrayWrapper;
ParseStateUtils.startContext(state, recursiveArrayNodeKind);
const recursiveExpressions: (Ast.InvokeExpression | Ast.ItemAccessExpression | Ast.TFieldAccessExpression)[] = [];
let continueReadingValues: boolean = true;
while (continueReadingValues) {
const maybeCurrentTokenKind: Token.TokenKind | undefined = state.maybeCurrentTokenKind;
if (maybeCurrentTokenKind === Token.TokenKind.LeftParenthesis) {
recursiveExpressions.push(parser.readInvokeExpression(state, parser));
} else if (maybeCurrentTokenKind === Token.TokenKind.LeftBrace) {
recursiveExpressions.push(parser.readItemAccessExpression(state, parser));
} else if (maybeCurrentTokenKind === Token.TokenKind.LeftBracket) {
const bracketExpression: Ast.TFieldAccessExpression = DisambiguationUtils.readAmbiguousBracket(
state,
parser,
[
Disambiguation.BracketDisambiguation.FieldSelection,
Disambiguation.BracketDisambiguation.FieldProjection,
],
) as Ast.TFieldAccessExpression;
recursiveExpressions.push(bracketExpression);
} else {
continueReadingValues = false;
}
}
const recursiveArray: Ast.IArrayWrapper<
Ast.InvokeExpression | Ast.ItemAccessExpression | Ast.TFieldAccessExpression
> = {
...ParseStateUtils.assertGetContextNodeMetadata(state),
kind: recursiveArrayNodeKind,
isLeaf: false,
elements: recursiveExpressions,
};
ParseStateUtils.endContext(state, recursiveArray);
const recursivePrimaryExpression: Ast.RecursivePrimaryExpression = {
...ParseStateUtils.assertGetContextNodeMetadata(state),
kind: nodeKind,
isLeaf: false,
head,
recursiveExpressions: recursiveArray,
};
ParseStateUtils.endContext(state, recursivePrimaryExpression);
trace.exit({ [NaiveTraceConstant.TokenIndex]: state.tokenIndex });
return recursivePrimaryExpression;
}