implements PR020
This commit is contained in:
parent
eb469fd68c
commit
d2287a0a58
@ -21,17 +21,26 @@ public final class PbsFrontendCompiler {
|
||||
final FileId fileId,
|
||||
final String source,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var admissionBaseline = diagnostics.errorCount();
|
||||
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||
final var ast = PbsParser.parse(tokens, fileId, diagnostics);
|
||||
return compileParsedFile(fileId, ast, diagnostics);
|
||||
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics);
|
||||
if (diagnostics.errorCount() > admissionBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
}
|
||||
return irBackendFile;
|
||||
}
|
||||
|
||||
public IRBackendFile compileParsedFile(
|
||||
final FileId fileId,
|
||||
final PbsAst.File ast,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var semanticsErrorBaseline = diagnostics.errorCount();
|
||||
declarationSemanticsValidator.validate(ast, diagnostics);
|
||||
flowSemanticsValidator.validate(ast, diagnostics);
|
||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
}
|
||||
return new IRBackendFile(fileId, lowerFunctions(fileId, ast));
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import p.studio.compiler.pbs.parser.PbsBarrelParser;
|
||||
import p.studio.compiler.pbs.parser.PbsParser;
|
||||
import p.studio.compiler.source.Span;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
@ -22,8 +23,11 @@ import p.studio.utilities.logs.LogAggregator;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
@ -39,6 +43,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
final var irBackendAggregator = IRBackend.aggregator();
|
||||
final var parsedSourceFiles = new ArrayList<ParsedSourceFile>();
|
||||
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates = new LinkedHashMap<>();
|
||||
final var moduleKeyByFile = new HashMap<FileId, String>();
|
||||
final var failedModuleKeys = new HashSet<String>();
|
||||
|
||||
for (final var pId : ctx.stack.reverseTopologicalOrder) {
|
||||
final var projectDescriptor = ctx.projectTable.get(pId);
|
||||
@ -49,24 +55,36 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
sourceHandle.readUtf8().ifPresentOrElse(
|
||||
utf8Content -> {
|
||||
final var coordinates = resolveModuleCoordinates(projectDescriptor, sourceHandle);
|
||||
final var moduleKey = moduleKey(coordinates);
|
||||
final var moduleUnit = modulesByCoordinates.computeIfAbsent(
|
||||
coordinates,
|
||||
ignored -> new MutableModuleUnit());
|
||||
switch (sourceHandle.getExtension()) {
|
||||
case "pbs" -> {
|
||||
moduleKeyByFile.put(fId, moduleKey);
|
||||
final var parseErrorBaseline = diagnostics.errorCount();
|
||||
final var ast = parseSourceFile(fId, utf8Content, diagnostics);
|
||||
moduleUnit.sources.add(new PbsModuleVisibilityValidator.SourceFile(fId, ast));
|
||||
parsedSourceFiles.add(new ParsedSourceFile(fId, ast));
|
||||
parsedSourceFiles.add(new ParsedSourceFile(fId, ast, moduleKey));
|
||||
if (diagnostics.errorCount() > parseErrorBaseline) {
|
||||
failedModuleKeys.add(moduleKey);
|
||||
}
|
||||
}
|
||||
case "barrel" -> {
|
||||
moduleKeyByFile.put(fId, moduleKey);
|
||||
if ("mod.barrel".equals(sourceHandle.getFilename())) {
|
||||
final var parseErrorBaseline = diagnostics.errorCount();
|
||||
final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics);
|
||||
moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst));
|
||||
if (diagnostics.errorCount() > parseErrorBaseline) {
|
||||
failedModuleKeys.add(moduleKey);
|
||||
}
|
||||
} else {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name(),
|
||||
"Only 'mod.barrel' is allowed as barrel filename",
|
||||
new Span(fId, 0, utf8Content.getBytes(StandardCharsets.UTF_8).length));
|
||||
failedModuleKeys.add(moduleKey);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
@ -89,8 +107,12 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
ReadOnlyList.wrap(moduleUnit.barrels)));
|
||||
}
|
||||
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), diagnostics);
|
||||
markModulesWithLinkingErrors(diagnostics, moduleKeyByFile, failedModuleKeys);
|
||||
|
||||
for (final var parsedSource : parsedSourceFiles) {
|
||||
if (failedModuleKeys.contains(parsedSource.moduleKey())) {
|
||||
continue;
|
||||
}
|
||||
final var irBackendFile = frontendCompiler.compileParsedFile(parsedSource.fileId(), parsedSource.ast(), diagnostics);
|
||||
irBackendAggregator.merge(irBackendFile);
|
||||
}
|
||||
@ -145,6 +167,28 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
return sourceHandle.getRelativePath();
|
||||
}
|
||||
|
||||
private void markModulesWithLinkingErrors(
|
||||
final DiagnosticSink diagnostics,
|
||||
final Map<FileId, String> moduleKeyByFile,
|
||||
final Set<String> failedModuleKeys) {
|
||||
for (final var diagnostic : diagnostics) {
|
||||
if (!diagnostic.getSeverity().isError()) {
|
||||
continue;
|
||||
}
|
||||
if (diagnostic.getPhase() != DiagnosticPhase.LINKING) {
|
||||
continue;
|
||||
}
|
||||
final var moduleKey = moduleKeyByFile.get(diagnostic.getSpan().getFileId());
|
||||
if (moduleKey != null) {
|
||||
failedModuleKeys.add(moduleKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String moduleKey(final PbsModuleVisibilityValidator.ModuleCoordinates coordinates) {
|
||||
return coordinates.project() + ":" + String.join("/", coordinates.pathSegments().asList());
|
||||
}
|
||||
|
||||
private static final class MutableModuleUnit {
|
||||
private final ArrayList<PbsModuleVisibilityValidator.SourceFile> sources = new ArrayList<>();
|
||||
private final ArrayList<PbsModuleVisibilityValidator.BarrelFile> barrels = new ArrayList<>();
|
||||
@ -152,6 +196,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
|
||||
private record ParsedSourceFile(
|
||||
FileId fileId,
|
||||
PbsAst.File ast) {
|
||||
PbsAst.File ast,
|
||||
String moduleKey) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package p.studio.compiler.pbs;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.pbs.lexer.LexErrors;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
@ -44,10 +45,11 @@ class PbsFrontendCompilerTest {
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
compiler.compileFile(new FileId(0), source, diagnostics);
|
||||
final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name())));
|
||||
assertEquals(0, fileBackend.functions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -59,9 +61,42 @@ class PbsFrontendCompilerTest {
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
compiler.compileFile(new FileId(0), source, diagnostics);
|
||||
final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name())));
|
||||
assertEquals(2, fileBackend.functions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotLowerWhenSyntaxErrorsExist() {
|
||||
final var source = """
|
||||
$
|
||||
fn run() -> int { return 1; }
|
||||
""";
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(LexErrors.E_LEX_INVALID_CHAR.name())));
|
||||
assertEquals(0, fileBackend.functions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotLowerWhenStaticSemanticsErrorsExist() {
|
||||
final var source = """
|
||||
fn run(x: int) -> int { return x; }
|
||||
fn run(y: int) -> int { return y; }
|
||||
""";
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name())));
|
||||
assertEquals(0, fileBackend.functions().size());
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PBSFrontendPhaseServiceTest {
|
||||
@ -61,13 +62,115 @@ class PBSFrontendPhaseServiceTest {
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PBSFrontendPhaseService().compile(
|
||||
final var irBackend = new PBSFrontendPhaseService().compile(
|
||||
ctx,
|
||||
diagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name())));
|
||||
assertEquals(0, irBackend.getFunctions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLowerOnlyModulesThatPassAdmissionGates() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-mixed");
|
||||
final var sourceRoot = projectRoot.resolve("src");
|
||||
final var validModulePath = sourceRoot.resolve("valid");
|
||||
final var invalidModulePath = sourceRoot.resolve("invalid");
|
||||
Files.createDirectories(validModulePath);
|
||||
Files.createDirectories(invalidModulePath);
|
||||
|
||||
final var validSource = validModulePath.resolve("source.pbs");
|
||||
final var validBarrel = validModulePath.resolve("mod.barrel");
|
||||
final var invalidSource = invalidModulePath.resolve("source.pbs");
|
||||
Files.writeString(validSource, """
|
||||
fn good(v: int) -> int {
|
||||
return v;
|
||||
}
|
||||
""");
|
||||
Files.writeString(validBarrel, "pub fn good(v: int) -> int;");
|
||||
Files.writeString(invalidSource, """
|
||||
fn bad(v: int) -> int {
|
||||
return v;
|
||||
}
|
||||
""");
|
||||
|
||||
final var projectTable = new ProjectTable();
|
||||
final var fileTable = new FileTable(1);
|
||||
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(projectRoot)
|
||||
.name("core")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||
.build());
|
||||
|
||||
registerFile(projectId, projectRoot, validSource, fileTable);
|
||||
registerFile(projectId, projectRoot, validBarrel, fileTable);
|
||||
registerFile(projectId, projectRoot, invalidSource, fileTable);
|
||||
|
||||
final var ctx = new FrontendPhaseContext(
|
||||
projectTable,
|
||||
fileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
final var irBackend = new PBSFrontendPhaseService().compile(
|
||||
ctx,
|
||||
diagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsLinkErrors.E_LINK_MISSING_BARREL.name())));
|
||||
assertEquals(1, irBackend.getFunctions().size());
|
||||
assertEquals("good", irBackend.getFunctions().getFirst().name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLowerAllSourcesWhenNoAdmissionErrorsExist() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-valid");
|
||||
final var sourceRoot = projectRoot.resolve("src");
|
||||
final var modulePath = sourceRoot.resolve("math");
|
||||
Files.createDirectories(modulePath);
|
||||
|
||||
final var sourceFile = modulePath.resolve("source.pbs");
|
||||
final var modBarrel = modulePath.resolve("mod.barrel");
|
||||
Files.writeString(sourceFile, """
|
||||
fn run() -> int { return 1; }
|
||||
fn sum(a: int, b: int) -> int { return a + b; }
|
||||
""");
|
||||
Files.writeString(modBarrel, """
|
||||
pub fn run() -> int;
|
||||
pub fn sum(a: int, b: int) -> int;
|
||||
""");
|
||||
|
||||
final var projectTable = new ProjectTable();
|
||||
final var fileTable = new FileTable(1);
|
||||
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(projectRoot)
|
||||
.name("core")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||
.build());
|
||||
|
||||
registerFile(projectId, projectRoot, sourceFile, fileTable);
|
||||
registerFile(projectId, projectRoot, modBarrel, fileTable);
|
||||
|
||||
final var ctx = new FrontendPhaseContext(
|
||||
projectTable,
|
||||
fileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
final var irBackend = new PBSFrontendPhaseService().compile(
|
||||
ctx,
|
||||
diagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertTrue(diagnostics.isEmpty());
|
||||
assertEquals(2, irBackend.getFunctions().size());
|
||||
}
|
||||
|
||||
private void registerFile(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user