Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: extensions for the NodeMapper #606

Merged
merged 9 commits into from
Oct 6, 2023
5 changes: 3 additions & 2 deletions src/language/builtins/safe-ds-core-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SafeDsServices } from '../safe-ds-module.js';
import { resolveRelativePathToBuiltinFile } from './fileFinder.js';
import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js';
import { LangiumDocuments } from 'langium';
import { moduleMembersOrEmpty } from '../helpers/shortcuts.js';
import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js';

const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub');

Expand All @@ -23,9 +23,10 @@ export class SafeDsCoreClasses {
return this.cachedAny;
}

private cachedBoolean: SdsClass | undefined;
/* c8 ignore stop */

private cachedBoolean: SdsClass | undefined;

get Boolean(): SdsClass | undefined {
if (!this.cachedBoolean) {
this.cachedBoolean = this.getClass('Boolean');
Expand Down
2 changes: 1 addition & 1 deletion src/language/formatting/safe-ds-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
isAstNode,
} from 'langium';
import * as ast from '../generated/ast.js';
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/shortcuts.js';
import noSpace = Formatting.noSpace;
import newLine = Formatting.newLine;
import newLines = Formatting.newLines;
import oneSpace = Formatting.oneSpace;
import indent = Formatting.indent;
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/nodeProperties.js';

const newLinesWithIndent = function (count: number, options?: FormattingActionOptions): FormattingAction {
return {
Expand Down
File renamed without changes.
36 changes: 0 additions & 36 deletions src/language/helpers/checks.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/language/helpers/collectionUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Returns the unique element in the array, or `undefined` if none or multiple exist.
*/
export const uniqueOrUndefined = <T>(elements: T[]): T | undefined => {
if (elements.length === 1) {
return elements[0];
}

return undefined;
};

/**
* Returns the elements of the array that are labeled the same as a previous element. The first element with a label is
* not included. Neither are elements with an undefined label.
*/
export const duplicatesBy = function* <T, K>(
elements: Iterable<T>,
labeler: (element: T) => K | undefined,
): Generator<T, void> {
const knownLabels = new Set<K>();

for (const element of elements) {
const label = labeler(element);
if (label === undefined) {
continue;
}

if (knownLabels.has(label)) {
yield element;
} else {
knownLabels.add(label);
}
}
};
34 changes: 34 additions & 0 deletions src/language/helpers/idManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Handles the mapping of objects, usually nodes of an Safe-DS AST, to their IDs.
*/
export class IdManager<T extends WeakKey> {
/**
* Maps an object to an ID.
*/
private objToId: WeakMap<T, Id> = new WeakMap();

/**
* The next available ID.
*/
private nextId = 0;

/**
* Assigns the next available ID to the given object unless it already has one and returns the ID for this object.
*/
assignId(obj: T): Id {
if (!this.objToId.has(obj)) {
this.objToId.set(obj, this.nextId++);
}
return this.objToId.get(obj)!;
}

/**
* Removes all mappings between object and ID and resets the counter.
*/
reset() {
this.objToId = new WeakMap();
this.nextId = 0;
}
}

export type Id = number;
179 changes: 179 additions & 0 deletions src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
isSdsAssignment,
isSdsAttribute,
isSdsBlockLambdaResult,
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsEnumVariant,
isSdsFunction,
isSdsModule,
isSdsModuleMember,
isSdsPlaceholder,
isSdsSegment,
isSdsTypeParameterList,
SdsAbstractCall,
SdsAnnotatedObject,
SdsAnnotationCall,
SdsArgument,
SdsAssignee,
SdsAssignment,
SdsBlock,
SdsBlockLambda,
SdsBlockLambdaResult,
SdsCallable,
SdsClass,
SdsClassMember,
SdsDeclaration,
SdsEnum,
SdsEnumVariant,
SdsImport,
SdsImportedDeclaration,
SdsLiteral,
SdsLiteralType,
SdsModule,
SdsModuleMember,
SdsNamedTypeDeclaration,
SdsParameter,
SdsPlaceholder,
SdsQualifiedImport,
SdsResult,
SdsResultList,
SdsStatement,
SdsTypeArgument,
SdsTypeArgumentList,
SdsTypeParameter,
SdsTypeParameterList,
} from '../generated/ast.js';
import { AstNode, getContainerOfType, stream } from 'langium';

// -------------------------------------------------------------------------------------------------
// Checks
// -------------------------------------------------------------------------------------------------

export const isInternal = (node: SdsDeclaration): boolean => {
return isSdsSegment(node) && node.visibility === 'internal';
};

export const isNamedArgument = (node: SdsArgument): boolean => {
return Boolean(node.parameter);
};

export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
return Boolean(node.typeParameter);
};

export const isStatic = (node: SdsClassMember): boolean => {
if (isSdsClass(node) || isSdsEnum(node)) {
return true;
} else if (isSdsAttribute(node)) {
return node.isStatic;
} else if (isSdsFunction(node)) {
return node.isStatic;
} else {
/* c8 ignore next 2 */
return false;
}
};

// -------------------------------------------------------------------------------------------------
// Accessors for list elements
// -------------------------------------------------------------------------------------------------

export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] => {
if (!node) {
/* c8 ignore next 2 */
return [];
}

if (isSdsDeclaration(node)) {
return node?.annotationCallList?.annotationCalls ?? node?.annotationCalls ?? [];
} else {
/* c8 ignore next 2 */
return node?.annotationCalls ?? [];
}
};
export const argumentsOrEmpty = (node: SdsAbstractCall | undefined): SdsArgument[] => {
return node?.argumentList?.arguments ?? [];
};
export const assigneesOrEmpty = (node: SdsAssignment | undefined): SdsAssignee[] => {
return node?.assigneeList?.assignees ?? [];
};
export const blockLambdaResultsOrEmpty = (node: SdsBlockLambda | undefined): SdsBlockLambdaResult[] => {
return stream(statementsOrEmpty(node?.body))
.filter(isSdsAssignment)
.flatMap(assigneesOrEmpty)
.filter(isSdsBlockLambdaResult)
.toArray();
};
export const importedDeclarationsOrEmpty = (node: SdsQualifiedImport | undefined): SdsImportedDeclaration[] => {
return node?.importedDeclarationList?.importedDeclarations ?? [];
};

