diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java index 35a78daa..b54da65d 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java @@ -5,6 +5,8 @@ import p.studio.compiler.models.IRReservedMetadata; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.Diagnostics; import p.studio.compiler.source.diagnostics.RelatedSpan; +import p.studio.compiler.source.identifiers.HostBindingId; +import p.studio.compiler.source.tables.HostBindingTable; import p.studio.utilities.structures.ReadOnlyList; import java.util.HashMap; @@ -25,7 +27,8 @@ public final class PbsHostAdmissionValidator { final var knownCapabilities = normalizedSet(context.knownCapabilities()); final var declaredCapabilities = normalizedSet(context.declaredCapabilities()); final var requiredCapabilities = new LinkedHashSet(); - final var firstCapabilityByHostBinding = new HashMap(); + final var hostBindingTable = new HostBindingTable(); + final var firstCapabilityByHostBinding = new HashMap(); for (final var hostBinding : metadata.hostMethodBindings()) { final var normalizedCapability = normalize(hostBinding.requiredCapability()); @@ -68,12 +71,12 @@ public final class PbsHostAdmissionValidator { continue; } - final var bindingKey = "%s|%s|%d".formatted( + final var hostBindingId = hostBindingTable.register( hostBinding.abiModule(), hostBinding.abiMethod(), hostBinding.abiVersion()); final var firstBindingCapability = firstCapabilityByHostBinding.putIfAbsent( - bindingKey, + hostBindingId, new FirstBindingCapability(normalizedCapability, hostBinding.span())); if (firstBindingCapability != null && !firstBindingCapability.capability().equals(normalizedCapability)) { Diagnostics.error( diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/HostBindingId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/HostBindingId.java new file mode 100644 index 00000000..6c171e5a --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/HostBindingId.java @@ -0,0 +1,7 @@ +package p.studio.compiler.source.identifiers; + +public class HostBindingId extends SourceIdentifier { + public HostBindingId(final int id) { + super(id); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/HostBindingRef.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/HostBindingRef.java new file mode 100644 index 00000000..89df6b13 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/HostBindingRef.java @@ -0,0 +1,19 @@ +package p.studio.compiler.source.tables; + +import java.util.Objects; + +public record HostBindingRef( + String module, + String name, + long version) { + public HostBindingRef { + module = Objects.requireNonNull(module, "module"); + name = Objects.requireNonNull(name, "name"); + if (module.isBlank() || name.isBlank()) { + throw new IllegalArgumentException("module and name must not be blank"); + } + if (version < 0) { + throw new IllegalArgumentException("version must be non-negative"); + } + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/HostBindingTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/HostBindingTable.java new file mode 100644 index 00000000..95afbe00 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/HostBindingTable.java @@ -0,0 +1,17 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.HostBindingId; + +public class HostBindingTable extends InternTable { + + public HostBindingTable() { + super(HostBindingId::new); + } + + public HostBindingId register( + final String module, + final String name, + final long version) { + return register(new HostBindingRef(module, name, version)); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/HostBindingTableTest.java b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/HostBindingTableTest.java new file mode 100644 index 00000000..2e9850d7 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/HostBindingTableTest.java @@ -0,0 +1,30 @@ +package p.studio.compiler.source.tables; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class HostBindingTableTest { + + @Test + void shouldInternEqualHostBindingsToSameIdentifier() { + final var table = new HostBindingTable(); + + final var first = table.register("gfx", "draw_pixel", 1); + final var second = table.register("gfx", "draw_pixel", 1); + final var third = table.register("gfx", "draw_pixel", 2); + + assertEquals(first, second); + assertNotEquals(first, third); + assertEquals(2, table.size()); + } + + @Test + void shouldRejectInvalidHostBindingContract() { + assertThrows(IllegalArgumentException.class, () -> new HostBindingRef("", "draw_pixel", 1)); + assertThrows(IllegalArgumentException.class, () -> new HostBindingRef("gfx", "", 1)); + assertThrows(IllegalArgumentException.class, () -> new HostBindingRef("gfx", "draw_pixel", -1)); + } +}