diff --git a/discussion/index.ndjson b/discussion/index.ndjson index dcecd0b2..bc92b81a 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -4,6 +4,6 @@ {"type":"discussion","id":"DSC-0003","status":"done","ticket":"packer-docs-import","title":"Import docs/packer into discussion-framework artifacts","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","migration","discussion-framework","docs-import"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0009","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0009-mental-model-packer-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0010","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0010-asset-identity-and-runtime-contract-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0011","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0011-foundations-workspace-runtime-and-build-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0012","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0012-runtime-ownership-and-studio-boundary-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0013","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0013-metadata-convergence-and-runtime-sink-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0014","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0014-pack-wizard-summary-validation-and-pack-execution-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0015","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0015-tile-bank-packing-contract-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0017","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0017-packer-docs-import-pattern.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"}]} {"type":"discussion","id":"DSC-0004","status":"open","ticket":"tilemap-and-metatile-runtime-binary-layout","title":"Tilemap and Metatile Runtime Binary Layout","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","legacy-import","tilemap","metatile","runtime-layout"],"agendas":[{"id":"AGD-0004","file":"AGD-0004-tilemap-and-metatile-runtime-binary-layout.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0005","status":"open","ticket":"variable-tile-bank-palette-serialization","title":"Variable Tile Bank Palette Serialization","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","legacy-import","tile-bank","palette-serialization","versioning"],"agendas":[{"id":"AGD-0005","file":"AGD-0005-variable-tile-bank-palette-serialization.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0006","status":"open","ticket":"pbs-game-facing-asset-refs-and-call-result-discard","title":"PBS Game-Facing Asset References and Ignored Call Result Lowering","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","ergonomics","lowering","runtime","asset-identity","expression-statements"],"agendas":[{"id":"AGD-0006","file":"AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-pbs-asset-address-surface-and-be-lowering.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"},{"id":"DEC-0006","file":"DEC-0006-pbs-ignored-values-lowering-and-warning.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-pbs-asset-address-surface-spec-propagation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0006","file":"PLN-0006-pbs-asset-address-surface-implementation.md","status":"review","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0007","file":"PLN-0007-pbs-ignored-values-warning-implementation.md","status":"review","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0006"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0006","status":"open","ticket":"pbs-game-facing-asset-refs-and-call-result-discard","title":"PBS Game-Facing Asset References and Ignored Call Result Lowering","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","ergonomics","lowering","runtime","asset-identity","expression-statements"],"agendas":[{"id":"AGD-0006","file":"AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-pbs-asset-address-surface-and-be-lowering.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"},{"id":"DEC-0006","file":"DEC-0006-pbs-ignored-values-lowering-and-warning.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-pbs-asset-address-surface-spec-propagation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0006","file":"PLN-0006-pbs-asset-address-surface-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0007","file":"PLN-0007-pbs-ignored-values-warning-implementation.md","status":"review","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0006"]}],"lessons":[]} {"type":"discussion","id":"DSC-0007","status":"done","ticket":"pbs-learn-to-discussion-lessons-migration","title":"Migrate PBS Learn Documents into Discussion Lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","migration","discussion-framework","lessons","learn-prune"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0018","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0018-pbs-ast-and-parser-contract-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0019","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0019-pbs-name-resolution-and-linking-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0020","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0020-pbs-runtime-values-identity-memory-boundaries-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0021","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0021-pbs-diagnostics-and-conformance-governance-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0022","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0022-pbs-globals-lifecycle-and-published-entrypoint-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} {"type":"discussion","id":"DSC-0008","status":"done","ticket":"pbs-low-level-asset-manager-surface","title":"PBS Low-Level Asset Manager Surface for Runtime AssetManager","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","runtime","asset-manager","host-abi","stdlib","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0023","file":"discussion/lessons/DSC-0008-pbs-low-level-asset-manager-surface/LSN-0023-lowassets-runtime-aligned-sdk-surface.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} diff --git a/discussion/workflow/plans/PLN-0006-pbs-asset-address-surface-implementation.md b/discussion/workflow/plans/PLN-0006-pbs-asset-address-surface-implementation.md index 836c4b6e..a6c56c80 100644 --- a/discussion/workflow/plans/PLN-0006-pbs-asset-address-surface-implementation.md +++ b/discussion/workflow/plans/PLN-0006-pbs-asset-address-surface-implementation.md @@ -2,9 +2,9 @@ id: PLN-0006 ticket: pbs-game-facing-asset-refs-and-call-result-discard title: Implement DEC-0005 Addressable Surface, FESurfaceContext, and Backend Asset Lowering -status: review +status: done created: 2026-03-27 -completed: +completed: 2026-03-27 tags: [compiler, pbs, implementation, addressable, lowering, be-fe-contract, asset-identity] --- 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 ee7f1d0b..e801a9e8 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 @@ -1,6 +1,7 @@ package p.studio.compiler.pbs; import p.studio.compiler.messages.HostAdmissionContext; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.models.IRBackendExecutableFunction; import p.studio.compiler.models.IRBackendFile; import p.studio.compiler.models.IRFunction; @@ -61,6 +62,16 @@ public final class PbsFrontendCompiler { final DiagnosticSink diagnostics, final SourceKind sourceKind, final HostAdmissionContext hostAdmissionContext) { + return compileFile(fileId, source, diagnostics, sourceKind, hostAdmissionContext, FESurfaceContext.empty()); + } + + public IRBackendFile compileFile( + final FileId fileId, + final String source, + final DiagnosticSink diagnostics, + final SourceKind sourceKind, + final HostAdmissionContext hostAdmissionContext, + final FESurfaceContext feSurfaceContext) { final var nameTable = new NameTable(); final var admissionBaseline = diagnostics.errorCount(); final var tokens = PbsLexer.lex(source, fileId, diagnostics); @@ -76,6 +87,7 @@ public final class PbsFrontendCompiler { ModuleId.none(), ReadOnlyList.empty(), hostAdmissionContext, + feSurfaceContext, nameTable); if (diagnostics.errorCount() > admissionBaseline) { return IRBackendFile.empty(fileId); @@ -91,6 +103,7 @@ public final class PbsFrontendCompiler { final ModuleId moduleId, final ReadOnlyList modulePool, final HostAdmissionContext hostAdmissionContext, + final FESurfaceContext feSurfaceContext, final NameTable nameTable) { return compileParsedFile( fileId, @@ -100,6 +113,7 @@ public final class PbsFrontendCompiler { moduleId, modulePool, hostAdmissionContext, + feSurfaceContext, nameTable, ReadOnlyList.empty(), ReadOnlyList.empty(), @@ -116,6 +130,7 @@ public final class PbsFrontendCompiler { final ModuleId moduleId, final ReadOnlyList modulePool, final HostAdmissionContext hostAdmissionContext, + final FESurfaceContext feSurfaceContext, final NameTable nameTable, final ReadOnlyList supplementalTopDecls, final ReadOnlyList importedCallables, @@ -134,6 +149,9 @@ public final class PbsFrontendCompiler { final var effectiveImportedGlobals = importedGlobals == null ? ReadOnlyList.empty() : importedGlobals; + final var effectiveFESurfaceContext = feSurfaceContext == null + ? FESurfaceContext.empty() + : feSurfaceContext; final var effectiveImportedReservedMetadata = importedReservedMetadata == null ? IRReservedMetadata.empty() : importedReservedMetadata; @@ -147,7 +165,7 @@ public final class PbsFrontendCompiler { effectiveModuleId, effectiveImportedGlobals, diagnostics); - flowSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics); + flowSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, effectiveFESurfaceContext, diagnostics); lifecycleSemanticsValidator.validate(ast, effectiveSupplementalTopDecls, diagnostics); if (diagnostics.errorCount() > semanticsErrorBaseline) { return IRBackendFile.empty(fileId); @@ -179,6 +197,7 @@ public final class PbsFrontendCompiler { effectiveModuleId, mergeReservedMetadata(reservedMetadata, effectiveImportedReservedMetadata), effectiveNameTable, + effectiveFESurfaceContext, diagnostics, effectiveImportedCallables); if (diagnostics.errorCount() > loweringErrorBaseline) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java index 0a3ca1fe..7fbb9aa6 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java @@ -378,7 +378,7 @@ final class PbsExecutableBodyLowerer { handleExpr.span(), context); case PbsAst.AsExpr asExpr -> lowerExpression(asExpr.expression(), context); - case PbsAst.MemberExpr memberExpr -> lowerExpression(memberExpr.receiver(), context); + case PbsAst.MemberExpr memberExpr -> lowerMemberExpression(memberExpr, context); case PbsAst.PropagateExpr propagateExpr -> lowerExpression(propagateExpr.expression(), context); case PbsAst.GroupExpr groupExpr -> lowerExpression(groupExpr.expression(), context); case PbsAst.NewExpr newExpr -> lowerExpressionList(newExpr.arguments(), context); @@ -426,6 +426,25 @@ final class PbsExecutableBodyLowerer { } } + private void lowerMemberExpression( + final PbsAst.MemberExpr memberExpr, + final PbsExecutableLoweringContext context) { + final var assetReference = flattenAssetReference(memberExpr); + if (assetReference != null) { + final var assetId = context.resolveAssetId(assetReference); + if (assetId == null) { + reportUnsupportedLowering( + "asset reference does not resolve in backend-owned surface: " + assetReference, + memberExpr.span(), + context); + return; + } + emitPushI32(assetId, memberExpr.span(), context); + return; + } + lowerExpression(memberExpr.receiver(), context); + } + private void lowerIfExpression( final PbsAst.IfExpr ifExpr, final PbsExecutableLoweringContext context) { @@ -456,6 +475,9 @@ final class PbsExecutableBodyLowerer { private void lowerIdentifierExpression( final PbsAst.IdentifierExpr identifierExpr, final PbsExecutableLoweringContext context) { + if ("assets".equals(identifierExpr.name())) { + return; + } final var slot = context.localSlotByNameId().get(context.nameTable().register(identifierExpr.name())); if (slot != null) { emitGetLocal(slot, identifierExpr.span(), context); @@ -472,6 +494,20 @@ final class PbsExecutableBodyLowerer { } } + private String flattenAssetReference(final PbsAst.Expression expression) { + if (expression instanceof PbsAst.IdentifierExpr identifierExpr) { + return "assets".equals(identifierExpr.name()) ? "assets" : null; + } + if (!(expression instanceof PbsAst.MemberExpr memberExpr)) { + return null; + } + final var receiverPath = flattenAssetReference(memberExpr.receiver()); + if (receiverPath == null || receiverPath.isBlank()) { + return null; + } + return receiverPath + "." + memberExpr.memberName(); + } + private void lowerConstExpression( final PbsAst.Expression expression, final PbsExecutableLoweringContext context) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java index 8bb642a6..4cb63594 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java @@ -1,6 +1,7 @@ package p.studio.compiler.pbs.lowering; import p.studio.compiler.models.IRBackendExecutableFunction; +import p.studio.compiler.messages.Addressable; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.identifiers.NameId; import p.studio.compiler.source.tables.IntrinsicTable; @@ -66,6 +67,10 @@ final class PbsExecutableLoweringContext { return metadataIndex.constDeclByNameId(); } + Map addressableByAddress() { + return metadataIndex.addressableByAddress(); + } + Map builtinCanonicalBySourceType() { return metadataIndex.builtinCanonicalBySourceType(); } @@ -140,6 +145,14 @@ final class PbsExecutableLoweringContext { return constDeclByNameId().get(nameTable.register(constName)); } + Integer resolveAssetId(final String address) { + if (address == null || address.isBlank()) { + return null; + } + final var addressable = addressableByAddress().get(address); + return addressable == null ? null : addressable.assetId(); + } + int declareLocalSlot(final String localName) { final var nameId = nameTable.register(localName); final var existing = localSlotByNameId.get(nameId); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java index 7bc353b1..9983c3de 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java @@ -2,6 +2,7 @@ package p.studio.compiler.pbs.lowering; import p.studio.compiler.models.IRBackendExecutableFunction; import p.studio.compiler.models.IRReservedMetadata; +import p.studio.compiler.messages.Addressable; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.NameId; @@ -46,6 +47,7 @@ public class PbsExecutableLoweringModels { Map builtinConstOwnerByNameId, Map globalSlotByNameId, Map constDeclByNameId, + Map addressableByAddress, Map> intrinsicByOwnerAndMethod, Map intrinsicReturnOwnerByCanonical) { } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java index 99da2dd6..cb9b01af 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java @@ -2,6 +2,7 @@ package p.studio.compiler.pbs.lowering; import p.studio.compiler.models.IRBackendExecutableFunction; import p.studio.compiler.models.IRReservedMetadata; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.pbs.PbsFrontendCompiler; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.source.diagnostics.DiagnosticSink; @@ -29,6 +30,7 @@ public final class PbsExecutableLoweringService { final ModuleId moduleId, final IRReservedMetadata reservedMetadata, final NameTable nameTable, + final FESurfaceContext feSurfaceContext, final DiagnosticSink diagnostics, final ReadOnlyList importedCallables) { final var normalizedModuleId = moduleId == null ? ModuleId.none() : moduleId; @@ -37,6 +39,7 @@ public final class PbsExecutableLoweringService { supplementalTopDecls, reservedMetadata, nameTable, + feSurfaceContext, diagnostics); final var callableRegistry = callableRegistryFactory.create( ast, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java index a189bebd..978efae2 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java @@ -1,6 +1,8 @@ package p.studio.compiler.pbs.lowering; import p.studio.compiler.models.IRReservedMetadata; +import p.studio.compiler.messages.Addressable; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; import p.studio.compiler.source.diagnostics.DiagnosticPhase; @@ -23,12 +25,14 @@ final class PbsExecutableMetadataIndexFactory { final ReadOnlyList supplementalTopDecls, final IRReservedMetadata reservedMetadata, final NameTable nameTable, + final FESurfaceContext feSurfaceContext, final DiagnosticSink diagnostics) { final var hostByMethodName = indexHostBindingsByMethodName(reservedMetadata, nameTable); final var builtinCanonicalBySourceType = indexBuiltinCanonicalBySourceType(reservedMetadata); final var builtinConstOwnerByNameId = indexBuiltinConstOwners(reservedMetadata, nameTable); final var globalSlotByNameId = indexGlobalSlots(ast, nameTable); final var constDeclByNameId = indexConstDecls(ast, nameTable); + final var addressableByAddress = indexAddressables(feSurfaceContext); final var builtinSignatureByOwnerAndMethod = indexBuiltinSignatures( ast, supplementalTopDecls, @@ -48,6 +52,7 @@ final class PbsExecutableMetadataIndexFactory { builtinConstOwnerByNameId, globalSlotByNameId, constDeclByNameId, + addressableByAddress, intrinsicByOwnerAndMethod, intrinsicReturnOwnerByCanonical); } @@ -112,6 +117,18 @@ final class PbsExecutableMetadataIndexFactory { return constDeclByNameId; } + private Map indexAddressables(final FESurfaceContext feSurfaceContext) { + final var addressableByAddress = new HashMap(); + final var effectiveSurfaceContext = feSurfaceContext == null ? FESurfaceContext.empty() : feSurfaceContext; + for (final var addressable : effectiveSurfaceContext.assets()) { + if (addressable == null || addressable.address().isBlank()) { + continue; + } + addressableByAddress.putIfAbsent(addressable.address(), addressable); + } + return addressableByAddress; + } + private Map indexBuiltinSignatures( final PbsAst.File ast, final ReadOnlyList supplementalTopDecls, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java index 732d525d..17f0bc3b 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java @@ -1,5 +1,6 @@ package p.studio.compiler.pbs.semantics; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Model; import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Scope; @@ -27,8 +28,9 @@ final class PbsFlowBodyAnalyzer { public void validate( final PbsAst.File ast, final ReadOnlyList supplementalTopDecls, + final FESurfaceContext feSurfaceContext, final DiagnosticSink diagnostics) { - final var model = Model.from(ast, supplementalTopDecls); + final var model = Model.from(ast, supplementalTopDecls, feSurfaceContext, diagnostics); for (final var topDecl : ast.topDecls()) { validateTopDecl(topDecl, model, diagnostics); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowCallableResolutionAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowCallableResolutionAnalyzer.java index c8efde31..9be1c815 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowCallableResolutionAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowCallableResolutionAnalyzer.java @@ -70,6 +70,11 @@ final class PbsFlowCallableResolutionAnalyzer { ExprResult analyzeMemberExpression( final PbsAst.MemberExpr memberExpr, final PbsFlowExpressionContext context) { + final var assetReference = flattenAssetReference(memberExpr); + if (assetReference != null) { + return resolveAssetReference(assetReference, memberExpr, context); + } + final var receiver = analyzeValueExpression(memberExpr.receiver(), context, null).type(); final var model = context.model(); final var diagnostics = context.diagnostics(); @@ -320,6 +325,47 @@ final class PbsFlowCallableResolutionAnalyzer { && receiver.name().equals(currentReceiverType.name()); } + private ExprResult resolveAssetReference( + final String assetReference, + final PbsAst.MemberExpr memberExpr, + final PbsFlowExpressionContext context) { + final var diagnostics = context.diagnostics(); + final var model = context.model(); + final var resolvedAssetId = model.resolveAssetId(assetReference); + if (resolvedAssetId != null) { + return ExprResult.type(TypeView.addressable(assetReference)); + } + if (model.isAssetNamespace(assetReference)) { + if (context.use() == ExprUse.VALUE) { + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Asset namespace '%s' is not a terminal Addressable value".formatted(assetReference), + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.type(TypeView.assetNamespace(assetReference)); + } + Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Asset reference '%s' does not resolve in the backend-provided surface".formatted(assetReference), + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + private String flattenAssetReference(final PbsAst.Expression expression) { + if (expression instanceof PbsAst.IdentifierExpr identifierExpr) { + return "assets".equals(identifierExpr.name()) ? "assets" : null; + } + if (!(expression instanceof PbsAst.MemberExpr memberExpr)) { + return null; + } + final var receiverPath = flattenAssetReference(memberExpr.receiver()); + if (receiverPath == null || receiverPath.isBlank()) { + return null; + } + return receiverPath + "." + memberExpr.memberName(); + } + private ExprResult analyzeValueExpression( final PbsAst.Expression expression, final PbsFlowExpressionContext context, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java index f4fc132c..ee33549a 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java @@ -1,8 +1,11 @@ package p.studio.compiler.pbs.semantics; import lombok.experimental.UtilityClass; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.source.Span; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.diagnostics.Diagnostics; import p.studio.utilities.structures.ReadOnlyList; import java.util.*; @@ -44,7 +47,9 @@ final class PbsFlowSemanticSupport { OPTIONAL, RESULT, TUPLE, - TYPE_REF + TYPE_REF, + ADDRESSABLE, + ASSET_NAMESPACE } record TupleField( @@ -126,6 +131,14 @@ final class PbsFlowSemanticSupport { static TypeView typeRef(final String name) { return new TypeView(Kind.TYPE_REF, name, List.of(), null, null, List.of(), null); } + + static TypeView addressable(final String address) { + return new TypeView(Kind.ADDRESSABLE, address, List.of(), null, null, List.of(), null); + } + + static TypeView assetNamespace(final String path) { + return new TypeView(Kind.ASSET_NAMESPACE, path, List.of(), null, null, List.of(), null); + } } record CallableSymbol( @@ -176,14 +189,18 @@ final class PbsFlowSemanticSupport { final Set knownEnumNames = new HashSet<>(); final Set knownErrorNames = new HashSet<>(); final Set knownCallbackNames = new HashSet<>(); + final Map assetIdByAddress = new HashMap<>(); + final Set assetNamespacePrefixes = new HashSet<>(); static Model from(final PbsAst.File ast) { - return from(ast, ReadOnlyList.empty()); + return from(ast, ReadOnlyList.empty(), FESurfaceContext.empty(), DiagnosticSink.empty()); } static Model from( final PbsAst.File ast, - final ReadOnlyList supplementalTopDecls) { + final ReadOnlyList supplementalTopDecls, + final FESurfaceContext feSurfaceContext, + final DiagnosticSink diagnostics) { final var model = new Model(); for (final var topDecl : ast.topDecls()) { model.registerKnownTopDecl(topDecl); @@ -197,6 +214,7 @@ final class PbsFlowSemanticSupport { for (final var topDecl : supplementalTopDecls) { model.ingestTopDecl(topDecl); } + model.ingestAssetSurface(feSurfaceContext, diagnostics); return model; } @@ -376,6 +394,72 @@ final class PbsFlowSemanticSupport { globalTypes.put(globalDecl.name(), typeFrom(globalDecl.explicitType())); } } + + private void ingestAssetSurface( + final FESurfaceContext feSurfaceContext, + final DiagnosticSink diagnostics) { + final var effectiveSurfaceContext = feSurfaceContext == null ? FESurfaceContext.empty() : feSurfaceContext; + for (final var addressable : effectiveSurfaceContext.assets()) { + if (addressable == null || addressable.address().isBlank()) { + continue; + } + assetIdByAddress.put(addressable.address(), addressable.assetId()); + registerAssetNamespaces(addressable.address()); + } + for (final var address : assetIdByAddress.keySet()) { + for (final var prefix : namespacePrefixesOf(address)) { + if (assetIdByAddress.containsKey(prefix)) { + Diagnostics.error( + diagnostics, + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Asset surface collision: '%s' cannot be both terminal and namespace prefix".formatted(prefix), + astSyntheticSpan()); + } + } + } + } + + boolean hasAssetNamespaceRoot() { + return assetNamespacePrefixes.contains("assets") || assetIdByAddress.containsKey("assets"); + } + + boolean isAssetNamespace(final String path) { + return assetNamespacePrefixes.contains(path); + } + + Integer resolveAssetId(final String address) { + return assetIdByAddress.get(address); + } + + private void registerAssetNamespaces(final String address) { + for (final var prefix : namespacePrefixesOf(address)) { + assetNamespacePrefixes.add(prefix); + } + } + + private List namespacePrefixesOf(final String address) { + final var segments = address.split("\\."); + final var prefixes = new ArrayList(); + if (segments.length <= 1) { + return prefixes; + } + final var builder = new StringBuilder(); + for (int i = 0; i < segments.length - 1; i++) { + if (segments[i] == null || segments[i].isBlank()) { + break; + } + if (builder.length() > 0) { + builder.append('.'); + } + builder.append(segments[i]); + prefixes.add(builder.toString()); + } + return prefixes; + } + + private Span astSyntheticSpan() { + return Span.none(); + } private CallableSymbol callableFrom( final String name, @@ -429,6 +513,9 @@ final class PbsFlowSemanticSupport { if ("str".equals(typeRef.name())) { yield TypeView.str(); } + if ("Addressable".equals(typeRef.name())) { + yield TypeView.addressable(typeRef.name()); + } if (structs.containsKey(typeRef.name()) || knownStructNames.contains(typeRef.name())) { yield TypeView.struct(typeRef.name()); } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java index a9816dc7..e459aad7 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java @@ -1,5 +1,6 @@ package p.studio.compiler.pbs.semantics; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.utilities.structures.ReadOnlyList; @@ -8,13 +9,14 @@ public final class PbsFlowSemanticsValidator { private final PbsFlowBodyAnalyzer flowBodyAnalyzer = new PbsFlowBodyAnalyzer(); public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) { - flowBodyAnalyzer.validate(ast, ReadOnlyList.empty(), diagnostics); + flowBodyAnalyzer.validate(ast, ReadOnlyList.empty(), FESurfaceContext.empty(), diagnostics); } public void validate( final PbsAst.File ast, final ReadOnlyList supplementalTopDecls, + final FESurfaceContext feSurfaceContext, final DiagnosticSink diagnostics) { - flowBodyAnalyzer.validate(ast, supplementalTopDecls, diagnostics); + flowBodyAnalyzer.validate(ast, supplementalTopDecls, feSurfaceContext, diagnostics); } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java index 1ca7a0bc..618969fb 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowStructuralExpressionAnalyzer.java @@ -70,6 +70,10 @@ final class PbsFlowStructuralExpressionAnalyzer { return ExprResult.type(receiverType); } if (expression instanceof PbsAst.IdentifierExpr identifierExpr) { + if ("assets".equals(identifierExpr.name()) && model.hasAssetNamespaceRoot()) { + return ExprResult.type(TypeView.assetNamespace("assets")); + } + final var localType = context.scope().resolve(identifierExpr.name()); if (localType != null) { return ExprResult.type(localType); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java index b15e695f..40619f60 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java @@ -47,7 +47,8 @@ final class PbsFlowTypeOps { } return switch (actual.kind()) { case UNIT, INT, FLOAT, BOOL, STR -> true; - case STRUCT, SERVICE, CONTRACT, CALLBACK, ENUM, ERROR, TYPE_REF -> actual.name().equals(expected.name()); + case STRUCT, SERVICE, CONTRACT, CALLBACK, ENUM, ERROR, TYPE_REF, ASSET_NAMESPACE -> actual.name().equals(expected.name()); + case ADDRESSABLE -> true; case OPTIONAL -> compatible(actual.inner(), expected.inner()); case RESULT -> compatible(actual.errorType(), expected.errorType()) && compatible(actual.inner(), expected.inner()); case TUPLE -> tupleCompatible(actual, expected); @@ -173,6 +174,9 @@ final class PbsFlowTypeOps { if ("str".equals(name)) { return TypeView.str(); } + if ("Addressable".equals(name)) { + return TypeView.addressable(name); + } if (model.structs.containsKey(name)) { return TypeView.struct(name); } 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 a293e925..00543560 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 @@ -2,6 +2,7 @@ package p.studio.compiler.services; import lombok.extern.slf4j.Slf4j; import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.messages.FrontendPhaseContext; import p.studio.compiler.models.IRBackend; import p.studio.compiler.models.IRBackendExecutableFunction; @@ -59,6 +60,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { final LogAggregator logs, final BuildingIssueSink issues) { final var nameTable = ctx.nameTable(); + final var feSurfaceContext = ctx.feSurfaceContext(); final var assembly = moduleAssemblyService.assemble(ctx, nameTable, diagnostics, issues); final var parsedSourceFiles = assembly.parsedSourceFiles(); final var importedSemanticContexts = importedSemanticContextService.build(parsedSourceFiles, assembly.moduleTable()); @@ -85,6 +87,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { parsedSource.moduleId(), canonicalModulePool, ctx.hostAdmissionContext(), + feSurfaceContext, nameTable, importedSemanticContext.supplementalTopDecls(), importedSemanticContext.importedCallables(), 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 868a2a75..7a22f6fe 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 @@ -1,6 +1,8 @@ package p.studio.compiler.pbs; import org.junit.jupiter.api.Test; +import p.studio.compiler.messages.Addressable; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.messages.HostAdmissionContext; import p.studio.compiler.models.IRHiddenGlobalKind; import p.studio.compiler.models.IRGlobalVisibility; @@ -458,6 +460,58 @@ class PbsFrontendCompilerTest { && h.abiVersion() == 1)); } + @Test + void shouldLowerBackendOwnedAssetReferenceToAssetId() { + final var source = """ + fn boot() -> Addressable { + return assets.ui.panel; + } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile( + new FileId(12), + source, + diagnostics, + SourceKind.PROJECT, + HostAdmissionContext.permissiveDefault(), + new FESurfaceContext(ReadOnlyList.from(new Addressable("assets.ui.panel", 37)))); + + assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); + final var executableBoot = fileBackend.executableFunctions().stream() + .filter(fn -> fn.callableName().equals("boot")) + .findFirst() + .orElseThrow(); + assertTrue(executableBoot.instructions().stream().anyMatch(i -> + i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_I32 + && "37".equals(i.label()))); + } + + @Test + void shouldRejectUnresolvedBackendOwnedAssetReference() { + final var source = """ + fn boot() -> Addressable { + return assets.ui.missing; + } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile( + new FileId(13), + source, + diagnostics, + SourceKind.PROJECT, + HostAdmissionContext.permissiveDefault(), + new FESurfaceContext(ReadOnlyList.from(new Addressable("assets.ui.panel", 37)))); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name()) + && d.getMessage().contains("assets.ui.missing"))); + assertEquals(0, fileBackend.executableFunctions().size()); + } + @Test void shouldRejectHostBindingWithoutCapabilityAtHostAdmission() { final var source = """ 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 2f508fcc..ba426dfd 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 @@ -2,7 +2,9 @@ package p.studio.compiler.services; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import p.studio.compiler.messages.Addressable; import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.messages.FESurfaceContext; import p.studio.compiler.messages.FrontendPhaseContext; import p.studio.compiler.models.IRGlobalVisibility; import p.studio.compiler.models.BuildStack; @@ -40,6 +42,61 @@ class PBSFrontendPhaseServiceTest { @TempDir Path tempDir; + @Test + void shouldPropagateBackendOwnedAssetSurfaceIntoPbsFrontendCompilation() throws IOException { + final var projectRoot = tempDir.resolve("project-assets"); + final var sourceRoot = projectRoot.resolve("src"); + final var modulePath = sourceRoot.resolve("main"); + Files.createDirectories(modulePath); + + final var sourceFile = modulePath.resolve("source.pbs"); + final var modBarrel = modulePath.resolve("mod.barrel"); + Files.writeString(sourceFile, """ + [Frame] + fn frame() -> void { + let asset: Addressable = assets.ui.panel; + 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("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))), + 1, + p.studio.compiler.messages.HostAdmissionContext.permissiveDefault(), + new FESurfaceContext(ReadOnlyList.from(new Addressable("assets.ui.panel", 91)))); + final var diagnostics = DiagnosticSink.empty(); + + final var irBackend = new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); + final var frameFn = irBackend.getExecutableFunctions().stream() + .filter(function -> "frame".equals(function.callableName())) + .findFirst() + .orElseThrow(); + assertTrue(frameFn.instructions().stream().anyMatch(instruction -> + instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_I32 + && "91".equals(instruction.label()))); + } + @Test void shouldReportInvalidBarrelFilename() throws IOException { final var projectRoot = tempDir.resolve("project"); diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/Addressable.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/Addressable.java new file mode 100644 index 00000000..5913b8d6 --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/Addressable.java @@ -0,0 +1,17 @@ +package p.studio.compiler.messages; + +public record Addressable( + String address, + int assetId) { + + public Addressable { + address = normalize(address); + } + + private static String normalize(final String address) { + if (address == null) { + return ""; + } + return address.trim(); + } +} diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FESurfaceContext.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FESurfaceContext.java new file mode 100644 index 00000000..685b932a --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FESurfaceContext.java @@ -0,0 +1,32 @@ +package p.studio.compiler.messages; + +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +public record FESurfaceContext( + ReadOnlyList assets) { + + public FESurfaceContext { + assets = normalizeAssets(assets); + } + + public static FESurfaceContext empty() { + return new FESurfaceContext(ReadOnlyList.empty()); + } + + private static ReadOnlyList normalizeAssets(final ReadOnlyList assets) { + if (assets == null || assets.isEmpty()) { + return ReadOnlyList.empty(); + } + final var dedup = new LinkedHashMap(); + for (final var asset : assets) { + if (asset == null || asset.address().isBlank()) { + continue; + } + dedup.put(asset.address(), asset); + } + return ReadOnlyList.wrap(new ArrayList<>(dedup.values())); + } +} diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FrontendPhaseContext.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FrontendPhaseContext.java index 88c29fc3..321cdc2a 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FrontendPhaseContext.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/messages/FrontendPhaseContext.java @@ -14,12 +14,13 @@ public class FrontendPhaseContext { private final NameTable nameTable; private final int stdlibVersion; private final HostAdmissionContext hostAdmissionContext; + private final FESurfaceContext feSurfaceContext; public FrontendPhaseContext( final ProjectTableReader projectTable, final FileTableReader fileTable, final BuildStack stack) { - this(projectTable, fileTable, stack, 1, HostAdmissionContext.permissiveDefault()); + this(projectTable, fileTable, stack, 1, HostAdmissionContext.permissiveDefault(), FESurfaceContext.empty()); } public FrontendPhaseContext( @@ -27,7 +28,7 @@ public class FrontendPhaseContext { final FileTableReader fileTable, final BuildStack stack, final int stdlibVersion) { - this(projectTable, fileTable, stack, stdlibVersion, HostAdmissionContext.permissiveDefault()); + this(projectTable, fileTable, stack, stdlibVersion, HostAdmissionContext.permissiveDefault(), FESurfaceContext.empty()); } public FrontendPhaseContext( @@ -36,6 +37,16 @@ public class FrontendPhaseContext { final BuildStack stack, final int stdlibVersion, final HostAdmissionContext hostAdmissionContext) { + this(projectTable, fileTable, stack, stdlibVersion, hostAdmissionContext, FESurfaceContext.empty()); + } + + public FrontendPhaseContext( + final ProjectTableReader projectTable, + final FileTableReader fileTable, + final BuildStack stack, + final int stdlibVersion, + final HostAdmissionContext hostAdmissionContext, + final FESurfaceContext feSurfaceContext) { this.projectTable = projectTable; this.fileTable = fileTable; this.stack = stack; @@ -44,6 +55,9 @@ public class FrontendPhaseContext { this.hostAdmissionContext = hostAdmissionContext == null ? HostAdmissionContext.permissiveDefault() : hostAdmissionContext; + this.feSurfaceContext = feSurfaceContext == null + ? FESurfaceContext.empty() + : feSurfaceContext; } public SourceKind sourceKind(final ProjectId projectId) { @@ -61,4 +75,8 @@ public class FrontendPhaseContext { public NameTable nameTable() { return nameTable; } + + public FESurfaceContext feSurfaceContext() { + return feSurfaceContext; + } }