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: generation #634

Merged
merged 64 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
7f982e8
feat: Begin of generator work
WinPlay02 Oct 10, 2023
2c585cb
feat: More Expression generation, blocklambda management, import mana…
WinPlay02 Oct 11, 2023
c5887db
feat: Use python names for generation, when available
WinPlay02 Oct 11, 2023
f48a1cb
fix: when loading a generation test case, load all files first before…
WinPlay02 Oct 12, 2023
7a8b582
Merge remote-tracking branch 'origin/main'
WinPlay02 Oct 12, 2023
e48802f
refactor: use IdManager instead of own BlockLambdaIdManager
WinPlay02 Oct 12, 2023
bcfebe4
test: enable generation test that pass
WinPlay02 Oct 12, 2023
8dd9918
chore: convert imports to a map with unique string keys for each import
WinPlay02 Oct 16, 2023
423ebd2
Merge branch 'main' of https://github.com/Safe-DS/DSL into generation
WinPlay02 Oct 16, 2023
c0aa7c7
fix: Pass services to generation functions, to get correct annotation…
WinPlay02 Oct 16, 2023
7325e10
fix: Use more general functions to get parameters / results from a call
WinPlay02 Oct 16, 2023
af753b3
feat: generate references (and imports) correctly
WinPlay02 Oct 16, 2023
54452ab
feat: sorting parameters + treating optional parameters correctly
WinPlay02 Oct 16, 2023
f4af467
chore: cleanup unneeded code
WinPlay02 Oct 16, 2023
a2969c8
test: remove varargs from test "expressions/expression lambda" and en…
WinPlay02 Oct 17, 2023
75196cb
test: remove varargs from test "expressions/block lambda" and enable
WinPlay02 Oct 17, 2023
b9c44ec
test: remove varargs from test "expressions/call" and enable
WinPlay02 Oct 17, 2023
0f3587d
test: remove varargs from test "declarations/two steps" and enable
WinPlay02 Oct 17, 2023
d928b05
test: remove varargs and lambda parameter annotations from test "decl…
WinPlay02 Oct 17, 2023
dc72b55
test: remove placeholder saving from test "statements/assignment" and…
WinPlay02 Oct 17, 2023
ce24850
test: convert varargs to list in test "expression/indexed access" and…
WinPlay02 Oct 17, 2023
4502b6b
feat: generate code for lists and maps
WinPlay02 Oct 17, 2023
7e6a2d8
Merge branch 'main' of https://github.com/Safe-DS/DSL into generation
WinPlay02 Oct 17, 2023
3e2eceb
test: use expressions for list and map literals in complex literal test
WinPlay02 Oct 17, 2023
68857e9
chore: refactor swich(true) to if-else, assignments should always con…
WinPlay02 Oct 17, 2023
dea3f89
Merge branch 'main' of https://github.com/Safe-DS/DSL into generation
WinPlay02 Oct 17, 2023
b64e269
fix: member expressions may be undefined in ast, but should be define…
WinPlay02 Oct 17, 2023
6c00c44
chore: removed unneeded type assertions; optimize getting pipelines a…
WinPlay02 Oct 17, 2023
ac6d7cc
chore: reorder functions to be more readable
WinPlay02 Oct 17, 2023
0bbf5fe
fix: Files for tests are now all read first, then diagnostics are run
WinPlay02 Oct 18, 2023
b4cb0a1
Merge branch 'main' of https://github.com/Safe-DS/DSL into generation
WinPlay02 Oct 18, 2023
63f0f66
fix: Linter
WinPlay02 Oct 18, 2023
9644f8d
style: apply automated linter fixes
megalinter-bot Oct 18, 2023
564896a
test: rename gen__skip__context_same_package.py to gen_context_same_p…
WinPlay02 Oct 19, 2023
bcf9fb5
fix: yielded placeholders in block lambdas are now assigned a prefix
WinPlay02 Oct 19, 2023
493f8b5
feat: add wildcard imports
WinPlay02 Oct 19, 2023
d3da299
chore: simplified import generation
WinPlay02 Oct 19, 2023
600d884
test: added test "expression/block lambda result"
WinPlay02 Oct 19, 2023
16c1aa3
test: added test of null-safe enum variant calls to "expression/enum …
WinPlay02 Oct 19, 2023
6bd6075
Merge branch 'main' of https://github.com/Safe-DS/DSL into generation
WinPlay02 Oct 20, 2023
8773e23
chore: adapt to new partial evaluator service
WinPlay02 Oct 20, 2023
5790487
fix: if a template string can be partially evaluated, use the result
WinPlay02 Oct 20, 2023
9d44e8f
chore: don't treat empty parameter lists specially when generating pa…
WinPlay02 Oct 20, 2023
c4146f1
Merge branch 'main' of https://github.com/Safe-DS/DSL into generation
WinPlay02 Oct 21, 2023
497a4dc
chore: ignore unreachable branches in coverage calculation for now
WinPlay02 Oct 21, 2023
a9afb95
style: apply automated linter fixes
megalinter-bot Oct 21, 2023
e1cb682
Merge branch 'main' into generation
lars-reimann Oct 21, 2023
926cdb2
test: remove unneeded code
lars-reimann Oct 21, 2023
a114bd1
fix: remove unneeded safe access to enum variants
lars-reimann Oct 21, 2023
3554d22
test: remove unneeded code
lars-reimann Oct 21, 2023
447bdc1
test: remove unneeded code
lars-reimann Oct 21, 2023
29f057e
feat: unique prefix for block lambda results
lars-reimann Oct 21, 2023
f89751c
feat: reserve prefix `__gen_` instead of `__block_lambda_`
lars-reimann Oct 21, 2023
8eb3881
feat: use new reserved prefix for block lambdas and their results
lars-reimann Oct 21, 2023
173b142
fix: also add prefix to results of segments, so they don't clash with…
lars-reimann Oct 21, 2023
b100049
style: apply automated linter fixes
megalinter-bot Oct 21, 2023
b5b1dbe
test: remove unneeded code
lars-reimann Oct 21, 2023
d1201f9
test: split tests for code generation for lists and maps
lars-reimann Oct 21, 2023
b30d649
docs: remove incorrect comment
lars-reimann Oct 21, 2023
a969226
refactor: shorten method to load documents
lars-reimann Oct 21, 2023
7f2c924
refactor: use the `loadDocuments` method where applicable
lars-reimann Oct 21, 2023
41fc1dd
style: apply automated linter fixes
megalinter-bot Oct 21, 2023
e029e77
chore: minor changes
lars-reimann Oct 21, 2023
28481d4
refactor: use interface for import data
lars-reimann Oct 21, 2023
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
595 changes: 579 additions & 16 deletions src/cli/generator.ts

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions src/language/validation/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,26 @@ import { isInPipelineFile, isInStubFile, isInTestFile } from '../helpers/fileExt
import { declarationIsAllowedInPipelineFile, declarationIsAllowedInStubFile } from './other/modules.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { listBuiltinFiles } from '../builtins/fileFinder.js';
import { CODEGEN_PREFIX } from '../../cli/generator.js';

