requires explicit frame markers for executable PBS projects
This commit is contained in:
parent
459f5e3b87
commit
14eaa0e0c0
@ -152,9 +152,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
entryPointModuleId = wrapperNamesByModule.keySet().iterator().next();
|
entryPointModuleId = wrapperNamesByModule.keySet().iterator().next();
|
||||||
entryPointCallableName = wrapperNamesByModule.get(entryPointModuleId);
|
entryPointCallableName = wrapperNamesByModule.get(entryPointModuleId);
|
||||||
} else {
|
} else {
|
||||||
final var legacyEntrypoint = resolveLegacyEntrypoint(baseBackend);
|
entryPointModuleId = baseBackend.getEntryPointModuleId();
|
||||||
entryPointModuleId = legacyEntrypoint.moduleId();
|
entryPointCallableName = baseBackend.getEntryPointCallableName();
|
||||||
entryPointCallableName = legacyEntrypoint.callableName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final var sortedModuleIds = new ArrayList<ModuleId>();
|
final var sortedModuleIds = new ArrayList<ModuleId>();
|
||||||
@ -397,19 +396,6 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
return (int) value;
|
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<ModuleId> blockedModulesByDependency(
|
private Set<ModuleId> blockedModulesByDependency(
|
||||||
final Set<ModuleId> failedModuleIds,
|
final Set<ModuleId> failedModuleIds,
|
||||||
final Map<ModuleId, Set<ModuleId>> dependenciesByModule) {
|
final Map<ModuleId, Set<ModuleId>> dependenciesByModule) {
|
||||||
@ -440,9 +426,4 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
ModuleId moduleId,
|
ModuleId moduleId,
|
||||||
p.studio.compiler.models.IRBackendFile irBackendFile) {
|
p.studio.compiler.models.IRBackendFile irBackendFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record LegacyEntrypoint(
|
|
||||||
ModuleId moduleId,
|
|
||||||
String callableName) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,34 +13,31 @@ final class PbsProjectLifecycleSemanticsValidator {
|
|||||||
final java.util.List<PbsParsedSourceFile> parsedSourceFiles,
|
final java.util.List<PbsParsedSourceFile> parsedSourceFiles,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
final var frames = new ArrayList<PbsAst.FunctionDecl>();
|
final var frames = new ArrayList<PbsAst.FunctionDecl>();
|
||||||
var sawLifecycleMarker = false;
|
var sawExecutableSource = false;
|
||||||
|
|
||||||
for (final var parsedSource : parsedSourceFiles) {
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
sawExecutableSource = true;
|
||||||
for (final var topDecl : parsedSource.ast().topDecls()) {
|
for (final var topDecl : parsedSource.ast().topDecls()) {
|
||||||
if (!(topDecl instanceof PbsAst.FunctionDecl functionDecl)) {
|
if (!(topDecl instanceof PbsAst.FunctionDecl functionDecl)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.NONE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sawLifecycleMarker = true;
|
|
||||||
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.FRAME) {
|
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.FRAME) {
|
||||||
frames.add(functionDecl);
|
frames.add(functionDecl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sawLifecycleMarker) {
|
if (!sawExecutableSource) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (frames.isEmpty()) {
|
if (frames.isEmpty()) {
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
p.studio.compiler.source.diagnostics.Diagnostics.error(
|
||||||
diagnostics,
|
diagnostics,
|
||||||
PbsSemanticsErrors.E_SEM_MISSING_PROJECT_FRAME.name(),
|
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());
|
parsedSourceFiles.getFirst().ast().span());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,8 +162,17 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
fn caller() -> int {
|
fn caller() -> int {
|
||||||
return target();
|
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 projectTable = new ProjectTable();
|
||||||
final var fileTable = new FileTable(1);
|
final var fileTable = new FileTable(1);
|
||||||
@ -221,11 +230,12 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
final var sourceFile = modulePath.resolve("source.pbs");
|
final var sourceFile = modulePath.resolve("source.pbs");
|
||||||
final var modBarrel = modulePath.resolve("mod.barrel");
|
final var modBarrel = modulePath.resolve("mod.barrel");
|
||||||
Files.writeString(sourceFile, """
|
Files.writeString(sourceFile, """
|
||||||
fn run() -> int { return 1; }
|
[Frame]
|
||||||
|
fn frame() -> void { return; }
|
||||||
fn sum(a: int, b: int) -> int { return a + b; }
|
fn sum(a: int, b: int) -> int { return a + b; }
|
||||||
""");
|
""");
|
||||||
Files.writeString(modBarrel, """
|
Files.writeString(modBarrel, """
|
||||||
pub fn run() -> int;
|
pub fn frame() -> void;
|
||||||
pub fn sum(a: int, b: int) -> int;
|
pub fn sum(a: int, b: int) -> int;
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -255,7 +265,51 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
|
|
||||||
assertTrue(diagnostics.isEmpty());
|
assertTrue(diagnostics.isEmpty());
|
||||||
assertEquals(2, irBackend.getFunctions().size());
|
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
|
@Test
|
||||||
@ -720,6 +774,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
Files.writeString(sourceFile, """
|
Files.writeString(sourceFile, """
|
||||||
import { Gfx } from @sdk:gfx;
|
import { Gfx } from @sdk:gfx;
|
||||||
|
|
||||||
|
[Frame]
|
||||||
fn frame() -> void
|
fn frame() -> void
|
||||||
{
|
{
|
||||||
Gfx.clear_565(6577);
|
Gfx.clear_565(6577);
|
||||||
@ -769,12 +824,22 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
Files.writeString(sourceFile, """
|
Files.writeString(sourceFile, """
|
||||||
import { Gfx } from @sdk:gfx;
|
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);
|
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 projectTable = new ProjectTable();
|
||||||
final var fileTable = new FileTable(1);
|
final var fileTable = new FileTable(1);
|
||||||
@ -1099,6 +1164,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
import { Log } from @sdk:log;
|
import { Log } from @sdk:log;
|
||||||
import { Input } from @sdk:input;
|
import { Input } from @sdk:input;
|
||||||
|
|
||||||
|
[Frame]
|
||||||
fn frame() -> void
|
fn frame() -> void
|
||||||
{
|
{
|
||||||
if (Input.pad().a().pressed())
|
if (Input.pad().a().pressed())
|
||||||
@ -1182,6 +1248,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
Files.writeString(sourceFile, """
|
Files.writeString(sourceFile, """
|
||||||
import { Input } from @sdk:input;
|
import { Input } from @sdk:input;
|
||||||
|
|
||||||
|
[Frame]
|
||||||
fn frame() -> void
|
fn frame() -> void
|
||||||
{
|
{
|
||||||
Input.pad().x().pressed();
|
Input.pad().x().pressed();
|
||||||
@ -1246,6 +1313,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
Files.writeString(sourceFile, """
|
Files.writeString(sourceFile, """
|
||||||
import { Input } from @sdk:input;
|
import { Input } from @sdk:input;
|
||||||
|
|
||||||
|
[Frame]
|
||||||
fn frame() -> void
|
fn frame() -> void
|
||||||
{
|
{
|
||||||
let touch = Input.touch();
|
let touch = Input.touch();
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Log } from @sdk:log;
|
|||||||
import { Input } from @sdk:input;
|
import { Input } from @sdk:input;
|
||||||
import { Gfx } from @sdk:gfx;
|
import { Gfx } from @sdk:gfx;
|
||||||
|
|
||||||
|
[Frame]
|
||||||
fn frame() -> void
|
fn frame() -> void
|
||||||
{
|
{
|
||||||
let touch = Input.touch();
|
let touch = Input.touch();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user