Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/powerquery-parser/language/ast/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export enum NodeKind {
RecursivePrimaryExpression = "RecursivePrimaryExpression",
RelationalExpression = "RelationalExpression",
Section = "Section",
SectionAccessExpression = "SectionAccessExpression",
SectionMember = "SectionMember",
TableType = "TableType",
TypePrimaryType = "TypePrimaryType",
Expand Down Expand Up @@ -346,7 +347,7 @@ export type TPrimaryExpression =
| ListExpression
| RecordExpression
| IdentifierExpression
// SectionAccessExpression
| SectionAccessExpression
| ParenthesizedExpression
| TFieldAccessExpression
| TRecursivePrimaryExpression
Expand Down Expand Up @@ -383,6 +384,18 @@ export interface IdentifierExpression extends INode {
readonly identifier: Identifier;
}

// ---------------------------------------------------------
// ---------- 12.2.3.13 Section-access expression ----------
// ---------------------------------------------------------

export interface SectionAccessExpression extends INode {
readonly kind: NodeKind.SectionAccessExpression;
readonly isLeaf: false;
readonly sectionIdentifier: Identifier;
readonly bangConstant: IConstant<Constant.MiscConstant.Bang>;
readonly memberIdentifier: Identifier;
}

// --------------------------------------------------------
// ---------- 12.2.3.14 Parenthesized expression ----------
// --------------------------------------------------------
Expand Down Expand Up @@ -919,6 +932,7 @@ export const NodeKindsForTPrimaryExpression: Set<NodeKind> = new Set([
NodeKind.ListExpression,
NodeKind.RecordExpression,
NodeKind.IdentifierExpression,
NodeKind.SectionAccessExpression,
NodeKind.ParenthesizedExpression,
NodeKind.InvokeExpression,
NodeKind.RecursivePrimaryExpression,
Expand Down
1 change: 1 addition & 0 deletions src/powerquery-parser/language/ast/astUtils/astUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ export function simplifyType(type: Ast.TType): SimplifiedType {
case Ast.NodeKind.RecordExpression:
case Ast.NodeKind.RecursivePrimaryExpression:
case Ast.NodeKind.RelationalExpression:
case Ast.NodeKind.SectionAccessExpression:
case Ast.NodeKind.TypePrimaryType:
case Ast.NodeKind.UnaryExpression:
throw new CommonError.InvariantError("this should never be reached", {
Expand Down
3 changes: 3 additions & 0 deletions src/powerquery-parser/language/ast/astUtils/typeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function isTBinOpExpressionKind(nodeKind: Ast.NodeKind): nodeKind is Ast.
case Ast.NodeKind.RecordType:
case Ast.NodeKind.RecursivePrimaryExpression:
case Ast.NodeKind.Section:
case Ast.NodeKind.SectionAccessExpression:
case Ast.NodeKind.SectionMember:
case Ast.NodeKind.TableType:
case Ast.NodeKind.TypePrimaryType:
Expand Down Expand Up @@ -160,6 +161,7 @@ export function isTKeyValuePair(node: Ast.TNode): node is Ast.TKeyValuePair {
case Ast.NodeKind.RecursivePrimaryExpression:
case Ast.NodeKind.RelationalExpression:
case Ast.NodeKind.Section:
case Ast.NodeKind.SectionAccessExpression:
case Ast.NodeKind.SectionMember:
case Ast.NodeKind.TableType:
case Ast.NodeKind.TypePrimaryType:
Expand Down Expand Up @@ -231,6 +233,7 @@ export function isTPairedConstant(node: Ast.TNode): node is Ast.TPairedConstant
case Ast.NodeKind.RecursivePrimaryExpression:
case Ast.NodeKind.RelationalExpression:
case Ast.NodeKind.Section:
case Ast.NodeKind.SectionAccessExpression:
case Ast.NodeKind.SectionMember:
case Ast.NodeKind.TableType:
case Ast.NodeKind.UnaryExpression:
Expand Down
2 changes: 2 additions & 0 deletions src/powerquery-parser/language/constant/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export enum KeywordConstant {
export enum MiscConstant {
Ampersand = "&",
AtSign = "@",
Bang = "!",
Comma = ",",
DotDot = "..",
Ellipsis = "...",
Expand Down Expand Up @@ -181,6 +182,7 @@ export const LogicalOperators: ReadonlyArray<LogicalOperator> = [LogicalOperator
export const MiscConstants: ReadonlyArray<MiscConstant> = [
MiscConstant.Ampersand,
MiscConstant.AtSign,
MiscConstant.Bang,
MiscConstant.Comma,
MiscConstant.DotDot,
MiscConstant.Ellipsis,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { assertNthChildXor, assertNthChildXorChecked, nthChildAstChecked, nthChildXorChecked } from "./childSelectors";
import { assertNthChildXor, assertNthChildXorChecked, nthChildAstChecked, nthChildXor } from "./childSelectors";
import { assertParentXor, assertParentXorChecked } from "./parentSelectors";
import { assertXor, assertXorChecked } from "./commonSelectors";
import { NodeIdMap, NodeIdMapUtils, XorNodeUtils } from "..";
import { TXorNode, XorNode } from "../xorNode";
import { AstXorNode, TXorNode, XorNode } from "../xorNode";
import { Ast } from "../../../language";
import { CommonError } from "../../../common";

Expand Down Expand Up @@ -49,11 +49,11 @@ export function assertRecursiveExpressionPreviousSibling<T extends Ast.TNode>(

return expectedNodeKinds
? assertNthChildXorChecked(
nodeIdMapCollection,
arrayWrapper.node.id,
indexOfPrimaryExpressionId - 1,
expectedNodeKinds,
)
nodeIdMapCollection,
arrayWrapper.node.id,
indexOfPrimaryExpressionId - 1,
expectedNodeKinds,
)
: assertNthChildXor(nodeIdMapCollection, arrayWrapper.node.id, indexOfPrimaryExpressionId - 1);
}
// It's the first element in ArrayWrapper, meaning we must grab RecursivePrimaryExpression.head
Expand All @@ -73,32 +73,42 @@ export function invokeExpressionIdentifier(
nodeId: number,
): XorNode<Ast.IdentifierExpression> | undefined {
const invokeExprXorNode: TXorNode = assertXorChecked(nodeIdMapCollection, nodeId, Ast.NodeKind.InvokeExpression);
const headXorNode: TXorNode | undefined = invokeExpressionHead(nodeIdMapCollection, invokeExprXorNode);

// The only place for an identifier in a RecursivePrimaryExpression is as the head, therefore an InvokeExpression
// only has a name if the InvokeExpression is the 0th element in the RecursivePrimaryExpressionArray.
return headXorNode ? identifierExpressionHead(invokeExprXorNode, headXorNode) : undefined;
}

// The only place for a name in a RecursivePrimaryExpression is as the head, therefore an InvokeExpression
// only has a name if the InvokeExpression is the 0th element in the RecursivePrimaryExpressionArray.
function invokeExpressionHead(
nodeIdMapCollection: NodeIdMap.Collection,
invokeExprXorNode: TXorNode,
): TXorNode | undefined {
if (invokeExprXorNode.node.attributeIndex !== 0) {
return undefined;
}

// Grab the RecursivePrimaryExpression's head if it's an IdentifierExpression
const recursiveArrayXorNode: TXorNode = assertParentXor(nodeIdMapCollection, invokeExprXorNode.node.id);
const recursiveExprXorNode: TXorNode = assertParentXor(nodeIdMapCollection, recursiveArrayXorNode.node.id);

const headXorNode: XorNode<Ast.IdentifierExpression> | undefined = nthChildXorChecked<Ast.IdentifierExpression>(
return nthChildXor(
nodeIdMapCollection,
recursiveExprXorNode.node.id,
0,
Ast.NodeKind.IdentifierExpression,
0
);
}

// Throws if the IdentifierExpression hasn't finished parsing.
function identifierExpressionHead(
invokeExprXorNode: TXorNode,
headXorNode: TXorNode,
): AstXorNode<Ast.IdentifierExpression> | undefined {
// It's not an identifier expression so there's nothing we can do.
if (headXorNode === undefined) {
if (!XorNodeUtils.isNodeKind<Ast.IdentifierExpression>(headXorNode, Ast.NodeKind.IdentifierExpression)) {
return undefined;
}

// The only place for an identifier in a RecursivePrimaryExpression is as the head, therefore an InvokeExpression
// only has a name if the InvokeExpression is the 0th element in the RecursivePrimaryExpressionArray.
if (XorNodeUtils.isContext(headXorNode)) {
if (!XorNodeUtils.isAstChecked<Ast.IdentifierExpression>(headXorNode, Ast.NodeKind.IdentifierExpression)) {
const details: {
identifierExpressionNodeId: number;
invokeExpressionNodeId: number;
Expand All @@ -117,26 +127,43 @@ export function invokeExpressionIdentifier(
}

// Unboxes the identifier literal for function name if it exists.
// A section-access head (eg. `Section1!Foo(1)`) is returned as `Section1!Foo`.
export function invokeExpressionIdentifierLiteral(
nodeIdMapCollection: NodeIdMap.Collection,
nodeId: number,
): string | undefined {
assertXorChecked(nodeIdMapCollection, nodeId, Ast.NodeKind.InvokeExpression);
const invokeExprXorNode: TXorNode = assertXorChecked(nodeIdMapCollection, nodeId, Ast.NodeKind.InvokeExpression);
const headXorNode: TXorNode | undefined = invokeExpressionHead(nodeIdMapCollection, invokeExprXorNode);

const identifierExpressionXorNode: XorNode<Ast.IdentifierExpression> | undefined = invokeExpressionIdentifier(
nodeIdMapCollection,
nodeId,
if (headXorNode === undefined) {
return undefined;
}

const identifierExpressionXorNode: AstXorNode<Ast.IdentifierExpression> | undefined = identifierExpressionHead(
invokeExprXorNode,
headXorNode,
);

if (identifierExpressionXorNode === undefined || XorNodeUtils.isContext(identifierExpressionXorNode)) {
if (identifierExpressionXorNode !== undefined) {
const identifierExpression: Ast.IdentifierExpression = identifierExpressionXorNode.node;

return identifierExpression.inclusiveConstant === undefined
? identifierExpression.identifier.literal
: identifierExpression.inclusiveConstant.constantKind + identifierExpression.identifier.literal;
}

// A SectionAccessExpression head which is still being parsed is treated as having no name.
if (!XorNodeUtils.isAstChecked<Ast.SectionAccessExpression>(headXorNode, Ast.NodeKind.SectionAccessExpression)) {
return undefined;
}

const identifierExpression: Ast.IdentifierExpression = identifierExpressionXorNode.node;
const sectionAccessExpression: Ast.SectionAccessExpression = headXorNode.node;

return identifierExpression.inclusiveConstant === undefined
? identifierExpression.identifier.literal
: identifierExpression.inclusiveConstant.constantKind + identifierExpression.identifier.literal;
return (
sectionAccessExpression.sectionIdentifier.literal +
sectionAccessExpression.bangConstant.constantKind +
sectionAccessExpression.memberIdentifier.literal
);
}

// Unboxes the node if it's a identifier
Expand Down
11 changes: 10 additions & 1 deletion src/powerquery-parser/parser/parseState/parseStateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,23 @@ export function isOnGeneralizedIdentifierStart(state: ParseState, tokenIndex: nu
}
}

// section-access-expression: identifier ! identifier
export function isSectionAccessExpressionNext(state: ParseState, tokenIndexStart: number = state.tokenIndex): boolean {
return (
isTokenKind(state, Token.TokenKind.Identifier, tokenIndexStart) &&
isTokenKind(state, Token.TokenKind.Bang, tokenIndexStart + 1)
);
}

// Assumes a call to readPrimaryExpression has already happened.
export function isRecursivePrimaryExpressionNext(
state: ParseState,
tokenIndexStart: number = state.tokenIndex,
): boolean {
return (
// section-access-expression
// this.isOnTokenKind(TokenKind.Bang)
// is detected up front by isSectionAccessExpressionNext instead of here
// because its `identifier ! identifier` rather than a suffix on an arbitrary primary-expression
// field-access-expression
isTokenKind(state, Token.TokenKind.LeftBrace, tokenIndexStart) ||
// item-access-expression
Expand Down
7 changes: 7 additions & 0 deletions src/powerquery-parser/parser/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ export interface Parser {
correlationId: number | undefined,
) => Ast.IdentifierExpression;

// 12.2.3.13 Section-access expression
readonly readSectionAccessExpression: (
state: ParseState,
parser: Parser,
correlationId: number | undefined,
) => Ast.SectionAccessExpression;

// 12.2.3.14 Parenthesized expression
readonly readParenthesizedExpression: (
state: ParseState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export const CombinatorialParserV2: Parser = {

readIdentifierExpression: NaiveParseSteps.readIdentifierExpression,

readSectionAccessExpression: NaiveParseSteps.readSectionAccessExpression,

readParenthesizedExpression: NaiveParseSteps.readParenthesizedExpression,

readNotImplementedExpression: NaiveParseSteps.readNotImplementedExpression,
Expand Down Expand Up @@ -205,7 +207,10 @@ async function readUnaryExpression(
// PrimaryExpression
case Token.TokenKind.AtSign:
case Token.TokenKind.Identifier:
primaryExpression = NaiveParseSteps.readIdentifierExpression(state, parser, trace.id);
primaryExpression = ParseStateUtils.isSectionAccessExpressionNext(state)
? NaiveParseSteps.readSectionAccessExpression(state, parser, trace.id)
: NaiveParseSteps.readIdentifierExpression(state, parser, trace.id);

break;

case Token.TokenKind.LeftParenthesis:
Expand Down
61 changes: 60 additions & 1 deletion src/powerquery-parser/parser/parsers/naiveParseSteps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,9 @@ export async function readPrimaryExpression(
const isIdentifierExpressionNext: boolean =
currentTokenKind === TokenKind.AtSign || currentTokenKind === TokenKind.Identifier;

if (isIdentifierExpressionNext) {
if (ParseStateUtils.isSectionAccessExpressionNext(state)) {
primaryExpression = parser.readSectionAccessExpression(state, parser, trace.id);
} else if (isIdentifierExpressionNext) {
primaryExpression = parser.readIdentifierExpression(state, parser, trace.id);
} else {
switch (currentTokenKind) {
Expand Down Expand Up @@ -1201,6 +1203,63 @@ export function readIdentifierExpression(
return identifierExpression;
}

// ---------------------------------------------------------
// ---------- 12.2.3.13 Section-access expression ----------
// ---------------------------------------------------------

export function readSectionAccessExpression(
state: ParseState,
parser: Parser,
correlationId: number | undefined,
): Ast.SectionAccessExpression {
const nodeKind: Ast.NodeKind.SectionAccessExpression = Ast.NodeKind.SectionAccessExpression;

const trace: Trace = state.traceManager.entry(
NaiveTraceConstant.Parse,
readSectionAccessExpression.name,
correlationId,
{ [NaiveTraceConstant.TokenIndex]: state.tokenIndex },
);

state.cancellationToken?.throwIfCancelled();
ParseStateUtils.startContext(state, nodeKind);

const sectionIdentifier: Ast.Identifier = parser.readIdentifier(
state,
parser,
Ast.IdentifierContextKind.Value,
trace.id,
);

const bangConstant: Ast.IConstant<Constant.MiscConstant.Bang> = readTokenKindAsConstant(
state,
TokenKind.Bang,
Constant.MiscConstant.Bang,
trace.id,
);

const memberIdentifier: Ast.Identifier = parser.readIdentifier(
state,
parser,
Ast.IdentifierContextKind.Value,
trace.id,
);

const sectionAccessExpression: Ast.SectionAccessExpression = {
...ParseStateUtils.assertGetContextNodeMetadata(state),
kind: nodeKind,
isLeaf: false,
sectionIdentifier,
bangConstant,
memberIdentifier,
};

ParseStateUtils.endContext(state, sectionAccessExpression);
trace.exit({ [NaiveTraceConstant.TokenIndex]: state.tokenIndex });

return sectionAccessExpression;
}

// --------------------------------------------------------
// ---------- 12.2.3.14 Parenthesized expression ----------
// --------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export const RecursiveDescentParser: Parser = {

readIdentifierExpression: NaiveParseSteps.readIdentifierExpression,

readSectionAccessExpression: NaiveParseSteps.readSectionAccessExpression,

readParenthesizedExpression: NaiveParseSteps.readParenthesizedExpression,

readNotImplementedExpression: NaiveParseSteps.readNotImplementedExpression,
Expand Down
Loading