diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index 967e80d3..3e73e4f3 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -3,7 +3,11 @@ package p.studio.compiler.pbs; import p.studio.compiler.messages.HostAdmissionContext; import p.studio.compiler.models.IRBackendFile; import p.studio.compiler.models.IRFunction; +import p.studio.compiler.models.IRGlobal; import p.studio.compiler.models.IRReservedMetadata; +import p.studio.compiler.models.IRSyntheticCallableKind; +import p.studio.compiler.models.IRSyntheticFunction; +import p.studio.compiler.models.IRSyntheticOrigin; import p.studio.compiler.models.SourceKind; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.lexer.PbsLexer; @@ -149,6 +153,12 @@ public final class PbsFrontendCompiler { final ReadOnlyList functions = sourceKind == SourceKind.SDK_INTERFACE ? ReadOnlyList.empty() : lowerFunctions(fileId, ast); + final ReadOnlyList syntheticFunctions = sourceKind == SourceKind.SDK_INTERFACE + ? ReadOnlyList.empty() + : lowerSyntheticFunctions(fileId, effectiveModuleId, ast); + final ReadOnlyList globals = sourceKind == SourceKind.SDK_INTERFACE + ? ReadOnlyList.empty() + : lowerGlobals(fileId, effectiveModuleId, ast); final var loweringErrorBaseline = diagnostics.errorCount(); final var executableLowering = executableLoweringService.lower( fileId, @@ -165,6 +175,8 @@ public final class PbsFrontendCompiler { return new IRBackendFile( fileId, functions, + syntheticFunctions, + globals, executableLowering.executableFunctions(), reservedMetadata, effectiveModulePool, @@ -210,6 +222,78 @@ public final class PbsFrontendCompiler { return ReadOnlyList.wrap(functions); } + private ReadOnlyList lowerGlobals( + final FileId fileId, + final ModuleId moduleId, + final PbsAst.File ast) { + final var globals = new ArrayList(); + for (final var topDecl : ast.topDecls()) { + if (!(topDecl instanceof PbsAst.GlobalDecl globalDecl)) { + continue; + } + globals.add(new IRGlobal( + fileId, + moduleId, + globalDecl.name(), + PbsReservedMetadataExtractor.typeSurfaceKey(globalDecl.explicitType()), + globalDecl.span())); + } + return ReadOnlyList.wrap(globals); + } + + private ReadOnlyList lowerSyntheticFunctions( + final FileId fileId, + final ModuleId moduleId, + final PbsAst.File ast) { + final var syntheticFunctions = new ArrayList(); + PbsAst.FunctionDecl initDecl = null; + PbsAst.FunctionDecl frameDecl = null; + PbsAst.GlobalDecl firstGlobalDecl = null; + for (final var topDecl : ast.topDecls()) { + if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { + if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.INIT && initDecl == null) { + initDecl = functionDecl; + } + if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.FRAME && frameDecl == null) { + frameDecl = functionDecl; + } + } + if (topDecl instanceof PbsAst.GlobalDecl globalDecl && firstGlobalDecl == null) { + firstGlobalDecl = globalDecl; + } + } + + final var fileInitAnchorSpan = initDecl != null ? initDecl.span() : firstGlobalDecl == null ? null : firstGlobalDecl.span(); + final var fileInitAnchorName = initDecl != null ? initDecl.name() : firstGlobalDecl == null ? "" : firstGlobalDecl.name(); + if (fileInitAnchorSpan != null) { + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + "__pbs.file_init", + IRSyntheticCallableKind.FILE_INIT_FRAGMENT, + new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan))); + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + "__pbs.module_init", + IRSyntheticCallableKind.MODULE_INIT, + new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan))); + } + if (initDecl != null && frameDecl != null) { + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + "__pbs.project_init", + IRSyntheticCallableKind.PROJECT_INIT, + new IRSyntheticOrigin(fileId, initDecl.name(), initDecl.span()))); + } + if (frameDecl != null) { + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + "__pbs.frame_wrapper", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(fileId, frameDecl.name(), frameDecl.span()))); + } + return ReadOnlyList.wrap(syntheticFunctions); + } + private IRReservedMetadata mergeReservedMetadata( final IRReservedMetadata primary, final IRReservedMetadata imported) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java index e2351072..07cdb0b5 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java @@ -2,6 +2,7 @@ package p.studio.compiler.pbs; import org.junit.jupiter.api.Test; import p.studio.compiler.messages.HostAdmissionContext; +import p.studio.compiler.models.IRSyntheticCallableKind; import p.studio.compiler.models.SourceKind; import p.studio.compiler.pbs.lexer.LexErrors; import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; @@ -40,6 +41,33 @@ class PbsFrontendCompilerTest { assertEquals(1, functions.get(1).parameterCount()); } + @Test + void shouldExposeGlobalsAndSyntheticLifecycleArtifactsInBackendFile() { + final var source = """ + declare global SCORE: int = 0; + + [Init] + fn boot() -> void { return; } + + [Frame] + fn frame() -> void { return; } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile(new FileId(1), source, diagnostics); + + assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); + assertEquals(1, fileBackend.globals().size()); + assertEquals("SCORE", fileBackend.globals().getFirst().name()); + assertEquals(4, fileBackend.syntheticFunctions().size()); + assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.FILE_INIT_FRAGMENT)); + assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.MODULE_INIT)); + assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.PROJECT_INIT)); + assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER)); + assertTrue(fileBackend.syntheticFunctions().stream().allMatch(fn -> !fn.origin().anchorCallableName().isBlank())); + } + @Test void shouldReportDuplicateFunctionNames() { final var source = """ diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java index 0e588943..e08dd259 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java @@ -21,6 +21,10 @@ public class IRBackend { @Builder.Default private final ReadOnlyList functions = ReadOnlyList.empty(); @Builder.Default + private final ReadOnlyList syntheticFunctions = ReadOnlyList.empty(); + @Builder.Default + private final ReadOnlyList globals = ReadOnlyList.empty(); + @Builder.Default private final ReadOnlyList executableFunctions = ReadOnlyList.empty(); @Builder.Default private final ReadOnlyList modulePool = ReadOnlyList.empty(); @@ -39,6 +43,8 @@ public class IRBackend { private String entryPointCallableName; private ModuleId entryPointModuleId = ModuleId.none(); private final ArrayList functions = new ArrayList<>(); + private final ArrayList syntheticFunctions = new ArrayList<>(); + private final ArrayList globals = new ArrayList<>(); private final ArrayList executableFunctions = new ArrayList<>(); private final ModuleTable moduleTable = new ModuleTable(); private final CallableTable callableTable = new CallableTable(); @@ -54,6 +60,21 @@ public class IRBackend { } functions.addAll(backendFile.functions().asList()); final var moduleRemap = reindexModules(backendFile.modulePool()); + for (final var syntheticFunction : backendFile.syntheticFunctions()) { + syntheticFunctions.add(new IRSyntheticFunction( + remapModuleId(syntheticFunction.moduleId(), moduleRemap, "synthetic function"), + syntheticFunction.callableName(), + syntheticFunction.kind(), + syntheticFunction.origin())); + } + for (final var global : backendFile.globals()) { + globals.add(new IRGlobal( + global.fileId(), + remapModuleId(global.moduleId(), moduleRemap, "global"), + global.name(), + global.declaredTypeSurface(), + global.span())); + } final var callableRemap = reindexCallables(backendFile.callableSignatures(), moduleRemap); final var intrinsicRemap = reindexIntrinsics(backendFile.intrinsicPool()); for (final var function : backendFile.executableFunctions()) { @@ -248,6 +269,8 @@ public class IRBackend { .entryPointCallableName(resolveEntryPointCallableName()) .entryPointModuleId(resolveEntryPointModuleId()) .functions(ReadOnlyList.wrap(functions)) + .syntheticFunctions(ReadOnlyList.wrap(syntheticFunctions)) + .globals(ReadOnlyList.wrap(globals)) .executableFunctions(ReadOnlyList.wrap(executableFunctions)) .modulePool(emitModulePool()) .callableSignatures(emitCallableSignatures()) @@ -278,6 +301,8 @@ public class IRBackend { .append('@') .append(entryPointModuleId == null || entryPointModuleId.isNone() ? "-" : entryPointModuleId.getIndex()) .append(", functions=").append(functions.size()) + .append(", syntheticFunctions=").append(syntheticFunctions.size()) + .append(", globals=").append(globals.size()) .append(", executableFunctions=").append(executableFunctions.size()) .append(", modulePool=").append(modulePool.size()) .append(", callableSignatures=").append(callableSignatures.size()) diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java index f32d72a9..dc8084e1 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java @@ -11,6 +11,8 @@ import java.util.Objects; public record IRBackendFile( FileId fileId, ReadOnlyList functions, + ReadOnlyList syntheticFunctions, + ReadOnlyList globals, ReadOnlyList executableFunctions, IRReservedMetadata reservedMetadata, ReadOnlyList modulePool, @@ -19,6 +21,8 @@ public record IRBackendFile( public IRBackendFile { fileId = Objects.requireNonNull(fileId, "fileId"); functions = functions == null ? ReadOnlyList.empty() : functions; + syntheticFunctions = syntheticFunctions == null ? ReadOnlyList.empty() : syntheticFunctions; + globals = globals == null ? ReadOnlyList.empty() : globals; executableFunctions = executableFunctions == null ? ReadOnlyList.empty() : executableFunctions; reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata; modulePool = modulePool == null ? ReadOnlyList.empty() : modulePool; @@ -33,6 +37,8 @@ public record IRBackendFile( fileId, functions, ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty(), ReadOnlyList.empty(), @@ -44,6 +50,8 @@ public record IRBackendFile( fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty(), ReadOnlyList.empty(), @@ -58,6 +66,8 @@ public record IRBackendFile( fileId, functions, ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty(), reservedMetadata, ReadOnlyList.empty(), ReadOnlyList.empty(), @@ -74,10 +84,32 @@ public record IRBackendFile( this( fileId, functions, + ReadOnlyList.empty(), + ReadOnlyList.empty(), executableFunctions, reservedMetadata, ReadOnlyList.empty(), callableSignatures, intrinsicPool); } + + public IRBackendFile( + final FileId fileId, + final ReadOnlyList functions, + final ReadOnlyList executableFunctions, + final IRReservedMetadata reservedMetadata, + final ReadOnlyList modulePool, + final ReadOnlyList callableSignatures, + final ReadOnlyList intrinsicPool) { + this( + fileId, + functions, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + executableFunctions, + reservedMetadata, + modulePool, + callableSignatures, + intrinsicPool); + } } diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRGlobal.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRGlobal.java new file mode 100644 index 00000000..8071fab1 --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRGlobal.java @@ -0,0 +1,23 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.Span; +import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.identifiers.ModuleId; + +import java.util.Objects; + +public record IRGlobal( + FileId fileId, + ModuleId moduleId, + String name, + String declaredTypeSurface, + Span span) { + public IRGlobal { + fileId = Objects.requireNonNull(fileId, "fileId"); + moduleId = moduleId == null ? ModuleId.none() : moduleId; + name = Objects.requireNonNull(name, "name"); + declaredTypeSurface = declaredTypeSurface == null ? "" : declaredTypeSurface; + span = span == null ? Span.none() : span; + } +} + diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticCallableKind.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticCallableKind.java new file mode 100644 index 00000000..168dab75 --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticCallableKind.java @@ -0,0 +1,9 @@ +package p.studio.compiler.models; + +public enum IRSyntheticCallableKind { + FILE_INIT_FRAGMENT, + MODULE_INIT, + PROJECT_INIT, + PUBLISHED_FRAME_WRAPPER +} + diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticFunction.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticFunction.java new file mode 100644 index 00000000..d8c0ca7d --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticFunction.java @@ -0,0 +1,19 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.identifiers.ModuleId; + +import java.util.Objects; + +public record IRSyntheticFunction( + ModuleId moduleId, + String callableName, + IRSyntheticCallableKind kind, + IRSyntheticOrigin origin) { + public IRSyntheticFunction { + moduleId = moduleId == null ? ModuleId.none() : moduleId; + callableName = Objects.requireNonNull(callableName, "callableName"); + kind = Objects.requireNonNull(kind, "kind"); + origin = Objects.requireNonNull(origin, "origin"); + } +} + diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticOrigin.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticOrigin.java new file mode 100644 index 00000000..c5cb5ecc --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRSyntheticOrigin.java @@ -0,0 +1,18 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.Span; +import p.studio.compiler.source.identifiers.FileId; + +import java.util.Objects; + +public record IRSyntheticOrigin( + FileId fileId, + String anchorCallableName, + Span span) { + public IRSyntheticOrigin { + fileId = Objects.requireNonNull(fileId, "fileId"); + anchorCallableName = anchorCallableName == null ? "" : anchorCallableName; + span = span == null ? Span.none() : span; + } +} +