Skip to content

Commit

Permalink
feat: very basic type computer (#596)
Browse files Browse the repository at this point in the history
Closes partially #541

### Summary of Changes

Add a very basic implementation of a type computer that can only deal
with literals and template strings. It does, however, lay the groundwork
to implement more complex features later, by adding a system to access
builtin classes like `String` and registering the type computer as an
added service.

---------

Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 1, 2023
1 parent 928b520 commit b3d786c
Show file tree
Hide file tree
Showing 48 changed files with 1,258 additions and 90 deletions.
37 changes: 37 additions & 0 deletions src/language/builtins/fileFinder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import path from 'path';
import { URI } from 'langium';
import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js';
import { globSync } from 'glob';

let builtinsPath: string;
if (__filename.endsWith('.ts')) {
// Before running ESBuild
builtinsPath = path.join(__dirname, '..', '..', 'resources', 'builtins');
} /* c8 ignore start */ else {
// After running ESBuild
builtinsPath = path.join(__dirname, '..', 'resources', 'builtins');
} /* c8 ignore stop */

/**
* Lists all Safe-DS files in `src/resources/builtins`.
*
* @return URIs of all discovered files.
*/
export const listBuiltinsFiles = (): URI[] => {
const pattern = `**/*.{${SAFE_DS_FILE_EXTENSIONS.join(',')}}`;
const relativePaths = globSync(pattern, { cwd: builtinsPath, nodir: true });
return relativePaths.map((relativePath) => {
const absolutePath = path.join(builtinsPath, relativePath);
return URI.file(absolutePath);
});
};

/**
* Resolves a relative path to a builtin file.
*
* @param relativePath
*/
export const resolveRelativePathToBuiltinFile = (relativePath: string): URI => {
const absolutePath = path.join(builtinsPath, relativePath);
return URI.file(absolutePath);
};
93 changes: 93 additions & 0 deletions src/language/builtins/safe-ds-core-classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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';

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

export class SafeDsCoreClasses {
private readonly langiumDocuments: LangiumDocuments;

constructor(services: SafeDsServices) {
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
}

private cachedAny: SdsClass | undefined;

/* c8 ignore start */
get Any(): SdsClass | undefined {
if (!this.cachedAny) {
this.cachedAny = this.getClass('Any');
}
return this.cachedAny;
}

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

get Boolean(): SdsClass | undefined {
if (!this.cachedBoolean) {
this.cachedBoolean = this.getClass('Boolean');
}
return this.cachedBoolean;
}

private cachedFloat: SdsClass | undefined;

get Float(): SdsClass | undefined {
if (!this.cachedFloat) {
this.cachedFloat = this.getClass('Float');
}
return this.cachedFloat;
}

private cachedInt: SdsClass | undefined;

get Int(): SdsClass | undefined {
if (!this.cachedInt) {
this.cachedInt = this.getClass('Int');
}
return this.cachedInt;
}

private cachedNothing: SdsClass | undefined;

get Nothing(): SdsClass | undefined {
if (!this.cachedNothing) {
this.cachedNothing = this.getClass('Nothing');
}
return this.cachedNothing;
}

private cachedString: SdsClass | undefined;

get String(): SdsClass | undefined {
if (!this.cachedString) {
this.cachedString = this.getClass('String');
}
return this.cachedString;
}

private getClass(name: string): SdsClass | undefined {
if (!this.langiumDocuments.hasDocument(CORE_CLASSES_URI)) {
/* c8 ignore next 2 */
return undefined;
}

const document = this.langiumDocuments.getOrCreateDocument(CORE_CLASSES_URI);
const root = document.parseResult.value;
if (!isSdsModule(root)) {
/* c8 ignore next 2 */
return undefined;
}

const firstMatchingModuleMember = moduleMembersOrEmpty(root).find((m) => m.name === name);
if (!isSdsClass(firstMatchingModuleMember)) {
/* c8 ignore next 2 */
return undefined;
}

return firstMatchingModuleMember;
}
}
30 changes: 3 additions & 27 deletions src/language/builtins/safe-ds-workspace-manager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices, URI } from 'langium';
import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices } from 'langium';
import { WorkspaceFolder } from 'vscode-languageserver';
import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js';
import { globSync } from 'glob';
import path from 'path';
import { listBuiltinsFiles } from './fileFinder.js';

export class SafeDsWorkspaceManager extends DefaultWorkspaceManager {
private documentFactory: LangiumDocumentFactory;
Expand All @@ -18,31 +16,9 @@ export class SafeDsWorkspaceManager extends DefaultWorkspaceManager {
): Promise<void> {
await super.loadAdditionalDocuments(folders, collector);

// Load builtin files
for (const uri of listBuiltinsFiles()) {
collector(this.documentFactory.create(uri));
}
}
}

let builtinsPath: string;
if (__filename.endsWith('.ts')) {
// Before running ESBuild
builtinsPath = path.join(__dirname, '..', '..', 'resources', 'builtins');
} /* c8 ignore start */ else {
// After running ESBuild
builtinsPath = path.join(__dirname, '..', 'resources', 'builtins');
} /* c8 ignore stop */

