Skip to content
Draft
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
8 changes: 6 additions & 2 deletions src/slang-nodes/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,19 @@ export class Expression extends SlangNode {
| ElementaryType['variant']
| TerminalNode;

constructor(ast: ast.Expression, collected: CollectedMetadata) {
constructor(
ast: ast.Expression,
collected: CollectedMetadata,
endOfChain?: boolean
) {
super(ast, collected);

const variant = ast.variant;
if (variant instanceof SlangTerminalNode) {
this.variant = new TerminalNode(variant, collected);
return;
}
this.variant = createNonterminalVariant(variant, collected);
this.variant = createNonterminalVariant(variant, collected, endOfChain);

this.updateMetadata(this.variant);
}
Expand Down
10 changes: 8 additions & 2 deletions src/slang-nodes/FunctionCallExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ export class FunctionCallExpression extends SlangNode {

arguments: ArgumentsDeclaration['variant'];

constructor(ast: ast.FunctionCallExpression, collected: CollectedMetadata) {
constructor(
ast: ast.FunctionCallExpression,
collected: CollectedMetadata,
endOfChain?: boolean
) {
super(ast, collected);

this.operand = extractVariant(new Expression(ast.operand, collected));
this.operand = extractVariant(
new Expression(ast.operand, collected, endOfChain)
);
this.arguments = extractVariant(
new ArgumentsDeclaration(ast.arguments, collected)
);
Expand Down
10 changes: 8 additions & 2 deletions src/slang-nodes/IndexAccessExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ export class IndexAccessExpression extends SlangNode {

end?: IndexAccessEnd;

constructor(ast: ast.IndexAccessExpression, collected: CollectedMetadata) {
constructor(
ast: ast.IndexAccessExpression,
collected: CollectedMetadata,
endOfChain?: boolean
) {
super(ast, collected);

this.operand = extractVariant(new Expression(ast.operand, collected));
this.operand = extractVariant(
new Expression(ast.operand, collected, endOfChain)
);
if (ast.start) {
this.start = extractVariant(new Expression(ast.start, collected));
}
Expand Down
52 changes: 15 additions & 37 deletions src/slang-nodes/MemberAccessExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,19 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { doc } from 'prettier';
import { isLabel } from '../slang-utils/is-label.js';
import { extractVariant } from '../slang-utils/extract-variant.js';
import { isChainableExpression } from '../slang-utils/is-chainable-expression.js';
import { memberAccessChainLabel } from '../slang-printers/print-member-access-chain-item.js';
import { SlangNode } from './SlangNode.js';
import { Expression } from './Expression.js';
import { TerminalNode } from './TerminalNode.js';

import type * as ast from '@nomicfoundation/slang/ast';
import type { AstPath, Doc } from 'prettier';
import type { Doc } from 'prettier';
import type { CollectedMetadata, PrintFunction } from '../types.d.ts';
import type { ChainableExpression, PrintableNode } from './types.d.ts';

const { group, indent, label, softline } = doc.builders;

const separatorLabel = Symbol('separator');

function isEndOfChain(
node: ChainableExpression,
path: AstPath<PrintableNode>
): boolean {
for (let i = 1, current = node, parent; ; i++, current = parent) {
parent = path.getNode(i)!;
if (!isChainableExpression(parent)) break;

switch (parent.kind) {
case NonterminalKind.MemberAccessExpression:
// If `parent` is a MemberAccessExpression we are not at the end
// of the chain.
return false;
case NonterminalKind.IndexAccessExpression:
// If `parent` is an IndexAccessExpression and `current` is not
// the operand then it must be the start or the end in which case it is
// the end of the chain.
if (current !== parent.operand) return true;
break;
case NonterminalKind.FunctionCallExpression:
// If `parent` is a FunctionCallExpression and `current` is not
// the operand then it must be and argument in which case it is the end
// of the chain.
if (current !== parent.operand) return true;
break;
}
}
return true;
}

/**
* processChain expects the doc[] of the full chain of MemberAccess.
*
Expand Down Expand Up @@ -111,20 +79,30 @@ function processChain(chain: Doc[]): Doc {
export class MemberAccessExpression extends SlangNode {
readonly kind = NonterminalKind.MemberAccessExpression;

readonly #endOfChain: boolean;

operand: Expression['variant'];

member: TerminalNode;

constructor(ast: ast.MemberAccessExpression, collected: CollectedMetadata) {
constructor(
ast: ast.MemberAccessExpression,
collected: CollectedMetadata,
endOfChain = true
) {
super(ast, collected);

this.operand = extractVariant(new Expression(ast.operand, collected));
this.operand = extractVariant(
new Expression(ast.operand, collected, false)
);
this.member = new TerminalNode(ast.member, collected);

this.updateMetadata(this.operand);

this.#endOfChain = endOfChain;
}

print(print: PrintFunction, path: AstPath<MemberAccessExpression>): Doc {
print(print: PrintFunction): Doc {
let operandDoc = print('operand');
if (Array.isArray(operandDoc)) {
operandDoc = operandDoc.flat();
Expand All @@ -136,6 +114,6 @@ export class MemberAccessExpression extends SlangNode {
print('member')
].flat();

return isEndOfChain(this, path) ? processChain(document) : document;
return this.#endOfChain ? processChain(document) : document;
}
}
12 changes: 11 additions & 1 deletion src/slang-printers/print-assignment-right-side.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { isChainableExpression } from '../slang-utils/is-chainable-expression.js';
import { createKindCheckFunction } from '../slang-utils/create-kind-check-function.js';
import { printIndentedGroupOrSpacedDocument } from './print-indented-group-or-spaced-document.js';

import type { Doc, doc } from 'prettier';
import type { Expression } from '../slang-nodes/Expression.ts';
import type {
ChainableExpression,
PrintableNode
} from '../slang-nodes/types.d.ts';

const isChainableExpression = createKindCheckFunction([
NonterminalKind.FunctionCallExpression,
NonterminalKind.IndexAccessExpression,
NonterminalKind.MemberAccessExpression
]) as (node: PrintableNode) => node is ChainableExpression;

export function printAssignmentRightSide(
document: Doc,
Expand Down
22 changes: 15 additions & 7 deletions src/slang-utils/create-nonterminal-variant-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,32 @@ import type {
SlangAstNodeClass
} from '../types.d.ts';

type NodeConstructor<T> = new (ast: any, collected: CollectedMetadata) => T;
type NodeConstructor<T> = new (
ast: any,
collected: CollectedMetadata,
endOfChain?: boolean
) => T;
type SlangPolymorphicNode = Extract<SlangAstNode, { variant: unknown }>;

type NonterminalVariantFactory<
U extends SlangPolymorphicNode,
T extends StrictPolymorphicNode
> = (variant: U['variant'], collected: CollectedMetadata) => T['variant'];
> = (
variant: U['variant'],
collected: CollectedMetadata,
endOfChain?: boolean
) => T['variant'];

export function createNonterminalVariantSimpleCreator<
U extends SlangPolymorphicNode,
T extends StrictPolymorphicNode
>(
constructors: [SlangAstNodeClass, NodeConstructor<T['variant']>][]
): NonterminalVariantFactory<U, T> {
return (variant, collected) => {
return (variant, collected, endOfChain) => {
for (const [slangAstClass, constructor] of constructors) {
if (variant instanceof slangAstClass)
return new constructor(variant, collected);
return new constructor(variant, collected, endOfChain);
}

throw new Error(`Unexpected variant: ${JSON.stringify(variant)}`);
Expand All @@ -46,12 +54,12 @@ export function createNonterminalVariantCreator<
constructors
);

return (variant, collected) => {
return (variant, collected, endOfChain) => {
for (const [slangAstClass, constructor] of extractVariantConstructors) {
if (variant instanceof slangAstClass)
return extractVariant(new constructor(variant, collected));
return extractVariant(new constructor(variant, collected, endOfChain));
}

return simpleCreator(variant, collected);
return simpleCreator(variant, collected, endOfChain);
};
}
13 changes: 0 additions & 13 deletions src/slang-utils/is-chainable-expression.ts

This file was deleted.

Loading