implements PLN-0006
This commit is contained in:
parent
e0c57814e1
commit
4a6cc35989
@ -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"}]}
|
||||
|
||||
@ -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]
|
||||
---
|
||||
|
||||
|
||||
@ -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<ModuleReference> 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<ModuleReference> modulePool,
|
||||
final HostAdmissionContext hostAdmissionContext,
|
||||
final FESurfaceContext feSurfaceContext,
|
||||
final NameTable nameTable,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final ReadOnlyList<ImportedCallableSurface> importedCallables,
|
||||
@ -134,6 +149,9 @@ public final class PbsFrontendCompiler {
|
||||
final var effectiveImportedGlobals = importedGlobals == null
|
||||
? ReadOnlyList.<ImportedGlobalSurface>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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<String, Addressable> addressableByAddress() {
|
||||
return metadataIndex.addressableByAddress();
|
||||
}
|
||||
|
||||
Map<String, String> 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);
|
||||
|
||||
@ -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<NameId, String> builtinConstOwnerByNameId,
|
||||
Map<NameId, Integer> globalSlotByNameId,
|
||||
Map<NameId, PbsAst.ConstDecl> constDeclByNameId,
|
||||
Map<String, Addressable> addressableByAddress,
|
||||
Map<PbsIntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod,
|
||||
Map<PbsIntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical) {
|
||||
}
|
||||
|
||||
@ -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<PbsFrontendCompiler.ImportedCallableSurface> 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,
|
||||
|
||||
@ -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<PbsAst.TopDecl> 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<String, Addressable> indexAddressables(final FESurfaceContext feSurfaceContext) {
|
||||
final var addressableByAddress = new HashMap<String, Addressable>();
|
||||
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<PbsIntrinsicOwnerMethodKey, PbsAst.FunctionSignature> indexBuiltinSignatures(
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
|
||||
@ -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<PbsAst.TopDecl> 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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<String> knownEnumNames = new HashSet<>();
|
||||
final Set<String> knownErrorNames = new HashSet<>();
|
||||
final Set<String> knownCallbackNames = new HashSet<>();
|
||||
final Map<String, Integer> assetIdByAddress = new HashMap<>();
|
||||
final Set<String> 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<PbsAst.TopDecl> supplementalTopDecls) {
|
||||
final ReadOnlyList<PbsAst.TopDecl> 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<String> namespacePrefixesOf(final String address) {
|
||||
final var segments = address.split("\\.");
|
||||
final var prefixes = new ArrayList<String>();
|
||||
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());
|
||||
}
|
||||
|
||||
@ -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<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final FESurfaceContext feSurfaceContext,
|
||||
final DiagnosticSink diagnostics) {
|
||||
flowBodyAnalyzer.validate(ast, supplementalTopDecls, diagnostics);
|
||||
flowBodyAnalyzer.validate(ast, supplementalTopDecls, feSurfaceContext, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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 = """
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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<Addressable> assets) {
|
||||
|
||||
public FESurfaceContext {
|
||||
assets = normalizeAssets(assets);
|
||||
}
|
||||
|
||||
public static FESurfaceContext empty() {
|
||||
return new FESurfaceContext(ReadOnlyList.empty());
|
||||
}
|
||||
|
||||
private static ReadOnlyList<Addressable> normalizeAssets(final ReadOnlyList<Addressable> assets) {
|
||||
if (assets == null || assets.isEmpty()) {
|
||||
return ReadOnlyList.empty();
|
||||
}
|
||||
final var dedup = new LinkedHashMap<String, Addressable>();
|
||||
for (final var asset : assets) {
|
||||
if (asset == null || asset.address().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
dedup.put(asset.address(), asset);
|
||||
}
|
||||
return ReadOnlyList.wrap(new ArrayList<>(dedup.values()));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user