/**
* Lists all Safe-DS files in `src/resources/builtins`.
*
* @return URIs of all discovered files.
*/
export const listBuiltinsFiles = (): URI[] => {
const pattern = `**/*.{${SAFE_DS_FILE_EXTENSIONS.join(',')}}`;
const relativePaths = globSync(pattern, { cwd: builtinsPath, nodir: true });
return relativePaths.map((relativePath) => {
const absolutePath = path.join(builtinsPath, relativePath);
return URI.file(absolutePath);
});
};
6 changes: 6 additions & 0 deletions src/language/helpers/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
isSdsBlockLambdaResult,
isSdsDeclaration,
isSdsModule,
isSdsModuleMember,
isSdsPlaceholder,
SdsAnnotatedObject,
SdsAnnotationCall,
Expand All @@ -19,6 +20,7 @@ import {
SdsLiteral,
SdsLiteralType,
SdsModule,
SdsModuleMember,
SdsParameter,
SdsParameterList,
SdsPlaceholder,
Expand Down Expand Up @@ -77,6 +79,10 @@ export const importsOrEmpty = function (node: SdsModule | undefined): SdsImport[
return node?.imports ?? [];
};

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

export const packageNameOrNull = function (node: AstNode | undefined): string | null {
return getContainerOfType(node, isSdsModule)?.name ?? null;
};
Expand Down
18 changes: 13 additions & 5 deletions src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ import {
PartialLangiumServices,
} from 'langium';
import { SafeDsGeneratedModule, SafeDsGeneratedSharedModule } from './generated/module.js';
import { registerValidationChecks, SafeDsValidator } from './validation/safe-ds-validator.js';
import { registerValidationChecks } from './validation/safe-ds-validator.js';
import { SafeDsFormatter } from './formatting/safe-ds-formatter.js';
import { SafeDsWorkspaceManager } from './builtins/safe-ds-workspace-manager.js';
import { SafeDsScopeComputation } from './scoping/safe-ds-scope-computation.js';
import { SafeDsScopeProvider } from './scoping/safe-ds-scope-provider.js';
import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js';
import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js';
import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js';

/**
* Declaration of custom services - add your own service classes here.
*/
export type SafeDsAddedServices = {
validation: {
SafeDsValidator: SafeDsValidator;
builtins: {
CoreClasses: SafeDsCoreClasses;
};
types: {
TypeComputer: SafeDsTypeComputer;
};
};

Expand All @@ -38,6 +43,9 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices;
* selected services, while the custom services must be fully specified.
*/
export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeDsAddedServices> = {
builtins: {
CoreClasses: (services) => new SafeDsCoreClasses(services),
},
lsp: {
Formatter: () => new SafeDsFormatter(),
},
Expand All @@ -48,8 +56,8 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
ScopeComputation: (services) => new SafeDsScopeComputation(services),
ScopeProvider: (services) => new SafeDsScopeProvider(services),
},
validation: {
SafeDsValidator: () => new SafeDsValidator(),
types: {
TypeComputer: (services) => new SafeDsTypeComputer(services),
},
};

Expand Down
18 changes: 9 additions & 9 deletions src/language/scoping/safe-ds-scope-provider.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {
AstNode,
AstNodeDescription,
AstNodeDescriptionProvider,
AstNodeLocator,
DefaultScopeProvider,
EMPTY_SCOPE,
getContainerOfType,
getDocument,
LangiumDocuments,
LangiumServices,
MultiMap,
ReferenceInfo,
Scope,
Expand Down Expand Up @@ -59,18 +57,20 @@ import {
} from '../helpers/shortcuts.js';
import { isContainedIn } from '../helpers/ast.js';
import { isStatic, isWildcardImport } from '../helpers/checks.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';

export class SafeDsScopeProvider extends DefaultScopeProvider {
readonly documents: LangiumDocuments;
readonly astNodeDescriptionProvider: AstNodeDescriptionProvider;
readonly astNodeLocator: AstNodeLocator;
private readonly astNodeLocator: AstNodeLocator;
private readonly langiumDocuments: LangiumDocuments;
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: LangiumServices) {
constructor(services: SafeDsServices) {
super(services);

this.documents = services.shared.workspace.LangiumDocuments;
this.astNodeDescriptionProvider = services.workspace.AstNodeDescriptionProvider;
this.astNodeLocator = services.workspace.AstNodeLocator;
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
this.typeComputer = services.types.TypeComputer;
}

override getScope(context: ReferenceInfo): Scope {
Expand Down Expand Up @@ -388,7 +388,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
/* c8 ignore next 2 */
return nodeDescription.node;
}
const document = this.documents.getOrCreateDocument(nodeDescription.documentUri);
const document = this.langiumDocuments.getOrCreateDocument(nodeDescription.documentUri);
return this.astNodeLocator.getAstNode(document.parseResult.value, nodeDescription.path);
}

Expand Down
Loading

0 comments on commit b3d786c

Please sign in to comment.