diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoader.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoader.java index 298d07d6..4a35fef8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoader.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoader.java @@ -5,36 +5,50 @@ import p.studio.compiler.pbs.lexer.PbsLexer; import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator; import p.studio.compiler.pbs.parser.PbsBarrelParser; import p.studio.compiler.pbs.parser.PbsParser; +import p.studio.compiler.source.tables.FileTable; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.identifiers.ProjectId; +import p.studio.compiler.models.SourceHandle; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; public final class InterfaceModuleLoader { - private final AtomicInteger nextSyntheticFileId; - public InterfaceModuleLoader() { - this(1_000_000); } - public InterfaceModuleLoader(final int firstSyntheticFileId) { - this.nextSyntheticFileId = new AtomicInteger(firstSyntheticFileId); + public InterfaceModuleLoader(final int ignoredSyntheticSeed) { + this(); } public PbsModuleVisibilityValidator.ModuleUnit load( final StdlibModuleSource moduleSource, + final ProjectId syntheticOwnerProjectId, + final FileTable fileTable, final DiagnosticSink diagnostics) { final var sources = new ArrayList(moduleSource.sourceFiles().size()); - for (final var sourceFile : moduleSource.sourceFiles()) { - final var fileId = new FileId(nextSyntheticFileId.getAndIncrement()); + for (int i = 0; i < moduleSource.sourceFiles().size(); i++) { + final var sourceFile = moduleSource.sourceFiles().get(i); + final var fileId = registerSyntheticFile( + syntheticOwnerProjectId, + moduleSource, + sourceFile.logicalPath(), + sourceFile.source(), + fileTable); final var sourceAst = parseSource(sourceFile.source(), fileId, diagnostics); sources.add(new PbsModuleVisibilityValidator.SourceFile(fileId, sourceAst)); } final var barrels = new ArrayList(1); - final var barrelFileId = new FileId(nextSyntheticFileId.getAndIncrement()); + final var barrelFileId = registerSyntheticFile( + syntheticOwnerProjectId, + moduleSource, + "mod.barrel", + moduleSource.barrelSource(), + fileTable); final var barrelAst = parseBarrel(moduleSource.barrelSource(), barrelFileId, diagnostics); barrels.add(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst)); @@ -44,6 +58,34 @@ public final class InterfaceModuleLoader { ReadOnlyList.wrap(barrels)); } + private FileId registerSyntheticFile( + final ProjectId syntheticOwnerProjectId, + final StdlibModuleSource moduleSource, + final String logicalPath, + final String source, + final FileTable fileTable) { + final var normalizedLogicalPath = (logicalPath == null || logicalPath.isBlank()) + ? "source.pbs" + : logicalPath; + final var relativePath = Path.of("@stdlib") + .resolve(moduleSource.project()) + .resolve(String.join("/", moduleSource.pathSegments().asList())) + .resolve(normalizedLogicalPath); + final var canonPath = Path.of("/virtual/stdlib") + .resolve(moduleSource.project()) + .resolve(String.join("/", moduleSource.pathSegments().asList())) + .resolve(normalizedLogicalPath); + final var utf8 = source == null ? new byte[0] : source.getBytes(StandardCharsets.UTF_8); + final var sourceHandle = new SourceHandle( + syntheticOwnerProjectId, + relativePath, + canonPath, + utf8.length, + 0, + ignored -> () -> utf8); + return fileTable.register(sourceHandle); + } + private PbsAst.File parseSource( final String source, final FileId fileId, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java index b5fc23ff..038c61b8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java @@ -22,6 +22,8 @@ import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.DiagnosticPhase; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.ModuleId; +import p.studio.compiler.source.identifiers.ProjectId; +import p.studio.compiler.source.tables.FileTable; import p.studio.compiler.source.tables.ModuleReference; import p.studio.compiler.source.tables.ModuleTable; import p.studio.utilities.structures.ReadOnlyList; @@ -62,17 +64,25 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { final LogAggregator logs, final BuildingIssueSink issues) { final var nameTable = ctx.nameTable(); + if (!(ctx.fileTable instanceof FileTable writableFileTable)) { + throw new IllegalStateException("PBS frontend requires writable FileTable for synthetic stdlib sources"); + } final var irBackendAggregator = IRBackend.aggregator(); final var parsedSourceFiles = new ArrayList(); final Map modulesByCoordinates = new LinkedHashMap<>(); final var moduleTable = new ModuleTable(); final var moduleIdByFile = new HashMap(); final var failedModuleIds = new HashSet(); + final var projectIdByName = new HashMap(); + final var defaultSyntheticOwnerProjectId = ctx.stack.reverseTopologicalOrder.isEmpty() + ? null + : ctx.stack.reverseTopologicalOrder.getFirst(); for (final var pId : ctx.stack.reverseTopologicalOrder) { final var projectDescriptor = ctx.projectTable.get(pId); final var projectSourceKind = ctx.sourceKind(pId); final var fileIds = ctx.fileTable.getFiles(pId); + projectIdByName.putIfAbsent(projectDescriptor.getName(), pId); for (final var fId : fileIds) { final var sourceHandle = ctx.fileTable.get(fId); @@ -126,6 +136,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { parsedSourceFiles, moduleIdByFile, moduleTable, + writableFileTable, + projectIdByName, + defaultSyntheticOwnerProjectId, diagnostics, ctx.stdlibVersion()); @@ -181,6 +194,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { final ArrayList parsedSourceFiles, final Map moduleIdByFile, final ModuleTable moduleTable, + final FileTable fileTable, + final Map projectIdByName, + final ProjectId defaultSyntheticOwnerProjectId, final DiagnosticSink diagnostics, final int stdlibVersion) { final var stdlibEnvironment = stdlibEnvironmentResolver.resolve(stdlibVersion); @@ -203,7 +219,15 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { continue; } - final var loadedModule = interfaceModuleLoader.load(moduleSource.get(), diagnostics); + final var syntheticOwnerProjectId = projectIdByName.getOrDefault(target.project(), defaultSyntheticOwnerProjectId); + if (syntheticOwnerProjectId == null) { + continue; + } + final var loadedModule = interfaceModuleLoader.load( + moduleSource.get(), + syntheticOwnerProjectId, + fileTable, + diagnostics); final var moduleData = new MutableModuleUnit(); moduleData.sources.addAll(loadedModule.sourceFiles().asList()); moduleData.barrels.addAll(loadedModule.barrelFiles().asList()); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java new file mode 100644 index 00000000..2aab4962 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java @@ -0,0 +1,53 @@ +package p.studio.compiler.pbs.stdlib; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.models.ProjectDescriptor; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.tables.FileTable; +import p.studio.compiler.source.tables.ProjectTable; +import p.studio.utilities.structures.ReadOnlyList; + +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class InterfaceModuleLoaderTest { + + @Test + void shouldRegisterSyntheticStdlibFilesInFileTable() { + final var projectTable = new ProjectTable(); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .name("app") + .version("1.0.0") + .rootPath(Path.of("/tmp/app")) + .sourceRoots(ReadOnlyList.wrap(java.util.List.of(Path.of("/tmp/app/src")))) + .build()); + final var fileTable = new FileTable(1); + final var diagnostics = DiagnosticSink.empty(); + final var moduleSource = new StdlibModuleSource( + "sdk", + ReadOnlyList.wrap(java.util.List.of("gfx")), + ReadOnlyList.wrap(java.util.List.of(new StdlibModuleSource.SourceFile( + "source.pbs", + "declare host Gfx { fn draw() -> void; }"))), + "pub host Gfx;"); + + final var module = new InterfaceModuleLoader().load(moduleSource, projectId, fileTable, diagnostics); + + assertTrue(diagnostics.isEmpty()); + assertEquals(1, module.sourceFiles().size()); + assertEquals(1, module.barrelFiles().size()); + assertFalse(module.sourceFiles().getFirst().fileId().isNone()); + assertFalse(module.barrelFiles().getFirst().fileId().isNone()); + assertTrue(fileTable.get(module.sourceFiles().getFirst().fileId()).readUtf8().isPresent()); + assertTrue(fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().isPresent()); + assertEquals( + "declare host Gfx { fn draw() -> void; }", + fileTable.get(module.sourceFiles().getFirst().fileId()).readUtf8().orElseThrow()); + assertEquals( + "pub host Gfx;", + fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow()); + } +}