diff --git a/src/powerquery-parser/language/ast/ast.ts b/src/powerquery-parser/language/ast/ast.ts index dd76b7e3..c8bdd294 100644 --- a/src/powerquery-parser/language/ast/ast.ts +++ b/src/powerquery-parser/language/ast/ast.ts @@ -58,6 +58,7 @@ export enum NodeKind { RecursivePrimaryExpression = "RecursivePrimaryExpression", RelationalExpression = "RelationalExpression", Section = "Section", + SectionAccessExpression = "SectionAccessExpression", SectionMember = "SectionMember", TableType = "TableType", TypePrimaryType = "TypePrimaryType", @@ -346,7 +347,7 @@ export type TPrimaryExpression = | ListExpression | RecordExpression | IdentifierExpression - // SectionAccessExpression + | SectionAccessExpression | ParenthesizedExpression | TFieldAccessExpression | TRecursivePrimaryExpression @@ -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; + readonly memberIdentifier: Identifier; +} + // -------------------------------------------------------- // ---------- 12.2.3.14 Parenthesized expression ---------- // -------------------------------------------------------- @@ -919,6 +932,7 @@ export const NodeKindsForTPrimaryExpression: Set = new Set([ NodeKind.ListExpression, NodeKind.RecordExpression, NodeKind.IdentifierExpression, + NodeKind.SectionAccessExpression, NodeKind.ParenthesizedExpression, NodeKind.InvokeExpression, NodeKind.RecursivePrimaryExpression, diff --git a/src/powerquery-parser/language/ast/astUtils/astUtils.ts b/src/powerquery-parser/language/ast/astUtils/astUtils.ts index 6bac4bc8..8115d91b 100644 --- a/src/powerquery-parser/language/ast/astUtils/astUtils.ts +++ b/src/powerquery-parser/language/ast/astUtils/astUtils.ts @@ -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", { diff --git a/src/powerquery-parser/language/ast/astUtils/typeGuards.ts b/src/powerquery-parser/language/ast/astUtils/typeGuards.ts index 94137105..4adfbd84 100644 --- a/src/powerquery-parser/language/ast/astUtils/typeGuards.ts +++ b/src/powerquery-parser/language/ast/astUtils/typeGuards.ts @@ -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: @@ -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: @@ -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: diff --git a/src/powerquery-parser/language/constant/constant.ts b/src/powerquery-parser/language/constant/constant.ts index 8c01dd73..69465a55 100644 --- a/src/powerquery-parser/language/constant/constant.ts +++ b/src/powerquery-parser/language/constant/constant.ts @@ -57,6 +57,7 @@ export enum KeywordConstant { export enum MiscConstant { Ampersand = "&", AtSign = "@", + Bang = "!", Comma = ",", DotDot = "..", Ellipsis = "...", @@ -181,6 +182,7 @@ export const LogicalOperators: ReadonlyArray = [LogicalOperator export const MiscConstants: ReadonlyArray = [ MiscConstant.Ampersand, MiscConstant.AtSign, + MiscConstant.Bang, MiscConstant.Comma, MiscConstant.DotDot, MiscConstant.Ellipsis, diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/specializedSelectors.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/specializedSelectors.ts index 39b50bb3..ceb974cb 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/specializedSelectors.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/specializedSelectors.ts @@ -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"; @@ -49,11 +49,11 @@ export function assertRecursiveExpressionPreviousSibling( 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 @@ -73,32 +73,42 @@ export function invokeExpressionIdentifier( nodeId: number, ): XorNode | 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 | undefined = nthChildXorChecked( + 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 | undefined { // It's not an identifier expression so there's nothing we can do. - if (headXorNode === undefined) { + if (!XorNodeUtils.isNodeKind(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(headXorNode, Ast.NodeKind.IdentifierExpression)) { const details: { identifierExpressionNodeId: number; invokeExpressionNodeId: number; @@ -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 | undefined = invokeExpressionIdentifier( - nodeIdMapCollection, - nodeId, + if (headXorNode === undefined) { + return undefined; + } + + const identifierExpressionXorNode: AstXorNode | 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(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 diff --git a/src/powerquery-parser/parser/parseState/parseStateUtils.ts b/src/powerquery-parser/parser/parseState/parseStateUtils.ts index 196edecc..223182b6 100644 --- a/src/powerquery-parser/parser/parseState/parseStateUtils.ts +++ b/src/powerquery-parser/parser/parseState/parseStateUtils.ts @@ -262,6 +262,14 @@ 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, @@ -269,7 +277,8 @@ export function isRecursivePrimaryExpressionNext( ): 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 diff --git a/src/powerquery-parser/parser/parser/parser.ts b/src/powerquery-parser/parser/parser/parser.ts index 0e1e5d37..1ee41829 100644 --- a/src/powerquery-parser/parser/parser/parser.ts +++ b/src/powerquery-parser/parser/parser/parser.ts @@ -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, diff --git a/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts b/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts index d2ec6826..5d54fa93 100644 --- a/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts +++ b/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts @@ -92,6 +92,8 @@ export const CombinatorialParserV2: Parser = { readIdentifierExpression: NaiveParseSteps.readIdentifierExpression, + readSectionAccessExpression: NaiveParseSteps.readSectionAccessExpression, + readParenthesizedExpression: NaiveParseSteps.readParenthesizedExpression, readNotImplementedExpression: NaiveParseSteps.readNotImplementedExpression, @@ -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: diff --git a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts index 571aab2d..54b12669 100644 --- a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts +++ b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts @@ -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) { @@ -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 = 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 ---------- // -------------------------------------------------------- diff --git a/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts b/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts index 923ea84a..9810715e 100644 --- a/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts +++ b/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts @@ -46,6 +46,8 @@ export const RecursiveDescentParser: Parser = { readIdentifierExpression: NaiveParseSteps.readIdentifierExpression, + readSectionAccessExpression: NaiveParseSteps.readSectionAccessExpression, + readParenthesizedExpression: NaiveParseSteps.readParenthesizedExpression, readNotImplementedExpression: NaiveParseSteps.readNotImplementedExpression, diff --git a/src/test/libraryTest/parser/error.test.ts b/src/test/libraryTest/parser/error.test.ts index 43ce9d8e..19c596a6 100644 --- a/src/test/libraryTest/parser/error.test.ts +++ b/src/test/libraryTest/parser/error.test.ts @@ -115,6 +115,61 @@ describe("Parser.Error", () => { }); }); + describe(`${Ast.NodeKind.SectionAccessExpression}`, () => { + it("ExpectedTokenKindError: Section1!", async () => { + const text: string = "Section1!"; + + const innerError: ParseError.TInnerParseError = ( + await AssertTestUtils.assertGetParseError(DefaultSettingsWithStrict, text) + ).innerError; + + expect(innerError instanceof ParseError.ExpectedTokenKindError).to.equal(true, innerError.message); + }); + + it("ExpectedTokenKindError: Section1!1", async () => { + const text: string = "Section1!1"; + + const innerError: ParseError.TInnerParseError = ( + await AssertTestUtils.assertGetParseError(DefaultSettingsWithStrict, text) + ).innerError; + + expect(innerError instanceof ParseError.ExpectedTokenKindError).to.equal(true, innerError.message); + }); + + // section-access-expression is `identifier ! identifier`, so it can't be chained... + it("UnusedTokensRemainError: Section1!Query1!Query2", async () => { + const text: string = "Section1!Query1!Query2"; + + const innerError: ParseError.TInnerParseError = ( + await AssertTestUtils.assertGetParseError(DefaultSettingsWithStrict, text) + ).innerError; + + expect(innerError instanceof ParseError.UnusedTokensRemainError).to.equal(true, innerError.message); + }); + + // ...nor does it accept an inclusive identifier reference on either the section name... + it("UnusedTokensRemainError: @Section1!Query1", async () => { + const text: string = "@Section1!Query1"; + + const innerError: ParseError.TInnerParseError = ( + await AssertTestUtils.assertGetParseError(DefaultSettingsWithStrict, text) + ).innerError; + + expect(innerError instanceof ParseError.UnusedTokensRemainError).to.equal(true, innerError.message); + }); + + // ...or the section member name. + it("ExpectedTokenKindError: Section1!@Query1", async () => { + const text: string = "Section1!@Query1"; + + const innerError: ParseError.TInnerParseError = ( + await AssertTestUtils.assertGetParseError(DefaultSettingsWithStrict, text) + ).innerError; + + expect(innerError instanceof ParseError.ExpectedTokenKindError).to.equal(true, innerError.message); + }); + }); + describe(`Dangling comma`, () => { it(`LetExpression`, async () => { const text: string = "let a = 1, in 1"; diff --git a/src/test/libraryTest/parser/identifierContextKind.test.ts b/src/test/libraryTest/parser/identifierContextKind.test.ts index e6066a57..472f2296 100644 --- a/src/test/libraryTest/parser/identifierContextKind.test.ts +++ b/src/test/libraryTest/parser/identifierContextKind.test.ts @@ -68,4 +68,11 @@ describe("Parser.IdentifierContextKind", () => { assertIdentifierIsInContext(parseOk, "foo", Ast.IdentifierContextKind.Key); assertIdentifierIsInContext(parseOk, "#date", Ast.IdentifierContextKind.Keyword); }); + + it("Section1!Query1", async () => { + const parseOk: ParseOk = await AssertTestUtils.assertGetParseOk(DefaultSettings, "Section1!Query1"); + + assertIdentifierIsInContext(parseOk, "Section1", Ast.IdentifierContextKind.Value); + assertIdentifierIsInContext(parseOk, "Query1", Ast.IdentifierContextKind.Value); + }); }); diff --git a/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts b/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts index 499a1b76..8fa7632a 100644 --- a/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts +++ b/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts @@ -309,6 +309,80 @@ describe(`nodeIdMapUtils`, () => { }); }); + describe(`invokeExpressionIdentifierLiteral`, () => { + it(`Ast`, async () => { + const text: string = `Foo(1)`; + + const parseOk: Task.ParseTaskOk = await AssertTestUtils.assertGetLexParseOk( + DefaultSettings, + text + ); + + const nodeIdMapCollection: NodeIdMap.Collection = parseOk.nodeIdMapCollection; + + const invokeExpressionNodeIds: Set = Assert.asDefined( + nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.InvokeExpression), + ); + + expect(invokeExpressionNodeIds.size).to.equal(1); + const invokeExpressionNodeId: number = Assert.asDefined(invokeExpressionNodeIds.values().next().value); + + const invokeExpressionIdentifierLiteral: string = Assert.asDefined( + NodeIdMapUtils.invokeExpressionIdentifierLiteral(nodeIdMapCollection, invokeExpressionNodeId), + ); + + expect(invokeExpressionIdentifierLiteral).to.equal("Foo"); + }); + + it(`Ast - section access`, async () => { + const text: string = `Section1!Foo(1)`; + + const parseOk: Task.ParseTaskOk = await AssertTestUtils.assertGetLexParseOk( + DefaultSettings, + text + ); + + const nodeIdMapCollection: NodeIdMap.Collection = parseOk.nodeIdMapCollection; + + const invokeExpressionNodeIds: Set = Assert.asDefined( + nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.InvokeExpression), + ); + + expect(invokeExpressionNodeIds.size).to.equal(1); + const invokeExpressionNodeId: number = Assert.asDefined(invokeExpressionNodeIds.values().next().value); + + const invokeExpressionIdentifierLiteral: string = Assert.asDefined( + NodeIdMapUtils.invokeExpressionIdentifierLiteral(nodeIdMapCollection, invokeExpressionNodeId), + ); + + expect(invokeExpressionIdentifierLiteral).to.equal("Section1!Foo"); + }); + + it(`Context - section access`, async () => { + const text: string = `Section1!Foo(1, `; + + const parseError: Task.ParseTaskParseError = await AssertTestUtils.assertGetLexParseError( + DefaultSettings, + text, + ); + + const nodeIdMapCollection: NodeIdMap.Collection = parseError.error.state.contextState.nodeIdMapCollection; + + const invokeExpressionNodeIds: Set = Assert.asDefined( + nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.InvokeExpression), + ); + + expect(invokeExpressionNodeIds.size).to.equal(1); + const invokeExpressionNodeId: number = Assert.asDefined(invokeExpressionNodeIds.values().next().value); + + const invokeExpressionIdentifierLiteral: string = Assert.asDefined( + NodeIdMapUtils.invokeExpressionIdentifierLiteral(nodeIdMapCollection, invokeExpressionNodeId), + ); + + expect(invokeExpressionIdentifierLiteral).to.equal("Section1!Foo"); + }); + }); + describe("wrappedContentXor", () => { it("Ast", async () => { const text: string = `[a = 1]`; diff --git a/src/test/libraryTest/parser/parseSimple.test.ts b/src/test/libraryTest/parser/parseSimple.test.ts index 4a383764..8a02f96d 100644 --- a/src/test/libraryTest/parser/parseSimple.test.ts +++ b/src/test/libraryTest/parser/parseSimple.test.ts @@ -1544,6 +1544,173 @@ describe("Parser.AbridgedNode", () => { }); }); + describe(`${Ast.NodeKind.SectionAccessExpression}`, () => { + it(`Section1!Query1`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1!Query1`, [ + [Ast.NodeKind.SectionAccessExpression, undefined], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + ]); + }); + + it(`Section1 ! Query1`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1 ! Query1`, [ + [Ast.NodeKind.SectionAccessExpression, undefined], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + ]); + }); + + it(`#"Section 1"!#"Query 1"`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`#"Section 1"!#"Query 1"`, [ + [Ast.NodeKind.SectionAccessExpression, undefined], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + ]); + }); + + it(`Section1!Query1 + 1`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1!Query1 + 1`, [ + [Ast.NodeKind.ArithmeticExpression, undefined], + [Ast.NodeKind.SectionAccessExpression, 0], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.LiteralExpression, 2], + ]); + }); + + it(`Section1!Query1[Column]`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1!Query1[Column]`, [ + [Ast.NodeKind.RecursivePrimaryExpression, undefined], + [Ast.NodeKind.SectionAccessExpression, 0], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.ArrayWrapper, 1], + [Ast.NodeKind.FieldSelector, 0], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.GeneralizedIdentifier, 1], + [Ast.NodeKind.Constant, 2], + ]); + }); + + it(`Section1!Function1(1)`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1!Function1(1)`, [ + [Ast.NodeKind.RecursivePrimaryExpression, undefined], + [Ast.NodeKind.SectionAccessExpression, 0], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.ArrayWrapper, 1], + [Ast.NodeKind.InvokeExpression, 0], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.ArrayWrapper, 1], + [Ast.NodeKind.Csv, 0], + [Ast.NodeKind.LiteralExpression, 0], + [Ast.NodeKind.Constant, 2], + ]); + }); + + it(`Section1!Query1{0}`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1!Query1{0}`, [ + [Ast.NodeKind.RecursivePrimaryExpression, undefined], + [Ast.NodeKind.SectionAccessExpression, 0], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.ArrayWrapper, 1], + [Ast.NodeKind.ItemAccessExpression, 0], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.LiteralExpression, 1], + [Ast.NodeKind.Constant, 2], + ]); + }); + + it(`(Section1!Query1)`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`(Section1!Query1)`, [ + [Ast.NodeKind.ParenthesizedExpression, undefined], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.SectionAccessExpression, 1], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.Constant, 2], + ]); + }); + + it(`let x = Section1!Query1 in x`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`let x = Section1!Query1 in x`, [ + [Ast.NodeKind.LetExpression, undefined], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.ArrayWrapper, 1], + [Ast.NodeKind.Csv, 0], + [Ast.NodeKind.IdentifierPairedExpression, 0], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.SectionAccessExpression, 2], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.Constant, 2], + [Ast.NodeKind.IdentifierExpression, 3], + [Ast.NodeKind.Identifier, 1], + ]); + }); + + it(`type table Section1!Row`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`type table Section1!Row`, [ + [Ast.NodeKind.TypePrimaryType, undefined], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.TableType, 1], + [Ast.NodeKind.Constant, 0], + [Ast.NodeKind.SectionAccessExpression, 1], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + ]); + }); + + it(`section Section1; x = 1; y = Section1!x;`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`section Section1; x = 1; y = Section1!x;`, [ + [Ast.NodeKind.Section, undefined], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.Constant, 3], + [Ast.NodeKind.ArrayWrapper, 4], + [Ast.NodeKind.SectionMember, 0], + [Ast.NodeKind.IdentifierPairedExpression, 2], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.LiteralExpression, 2], + [Ast.NodeKind.Constant, 3], + [Ast.NodeKind.SectionMember, 1], + [Ast.NodeKind.IdentifierPairedExpression, 2], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.SectionAccessExpression, 2], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + [Ast.NodeKind.Constant, 3], + ]); + }); + + it(`Section1!`, async () => { + await ParserTestUtils.runAbridgedNodeTest(`Section1!`, [ + [Ast.NodeKind.LogicalExpression, undefined], + [Ast.NodeKind.SectionAccessExpression, 0], + [Ast.NodeKind.Identifier, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.Identifier, 2], + ]); + }); + }); + describe(`${Ast.NodeKind.SectionMember}`, () => { it(`section; x = 1;`, async () => { await ParserTestUtils.runAbridgedNodeTest(`section; x = 1;`, [