export const literalsOrEmpty = (node: SdsLiteralType | undefined): SdsLiteral[] => {
return node?.literalList?.literals ?? [];
};
export const classMembersOrEmpty = (
node: SdsClass | undefined,
filterFunction: (member: SdsClassMember) => boolean = () => true,
): SdsClassMember[] => {
return node?.body?.members?.filter(filterFunction) ?? [];
};

export const enumVariantsOrEmpty = (node: SdsEnum | undefined): SdsEnumVariant[] => {
return node?.body?.variants ?? [];
};

export const importsOrEmpty = (node: SdsModule | undefined): SdsImport[] => {
return node?.imports ?? [];
};

export const moduleMembersOrEmpty = (node: SdsModule | undefined): SdsModuleMember[] => {
return node?.members?.filter(isSdsModuleMember) ?? [];
};

export const packageNameOrUndefined = (node: AstNode | undefined): string | undefined => {
return getContainerOfType(node, isSdsModule)?.name;
};

export const parametersOrEmpty = (node: SdsCallable | undefined): SdsParameter[] => {
return node?.parameterList?.parameters ?? [];
};

export const placeholdersOrEmpty = (node: SdsBlock | undefined): SdsPlaceholder[] => {
return stream(statementsOrEmpty(node))
.filter(isSdsAssignment)
.flatMap(assigneesOrEmpty)
.filter(isSdsPlaceholder)
.toArray();
};

export const resultsOrEmpty = (node: SdsResultList | undefined): SdsResult[] => {
return node?.results ?? [];
};

export const statementsOrEmpty = (node: SdsBlock | undefined): SdsStatement[] => {
return node?.statements ?? [];
};

export const typeArgumentsOrEmpty = (node: SdsTypeArgumentList | undefined): SdsTypeArgument[] => {
return node?.typeArguments ?? [];
};

export const typeParametersOrEmpty = (
node: SdsTypeParameterList | SdsNamedTypeDeclaration | undefined,
): SdsTypeParameter[] => {
if (!node) {
return [];
}

if (isSdsTypeParameterList(node)) {
return node.typeParameters;
} else if (isSdsClass(node)) {
return typeParametersOrEmpty(node.typeParameterList);
} else if (isSdsEnumVariant(node)) {
return typeParametersOrEmpty(node.typeParameterList);
} /* c8 ignore start */ else {
return [];
} /* c8 ignore stop */
};
Loading