export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix';
export const CODE_NAME_CODEGEN_PREFIX = 'name/codegen-prefix';
export const CODE_NAME_CASING = 'name/casing';
export const CODE_NAME_DUPLICATE = 'name/duplicate';

// -----------------------------------------------------------------------------
// Block lambda prefix
// Codegen prefix
// -----------------------------------------------------------------------------

export const nameMustNotStartWithBlockLambdaPrefix = (node: SdsDeclaration, accept: ValidationAcceptor) => {
export const nameMustNotStartWithCodegenPrefix = (node: SdsDeclaration, accept: ValidationAcceptor) => {
const name = node.name ?? '';
const blockLambdaPrefix = '__block_lambda_';
if (name.startsWith(blockLambdaPrefix)) {
if (name.startsWith(CODEGEN_PREFIX)) {
accept(
'error',
"Names of declarations must not start with '__block_lambda_'. This is reserved for code generation of block lambdas.",
`Names of declarations must not start with '${CODEGEN_PREFIX}'. This is reserved for code generation.`,
{
node,
property: 'name',
code: CODE_NAME_BLOCK_LAMBDA_PREFIX,
code: CODE_NAME_CODEGEN_PREFIX,
},
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
functionMustContainUniqueNames,
moduleMemberMustHaveNameThatIsUniqueInPackage,
moduleMustContainUniqueNames,
nameMustNotStartWithBlockLambdaPrefix,
nameMustNotStartWithCodegenPrefix,
nameShouldHaveCorrectCasing,
pipelineMustContainUniqueNames,
schemaMustContainUniqueNames,
Expand Down Expand Up @@ -188,7 +188,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsClassBody: [classBodyShouldNotBeEmpty],
SdsConstraintList: [constraintListShouldNotBeEmpty],
SdsDeclaration: [
nameMustNotStartWithBlockLambdaPrefix,
nameMustNotStartWithCodegenPrefix,
nameShouldHaveCorrectCasing,
pythonNameShouldDifferFromSafeDsName(services),
singleUseAnnotationsMustNotBeRepeated(services),
Expand Down
24 changes: 24 additions & 0 deletions tests/helpers/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ export const getErrors = async (services: LangiumServices, code: string): Promis
return diagnostics.filter((d) => d.severity === DiagnosticSeverity.Error);
};

/**
* Get all errors from a loaded document.
*
* @param services The language services.
* @param uri The URI of the document to check.
* @returns The errors.
*/
export const getErrorsAtURI = (services: LangiumServices, uri: URI): Diagnostic[] => {
const diagnostics = getDiagnosticsAtURI(services, uri);
return diagnostics.filter((d) => d.severity === DiagnosticSeverity.Error);
};

/**
* Get all diagnostics from a code snippet.
*
Expand All @@ -57,6 +69,18 @@ const getDiagnostics = async (services: LangiumServices, code: string): Promise<
return document.diagnostics ?? [];
};

/**
* Get all diagnostics from a loaded document.
*
* @param services The language services.
* @param uri The URI of the document to check.
* @returns The diagnostics.
*/
const getDiagnosticsAtURI = (services: LangiumServices, uri: URI): Diagnostic[] => {
const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
return document.diagnostics ?? [];
};

/**
* The code contains syntax errors.
*/
Expand Down
21 changes: 20 additions & 1 deletion tests/helpers/testResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import path from 'path';
import { globSync } from 'glob';
import { SAFE_DS_FILE_EXTENSIONS } from '../../src/language/helpers/fileExtensions.js';
import { group } from 'radash';
import { URI } from 'langium';
import { BuildOptions, LangiumDocument, URI } from 'langium';
import { SafeDsServices } from '../../src/language/safe-ds-module.js';

const TEST_RESOURCES_PATH = path.join(__dirname, '..', 'resources');

Expand Down Expand Up @@ -92,3 +93,21 @@ const isNotSkipped = (pathRelativeToResources: string) => {
const segments = pathRelativeToResources.split(path.sep);
return !segments.some((segment) => segment.startsWith('skip'));
};

/**
* Load the documents at the specified URIs into the workspace managed by the given services.
*
* @param services The language services.
* @param uris The URIs of the documents to load.
* @param options The build options.
* @returns The loaded documents.
*/
export const loadDocuments = async (
services: SafeDsServices,
uris: URI[],
options: BuildOptions = {},
): Promise<LangiumDocument[]> => {
const documents = uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
await services.shared.workspace.DocumentBuilder.build(documents, options);
return documents;
};
10 changes: 7 additions & 3 deletions tests/language/generation/creator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {
listTestPythonFiles,
listTestSafeDsFilesGroupedByParentDirectory,
loadDocuments,
uriToShortenedTestResourceName,
} from '../../helpers/testResources.js';
import path from 'path';
import fs from 'fs';
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
import { ErrorsInCodeError, getErrors } from '../../helpers/diagnostics.js';
import { ErrorsInCodeError, getErrorsAtURI } from '../../helpers/diagnostics.js';
import { findTestChecks } from '../../helpers/testChecks.js';
import { Location } from 'vscode-languageserver';
import { NodeFileSystem } from 'langium/node';
Expand All @@ -31,11 +32,14 @@ const createGenerationTest = async (parentDirectory: URI, inputUris: URI[]): Pro
const expectedOutputFiles = readExpectedOutputFiles(expectedOutputRoot, actualOutputRoot);
let runUntil: Location | undefined;

// Load all files, so they get linked
await loadDocuments(services, inputUris, { validation: true });

for (const uri of inputUris) {
const code = fs.readFileSync(uri.fsPath).toString();
const code = services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri).textDocument.getText();

// File must not contain any errors
const errors = await getErrors(services, code);
const errors = getErrorsAtURI(services, uri);
if (errors.length > 0) {
return invalidTest('FILE', new ErrorsInCodeError(errors, uri));
}
Expand Down
8 changes: 3 additions & 5 deletions tests/language/generation/testGeneration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createGenerationTests } from './creator.js';
import { SdsModule } from '../../../src/language/generated/ast.js';
import { generatePython } from '../../../src/cli/generator.js';
import fs from 'fs';
import { loadDocuments } from '../../helpers/testResources.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;
const generationTests = createGenerationTests();
Expand All @@ -27,18 +28,15 @@ describe('generation', async () => {
}

// Load all documents
const documents = test.inputUris.map((uri) =>
services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri),
);
await services.shared.workspace.DocumentBuilder.build(documents);
const documents = await loadDocuments(services, test.inputUris);

// Generate code for all documents
const actualOutputPaths: string[] = [];

for (const document of documents) {
const module = document.parseResult.value as SdsModule;
const fileName = document.uri.fsPath;
const generatedFilePaths = generatePython(module, fileName, test.actualOutputRoot.fsPath);
const generatedFilePaths = generatePython(services, module, fileName, test.actualOutputRoot.fsPath);
actualOutputPaths.push(...generatedFilePaths);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AssertionError } from 'assert';
import { locationToString } from '../../helpers/location.js';
import { createPartialEvaluationTests } from './creator.js';
import { getNodeByLocation } from '../../helpers/nodeFinder.js';
import { loadDocuments } from '../../helpers/testResources.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;
const partialEvaluator = services.evaluation.PartialEvaluator;
Expand All @@ -22,8 +23,7 @@ describe('partial evaluation', async () => {
}

// Load all documents
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
await services.shared.workspace.DocumentBuilder.build(documents);
await loadDocuments(services, test.uris);

// Ensure that partially evaluating nodes in the same equivalence class yields the same result
for (const equivalenceClassAssertion of test.equivalenceClassAssertions) {
Expand Down
4 changes: 2 additions & 2 deletions tests/language/scoping/testScoping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AssertionError } from 'assert';
import { isLocationEqual, locationToString } from '../../helpers/location.js';
import { createScopingTests, ExpectedReference } from './creator.js';
import { Location } from 'vscode-languageserver';
import { loadDocuments } from '../../helpers/testResources.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;

Expand All @@ -27,8 +28,7 @@ describe('scoping', async () => {
}

// Load all documents
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
await services.shared.workspace.DocumentBuilder.build(documents);
await loadDocuments(services, test.uris);

// Ensure all expected references match
for (const expectedReference of test.expectedReferences) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
listTestSafeDsFilesGroupedByParentDirectory,
uriToShortenedTestResourceName,
} from '../../helpers/testResources.js';
} from '../../../helpers/testResources.js';
import fs from 'fs';
import { findTestChecks } from '../../helpers/testChecks.js';
import { findTestChecks } from '../../../helpers/testChecks.js';
import { Location } from 'vscode-languageserver';
import { getSyntaxErrors, SyntaxErrorsInCodeError } from '../../helpers/diagnostics.js';
import { getSyntaxErrors, SyntaxErrorsInCodeError } from '../../../helpers/diagnostics.js';
import { EmptyFileSystem, URI } from 'langium';
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
import { TestDescription, TestDescriptionError } from '../../helpers/testDescription.js';
import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js';
import { TestDescription, TestDescriptionError } from '../../../helpers/testDescription.js';

const services = createSafeDsServices(EmptyFileSystem).SafeDs;
const rootResourceName = 'typing';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { afterEach, beforeEach, describe, it } from 'vitest';
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js';
import { NodeFileSystem } from 'langium/node';
import { clearDocuments } from 'langium/test';
import { AssertionError } from 'assert';
import { locationToString } from '../../helpers/location.js';
import { locationToString } from '../../../helpers/location.js';
import { createTypingTests } from './creator.js';
import { getNodeByLocation } from '../../helpers/nodeFinder.js';
import { getNodeByLocation } from '../../../helpers/nodeFinder.js';
import { loadDocuments } from '../../../helpers/testResources.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;
const typeComputer = services.types.TypeComputer;
Expand All @@ -27,8 +28,7 @@ describe('typing', async () => {
}

// Load all documents
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
await services.shared.workspace.DocumentBuilder.build(documents);
await loadDocuments(services, test.uris);

// Ensure all nodes in the equivalence class have the same type
for (const equivalenceClassAssertion of test.equivalenceClassAssertions) {
Expand Down
4 changes: 2 additions & 2 deletions tests/language/validation/testValidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
import { AssertionError } from 'assert';
import { clearDocuments, isRangeEqual } from 'langium/test';
import { locationToString } from '../../helpers/location.js';
import { loadDocuments } from '../../helpers/testResources.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;

Expand All @@ -26,8 +27,7 @@ describe('validation', async () => {
}

// Load all documents
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
await services.shared.workspace.DocumentBuilder.build(documents, { validation: true });
await loadDocuments(services, test.uris, { validation: true });

// Ensure all expected issues match
for (const expectedIssue of test.expectedIssues) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package tests.generator.parameterWithPythonName

fun f1(param: (a: Int, b: Int, c: Int) -> r: Int)
fun f2(param: (a: Int, b: Int, c: Int) -> ())

segment test(param1: Int, @PythonName("param_2") param2: Int, @PythonName("param_3") param3: Int = 0) {
f1((param1: Int, param2: Int, param3: Int = 0) -> 1);
f2((param1: Int, param2: Int, param3: Int = 0) {});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Steps ------------------------------------------------------------------------

def test(param1, param_2, param_3=0):
f1(lambda param1, param2, param3=0: 1)
def __gen_block_lambda_0(param1, param2, param3=0):
pass
f2(__gen_block_lambda_0)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ segment test1(a: Int, b: Int = 0) {
f();
}

segment test2(a: Int, vararg c: Int) {
segment test2(a: Int, c: Int) {
f();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
def test1(a, b=0):
f()

def test2(a, *c):
def test2(a, c):
f()
Empty file.
6 changes: 0 additions & 6 deletions tests/resources/generation/dummy/test.sdstest

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tests.generator.blockLambdaResult

fun g() -> a: Int

fun h(a: Int)

segment f1(l: (a: Int, b: Int) -> d: Int) {
h(l(1, 2).d);
}

segment f2(l: (a: Int, b: Int) -> (d1: Int, e1: Int)) {
h(l(1, 2).e1);
h(l(1, 2).d1);
}

pipeline test {

f1((a: Int, b: Int) {
yield d = g();
});
f2((a: Int, b: Int) {
yield d = g();
yield e = g();
});

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Steps ------------------------------------------------------------------------

def f1(l):
h(l(1, 2))

def f2(l):
h(l(1, 2)[1])
h(l(1, 2)[0])

# Pipelines --------------------------------------------------------------------

def test():
def __gen_block_lambda_0(a, b):
__gen_block_lambda_result_d = g()
return __gen_block_lambda_result_d
f1(__gen_block_lambda_0)
def __gen_block_lambda_1(a, b):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_e = g()
return __gen_block_lambda_result_d, __gen_block_lambda_result_e
f2(__gen_block_lambda_1)
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package tests.generator.blockLambda

fun f1(param: (a: Int, b: Int) -> r: Int)
fun f2(param: (a: Int, vararg b: Int) -> r: Int)
fun f3(param: () -> ())
fun f2(param: () -> ())

fun g() -> a: Int

pipeline test {
f1((a: Int, b: Int = 2) {
yield d = g();
});
f2((a: Int, vararg c: Int) {
f1((a: Int, c: Int) {
yield d = g();
});
f3(() {});
f2(() {});
}
Loading