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 6c58b5d3..018f5619 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 @@ -152,9 +152,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { entryPointModuleId = wrapperNamesByModule.keySet().iterator().next(); entryPointCallableName = wrapperNamesByModule.get(entryPointModuleId); } else { - final var legacyEntrypoint = resolveLegacyEntrypoint(baseBackend); - entryPointModuleId = legacyEntrypoint.moduleId(); - entryPointCallableName = legacyEntrypoint.callableName(); + entryPointModuleId = baseBackend.getEntryPointModuleId(); + entryPointCallableName = baseBackend.getEntryPointCallableName(); } final var sortedModuleIds = new ArrayList(); @@ -397,19 +396,6 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { return (int) value; } - private LegacyEntrypoint resolveLegacyEntrypoint(final IRBackend backend) { - for (final var candidateName : List.of("frame", "main")) { - final var candidates = backend.getExecutableFunctions().stream() - .filter(function -> candidateName.equals(function.callableName())) - .filter(function -> function.moduleId() != null && !function.moduleId().isNone()) - .toList(); - if (candidates.size() == 1) { - return new LegacyEntrypoint(candidates.getFirst().moduleId(), candidateName); - } - } - return new LegacyEntrypoint(backend.getEntryPointModuleId(), backend.getEntryPointCallableName()); - } - private Set blockedModulesByDependency( final Set failedModuleIds, final Map> dependenciesByModule) { @@ -440,9 +426,4 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { ModuleId moduleId, p.studio.compiler.models.IRBackendFile irBackendFile) { } - - private record LegacyEntrypoint( - ModuleId moduleId, - String callableName) { - } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PbsProjectLifecycleSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PbsProjectLifecycleSemanticsValidator.java index 60a388a1..f75ded0f 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PbsProjectLifecycleSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PbsProjectLifecycleSemanticsValidator.java @@ -13,34 +13,31 @@ final class PbsProjectLifecycleSemanticsValidator { final java.util.List parsedSourceFiles, final DiagnosticSink diagnostics) { final var frames = new ArrayList(); - var sawLifecycleMarker = false; + var sawExecutableSource = false; for (final var parsedSource : parsedSourceFiles) { if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) { continue; } + sawExecutableSource = true; for (final var topDecl : parsedSource.ast().topDecls()) { if (!(topDecl instanceof PbsAst.FunctionDecl functionDecl)) { continue; } - if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.NONE) { - continue; - } - sawLifecycleMarker = true; if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.FRAME) { frames.add(functionDecl); } } } - if (!sawLifecycleMarker) { + if (!sawExecutableSource) { return; } if (frames.isEmpty()) { p.studio.compiler.source.diagnostics.Diagnostics.error( diagnostics, PbsSemanticsErrors.E_SEM_MISSING_PROJECT_FRAME.name(), - "Lifecycle-marked executable sources must declare exactly one [Frame] function", + "Executable projects must declare exactly one [Frame] function", parsedSourceFiles.getFirst().ast().span()); return; } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java index d225c0c4..d3912507 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java @@ -162,8 +162,17 @@ class PBSFrontendPhaseServiceTest { fn caller() -> int { return target(); } + + [Frame] + fn frame() -> void { + caller(); + return; + } + """); + Files.writeString(barrelB, """ + pub fn caller() -> int; + pub fn frame() -> void; """); - Files.writeString(barrelB, "pub fn caller() -> int;"); final var projectTable = new ProjectTable(); final var fileTable = new FileTable(1); @@ -221,11 +230,12 @@ class PBSFrontendPhaseServiceTest { final var sourceFile = modulePath.resolve("source.pbs"); final var modBarrel = modulePath.resolve("mod.barrel"); Files.writeString(sourceFile, """ - fn run() -> int { return 1; } + [Frame] + fn frame() -> void { return; } fn sum(a: int, b: int) -> int { return a + b; } """); Files.writeString(modBarrel, """ - pub fn run() -> int; + pub fn frame() -> void; pub fn sum(a: int, b: int) -> int; """); @@ -255,7 +265,51 @@ class PBSFrontendPhaseServiceTest { assertTrue(diagnostics.isEmpty()); assertEquals(2, irBackend.getFunctions().size()); - assertEquals(2, irBackend.getExecutableFunctions().size()); + assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName()))); + assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> + function.callableName().startsWith("__pbs.frame_wrapper$m"))); + } + + @Test + void shouldRequireExplicitFrameMarkerForExecutableProjects() throws IOException { + final var projectRoot = tempDir.resolve("project-missing-frame-marker"); + final var sourceRoot = projectRoot.resolve("src"); + Files.createDirectories(sourceRoot); + + final var sourceFile = sourceRoot.resolve("main.pbs"); + final var modBarrel = sourceRoot.resolve("mod.barrel"); + Files.writeString(sourceFile, """ + fn frame() -> void { return; } + """); + Files.writeString(modBarrel, "pub fn frame() -> void;"); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("main") + .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(); + + new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_MISSING_PROJECT_FRAME.name())), + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); } @Test @@ -720,6 +774,7 @@ class PBSFrontendPhaseServiceTest { Files.writeString(sourceFile, """ import { Gfx } from @sdk:gfx; + [Frame] fn frame() -> void { Gfx.clear_565(6577); @@ -769,12 +824,22 @@ class PBSFrontendPhaseServiceTest { Files.writeString(sourceFile, """ import { Gfx } from @sdk:gfx; - fn frame() -> int + fn render() -> int { return Gfx.set_sprite(2, 0, 12, 18, 7, 3, true, false, true, 1); } + + [Frame] + fn frame() -> void + { + render(); + return; + } + """); + Files.writeString(modBarrel, """ + pub fn render() -> int; + pub fn frame() -> void; """); - Files.writeString(modBarrel, "pub fn frame() -> int;"); final var projectTable = new ProjectTable(); final var fileTable = new FileTable(1); @@ -1099,6 +1164,7 @@ class PBSFrontendPhaseServiceTest { import { Log } from @sdk:log; import { Input } from @sdk:input; + [Frame] fn frame() -> void { if (Input.pad().a().pressed()) @@ -1182,6 +1248,7 @@ class PBSFrontendPhaseServiceTest { Files.writeString(sourceFile, """ import { Input } from @sdk:input; + [Frame] fn frame() -> void { Input.pad().x().pressed(); @@ -1246,6 +1313,7 @@ class PBSFrontendPhaseServiceTest { Files.writeString(sourceFile, """ import { Input } from @sdk:input; + [Frame] fn frame() -> void { let touch = Input.touch(); diff --git a/test-projects/main/src/main.pbs b/test-projects/main/src/main.pbs index cfbf01bc..9e53bacc 100644 --- a/test-projects/main/src/main.pbs +++ b/test-projects/main/src/main.pbs @@ -4,6 +4,7 @@ import { Log } from @sdk:log; import { Input } from @sdk:input; import { Gfx } from @sdk:gfx; +[Frame] fn frame() -> void { let touch = Input.touch();